Repository: spring-projects/spring-security Branch: main Commit: e9f331c30c05 Files: 6671 Total size: 34.7 MB Directory structure: gitextract_5rb_dcug/ ├── .editorconfig ├── .gitattributes ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── bug.md │ │ ├── config.yml │ │ └── enhancement.md │ ├── ISSUE_TEMPLATE.md │ ├── PULL_REQUEST_TEMPLATE.md │ ├── dco.yml │ ├── dependabot.yml │ └── workflows/ │ ├── auto-merge-dependabot.yml │ ├── check-snapshots.yml │ ├── clean_build_artifacts.yml │ ├── codeql.yml │ ├── continuous-integration-workflow.yml │ ├── defer-issues.yml │ ├── deploy-docs.yml │ ├── finalize-release.yml │ ├── gradle-wrapper-upgrade-execution.yml │ ├── milestone-spring-releasetrain.yml │ ├── pr-build-workflow.yml │ ├── release-scheduler.yml │ ├── update-antora-ui-spring.yml │ └── update-scheduled-release-version.yml ├── .gitignore ├── .idea/ │ ├── checkstyle-idea.xml │ └── externalDependencies.xml ├── .sdkmanrc ├── .vscode/ │ └── settings.json ├── CONTRIBUTING.adoc ├── LICENSE.txt ├── README.adoc ├── RELEASE.adoc ├── access/ │ ├── spring-security-access.gradle │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── security/ │ │ ├── access/ │ │ │ ├── AccessDecisionManager.java │ │ │ ├── AccessDecisionVoter.java │ │ │ ├── AfterInvocationProvider.java │ │ │ ├── ConfigAttribute.java │ │ │ ├── SecurityConfig.java │ │ │ ├── SecurityMetadataSource.java │ │ │ ├── annotation/ │ │ │ │ ├── AnnotationMetadataExtractor.java │ │ │ │ ├── Jsr250MethodSecurityMetadataSource.java │ │ │ │ ├── Jsr250SecurityConfig.java │ │ │ │ ├── Jsr250Voter.java │ │ │ │ └── SecuredAnnotationSecurityMetadataSource.java │ │ │ ├── event/ │ │ │ │ ├── AbstractAuthorizationEvent.java │ │ │ │ ├── AuthenticationCredentialsNotFoundEvent.java │ │ │ │ ├── AuthorizationFailureEvent.java │ │ │ │ ├── AuthorizedEvent.java │ │ │ │ ├── LoggerListener.java │ │ │ │ ├── PublicInvocationEvent.java │ │ │ │ └── package-info.java │ │ │ ├── expression/ │ │ │ │ └── method/ │ │ │ │ ├── AbstractExpressionBasedMethodConfigAttribute.java │ │ │ │ ├── ExpressionBasedAnnotationAttributeFactory.java │ │ │ │ ├── ExpressionBasedPostInvocationAdvice.java │ │ │ │ ├── ExpressionBasedPreInvocationAdvice.java │ │ │ │ ├── PostInvocationExpressionAttribute.java │ │ │ │ └── PreInvocationExpressionAttribute.java │ │ │ ├── intercept/ │ │ │ │ ├── AbstractSecurityInterceptor.java │ │ │ │ ├── AfterInvocationManager.java │ │ │ │ ├── AfterInvocationProviderManager.java │ │ │ │ ├── InterceptorStatusToken.java │ │ │ │ ├── MethodInvocationPrivilegeEvaluator.java │ │ │ │ ├── NullRunAsManager.java │ │ │ │ ├── RunAsImplAuthenticationProvider.java │ │ │ │ ├── RunAsManager.java │ │ │ │ ├── RunAsManagerImpl.java │ │ │ │ ├── RunAsUserToken.java │ │ │ │ ├── aopalliance/ │ │ │ │ │ ├── MethodSecurityInterceptor.java │ │ │ │ │ ├── MethodSecurityMetadataSourceAdvisor.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── aspectj/ │ │ │ │ │ ├── AspectJCallback.java │ │ │ │ │ ├── AspectJMethodSecurityInterceptor.java │ │ │ │ │ ├── MethodInvocationAdapter.java │ │ │ │ │ └── package-info.java │ │ │ │ └── package-info.java │ │ │ ├── method/ │ │ │ │ ├── AbstractFallbackMethodSecurityMetadataSource.java │ │ │ │ ├── AbstractMethodSecurityMetadataSource.java │ │ │ │ ├── DelegatingMethodSecurityMetadataSource.java │ │ │ │ ├── MapBasedMethodSecurityMetadataSource.java │ │ │ │ ├── MethodSecurityMetadataSource.java │ │ │ │ ├── P.java │ │ │ │ └── package-info.java │ │ │ ├── prepost/ │ │ │ │ ├── PostInvocationAdviceProvider.java │ │ │ │ ├── PostInvocationAttribute.java │ │ │ │ ├── PostInvocationAuthorizationAdvice.java │ │ │ │ ├── PreInvocationAttribute.java │ │ │ │ ├── PreInvocationAuthorizationAdvice.java │ │ │ │ ├── PreInvocationAuthorizationAdviceVoter.java │ │ │ │ ├── PrePostAdviceReactiveMethodInterceptor.java │ │ │ │ ├── PrePostAnnotationSecurityMetadataSource.java │ │ │ │ └── PrePostInvocationAttributeFactory.java │ │ │ └── vote/ │ │ │ ├── AbstractAccessDecisionManager.java │ │ │ ├── AbstractAclVoter.java │ │ │ ├── AffirmativeBased.java │ │ │ ├── AuthenticatedVoter.java │ │ │ ├── ConsensusBased.java │ │ │ ├── RoleHierarchyVoter.java │ │ │ ├── RoleVoter.java │ │ │ ├── UnanimousBased.java │ │ │ └── package-info.java │ │ ├── acls/ │ │ │ ├── AclEntryVoter.java │ │ │ └── afterinvocation/ │ │ │ ├── AbstractAclProvider.java │ │ │ ├── AclEntryAfterInvocationCollectionFilteringProvider.java │ │ │ ├── AclEntryAfterInvocationProvider.java │ │ │ ├── ArrayFilterer.java │ │ │ ├── CollectionFilterer.java │ │ │ ├── Filterer.java │ │ │ └── package-info.java │ │ ├── messaging/ │ │ │ └── access/ │ │ │ ├── expression/ │ │ │ │ ├── EvaluationContextPostProcessor.java │ │ │ │ ├── ExpressionBasedMessageSecurityMetadataSourceFactory.java │ │ │ │ ├── MessageExpressionConfigAttribute.java │ │ │ │ └── MessageExpressionVoter.java │ │ │ └── intercept/ │ │ │ ├── ChannelSecurityInterceptor.java │ │ │ ├── DefaultMessageSecurityMetadataSource.java │ │ │ └── MessageSecurityMetadataSource.java │ │ └── web/ │ │ └── access/ │ │ ├── DefaultWebInvocationPrivilegeEvaluator.java │ │ ├── channel/ │ │ │ ├── AbstractRetryEntryPoint.java │ │ │ ├── ChannelDecisionManager.java │ │ │ ├── ChannelDecisionManagerImpl.java │ │ │ ├── ChannelEntryPoint.java │ │ │ ├── ChannelProcessingFilter.java │ │ │ ├── ChannelProcessor.java │ │ │ ├── InsecureChannelProcessor.java │ │ │ ├── RetryWithHttpEntryPoint.java │ │ │ ├── RetryWithHttpsEntryPoint.java │ │ │ ├── SecureChannelProcessor.java │ │ │ └── package-info.java │ │ ├── expression/ │ │ │ ├── DefaultWebSecurityExpressionHandler.java │ │ │ ├── ExpressionBasedFilterInvocationSecurityMetadataSource.java │ │ │ ├── WebExpressionConfigAttribute.java │ │ │ └── WebExpressionVoter.java │ │ └── intercept/ │ │ ├── DefaultFilterInvocationSecurityMetadataSource.java │ │ ├── FilterInvocationSecurityMetadataSource.java │ │ └── FilterSecurityInterceptor.java │ └── test/ │ └── java/ │ └── org/ │ └── springframework/ │ └── security/ │ ├── access/ │ │ ├── AuthenticationCredentialsNotFoundEventTests.java │ │ ├── AuthorizationFailureEventTests.java │ │ ├── AuthorizedEventTests.java │ │ ├── ITargetObject.java │ │ ├── OtherTargetObject.java │ │ ├── SecurityConfigTests.java │ │ ├── TargetObject.java │ │ ├── annotation/ │ │ │ ├── BusinessService.java │ │ │ ├── BusinessServiceImpl.java │ │ │ ├── Entity.java │ │ │ ├── ExpressionProtectedBusinessServiceImpl.java │ │ │ ├── Jsr250BusinessServiceImpl.java │ │ │ ├── Jsr250MethodSecurityMetadataSourceTests.java │ │ │ ├── Jsr250VoterTests.java │ │ │ ├── RequireAdminRole.java │ │ │ ├── RequireUserRole.java │ │ │ ├── SecuredAnnotationSecurityMetadataSourceTests.java │ │ │ └── sec2150/ │ │ │ ├── CrudRepository.java │ │ │ ├── MethodInvocationFactory.java │ │ │ └── PersonRepository.java │ │ ├── expression/ │ │ │ └── method/ │ │ │ ├── DefaultMethodSecurityExpressionHandlerTests.java │ │ │ ├── ExpressionBasedPreInvocationAdviceTests.java │ │ │ ├── MethodExpressionVoterTests.java │ │ │ ├── MethodSecurityEvaluationContextTests.java │ │ │ ├── MethodSecurityExpressionRootTests.java │ │ │ ├── PrePostAnnotationSecurityMetadataSourceTests.java │ │ │ └── SecurityRules.java │ │ ├── intercept/ │ │ │ ├── AbstractSecurityInterceptorTests.java │ │ │ ├── AfterInvocationProviderManagerTests.java │ │ │ ├── InterceptorStatusTokenTests.java │ │ │ ├── NullRunAsManagerTests.java │ │ │ ├── RunAsImplAuthenticationProviderTests.java │ │ │ ├── RunAsManagerImplTests.java │ │ │ ├── RunAsUserTokenTests.java │ │ │ ├── aopalliance/ │ │ │ │ ├── MethodSecurityInterceptorTests.java │ │ │ │ └── MethodSecurityMetadataSourceAdvisorTests.java │ │ │ ├── aspectj/ │ │ │ │ └── AspectJMethodSecurityInterceptorTests.java │ │ │ └── method/ │ │ │ ├── MapBasedMethodSecurityMetadataSourceTests.java │ │ │ ├── MethodInvocationPrivilegeEvaluatorTests.java │ │ │ └── MockMethodInvocation.java │ │ ├── method/ │ │ │ └── DelegatingMethodSecurityMetadataSourceTests.java │ │ ├── prepost/ │ │ │ ├── PostInvocationAdviceProviderTests.java │ │ │ └── PreInvocationAuthorizationAdviceVoterTests.java │ │ └── vote/ │ │ ├── AbstractAccessDecisionManagerTests.java │ │ ├── AbstractAclVoterTests.java │ │ ├── AffirmativeBasedTests.java │ │ ├── AuthenticatedVoterTests.java │ │ ├── ConsensusBasedTests.java │ │ ├── DenyAgainVoter.java │ │ ├── DenyVoter.java │ │ ├── RoleHierarchyVoterTests.java │ │ ├── RoleVoterTests.java │ │ └── UnanimousBasedTests.java │ ├── acls/ │ │ └── afterinvocation/ │ │ ├── AclEntryAfterInvocationCollectionFilteringProviderTests.java │ │ └── AclEntryAfterInvocationProviderTests.java │ ├── messaging/ │ │ └── access/ │ │ ├── expression/ │ │ │ ├── ExpressionBasedMessageSecurityMetadataSourceFactoryTests.java │ │ │ ├── MessageExpressionConfigAttributeTests.java │ │ │ └── MessageExpressionVoterTests.java │ │ └── intercept/ │ │ ├── ChannelSecurityInterceptorTests.java │ │ └── DefaultMessageSecurityMetadataSourceTests.java │ └── web/ │ └── access/ │ ├── DefaultWebInvocationPrivilegeEvaluatorTests.java │ ├── channel/ │ │ ├── ChannelDecisionManagerImplTests.java │ │ ├── ChannelProcessingFilterTests.java │ │ ├── InsecureChannelProcessorTests.java │ │ ├── RetryWithHttpEntryPointTests.java │ │ ├── RetryWithHttpsEntryPointTests.java │ │ └── SecureChannelProcessorTests.java │ ├── expression/ │ │ ├── DefaultWebSecurityExpressionHandlerTests.java │ │ ├── ExpressionBasedFilterInvocationSecurityMetadataSourceTests.java │ │ └── WebExpressionVoterTests.java │ └── intercept/ │ ├── DefaultFilterInvocationSecurityMetadataSourceTests.java │ └── FilterSecurityInterceptorTests.java ├── acl/ │ ├── spring-security-acl.gradle │ └── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── security/ │ │ │ └── acls/ │ │ │ ├── AclPermissionCacheOptimizer.java │ │ │ ├── AclPermissionEvaluator.java │ │ │ ├── aot/ │ │ │ │ └── hint/ │ │ │ │ ├── AclRuntimeHints.java │ │ │ │ └── package-info.java │ │ │ ├── domain/ │ │ │ │ ├── AbstractPermission.java │ │ │ │ ├── AccessControlEntryImpl.java │ │ │ │ ├── AclAuthorizationStrategy.java │ │ │ │ ├── AclAuthorizationStrategyImpl.java │ │ │ │ ├── AclFormattingUtils.java │ │ │ │ ├── AclImpl.java │ │ │ │ ├── AuditLogger.java │ │ │ │ ├── BasePermission.java │ │ │ │ ├── ConsoleAuditLogger.java │ │ │ │ ├── CumulativePermission.java │ │ │ │ ├── DefaultPermissionFactory.java │ │ │ │ ├── DefaultPermissionGrantingStrategy.java │ │ │ │ ├── GrantedAuthoritySid.java │ │ │ │ ├── IdentityUnavailableException.java │ │ │ │ ├── ObjectIdentityImpl.java │ │ │ │ ├── ObjectIdentityRetrievalStrategyImpl.java │ │ │ │ ├── PermissionFactory.java │ │ │ │ ├── PrincipalSid.java │ │ │ │ ├── SidRetrievalStrategyImpl.java │ │ │ │ ├── SpringCacheBasedAclCache.java │ │ │ │ └── package-info.java │ │ │ ├── jdbc/ │ │ │ │ ├── AclClassIdUtils.java │ │ │ │ ├── BasicLookupStrategy.java │ │ │ │ ├── JdbcAclService.java │ │ │ │ ├── JdbcMutableAclService.java │ │ │ │ ├── LookupStrategy.java │ │ │ │ └── package-info.java │ │ │ ├── model/ │ │ │ │ ├── AccessControlEntry.java │ │ │ │ ├── Acl.java │ │ │ │ ├── AclCache.java │ │ │ │ ├── AclDataAccessException.java │ │ │ │ ├── AclService.java │ │ │ │ ├── AlreadyExistsException.java │ │ │ │ ├── AuditableAccessControlEntry.java │ │ │ │ ├── AuditableAcl.java │ │ │ │ ├── ChildrenExistException.java │ │ │ │ ├── MutableAcl.java │ │ │ │ ├── MutableAclService.java │ │ │ │ ├── NotFoundException.java │ │ │ │ ├── ObjectIdentity.java │ │ │ │ ├── ObjectIdentityGenerator.java │ │ │ │ ├── ObjectIdentityRetrievalStrategy.java │ │ │ │ ├── OwnershipAcl.java │ │ │ │ ├── Permission.java │ │ │ │ ├── PermissionGrantingStrategy.java │ │ │ │ ├── Sid.java │ │ │ │ ├── SidRetrievalStrategy.java │ │ │ │ ├── UnloadedSidException.java │ │ │ │ └── package-info.java │ │ │ └── package-info.java │ │ └── resources/ │ │ ├── META-INF/ │ │ │ └── spring/ │ │ │ └── aot.factories │ │ ├── createAclSchema.sql │ │ ├── createAclSchemaMySQL.sql │ │ ├── createAclSchemaOracle.sql │ │ ├── createAclSchemaPostgres.sql │ │ ├── createAclSchemaSqlServer.sql │ │ ├── createAclSchemaWithAclClassIdType.sql │ │ └── select.sql │ └── test/ │ ├── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── security/ │ │ └── acls/ │ │ ├── AclFormattingUtilsTests.java │ │ ├── AclPermissionCacheOptimizerTests.java │ │ ├── AclPermissionEvaluatorTests.java │ │ ├── TargetObject.java │ │ ├── TargetObjectWithUUID.java │ │ ├── domain/ │ │ │ ├── AccessControlImplEntryTests.java │ │ │ ├── AclAuthorizationStrategyImplTests.java │ │ │ ├── AclImplTests.java │ │ │ ├── AclImplementationSecurityCheckTests.java │ │ │ ├── AuditLoggerTests.java │ │ │ ├── ObjectIdentityImplTests.java │ │ │ ├── ObjectIdentityRetrievalStrategyImplTests.java │ │ │ ├── PermissionTests.java │ │ │ └── SpecialPermission.java │ │ ├── jdbc/ │ │ │ ├── AbstractBasicLookupStrategyTests.java │ │ │ ├── AclClassIdUtilsTests.java │ │ │ ├── BasicLookupStrategyTests.java │ │ │ ├── BasicLookupStrategyTestsDbHelper.java │ │ │ ├── BasicLookupStrategyWithAclClassTypeTests.java │ │ │ ├── DatabaseSeeder.java │ │ │ ├── JdbcAclServiceTests.java │ │ │ ├── JdbcMutableAclServiceTests.java │ │ │ ├── JdbcMutableAclServiceTestsWithAclClassId.java │ │ │ └── SpringCacheBasedAclCacheTests.java │ │ └── sid/ │ │ ├── CustomSid.java │ │ ├── SidRetrievalStrategyTests.java │ │ └── SidTests.java │ └── resources/ │ ├── db/ │ │ └── sql/ │ │ └── test_data_hierarchy.sql │ ├── jdbcMutableAclServiceTests-context.xml │ ├── jdbcMutableAclServiceTestsWithAclClass-context.xml │ └── logback-test.xml ├── aspects/ │ ├── spring-security-aspects.gradle │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── security/ │ │ ├── access/ │ │ │ └── intercept/ │ │ │ └── aspectj/ │ │ │ └── aspect/ │ │ │ └── AnnotationSecurityAspect.aj │ │ └── authorization/ │ │ └── method/ │ │ └── aspectj/ │ │ ├── AbstractMethodInterceptorAspect.aj │ │ ├── JoinPointMethodInvocation.aj │ │ ├── PostAuthorizeAspect.aj │ │ ├── PostFilterAspect.aj │ │ ├── PreAuthorizeAspect.aj │ │ ├── PreFilterAspect.aj │ │ └── SecuredAspect.aj │ └── test/ │ ├── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── security/ │ │ ├── access/ │ │ │ └── intercept/ │ │ │ └── aspectj/ │ │ │ └── aspect/ │ │ │ └── AnnotationSecurityAspectTests.java │ │ └── authorization/ │ │ └── method/ │ │ └── aspectj/ │ │ ├── PostAuthorizeAspectTests.java │ │ ├── PostFilterAspectTests.java │ │ ├── PreAuthorizeAspectTests.java │ │ ├── PreFilterAspectTests.java │ │ └── SecuredAspectTests.java │ └── resources/ │ └── logback-test.xml ├── bom/ │ └── spring-security-bom.gradle ├── build.gradle ├── buildSrc/ │ ├── .idea/ │ │ ├── compiler.xml │ │ ├── gradle.xml │ │ ├── jarRepositories.xml │ │ ├── misc.xml │ │ ├── uiDesigner.xml │ │ └── workspace.xml │ ├── build.gradle │ ├── settings.gradle │ └── src/ │ ├── main/ │ │ ├── graphql/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── security/ │ │ │ └── convention/ │ │ │ └── versions/ │ │ │ ├── CreateIssue.graphql │ │ │ ├── FindCreateIssueInput.graphql │ │ │ ├── RateLimit.graphql │ │ │ └── schema.json │ │ ├── groovy/ │ │ │ ├── compile-warnings-error.gradle │ │ │ ├── io/ │ │ │ │ └── spring/ │ │ │ │ └── gradle/ │ │ │ │ ├── IncludeRepoTask.groovy │ │ │ │ └── convention/ │ │ │ │ ├── AbstractSpringJavaPlugin.groovy │ │ │ │ ├── ArtifactoryPlugin.groovy │ │ │ │ ├── CheckstylePlugin.groovy │ │ │ │ ├── DocsPlugin.groovy │ │ │ │ ├── EclipsePlugin.groovy │ │ │ │ ├── IntegrationTestPlugin.groovy │ │ │ │ ├── JacocoPlugin.groovy │ │ │ │ ├── JavadocApiPlugin.groovy │ │ │ │ ├── JavadocOptionsPlugin.groovy │ │ │ │ ├── ManagementConfigurationPlugin.java │ │ │ │ ├── MavenBomPlugin.groovy │ │ │ │ ├── RepositoryConventionPlugin.groovy │ │ │ │ ├── RootProjectPlugin.groovy │ │ │ │ ├── SchemaDeployPlugin.groovy │ │ │ │ ├── SchemaPlugin.groovy │ │ │ │ ├── SchemaZipPlugin.groovy │ │ │ │ ├── SortedProperties.groovy │ │ │ │ ├── SpringModulePlugin.groovy │ │ │ │ ├── SpringTestPlugin.groovy │ │ │ │ ├── TestsConfigurationPlugin.groovy │ │ │ │ └── Utils.groovy │ │ │ ├── java-toolchain.gradle │ │ │ ├── javadoc-warnings-error.gradle │ │ │ ├── security-kotlin.gradle │ │ │ ├── security-nullability.gradle │ │ │ └── test-compile-target-jdk25.gradle │ │ ├── java/ │ │ │ ├── lock/ │ │ │ │ ├── GlobalLockPlugin.java │ │ │ │ └── GlobalLockTask.java │ │ │ ├── org/ │ │ │ │ └── springframework/ │ │ │ │ ├── gradle/ │ │ │ │ │ ├── CopyPropertiesPlugin.java │ │ │ │ │ ├── classpath/ │ │ │ │ │ │ ├── CheckClasspathForProhibitedDependencies.java │ │ │ │ │ │ ├── CheckClasspathForProhibitedDependenciesPlugin.java │ │ │ │ │ │ └── CheckProhibitedDependenciesLifecyclePlugin.java │ │ │ │ │ ├── maven/ │ │ │ │ │ │ ├── MavenPublishingConventionsPlugin.java │ │ │ │ │ │ ├── PublishAllJavaComponentsPlugin.java │ │ │ │ │ │ ├── PublishArtifactsPlugin.java │ │ │ │ │ │ ├── PublishLocalPlugin.java │ │ │ │ │ │ ├── SpringMavenPlugin.java │ │ │ │ │ │ ├── SpringNexusPublishPlugin.java │ │ │ │ │ │ └── SpringSigningPlugin.java │ │ │ │ │ ├── propdeps/ │ │ │ │ │ │ ├── PropDepsEclipsePlugin.groovy │ │ │ │ │ │ ├── PropDepsIdeaPlugin.groovy │ │ │ │ │ │ └── PropDepsPlugin.groovy │ │ │ │ │ └── xsd/ │ │ │ │ │ └── CreateVersionlessXsdTask.java │ │ │ │ └── security/ │ │ │ │ ├── CheckExpectedBranchVersionPlugin.java │ │ │ │ └── convention/ │ │ │ │ └── versions/ │ │ │ │ ├── FileUtils.java │ │ │ │ ├── TransitiveDependencyLookupUtils.java │ │ │ │ └── VerifyDependenciesVersionsPlugin.java │ │ │ ├── s101/ │ │ │ │ ├── S101Configure.java │ │ │ │ ├── S101Configurer.java │ │ │ │ ├── S101Install.java │ │ │ │ ├── S101Plugin.java │ │ │ │ └── S101PluginExtension.java │ │ │ └── trang/ │ │ │ ├── RncToXsd.java │ │ │ └── TrangPlugin.java │ │ └── resources/ │ │ ├── META-INF/ │ │ │ └── gradle-plugins/ │ │ │ ├── io.spring.convention.artifactory.properties │ │ │ ├── io.spring.convention.bom.properties │ │ │ ├── io.spring.convention.checkstyle.properties │ │ │ ├── io.spring.convention.docs.properties │ │ │ ├── io.spring.convention.eclipse.properties │ │ │ ├── io.spring.convention.integration-test.properties │ │ │ ├── io.spring.convention.jacoco.properties │ │ │ ├── io.spring.convention.javadoc-api.properties │ │ │ ├── io.spring.convention.javadoc-options.properties │ │ │ ├── io.spring.convention.repository.properties │ │ │ ├── io.spring.convention.root.properties │ │ │ ├── io.spring.convention.spring-module.properties │ │ │ ├── io.spring.convention.spring-test.properties │ │ │ └── io.spring.convention.tests-configuration.properties │ │ └── s101/ │ │ ├── config.xml │ │ ├── project.java.hsp │ │ └── repository.xml │ └── test/ │ ├── java/ │ │ ├── io/ │ │ │ └── spring/ │ │ │ └── gradle/ │ │ │ ├── TestKit.java │ │ │ └── convention/ │ │ │ ├── IntegrationPluginTest.java │ │ │ ├── IntegrationTestPluginITest.java │ │ │ ├── JacocoPluginITest.java │ │ │ ├── JavadocApiPluginITest.java │ │ │ ├── JavadocApiPluginTest.java │ │ │ ├── RepositoryConventionPluginTests.java │ │ │ ├── ShowcaseITest.java │ │ │ ├── SpringMavenPluginITest.java │ │ │ ├── TestsConfigurationPluginITest.java │ │ │ └── UtilsTest.java │ │ └── org/ │ │ └── springframework/ │ │ └── gradle/ │ │ └── xsd/ │ │ └── CreateVersionlessXsdTaskTests.java │ └── resources/ │ ├── samples/ │ │ ├── integrationtest/ │ │ │ ├── withgroovy/ │ │ │ │ ├── build.gradle │ │ │ │ └── src/ │ │ │ │ └── integration-test/ │ │ │ │ └── groovy/ │ │ │ │ └── sample/ │ │ │ │ └── TheTest.groovy │ │ │ ├── withjava/ │ │ │ │ ├── build.gradle │ │ │ │ └── src/ │ │ │ │ └── integration-test/ │ │ │ │ └── java/ │ │ │ │ └── sample/ │ │ │ │ └── TheTest.java │ │ │ └── withpropdeps/ │ │ │ ├── build.gradle │ │ │ └── src/ │ │ │ └── integration-test/ │ │ │ └── java/ │ │ │ └── sample/ │ │ │ └── TheTest.java │ │ ├── jacoco/ │ │ │ └── java/ │ │ │ ├── build.gradle │ │ │ └── src/ │ │ │ ├── main/ │ │ │ │ └── java/ │ │ │ │ └── sample/ │ │ │ │ └── TheClass.java │ │ │ └── test/ │ │ │ └── java/ │ │ │ └── sample/ │ │ │ └── TheClassTest.java │ │ ├── javadocapi/ │ │ │ └── multimodule/ │ │ │ ├── api/ │ │ │ │ ├── build.gradle │ │ │ │ └── src/ │ │ │ │ └── main/ │ │ │ │ └── java/ │ │ │ │ └── sample/ │ │ │ │ └── Api.java │ │ │ ├── build.gradle │ │ │ ├── impl/ │ │ │ │ ├── build.gradle │ │ │ │ └── src/ │ │ │ │ └── main/ │ │ │ │ └── java/ │ │ │ │ └── sample/ │ │ │ │ └── Impl.java │ │ │ ├── sample/ │ │ │ │ ├── build.gradle │ │ │ │ └── src/ │ │ │ │ └── main/ │ │ │ │ └── java/ │ │ │ │ └── sample/ │ │ │ │ └── Sample.java │ │ │ └── settings.gradle │ │ ├── maven/ │ │ │ ├── install/ │ │ │ │ └── build.gradle │ │ │ ├── install-with-springio/ │ │ │ │ ├── build.gradle │ │ │ │ └── gradle/ │ │ │ │ └── dependency-management.gradle │ │ │ ├── signing/ │ │ │ │ ├── build.gradle │ │ │ │ └── settings.gradle │ │ │ └── upload/ │ │ │ └── build.gradle │ │ ├── showcase/ │ │ │ ├── Jenkinsfile │ │ │ ├── bom/ │ │ │ │ └── bom.gradle │ │ │ ├── build.gradle │ │ │ ├── etc/ │ │ │ │ └── checkstyle/ │ │ │ │ └── checkstyle.xml │ │ │ ├── settings.gradle │ │ │ ├── sgbcs-api/ │ │ │ │ ├── sgbcs-api.gradle │ │ │ │ └── src/ │ │ │ │ ├── main/ │ │ │ │ │ └── java/ │ │ │ │ │ └── api/ │ │ │ │ │ └── Api.java │ │ │ │ └── test/ │ │ │ │ └── java/ │ │ │ │ └── api/ │ │ │ │ └── ApiTest.java │ │ │ ├── sgbcs-core/ │ │ │ │ ├── sgbcs-core.gradle │ │ │ │ └── src/ │ │ │ │ ├── main/ │ │ │ │ │ ├── java/ │ │ │ │ │ │ └── core/ │ │ │ │ │ │ ├── CoreClass.java │ │ │ │ │ │ └── HasOptional.java │ │ │ │ │ └── resources/ │ │ │ │ │ ├── META-INF/ │ │ │ │ │ │ ├── spring.handlers │ │ │ │ │ │ └── spring.schemas │ │ │ │ │ └── org/ │ │ │ │ │ └── springframework/ │ │ │ │ │ └── springgradlebuildsample/ │ │ │ │ │ └── config/ │ │ │ │ │ ├── spring-springgradlebuildsample-2.0.xsd │ │ │ │ │ ├── spring-springgradlebuildsample-2.1.xsd │ │ │ │ │ └── spring-springgradlebuildsample-2.2.xsd │ │ │ │ └── test/ │ │ │ │ └── java/ │ │ │ │ └── core/ │ │ │ │ ├── CoreClassTest.java │ │ │ │ └── HasOptionalTest.java │ │ │ └── sgbcs-docs/ │ │ │ ├── sgbcs-docs.gradle │ │ │ └── src/ │ │ │ ├── docs/ │ │ │ │ └── asciidoc/ │ │ │ │ ├── docinfo.html │ │ │ │ ├── index.adoc │ │ │ │ └── subdir/ │ │ │ │ ├── _b.adoc │ │ │ │ └── _c.adoc │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── example/ │ │ │ └── StringUtils.java │ │ └── testsconfiguration/ │ │ ├── build.gradle │ │ ├── core/ │ │ │ ├── build.gradle │ │ │ └── src/ │ │ │ └── test/ │ │ │ └── java/ │ │ │ └── sample/ │ │ │ └── Dependency.java │ │ ├── settings.gradle │ │ └── web/ │ │ ├── build.gradle │ │ └── src/ │ │ └── test/ │ │ └── java/ │ │ └── sample/ │ │ └── DependencyTest.java │ └── test-private.pgp ├── cas/ │ ├── spring-security-cas.gradle │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── security/ │ │ └── cas/ │ │ ├── SamlServiceProperties.java │ │ ├── ServiceProperties.java │ │ ├── authentication/ │ │ │ ├── CasAssertionAuthenticationToken.java │ │ │ ├── CasAuthenticationProvider.java │ │ │ ├── CasAuthenticationToken.java │ │ │ ├── CasServiceTicketAuthenticationToken.java │ │ │ ├── NullStatelessTicketCache.java │ │ │ ├── ServiceAuthenticationDetails.java │ │ │ ├── SpringCacheBasedTicketCache.java │ │ │ ├── StatelessTicketCache.java │ │ │ └── package-info.java │ │ ├── jackson/ │ │ │ ├── AssertionImplMixin.java │ │ │ ├── AttributePrincipalImplMixin.java │ │ │ ├── CasAuthenticationTokenMixin.java │ │ │ ├── CasJacksonModule.java │ │ │ └── package-info.java │ │ ├── jackson2/ │ │ │ ├── AssertionImplMixin.java │ │ │ ├── AttributePrincipalImplMixin.java │ │ │ ├── CasAuthenticationTokenMixin.java │ │ │ ├── CasJackson2Module.java │ │ │ └── package-info.java │ │ ├── package-info.java │ │ ├── userdetails/ │ │ │ ├── AbstractCasAssertionUserDetailsService.java │ │ │ ├── GrantedAuthorityFromAssertionAttributesUserDetailsService.java │ │ │ └── package-info.java │ │ └── web/ │ │ ├── CasAuthenticationEntryPoint.java │ │ ├── CasAuthenticationFilter.java │ │ ├── CasGatewayAuthenticationRedirectFilter.java │ │ ├── CasGatewayResolverRequestMatcher.java │ │ ├── authentication/ │ │ │ ├── DefaultServiceAuthenticationDetails.java │ │ │ ├── ServiceAuthenticationDetailsSource.java │ │ │ └── package-info.java │ │ └── package-info.java │ └── test/ │ ├── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── security/ │ │ └── cas/ │ │ ├── authentication/ │ │ │ ├── AbstractStatelessTicketCacheTests.java │ │ │ ├── CasAuthenticationProviderTests.java │ │ │ ├── CasAuthenticationTokenTests.java │ │ │ ├── NullStatelessTicketCacheTests.java │ │ │ └── SpringCacheBasedTicketCacheTests.java │ │ ├── jackson/ │ │ │ └── CasAuthenticationTokenMixinTests.java │ │ ├── jackson2/ │ │ │ └── CasAuthenticationTokenMixinTests.java │ │ ├── userdetails/ │ │ │ └── GrantedAuthorityFromAssertionAttributesUserDetailsServiceTests.java │ │ └── web/ │ │ ├── CasAuthenticationEntryPointTests.java │ │ ├── CasAuthenticationFilterTests.java │ │ ├── CasGatewayAuthenticationRedirectFilterTests.java │ │ ├── CasGatewayResolverRequestMatcherTests.java │ │ ├── ServicePropertiesTests.java │ │ └── authentication/ │ │ └── DefaultServiceAuthenticationDetailsTests.java │ └── resources/ │ ├── logback-test.xml │ └── org/ │ └── springframework/ │ └── security/ │ └── cas/ │ └── web/ │ └── authentication/ │ ├── defaultserviceauthenticationdetails-explicit.xml │ └── defaultserviceauthenticationdetails-passivity.xml ├── class_mapping_from_2.0.x.txt ├── config/ │ ├── spring-security-config.gradle │ └── src/ │ ├── integration-test/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── security/ │ │ │ └── config/ │ │ │ ├── annotation/ │ │ │ │ ├── authentication/ │ │ │ │ │ └── ldap/ │ │ │ │ │ ├── LdapAuthenticationProviderBuilderSecurityBuilderTests.java │ │ │ │ │ ├── LdapAuthenticationProviderConfigurerTests.java │ │ │ │ │ ├── NamespaceLdapAuthenticationProviderTests.java │ │ │ │ │ └── NamespaceLdapAuthenticationProviderTestsConfigs.java │ │ │ │ ├── configurers/ │ │ │ │ │ └── WebAuthnWebDriverTests.java │ │ │ │ └── rsocket/ │ │ │ │ ├── AnonymousAuthenticationITests.java │ │ │ │ ├── HelloHandler.java │ │ │ │ ├── HelloRSocketITests.java │ │ │ │ ├── HelloRSocketObservationITests.java │ │ │ │ ├── HelloRSocketWithWebFluxITests.java │ │ │ │ ├── JwtITests.java │ │ │ │ ├── RSocketMessageHandlerConnectionITests.java │ │ │ │ ├── RSocketMessageHandlerITests.java │ │ │ │ └── SimpleAuthenticationITests.java │ │ │ └── ldap/ │ │ │ ├── EmbeddedLdapServerContextSourceFactoryBeanITests.java │ │ │ ├── Ldap247ITests.java │ │ │ ├── LdapBindAuthenticationManagerFactoryITests.java │ │ │ ├── LdapPasswordComparisonAuthenticationManagerFactoryITests.java │ │ │ ├── LdapProviderBeanDefinitionParserTests.java │ │ │ ├── LdapServerBeanDefinitionParserTests.java │ │ │ └── LdapUserServiceBeanDefinitionParserTests.java │ │ └── resources/ │ │ ├── logback-test.xml │ │ ├── test-server.ldif │ │ ├── test-server2.xldif │ │ └── users.xldif │ ├── main/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── security/ │ │ │ └── config/ │ │ │ ├── BeanIds.java │ │ │ ├── Customizer.java │ │ │ ├── DebugBeanDefinitionParser.java │ │ │ ├── Elements.java │ │ │ ├── ObjectPostProcessor.java │ │ │ ├── SecurityNamespaceHandler.java │ │ │ ├── ThrowingCustomizer.java │ │ │ ├── annotation/ │ │ │ │ ├── AbstractConfiguredSecurityBuilder.java │ │ │ │ ├── AbstractSecurityBuilder.java │ │ │ │ ├── AlreadyBuiltException.java │ │ │ │ ├── SecurityBuilder.java │ │ │ │ ├── SecurityConfigurer.java │ │ │ │ ├── SecurityConfigurerAdapter.java │ │ │ │ ├── authentication/ │ │ │ │ │ ├── ProviderManagerBuilder.java │ │ │ │ │ ├── builders/ │ │ │ │ │ │ └── AuthenticationManagerBuilder.java │ │ │ │ │ ├── configuration/ │ │ │ │ │ │ ├── AuthenticationConfiguration.java │ │ │ │ │ │ ├── AuthenticationManagerBeanRegistrationAotProcessor.java │ │ │ │ │ │ ├── EnableGlobalAuthentication.java │ │ │ │ │ │ ├── GlobalAuthenticationConfigurerAdapter.java │ │ │ │ │ │ ├── InitializeAuthenticationProviderBeanManagerConfigurer.java │ │ │ │ │ │ └── InitializeUserDetailsBeanManagerConfigurer.java │ │ │ │ │ └── configurers/ │ │ │ │ │ ├── ldap/ │ │ │ │ │ │ └── LdapAuthenticationProviderConfigurer.java │ │ │ │ │ ├── provisioning/ │ │ │ │ │ │ ├── InMemoryUserDetailsManagerConfigurer.java │ │ │ │ │ │ ├── JdbcUserDetailsManagerConfigurer.java │ │ │ │ │ │ └── UserDetailsManagerConfigurer.java │ │ │ │ │ └── userdetails/ │ │ │ │ │ ├── AbstractDaoAuthenticationConfigurer.java │ │ │ │ │ ├── DaoAuthenticationConfigurer.java │ │ │ │ │ ├── UserDetailsAwareConfigurer.java │ │ │ │ │ └── UserDetailsServiceConfigurer.java │ │ │ │ ├── authorization/ │ │ │ │ │ ├── AuthorizationManagerFactoryConfiguration.java │ │ │ │ │ ├── EnableMfaFiltersConfiguration.java │ │ │ │ │ ├── EnableMultiFactorAuthentication.java │ │ │ │ │ ├── MultiFactorAuthenticationSelector.java │ │ │ │ │ ├── MultiFactorCondition.java │ │ │ │ │ └── WhenWebAuthnRegisteredMfaConfiguration.java │ │ │ │ ├── configuration/ │ │ │ │ │ ├── AutowireBeanFactoryObjectPostProcessor.java │ │ │ │ │ └── ObjectPostProcessorConfiguration.java │ │ │ │ ├── method/ │ │ │ │ │ └── configuration/ │ │ │ │ │ ├── AuthorizationProxyConfiguration.java │ │ │ │ │ ├── AuthorizationProxyDataConfiguration.java │ │ │ │ │ ├── AuthorizationProxyWebConfiguration.java │ │ │ │ │ ├── DeferringMethodInterceptor.java │ │ │ │ │ ├── EnableGlobalMethodSecurity.java │ │ │ │ │ ├── EnableMethodSecurity.java │ │ │ │ │ ├── EnableReactiveMethodSecurity.java │ │ │ │ │ ├── GlobalMethodSecurityAspectJAutoProxyRegistrar.java │ │ │ │ │ ├── GlobalMethodSecurityConfiguration.java │ │ │ │ │ ├── GlobalMethodSecuritySelector.java │ │ │ │ │ ├── Jsr250MetadataSourceConfiguration.java │ │ │ │ │ ├── Jsr250MethodSecurityConfiguration.java │ │ │ │ │ ├── MethodObservationConfiguration.java │ │ │ │ │ ├── MethodSecurityAdvisorRegistrar.java │ │ │ │ │ ├── MethodSecurityAspectJAutoProxyRegistrar.java │ │ │ │ │ ├── MethodSecurityMetadataSourceAdvisorRegistrar.java │ │ │ │ │ ├── MethodSecuritySelector.java │ │ │ │ │ ├── PrePostMethodSecurityConfiguration.java │ │ │ │ │ ├── ReactiveAuthorizationManagerMethodSecurityConfiguration.java │ │ │ │ │ ├── ReactiveMethodObservationConfiguration.java │ │ │ │ │ ├── ReactiveMethodSecurityConfiguration.java │ │ │ │ │ ├── ReactiveMethodSecuritySelector.java │ │ │ │ │ └── SecuredMethodSecurityConfiguration.java │ │ │ │ ├── rsocket/ │ │ │ │ │ ├── EnableRSocketSecurity.java │ │ │ │ │ ├── PayloadInterceptorOrder.java │ │ │ │ │ ├── RSocketSecurity.java │ │ │ │ │ ├── RSocketSecurityConfiguration.java │ │ │ │ │ ├── ReactiveObservationConfiguration.java │ │ │ │ │ ├── ReactiveObservationImportSelector.java │ │ │ │ │ └── SecuritySocketAcceptorInterceptorConfiguration.java │ │ │ │ └── web/ │ │ │ │ ├── AbstractRequestMatcherRegistry.java │ │ │ │ ├── HttpSecurityBuilder.java │ │ │ │ ├── ServletRegistrationsSupport.java │ │ │ │ ├── WebSecurityConfigurer.java │ │ │ │ ├── builders/ │ │ │ │ │ ├── FilterOrderRegistration.java │ │ │ │ │ ├── HttpSecurity.java │ │ │ │ │ ├── WebSecurity.java │ │ │ │ │ └── WebSecurityFilterChainValidator.java │ │ │ │ ├── configuration/ │ │ │ │ │ ├── AutowiredWebSecurityConfigurersIgnoreParents.java │ │ │ │ │ ├── EnableWebSecurity.java │ │ │ │ │ ├── HttpSecurityConfiguration.java │ │ │ │ │ ├── OAuth2AuthorizationServerConfiguration.java │ │ │ │ │ ├── OAuth2ClientConfiguration.java │ │ │ │ │ ├── OAuth2ImportSelector.java │ │ │ │ │ ├── ObservationConfiguration.java │ │ │ │ │ ├── ObservationImportSelector.java │ │ │ │ │ ├── RegisterMissingBeanPostProcessor.java │ │ │ │ │ ├── SecurityReactorContextConfiguration.java │ │ │ │ │ ├── SpringWebMvcImportSelector.java │ │ │ │ │ ├── WebMvcSecurityConfiguration.java │ │ │ │ │ ├── WebSecurityConfiguration.java │ │ │ │ │ └── WebSecurityCustomizer.java │ │ │ │ ├── configurers/ │ │ │ │ │ ├── AbstractAuthenticationFilterConfigurer.java │ │ │ │ │ ├── AbstractConfigAttributeRequestMatcherRegistry.java │ │ │ │ │ ├── AbstractHttpConfigurer.java │ │ │ │ │ ├── AnonymousConfigurer.java │ │ │ │ │ ├── AuthorizeHttpRequestsConfigurer.java │ │ │ │ │ ├── ChannelSecurityConfigurer.java │ │ │ │ │ ├── CorsConfigurer.java │ │ │ │ │ ├── CsrfConfigurer.java │ │ │ │ │ ├── DefaultLoginPageConfigurer.java │ │ │ │ │ ├── ExceptionHandlingConfigurer.java │ │ │ │ │ ├── FormLoginConfigurer.java │ │ │ │ │ ├── HeadersConfigurer.java │ │ │ │ │ ├── HttpBasicConfigurer.java │ │ │ │ │ ├── HttpsRedirectConfigurer.java │ │ │ │ │ ├── JeeConfigurer.java │ │ │ │ │ ├── LogoutConfigurer.java │ │ │ │ │ ├── PasswordManagementConfigurer.java │ │ │ │ │ ├── PermitAllSupport.java │ │ │ │ │ ├── PortMapperConfigurer.java │ │ │ │ │ ├── RememberMeConfigurer.java │ │ │ │ │ ├── RequestCacheConfigurer.java │ │ │ │ │ ├── SecurityContextConfigurer.java │ │ │ │ │ ├── ServletApiConfigurer.java │ │ │ │ │ ├── SessionManagementConfigurer.java │ │ │ │ │ ├── WebAuthnConfigurer.java │ │ │ │ │ ├── X509Configurer.java │ │ │ │ │ ├── oauth2/ │ │ │ │ │ │ ├── client/ │ │ │ │ │ │ │ ├── OAuth2ClientConfigurer.java │ │ │ │ │ │ │ ├── OAuth2ClientConfigurerUtils.java │ │ │ │ │ │ │ ├── OAuth2LoginConfigurer.java │ │ │ │ │ │ │ ├── OidcBackChannelLogoutAuthentication.java │ │ │ │ │ │ │ ├── OidcBackChannelLogoutAuthenticationProvider.java │ │ │ │ │ │ │ ├── OidcBackChannelLogoutFilter.java │ │ │ │ │ │ │ ├── OidcBackChannelLogoutHandler.java │ │ │ │ │ │ │ ├── OidcBackChannelLogoutTokenValidator.java │ │ │ │ │ │ │ ├── OidcLogoutAuthenticationConverter.java │ │ │ │ │ │ │ ├── OidcLogoutAuthenticationToken.java │ │ │ │ │ │ │ ├── OidcLogoutConfigurer.java │ │ │ │ │ │ │ └── OidcUserRefreshedEventListener.java │ │ │ │ │ │ └── server/ │ │ │ │ │ │ ├── authorization/ │ │ │ │ │ │ │ ├── AbstractOAuth2Configurer.java │ │ │ │ │ │ │ ├── AuthorizationServerContextFilter.java │ │ │ │ │ │ │ ├── DefaultOAuth2TokenCustomizers.java │ │ │ │ │ │ │ ├── OAuth2AuthorizationEndpointConfigurer.java │ │ │ │ │ │ │ ├── OAuth2AuthorizationServerConfigurer.java │ │ │ │ │ │ │ ├── OAuth2AuthorizationServerMetadataEndpointConfigurer.java │ │ │ │ │ │ │ ├── OAuth2ClientAuthenticationConfigurer.java │ │ │ │ │ │ │ ├── OAuth2ClientRegistrationEndpointConfigurer.java │ │ │ │ │ │ │ ├── OAuth2ConfigurerUtils.java │ │ │ │ │ │ │ ├── OAuth2DeviceAuthorizationEndpointConfigurer.java │ │ │ │ │ │ │ ├── OAuth2DeviceVerificationEndpointConfigurer.java │ │ │ │ │ │ │ ├── OAuth2PushedAuthorizationRequestEndpointConfigurer.java │ │ │ │ │ │ │ ├── OAuth2TokenEndpointConfigurer.java │ │ │ │ │ │ │ ├── OAuth2TokenIntrospectionEndpointConfigurer.java │ │ │ │ │ │ │ ├── OAuth2TokenRevocationEndpointConfigurer.java │ │ │ │ │ │ │ ├── OidcClientRegistrationEndpointConfigurer.java │ │ │ │ │ │ │ ├── OidcConfigurer.java │ │ │ │ │ │ │ ├── OidcLogoutEndpointConfigurer.java │ │ │ │ │ │ │ ├── OidcProviderConfigurationEndpointConfigurer.java │ │ │ │ │ │ │ └── OidcUserInfoEndpointConfigurer.java │ │ │ │ │ │ └── resource/ │ │ │ │ │ │ ├── DPoPAuthenticationConfigurer.java │ │ │ │ │ │ └── OAuth2ResourceServerConfigurer.java │ │ │ │ │ ├── ott/ │ │ │ │ │ │ └── OneTimeTokenLoginConfigurer.java │ │ │ │ │ └── saml2/ │ │ │ │ │ ├── Saml2LoginConfigurer.java │ │ │ │ │ ├── Saml2LogoutConfigurer.java │ │ │ │ │ └── Saml2MetadataConfigurer.java │ │ │ │ ├── reactive/ │ │ │ │ │ ├── EnableWebFluxSecurity.java │ │ │ │ │ ├── ReactiveOAuth2ClientConfiguration.java │ │ │ │ │ ├── ReactiveOAuth2ClientImportSelector.java │ │ │ │ │ ├── ReactiveObservationConfiguration.java │ │ │ │ │ ├── ReactiveObservationImportSelector.java │ │ │ │ │ ├── ServerHttpSecurityConfiguration.java │ │ │ │ │ └── WebFluxSecurityConfiguration.java │ │ │ │ ├── servlet/ │ │ │ │ │ └── configuration/ │ │ │ │ │ └── WebMvcSecurityConfiguration.java │ │ │ │ └── socket/ │ │ │ │ ├── EnableWebSocketSecurity.java │ │ │ │ ├── MessageMatcherAuthorizationManagerConfiguration.java │ │ │ │ ├── WebSocketMessageBrokerSecurityConfiguration.java │ │ │ │ ├── WebSocketObservationConfiguration.java │ │ │ │ └── WebSocketObservationImportSelector.java │ │ │ ├── aot/ │ │ │ │ └── hint/ │ │ │ │ ├── OAuth2LoginRuntimeHints.java │ │ │ │ ├── WebMvcSecurityConfigurationRuntimeHints.java │ │ │ │ └── WebSecurityConfigurationRuntimeHints.java │ │ │ ├── authentication/ │ │ │ │ ├── AbstractUserDetailsServiceBeanDefinitionParser.java │ │ │ │ ├── AuthenticationManagerBeanDefinitionParser.java │ │ │ │ ├── AuthenticationManagerFactoryBean.java │ │ │ │ ├── AuthenticationProviderBeanDefinitionParser.java │ │ │ │ ├── JdbcUserServiceBeanDefinitionParser.java │ │ │ │ ├── PasswordEncoderParser.java │ │ │ │ ├── UserServiceBeanDefinitionParser.java │ │ │ │ └── package-info.java │ │ │ ├── core/ │ │ │ │ ├── GrantedAuthorityDefaults.java │ │ │ │ └── userdetails/ │ │ │ │ ├── ReactiveUserDetailsServiceResourceFactoryBean.java │ │ │ │ ├── UserDetailsMapFactoryBean.java │ │ │ │ └── UserDetailsResourceFactoryBean.java │ │ │ ├── crypto/ │ │ │ │ └── RsaKeyConversionServicePostProcessor.java │ │ │ ├── debug/ │ │ │ │ └── SecurityDebugBeanFactoryPostProcessor.java │ │ │ ├── http/ │ │ │ │ ├── AuthenticationConfigBuilder.java │ │ │ │ ├── AuthorizationFilterParser.java │ │ │ │ ├── ChannelAttributeFactory.java │ │ │ │ ├── CorsBeanDefinitionParser.java │ │ │ │ ├── CorsConfigurationSourceFactoryBean.java │ │ │ │ ├── CsrfBeanDefinitionParser.java │ │ │ │ ├── DefaultFilterChainValidator.java │ │ │ │ ├── FilterChainBeanDefinitionParser.java │ │ │ │ ├── FilterChainMapBeanDefinitionDecorator.java │ │ │ │ ├── FilterInvocationSecurityMetadataSourceParser.java │ │ │ │ ├── FormLoginBeanDefinitionParser.java │ │ │ │ ├── GrantedAuthorityDefaultsParserUtils.java │ │ │ │ ├── HeadersBeanDefinitionParser.java │ │ │ │ ├── HttpConfigurationBuilder.java │ │ │ │ ├── HttpFirewallBeanDefinitionParser.java │ │ │ │ ├── HttpSecurityBeanDefinitionParser.java │ │ │ │ ├── LogoutBeanDefinitionParser.java │ │ │ │ ├── MatcherType.java │ │ │ │ ├── MessageMatcherFactoryBean.java │ │ │ │ ├── OAuth2AuthorizedClientManagerRegistrar.java │ │ │ │ ├── OAuth2ClientBeanDefinitionParser.java │ │ │ │ ├── OAuth2ClientBeanDefinitionParserUtils.java │ │ │ │ ├── OAuth2ClientWebMvcSecurityPostProcessor.java │ │ │ │ ├── OAuth2LoginBeanDefinitionParser.java │ │ │ │ ├── OAuth2ResourceServerBeanDefinitionParser.java │ │ │ │ ├── OrderDecorator.java │ │ │ │ ├── PathPatternRequestMatcherFactoryBean.java │ │ │ │ ├── PortMappingsBeanDefinitionParser.java │ │ │ │ ├── RememberMeBeanDefinitionParser.java │ │ │ │ ├── RequestMatcherFactoryBean.java │ │ │ │ ├── Saml2LoginBeanDefinitionParser.java │ │ │ │ ├── Saml2LoginBeanDefinitionParserUtils.java │ │ │ │ ├── Saml2LogoutBeanDefinitionParser.java │ │ │ │ ├── Saml2LogoutBeanDefinitionParserUtils.java │ │ │ │ ├── SecurityFilters.java │ │ │ │ ├── SessionCreationPolicy.java │ │ │ │ ├── UserDetailsServiceFactoryBean.java │ │ │ │ ├── WebConfigUtils.java │ │ │ │ ├── WellKnownChangePasswordBeanDefinitionParser.java │ │ │ │ └── package-info.java │ │ │ ├── ldap/ │ │ │ │ ├── AbstractLdapAuthenticationManagerFactory.java │ │ │ │ ├── ContextSourceSettingPostProcessor.java │ │ │ │ ├── EmbeddedLdapServerContextSourceFactoryBean.java │ │ │ │ ├── LdapBindAuthenticationManagerFactory.java │ │ │ │ ├── LdapPasswordComparisonAuthenticationManagerFactory.java │ │ │ │ ├── LdapProviderBeanDefinitionParser.java │ │ │ │ ├── LdapServerBeanDefinitionParser.java │ │ │ │ ├── LdapUserServiceBeanDefinitionParser.java │ │ │ │ └── package-info.java │ │ │ ├── method/ │ │ │ │ ├── AspectJMethodMatcher.java │ │ │ │ ├── GlobalMethodSecurityBeanDefinitionParser.java │ │ │ │ ├── InterceptMethodsBeanDefinitionDecorator.java │ │ │ │ ├── MethodConfigUtils.java │ │ │ │ ├── MethodSecurityBeanDefinitionParser.java │ │ │ │ ├── MethodSecurityMetadataSourceBeanDefinitionParser.java │ │ │ │ ├── PointcutDelegatingAuthorizationManager.java │ │ │ │ ├── PrefixBasedMethodMatcher.java │ │ │ │ ├── ProtectPointcutPostProcessor.java │ │ │ │ └── package-info.java │ │ │ ├── oauth2/ │ │ │ │ └── client/ │ │ │ │ ├── ClientRegistrationsBeanDefinitionParser.java │ │ │ │ └── CommonOAuth2Provider.java │ │ │ ├── observation/ │ │ │ │ └── SecurityObservationSettings.java │ │ │ ├── package-info.java │ │ │ ├── provisioning/ │ │ │ │ └── UserDetailsManagerResourceFactoryBean.java │ │ │ ├── saml2/ │ │ │ │ └── RelyingPartyRegistrationsBeanDefinitionParser.java │ │ │ ├── web/ │ │ │ │ ├── PathPatternRequestMatcherBuilderFactoryBean.java │ │ │ │ ├── messaging/ │ │ │ │ │ └── PathPatternMessageMatcherBuilderFactoryBean.java │ │ │ │ └── server/ │ │ │ │ ├── AbstractServerWebExchangeMatcherRegistry.java │ │ │ │ ├── GenericHttpMessageConverterAdapter.java │ │ │ │ ├── HttpMessageConverters.java │ │ │ │ ├── OAuth2ErrorEncoder.java │ │ │ │ ├── OidcBackChannelLogoutAuthentication.java │ │ │ │ ├── OidcBackChannelLogoutReactiveAuthenticationManager.java │ │ │ │ ├── OidcBackChannelLogoutTokenValidator.java │ │ │ │ ├── OidcBackChannelLogoutWebFilter.java │ │ │ │ ├── OidcBackChannelServerLogoutHandler.java │ │ │ │ ├── OidcLogoutAuthenticationToken.java │ │ │ │ ├── OidcLogoutServerAuthenticationConverter.java │ │ │ │ ├── SecurityWebFiltersOrder.java │ │ │ │ └── ServerHttpSecurity.java │ │ │ └── websocket/ │ │ │ └── WebSocketMessageBrokerSecurityBeanDefinitionParser.java │ │ ├── kotlin/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── security/ │ │ │ └── config/ │ │ │ ├── annotation/ │ │ │ │ └── web/ │ │ │ │ ├── AbstractRequestMatcherDsl.kt │ │ │ │ ├── AnonymousDsl.kt │ │ │ │ ├── AuthorizeHttpRequestsDsl.kt │ │ │ │ ├── CorsDsl.kt │ │ │ │ ├── CsrfDsl.kt │ │ │ │ ├── ExceptionHandlingDsl.kt │ │ │ │ ├── FormLoginDsl.kt │ │ │ │ ├── HeadersDsl.kt │ │ │ │ ├── HttpBasicDsl.kt │ │ │ │ ├── HttpSecurityDsl.kt │ │ │ │ ├── HttpsRedirectDsl.kt │ │ │ │ ├── LogoutDsl.kt │ │ │ │ ├── OAuth2ClientDsl.kt │ │ │ │ ├── OAuth2LoginDsl.kt │ │ │ │ ├── OAuth2ResourceServerDsl.kt │ │ │ │ ├── OidcLogoutDsl.kt │ │ │ │ ├── OneTimeTokenLoginDsl.kt │ │ │ │ ├── PasswordManagementDsl.kt │ │ │ │ ├── PortMapperDsl.kt │ │ │ │ ├── RememberMeDsl.kt │ │ │ │ ├── RequestCacheDsl.kt │ │ │ │ ├── RequiresChannelDsl.kt │ │ │ │ ├── Saml2Dsl.kt │ │ │ │ ├── Saml2LogoutDsl.kt │ │ │ │ ├── Saml2MetadataDsl.kt │ │ │ │ ├── SecurityContextDsl.kt │ │ │ │ ├── SecurityMarker.kt │ │ │ │ ├── SessionManagementDsl.kt │ │ │ │ ├── WebAuthnDsl.kt │ │ │ │ ├── X509Dsl.kt │ │ │ │ ├── headers/ │ │ │ │ │ ├── CacheControlDsl.kt │ │ │ │ │ ├── ContentSecurityPolicyDsl.kt │ │ │ │ │ ├── ContentTypeOptionsDsl.kt │ │ │ │ │ ├── CrossOriginEmbedderPolicyDsl.kt │ │ │ │ │ ├── CrossOriginOpenerPolicyDsl.kt │ │ │ │ │ ├── CrossOriginResourcePolicyDsl.kt │ │ │ │ │ ├── FrameOptionsDsl.kt │ │ │ │ │ ├── HeadersSecurityMarker.kt │ │ │ │ │ ├── HttpPublicKeyPinningDsl.kt │ │ │ │ │ ├── HttpStrictTransportSecurityDsl.kt │ │ │ │ │ ├── PermissionsPolicyDsl.kt │ │ │ │ │ ├── ReferrerPolicyDsl.kt │ │ │ │ │ └── XssProtectionConfigDsl.kt │ │ │ │ ├── oauth2/ │ │ │ │ │ ├── client/ │ │ │ │ │ │ ├── AuthorizationCodeGrantDsl.kt │ │ │ │ │ │ └── OAuth2ClientSecurityMarker.kt │ │ │ │ │ ├── login/ │ │ │ │ │ │ ├── AuthorizationEndpointDsl.kt │ │ │ │ │ │ ├── OAuth2LoginSecurityMarker.kt │ │ │ │ │ │ ├── OidcBackChannelLogoutDsl.kt │ │ │ │ │ │ ├── RedirectionEndpointDsl.kt │ │ │ │ │ │ ├── TokenEndpointDsl.kt │ │ │ │ │ │ └── UserInfoEndpointDsl.kt │ │ │ │ │ └── resourceserver/ │ │ │ │ │ ├── JwtDsl.kt │ │ │ │ │ ├── OAuth2ResourceServerSecurityMarker.kt │ │ │ │ │ └── OpaqueTokenDsl.kt │ │ │ │ ├── saml2/ │ │ │ │ │ ├── LogoutRequestDsl.kt │ │ │ │ │ ├── LogoutResponseDsl.kt │ │ │ │ │ └── Saml2SecurityMarker.kt │ │ │ │ └── session/ │ │ │ │ ├── SessionConcurrencyDsl.kt │ │ │ │ ├── SessionFixationDsl.kt │ │ │ │ └── SessionSecurityMarker.kt │ │ │ └── web/ │ │ │ └── server/ │ │ │ ├── AuthorizeExchangeDsl.kt │ │ │ ├── ServerAnonymousDsl.kt │ │ │ ├── ServerCacheControlDsl.kt │ │ │ ├── ServerContentSecurityPolicyDsl.kt │ │ │ ├── ServerContentTypeOptionsDsl.kt │ │ │ ├── ServerCorsDsl.kt │ │ │ ├── ServerCrossOriginEmbedderPolicyDsl.kt │ │ │ ├── ServerCrossOriginOpenerPolicyDsl.kt │ │ │ ├── ServerCrossOriginResourcePolicyDsl.kt │ │ │ ├── ServerCsrfDsl.kt │ │ │ ├── ServerExceptionHandlingDsl.kt │ │ │ ├── ServerFormLoginDsl.kt │ │ │ ├── ServerFrameOptionsDsl.kt │ │ │ ├── ServerHeadersDsl.kt │ │ │ ├── ServerHttpBasicDsl.kt │ │ │ ├── ServerHttpSecurityDsl.kt │ │ │ ├── ServerHttpStrictTransportSecurityDsl.kt │ │ │ ├── ServerHttpsRedirectDsl.kt │ │ │ ├── ServerJwtDsl.kt │ │ │ ├── ServerLogoutDsl.kt │ │ │ ├── ServerOAuth2ClientDsl.kt │ │ │ ├── ServerOAuth2LoginDsl.kt │ │ │ ├── ServerOAuth2ResourceServerDsl.kt │ │ │ ├── ServerOidcBackChannelLogoutDsl.kt │ │ │ ├── ServerOidcLogoutDsl.kt │ │ │ ├── ServerOneTimeTokenLoginDsl.kt │ │ │ ├── ServerOpaqueTokenDsl.kt │ │ │ ├── ServerPasswordManagementDsl.kt │ │ │ ├── ServerPermissionsPolicyDsl.kt │ │ │ ├── ServerReferrerPolicyDsl.kt │ │ │ ├── ServerRequestCacheDsl.kt │ │ │ ├── ServerSecurityMarker.kt │ │ │ ├── ServerSessionConcurrencyDsl.kt │ │ │ ├── ServerSessionManagementDsl.kt │ │ │ ├── ServerX509Dsl.kt │ │ │ └── ServerXssProtectionDsl.kt │ │ └── resources/ │ │ ├── META-INF/ │ │ │ ├── spring/ │ │ │ │ └── aot.factories │ │ │ ├── spring.handlers │ │ │ └── spring.schemas │ │ └── org/ │ │ └── springframework/ │ │ └── security/ │ │ └── config/ │ │ ├── catalog.xml │ │ ├── spring-security-2.0.1.xsd │ │ ├── spring-security-2.0.2.xsd │ │ ├── spring-security-2.0.4.xsd │ │ ├── spring-security-2.0.xsd │ │ ├── spring-security-3.0.3.xsd │ │ ├── spring-security-3.0.xsd │ │ ├── spring-security-3.1.rnc │ │ ├── spring-security-3.1.xsd │ │ ├── spring-security-3.2.rnc │ │ ├── spring-security-3.2.xsd │ │ ├── spring-security-4.0.rnc │ │ ├── spring-security-4.0.xsd │ │ ├── spring-security-4.1.rnc │ │ ├── spring-security-4.1.xsd │ │ ├── spring-security-4.2.rnc │ │ ├── spring-security-4.2.xsd │ │ ├── spring-security-5.0.rnc │ │ ├── spring-security-5.0.xsd │ │ ├── spring-security-5.1.rnc │ │ ├── spring-security-5.1.xsd │ │ ├── spring-security-5.2.rnc │ │ ├── spring-security-5.2.xsd │ │ ├── spring-security-5.3.rnc │ │ ├── spring-security-5.3.xsd │ │ ├── spring-security-5.4.rnc │ │ ├── spring-security-5.4.xsd │ │ ├── spring-security-5.5.rnc │ │ ├── spring-security-5.5.xsd │ │ ├── spring-security-5.6.rnc │ │ ├── spring-security-5.6.xsd │ │ ├── spring-security-5.7.rnc │ │ ├── spring-security-5.7.xsd │ │ ├── spring-security-5.8.rnc │ │ ├── spring-security-5.8.xsd │ │ ├── spring-security-6.0.rnc │ │ ├── spring-security-6.0.xsd │ │ ├── spring-security-6.1.rnc │ │ ├── spring-security-6.1.xsd │ │ ├── spring-security-6.2.rnc │ │ ├── spring-security-6.2.xsd │ │ ├── spring-security-6.3.rnc │ │ ├── spring-security-6.3.xsd │ │ ├── spring-security-6.4.rnc │ │ ├── spring-security-6.4.xsd │ │ ├── spring-security-6.5.rnc │ │ ├── spring-security-6.5.xsd │ │ ├── spring-security-7.0.rnc │ │ ├── spring-security-7.0.xsd │ │ ├── spring-security-7.1.rnc │ │ ├── spring-security-7.1.xsd │ │ └── spring-security.xsl │ └── test/ │ ├── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── security/ │ │ ├── BeanNameCollectingPostProcessor.java │ │ ├── CollectingAppListener.java │ │ ├── SerializationSamples.java │ │ ├── SpringSecurityCoreVersionSerializableTests.java │ │ ├── config/ │ │ │ ├── ConfigTestUtils.java │ │ │ ├── DataSourcePopulator.java │ │ │ ├── FilterChainProxyConfigTests.java │ │ │ ├── InvalidConfigurationTests.java │ │ │ ├── MockAfterInvocationProvider.java │ │ │ ├── MockEventListener.java │ │ │ ├── MockSecurityContextHolderStrategy.java │ │ │ ├── MockTransactionManager.java │ │ │ ├── MockUserServiceBeanPostProcessor.java │ │ │ ├── PostProcessedMockUserDetailsService.java │ │ │ ├── SecurityNamespaceHandlerTests.java │ │ │ ├── TestBusinessBean.java │ │ │ ├── TestBusinessBeanImpl.java │ │ │ ├── TestDeferredSecurityContext.java │ │ │ ├── TransactionalTestBusinessBean.java │ │ │ ├── annotation/ │ │ │ │ ├── ConcereteSecurityConfigurerAdapter.java │ │ │ │ ├── ObjectPostProcessorTests.java │ │ │ │ ├── SecurityConfigurerAdapterClosureTests.java │ │ │ │ ├── SecurityConfigurerAdapterTests.java │ │ │ │ ├── SecurityContextChangedListenerArgumentMatchers.java │ │ │ │ ├── SecurityContextChangedListenerConfig.java │ │ │ │ ├── authentication/ │ │ │ │ │ ├── AuthenticationManagerBuilderTests.java │ │ │ │ │ ├── BaseAuthenticationConfig.java │ │ │ │ │ ├── NamespaceAuthenticationManagerTests.java │ │ │ │ │ ├── NamespaceAuthenticationProviderTests.java │ │ │ │ │ ├── NamespaceJdbcUserServiceTests.java │ │ │ │ │ ├── NamespacePasswordEncoderTests.java │ │ │ │ │ ├── PasswordEncoderConfigurerTests.java │ │ │ │ │ ├── configuration/ │ │ │ │ │ │ ├── AuthenticationConfigurationPublishTests.java │ │ │ │ │ │ ├── AuthenticationConfigurationTests.java │ │ │ │ │ │ ├── AuthenticationManagerBeanRegistrationAotProcessorTests.java │ │ │ │ │ │ └── EnableGlobalAuthenticationTests.java │ │ │ │ │ └── configurers/ │ │ │ │ │ ├── ldap/ │ │ │ │ │ │ └── LdapAuthenticationProviderConfigurerTests.java │ │ │ │ │ └── provisioning/ │ │ │ │ │ └── UserDetailsManagerConfigurerTests.java │ │ │ │ ├── authorization/ │ │ │ │ │ ├── EnableMultiFactorAuthenticationCustomizerTests.java │ │ │ │ │ ├── EnableMultiFactorAuthenticationFiltersSetTests.java │ │ │ │ │ ├── EnableMultiFactorAuthenticationMultipleCustomizersTests.java │ │ │ │ │ ├── EnableMultiFactorAuthenticationTests.java │ │ │ │ │ └── MultiFactorAuthenticationSelectorTests.java │ │ │ │ ├── configuration/ │ │ │ │ │ ├── AroundMethodInterceptor.java │ │ │ │ │ ├── AutowireBeanFactoryObjectPostProcessorTests.java │ │ │ │ │ └── MyAdvisedBean.java │ │ │ │ ├── issue50/ │ │ │ │ │ ├── ApplicationConfig.java │ │ │ │ │ ├── Issue50Tests.java │ │ │ │ │ ├── SecurityConfig.java │ │ │ │ │ ├── domain/ │ │ │ │ │ │ └── User.java │ │ │ │ │ └── repo/ │ │ │ │ │ └── UserRepository.java │ │ │ │ ├── method/ │ │ │ │ │ └── configuration/ │ │ │ │ │ ├── AuthorizationProxyConfigurationTests.java │ │ │ │ │ ├── Authz.java │ │ │ │ │ ├── DelegatingReactiveMessageService.java │ │ │ │ │ ├── EnableAuthorizationManagerReactiveMethodSecurityTests.java │ │ │ │ │ ├── EnableCustomMethodSecurity.java │ │ │ │ │ ├── EnableReactiveMethodSecurityTests.java │ │ │ │ │ ├── GlobalMethodSecurityConfigurationTests.java │ │ │ │ │ ├── MethodSecurityService.java │ │ │ │ │ ├── MethodSecurityServiceConfig.java │ │ │ │ │ ├── MethodSecurityServiceImpl.java │ │ │ │ │ ├── MyMasker.java │ │ │ │ │ ├── NamespaceGlobalMethodSecurityExpressionHandlerTests.java │ │ │ │ │ ├── NamespaceGlobalMethodSecurityTests.java │ │ │ │ │ ├── PrePostMethodSecurityConfigurationTests.java │ │ │ │ │ ├── PrePostReactiveMethodSecurityConfigurationTests.java │ │ │ │ │ ├── ReactiveMessageService.java │ │ │ │ │ ├── ReactiveMethodSecurityConfigurationTests.java │ │ │ │ │ ├── ReactiveMethodSecurityService.java │ │ │ │ │ ├── ReactiveMethodSecurityServiceImpl.java │ │ │ │ │ ├── RequireAdminRole.java │ │ │ │ │ ├── RequireUserRole.java │ │ │ │ │ ├── SampleEnableGlobalMethodSecurityTests.java │ │ │ │ │ ├── UserRecordWithEmailProtected.java │ │ │ │ │ ├── aot/ │ │ │ │ │ │ ├── EnableMethodSecurityAotTests.java │ │ │ │ │ │ ├── Message.java │ │ │ │ │ │ ├── MessageRepository.java │ │ │ │ │ │ ├── User.java │ │ │ │ │ │ └── UserProjection.java │ │ │ │ │ └── issue14637/ │ │ │ │ │ ├── ApplicationConfig.java │ │ │ │ │ ├── Issue14637Tests.java │ │ │ │ │ ├── SecurityConfig.java │ │ │ │ │ ├── domain/ │ │ │ │ │ │ └── Entry.java │ │ │ │ │ └── repo/ │ │ │ │ │ └── EntryRepository.java │ │ │ │ ├── sec2758/ │ │ │ │ │ └── Sec2758Tests.java │ │ │ │ └── web/ │ │ │ │ ├── AbstractConfiguredSecurityBuilderTests.java │ │ │ │ ├── AbstractRequestMatcherRegistryAnyMatcherTests.java │ │ │ │ ├── AbstractRequestMatcherRegistryNoMvcTests.java │ │ │ │ ├── AbstractRequestMatcherRegistryTests.java │ │ │ │ ├── HttpSecurityHeadersTests.java │ │ │ │ ├── builders/ │ │ │ │ │ ├── FilterOrderRegistrationTests.java │ │ │ │ │ ├── HttpConfigurationTests.java │ │ │ │ │ ├── HttpSecurityAuthenticationManagerTests.java │ │ │ │ │ ├── HttpSecurityDeferAddFilterTests.java │ │ │ │ │ ├── NamespaceHttpTests.java │ │ │ │ │ ├── TestHttpSecurities.java │ │ │ │ │ ├── WebSecurityFilterChainValidatorTests.java │ │ │ │ │ └── WebSecurityTests.java │ │ │ │ ├── configuration/ │ │ │ │ │ ├── AuthenticationPrincipalArgumentResolverTests.java │ │ │ │ │ ├── AuthorizationManagerWebInvocationPrivilegeEvaluatorConfigTests.java │ │ │ │ │ ├── DeferHttpSessionJavaConfigTests.java │ │ │ │ │ ├── EnableWebSecurityTests.java │ │ │ │ │ ├── HttpSecurityConfigurationTests.java │ │ │ │ │ ├── OAuth2AuthorizationServerConfigurationTests.java │ │ │ │ │ ├── OAuth2AuthorizedClientManagerConfigurationTests.java │ │ │ │ │ ├── OAuth2ClientConfigurationTests.java │ │ │ │ │ ├── RegisterMissingBeanPostProcessorTests.java │ │ │ │ │ ├── SecurityReactorContextConfigurationResourceServerTests.java │ │ │ │ │ ├── SecurityReactorContextConfigurationTests.java │ │ │ │ │ ├── WebMvcSecurityConfigurationTests.java │ │ │ │ │ ├── WebSecurityConfigurationTests.java │ │ │ │ │ └── sec2377/ │ │ │ │ │ ├── Sec2377Tests.java │ │ │ │ │ ├── a/ │ │ │ │ │ │ └── Sec2377AConfig.java │ │ │ │ │ └── b/ │ │ │ │ │ └── Sec2377BConfig.java │ │ │ │ ├── configurers/ │ │ │ │ │ ├── AbstractConfigAttributeRequestMatcherRegistryTests.java │ │ │ │ │ ├── AnonymousConfigurerTests.java │ │ │ │ │ ├── AuthorizeHttpRequestsConfigurerTests.java │ │ │ │ │ ├── ChannelSecurityConfigurerTests.java │ │ │ │ │ ├── CorsConfigurerTests.java │ │ │ │ │ ├── CsrfConfigurerIgnoringRequestMatchersTests.java │ │ │ │ │ ├── CsrfConfigurerNoWebMvcTests.java │ │ │ │ │ ├── CsrfConfigurerTests.java │ │ │ │ │ ├── DefaultFiltersTests.java │ │ │ │ │ ├── DefaultLoginPageConfigurerTests.java │ │ │ │ │ ├── ExceptionHandlingConfigurerAccessDeniedHandlerTests.java │ │ │ │ │ ├── ExceptionHandlingConfigurerTests.java │ │ │ │ │ ├── FormLoginConfigurerTests.java │ │ │ │ │ ├── HeadersConfigurerEagerHeadersTests.java │ │ │ │ │ ├── HeadersConfigurerTests.java │ │ │ │ │ ├── HttpBasicConfigurerTests.java │ │ │ │ │ ├── HttpSecurityLogoutTests.java │ │ │ │ │ ├── HttpSecurityObservationTests.java │ │ │ │ │ ├── HttpSecurityRequestMatchersTests.java │ │ │ │ │ ├── HttpSecuritySecurityMatchersNoMvcTests.java │ │ │ │ │ ├── HttpSecuritySecurityMatchersTests.java │ │ │ │ │ ├── HttpsRedirectConfigurerTests.java │ │ │ │ │ ├── JeeConfigurerTests.java │ │ │ │ │ ├── LogoutConfigurerClearSiteDataTests.java │ │ │ │ │ ├── LogoutConfigurerTests.java │ │ │ │ │ ├── NamespaceDebugTests.java │ │ │ │ │ ├── NamespaceHttpAnonymousTests.java │ │ │ │ │ ├── NamespaceHttpBasicTests.java │ │ │ │ │ ├── NamespaceHttpCustomFilterTests.java │ │ │ │ │ ├── NamespaceHttpExpressionHandlerTests.java │ │ │ │ │ ├── NamespaceHttpFirewallTests.java │ │ │ │ │ ├── NamespaceHttpFormLoginTests.java │ │ │ │ │ ├── NamespaceHttpHeadersTests.java │ │ │ │ │ ├── NamespaceHttpInterceptUrlTests.java │ │ │ │ │ ├── NamespaceHttpJeeTests.java │ │ │ │ │ ├── NamespaceHttpLogoutTests.java │ │ │ │ │ ├── NamespaceHttpPortMappingsTests.java │ │ │ │ │ ├── NamespaceHttpRequestCacheTests.java │ │ │ │ │ ├── NamespaceHttpServerAccessDeniedHandlerTests.java │ │ │ │ │ ├── NamespaceHttpX509Tests.java │ │ │ │ │ ├── NamespaceRememberMeTests.java │ │ │ │ │ ├── NamespaceSessionManagementTests.java │ │ │ │ │ ├── PasswordManagementConfigurerTests.java │ │ │ │ │ ├── PermitAllSupportTests.java │ │ │ │ │ ├── PortMapperConfigurerTests.java │ │ │ │ │ ├── RememberMeConfigurerTests.java │ │ │ │ │ ├── RequestCacheConfigurerTests.java │ │ │ │ │ ├── RequestMatcherConfigurerTests.java │ │ │ │ │ ├── SecurityContextConfigurerTests.java │ │ │ │ │ ├── ServletApiConfigurerTests.java │ │ │ │ │ ├── SessionManagementConfigurerServlet31Tests.java │ │ │ │ │ ├── SessionManagementConfigurerSessionAuthenticationStrategyTests.java │ │ │ │ │ ├── SessionManagementConfigurerSessionCreationPolicyTests.java │ │ │ │ │ ├── SessionManagementConfigurerTests.java │ │ │ │ │ ├── SessionManagementConfigurerTransientAuthenticationTests.java │ │ │ │ │ ├── UrlAuthorizationsTests.java │ │ │ │ │ ├── WebAuthnConfigurerTests.java │ │ │ │ │ ├── X509ConfigurerTests.java │ │ │ │ │ ├── oauth2/ │ │ │ │ │ │ ├── client/ │ │ │ │ │ │ │ ├── OAuth2ClientConfigurerTests.java │ │ │ │ │ │ │ ├── OAuth2LoginConfigurerTests.java │ │ │ │ │ │ │ ├── OidcBackChannelLogoutHandlerTests.java │ │ │ │ │ │ │ ├── OidcLogoutConfigurerTests.java │ │ │ │ │ │ │ ├── OidcUserRefreshedEventListenerConfigurationTests.java │ │ │ │ │ │ │ └── OidcUserRefreshedEventListenerTests.java │ │ │ │ │ │ └── server/ │ │ │ │ │ │ ├── authorization/ │ │ │ │ │ │ │ ├── AuthorizationServerContextFilterTests.java │ │ │ │ │ │ │ ├── DefaultOAuth2TokenCustomizersTests.java │ │ │ │ │ │ │ ├── JwkSetTests.java │ │ │ │ │ │ │ ├── OAuth2AuthorizationCodeGrantTests.java │ │ │ │ │ │ │ ├── OAuth2AuthorizationServerMetadataTests.java │ │ │ │ │ │ │ ├── OAuth2ClientCredentialsGrantTests.java │ │ │ │ │ │ │ ├── OAuth2ClientRegistrationTests.java │ │ │ │ │ │ │ ├── OAuth2DeviceCodeGrantTests.java │ │ │ │ │ │ │ ├── OAuth2RefreshTokenGrantTests.java │ │ │ │ │ │ │ ├── OAuth2TokenExchangeGrantTests.java │ │ │ │ │ │ │ ├── OAuth2TokenIntrospectionTests.java │ │ │ │ │ │ │ ├── OAuth2TokenRevocationTests.java │ │ │ │ │ │ │ ├── OidcClientRegistrationTests.java │ │ │ │ │ │ │ ├── OidcProviderConfigurationTests.java │ │ │ │ │ │ │ ├── OidcTests.java │ │ │ │ │ │ │ └── OidcUserInfoTests.java │ │ │ │ │ │ └── resource/ │ │ │ │ │ │ ├── DPoPAuthenticationConfigurerTests.java │ │ │ │ │ │ ├── OAuth2ProtectedResourceMetadataTests.java │ │ │ │ │ │ └── OAuth2ResourceServerConfigurerTests.java │ │ │ │ │ ├── ott/ │ │ │ │ │ │ └── OneTimeTokenLoginConfigurerTests.java │ │ │ │ │ └── saml2/ │ │ │ │ │ ├── Saml2LoginConfigurerTests.java │ │ │ │ │ ├── Saml2LogoutConfigurerTests.java │ │ │ │ │ ├── Saml2MetadataConfigurerTests.java │ │ │ │ │ └── TestSaml2Credentials.java │ │ │ │ ├── reactive/ │ │ │ │ │ ├── EnableWebFluxSecurityTests.java │ │ │ │ │ ├── ReactiveOAuth2AuthorizedClientManagerConfigurationTests.java │ │ │ │ │ ├── ReactiveOAuth2ClientImportSelectorTests.java │ │ │ │ │ ├── ServerHttpSecurityConfigurationBuilder.java │ │ │ │ │ ├── ServerHttpSecurityConfigurationTests.java │ │ │ │ │ └── WebFluxSecurityConfigurationTests.java │ │ │ │ └── socket/ │ │ │ │ ├── SyncExecutorSubscribableChannelPostProcessor.java │ │ │ │ ├── TestDeferredCsrfToken.java │ │ │ │ ├── WebSocketMessageBrokerSecurityConfigurationDocTests.java │ │ │ │ └── WebSocketMessageBrokerSecurityConfigurationTests.java │ │ │ ├── aot/ │ │ │ │ └── hint/ │ │ │ │ ├── OAuth2LoginRuntimeHintsTests.java │ │ │ │ ├── WebMvcSecurityConfigurationRuntimeHintsTests.java │ │ │ │ └── WebSecurityConfigurationRuntimeHintsTests.java │ │ │ ├── authentication/ │ │ │ │ ├── AuthenticationConfigurationGh3935Tests.java │ │ │ │ ├── AuthenticationManagerBeanDefinitionParserTests.java │ │ │ │ ├── AuthenticationProviderBeanDefinitionParserTests.java │ │ │ │ ├── JdbcUserServiceBeanDefinitionParserTests.java │ │ │ │ ├── PasswordEncoderParserTests.java │ │ │ │ └── UserServiceBeanDefinitionParserTests.java │ │ │ ├── core/ │ │ │ │ ├── GrantedAuthorityDefaultsJcTests.java │ │ │ │ ├── GrantedAuthorityDefaultsXmlTests.java │ │ │ │ ├── HelloWorldMessageService.java │ │ │ │ ├── MessageService.java │ │ │ │ └── userdetails/ │ │ │ │ ├── ReactiveUserDetailsServiceResourceFactoryBeanPropertiesResourceITests.java │ │ │ │ ├── ReactiveUserDetailsServiceResourceFactoryBeanPropertiesResourceLocationITests.java │ │ │ │ ├── ReactiveUserDetailsServiceResourceFactoryBeanStringITests.java │ │ │ │ └── UserDetailsResourceFactoryBeanTests.java │ │ │ ├── crypto/ │ │ │ │ └── RsaKeyConversionServicePostProcessorTests.java │ │ │ ├── debug/ │ │ │ │ ├── AuthProviderDependency.java │ │ │ │ ├── SecurityDebugBeanFactoryPostProcessorTests.java │ │ │ │ └── TestAuthenticationProvider.java │ │ │ ├── doc/ │ │ │ │ ├── Attribute.java │ │ │ │ ├── Element.java │ │ │ │ ├── SpringSecurityXsdParser.java │ │ │ │ ├── XmlNode.java │ │ │ │ ├── XmlParser.java │ │ │ │ ├── XmlSupport.java │ │ │ │ └── XsdDocumentedTests.java │ │ │ ├── http/ │ │ │ │ ├── AccessDeniedConfigTests.java │ │ │ │ ├── CsrfBeanDefinitionParserTests.java │ │ │ │ ├── CsrfConfigTests.java │ │ │ │ ├── DefaultFilterChainValidatorTests.java │ │ │ │ ├── DeferHttpSessionXmlConfigTests.java │ │ │ │ ├── FilterSecurityMetadataSourceBeanDefinitionParserTests.java │ │ │ │ ├── FormLoginBeanDefinitionParserTests.java │ │ │ │ ├── FormLoginConfigTests.java │ │ │ │ ├── HttpConfigTests.java │ │ │ │ ├── HttpCorsConfigTests.java │ │ │ │ ├── HttpHeadersConfigTests.java │ │ │ │ ├── HttpInterceptUrlTests.java │ │ │ │ ├── InterceptUrlConfigTests.java │ │ │ │ ├── MiscHttpConfigTests.java │ │ │ │ ├── MultiHttpBlockConfigTests.java │ │ │ │ ├── NamespaceHttpBasicTests.java │ │ │ │ ├── OAuth2AuthorizedClientManagerRegistrarTests.java │ │ │ │ ├── OAuth2ClientBeanDefinitionParserTests.java │ │ │ │ ├── OAuth2LoginBeanDefinitionParserTests.java │ │ │ │ ├── OAuth2ResourceServerBeanDefinitionParserTests.java │ │ │ │ ├── PlaceHolderAndELConfigTests.java │ │ │ │ ├── RememberMeConfigTests.java │ │ │ │ ├── Saml2LoginBeanDefinitionParserTests.java │ │ │ │ ├── Saml2LogoutBeanDefinitionParserTests.java │ │ │ │ ├── SecurityContextHolderAwareRequestConfigTests.java │ │ │ │ ├── SecurityFiltersAssertions.java │ │ │ │ ├── SessionManagementConfigServlet31Tests.java │ │ │ │ ├── SessionManagementConfigTests.java │ │ │ │ ├── SessionManagementConfigTransientAuthenticationTests.java │ │ │ │ ├── WebConfigUtilsTests.java │ │ │ │ ├── WellKnownChangePasswordBeanDefinitionParserTests.java │ │ │ │ └── customconfigurer/ │ │ │ │ ├── CustomConfigurer.java │ │ │ │ └── CustomHttpSecurityConfigurerTests.java │ │ │ ├── method/ │ │ │ │ ├── Contact.java │ │ │ │ ├── ContactPermission.java │ │ │ │ ├── GlobalMethodSecurityBeanDefinitionParserTests.java │ │ │ │ ├── InterceptMethodsBeanDefinitionDecoratorTests.java │ │ │ │ ├── Jsr250AnnotationDrivenBeanDefinitionParserTests.java │ │ │ │ ├── MethodSecurityBeanDefinitionParserTests.java │ │ │ │ ├── PreAuthorizeAdminRole.java │ │ │ │ ├── PreAuthorizeServiceImpl.java │ │ │ │ ├── PreAuthorizeTests.java │ │ │ │ ├── Sec2196Tests.java │ │ │ │ ├── SecuredAdminRole.java │ │ │ │ ├── SecuredAnnotationDrivenBeanDefinitionParserTests.java │ │ │ │ ├── SecuredServiceImpl.java │ │ │ │ ├── SecuredTests.java │ │ │ │ ├── TestPermissionEvaluator.java │ │ │ │ ├── configuration/ │ │ │ │ │ └── Gh4020GlobalMethodSecurityConfigurationTests.java │ │ │ │ ├── sec2136/ │ │ │ │ │ ├── JpaPermissionEvaluator.java │ │ │ │ │ └── Sec2136Tests.java │ │ │ │ └── sec2499/ │ │ │ │ └── Sec2499Tests.java │ │ │ ├── oauth2/ │ │ │ │ └── client/ │ │ │ │ ├── ClientRegistrationsBeanDefinitionParserTests.java │ │ │ │ └── CommonOAuth2ProviderTests.java │ │ │ ├── observation/ │ │ │ │ └── SecurityObservationSettingsTests.java │ │ │ ├── provisioning/ │ │ │ │ ├── UserDetailsManagerResourceFactoryBeanPropertiesResourceITests.java │ │ │ │ ├── UserDetailsManagerResourceFactoryBeanPropertiesResourceLocationITests.java │ │ │ │ └── UserDetailsManagerResourceFactoryBeanStringITests.java │ │ │ ├── saml2/ │ │ │ │ └── RelyingPartyRegistrationsBeanDefinitionParserTests.java │ │ │ ├── test/ │ │ │ │ ├── SpringTestContext.java │ │ │ │ ├── SpringTestContextExtension.java │ │ │ │ └── SpringTestParentApplicationContextExecutionListener.java │ │ │ ├── users/ │ │ │ │ ├── AuthenticationTestConfiguration.java │ │ │ │ └── ReactiveAuthenticationTestConfiguration.java │ │ │ ├── util/ │ │ │ │ ├── InMemoryXmlApplicationContext.java │ │ │ │ ├── InMemoryXmlWebApplicationContext.java │ │ │ │ └── SpringSecurityVersions.java │ │ │ ├── web/ │ │ │ │ ├── PathPatternRequestMatcherBuilderFactoryBeanTests.java │ │ │ │ └── server/ │ │ │ │ ├── AuthorizeExchangeSpecTests.java │ │ │ │ ├── CorsSpecTests.java │ │ │ │ ├── ExceptionHandlingSpecTests.java │ │ │ │ ├── FormLoginTests.java │ │ │ │ ├── HeaderSpecTests.java │ │ │ │ ├── HttpsRedirectSpecTests.java │ │ │ │ ├── LogoutSpecTests.java │ │ │ │ ├── OAuth2ClientSpecTests.java │ │ │ │ ├── OAuth2LoginTests.java │ │ │ │ ├── OAuth2ResourceServerSpecTests.java │ │ │ │ ├── OidcBackChannelServerLogoutHandlerTests.java │ │ │ │ ├── OidcLogoutSpecTests.java │ │ │ │ ├── OneTimeTokenLoginSpecTests.java │ │ │ │ ├── PasswordManagementSpecTests.java │ │ │ │ ├── RequestCacheTests.java │ │ │ │ ├── ServerHttpSecurityTests.java │ │ │ │ ├── SessionManagementSpecTests.java │ │ │ │ └── TestingServerHttpSecurity.java │ │ │ └── websocket/ │ │ │ ├── MessageSecurityPostProcessorTests.java │ │ │ └── WebSocketMessageBrokerConfigTests.java │ │ ├── htmlunit/ │ │ │ └── server/ │ │ │ ├── HtmlUnitWebTestClient.java │ │ │ ├── MockWebResponseBuilder.java │ │ │ ├── WebTestClientHtmlUnitDriverBuilder.java │ │ │ ├── WebTestClientHtmlUnitDriverBuilderTests.java │ │ │ └── WebTestClientWebConnection.java │ │ ├── intercept/ │ │ │ └── method/ │ │ │ └── aopalliance/ │ │ │ └── MethodSecurityInterceptorWithAopConfigTests.java │ │ └── test/ │ │ ├── support/ │ │ │ ├── ClassPathExclusions.java │ │ │ ├── ClassPathOverrides.java │ │ │ ├── ForkedClassPath.java │ │ │ ├── ModifiedClassPathClassLoader.java │ │ │ └── ModifiedClassPathExtension.java │ │ └── web/ │ │ └── servlet/ │ │ └── RequestCacheResultMatcher.java │ ├── kotlin/ │ │ └── org/ │ │ └── springframework/ │ │ └── security/ │ │ └── config/ │ │ ├── annotation/ │ │ │ ├── method/ │ │ │ │ └── configuration/ │ │ │ │ ├── KotlinEnableReactiveMethodSecurityTests.kt │ │ │ │ ├── KotlinReactiveMessageService.kt │ │ │ │ └── KotlinReactiveMessageServiceImpl.kt │ │ │ └── web/ │ │ │ ├── AnonymousDslTests.kt │ │ │ ├── AuthorizeHttpRequestsDslTests.kt │ │ │ ├── CorsDslTests.kt │ │ │ ├── CsrfDslTests.kt │ │ │ ├── ExceptionHandlingDslTests.kt │ │ │ ├── FormLoginDslTests.kt │ │ │ ├── HeadersDslTests.kt │ │ │ ├── HttpBasicDslTests.kt │ │ │ ├── HttpSecurityDslTests.kt │ │ │ ├── HttpsRedirectDslTests.kt │ │ │ ├── LogoutDslTests.kt │ │ │ ├── OAuth2ClientDslTests.kt │ │ │ ├── OAuth2LoginDslTests.kt │ │ │ ├── OAuth2ResourceServerDslTests.kt │ │ │ ├── OidcLogoutDslTests.kt │ │ │ ├── OneTimeTokenLoginDslTests.kt │ │ │ ├── PasswordManagementDslTests.kt │ │ │ ├── PortMapperDslTests.kt │ │ │ ├── RememberMeDslTests.kt │ │ │ ├── RequestCacheDslTests.kt │ │ │ ├── RequiresChannelDslTests.kt │ │ │ ├── Saml2DslTests.kt │ │ │ ├── Saml2LogoutDslTests.kt │ │ │ ├── Saml2MetadataDslTests.kt │ │ │ ├── SecurityContextDslTests.kt │ │ │ ├── SessionManagementDslTests.kt │ │ │ ├── WebAuthnDslTests.kt │ │ │ ├── X509DslTests.kt │ │ │ ├── headers/ │ │ │ │ ├── CacheControlDslTests.kt │ │ │ │ ├── ContentSecurityPolicyDslTests.kt │ │ │ │ ├── ContentTypeOptionsDslTests.kt │ │ │ │ ├── FrameOptionsDslTests.kt │ │ │ │ ├── HttpPublicKeyPinningDslTests.kt │ │ │ │ ├── HttpStrictTransportSecurityDslTests.kt │ │ │ │ ├── ReferrerPolicyDslTests.kt │ │ │ │ └── XssProtectionConfigDslTests.kt │ │ │ ├── oauth2/ │ │ │ │ ├── client/ │ │ │ │ │ └── AuthorizationCodeGrantDslTests.kt │ │ │ │ ├── login/ │ │ │ │ │ ├── AuthorizationEndpointDslTests.kt │ │ │ │ │ ├── RedirectionEndpointDslTests.kt │ │ │ │ │ ├── TokenEndpointDslTests.kt │ │ │ │ │ └── UserInfoEndpointDslTests.kt │ │ │ │ └── resourceserver/ │ │ │ │ ├── JwtDslTests.kt │ │ │ │ └── OpaqueTokenDslTests.kt │ │ │ └── session/ │ │ │ ├── SessionConcurrencyDslTests.kt │ │ │ └── SessionFixationDslTests.kt │ │ └── web/ │ │ └── server/ │ │ ├── AuthorizeExchangeDslTests.kt │ │ ├── ServerAnonymousDslTests.kt │ │ ├── ServerCacheControlDslTests.kt │ │ ├── ServerContentSecurityPolicyDslTests.kt │ │ ├── ServerContentTypeOptionsDslTests.kt │ │ ├── ServerCorsDslTests.kt │ │ ├── ServerCsrfDslTests.kt │ │ ├── ServerExceptionHandlingDslTests.kt │ │ ├── ServerFormLoginDslTests.kt │ │ ├── ServerFrameOptionsDslTests.kt │ │ ├── ServerHeadersDslTests.kt │ │ ├── ServerHttpBasicDslTests.kt │ │ ├── ServerHttpSecurityDslTests.kt │ │ ├── ServerHttpStrictTransportSecurityDslTests.kt │ │ ├── ServerHttpsRedirectDslTests.kt │ │ ├── ServerJwtDslTests.kt │ │ ├── ServerLogoutDslTests.kt │ │ ├── ServerOAuth2ClientDslTests.kt │ │ ├── ServerOAuth2LoginDslTests.kt │ │ ├── ServerOAuth2ResourceServerDslTests.kt │ │ ├── ServerOidcLogoutDslTests.kt │ │ ├── ServerOneTimeTokenLoginDslTests.kt │ │ ├── ServerOpaqueTokenDslTests.kt │ │ ├── ServerPasswordManagementDslTests.kt │ │ ├── ServerPermissionsPolicyDslTests.kt │ │ ├── ServerReferrerPolicyDslTests.kt │ │ ├── ServerRequestCacheDslTests.kt │ │ ├── ServerSessionManagementDslTests.kt │ │ ├── ServerX509DslTests.kt │ │ └── ServerXssProtectionDslTests.kt │ └── resources/ │ ├── CustomJdbcUserServiceSampleConfig.sql │ ├── logback-test.xml │ ├── org/ │ │ └── springframework/ │ │ └── security/ │ │ ├── config/ │ │ │ ├── annotation/ │ │ │ │ ├── configuration/ │ │ │ │ │ └── AutowireBeanFactoryObjectPostProcessorTests-aopconfig.xml │ │ │ │ └── web/ │ │ │ │ ├── configuration/ │ │ │ │ │ ├── simple.priv │ │ │ │ │ └── simple.pub │ │ │ │ └── configurers/ │ │ │ │ └── oauth2/ │ │ │ │ └── server/ │ │ │ │ └── resource/ │ │ │ │ ├── OAuth2ResourceServerConfigurerTests-Active.json │ │ │ │ ├── OAuth2ResourceServerConfigurerTests-ActiveNoScopes.json │ │ │ │ ├── OAuth2ResourceServerConfigurerTests-Default.jwks │ │ │ │ ├── OAuth2ResourceServerConfigurerTests-Empty.jwks │ │ │ │ ├── OAuth2ResourceServerConfigurerTests-Expired.token │ │ │ │ ├── OAuth2ResourceServerConfigurerTests-ExpiresAt4687177990.token │ │ │ │ ├── OAuth2ResourceServerConfigurerTests-Inactive.json │ │ │ │ ├── OAuth2ResourceServerConfigurerTests-Kid.token │ │ │ │ ├── OAuth2ResourceServerConfigurerTests-MalformedPayload.token │ │ │ │ ├── OAuth2ResourceServerConfigurerTests-TooEarly.token │ │ │ │ ├── OAuth2ResourceServerConfigurerTests-TwoKeys.jwks │ │ │ │ ├── OAuth2ResourceServerConfigurerTests-Unsigned.token │ │ │ │ ├── OAuth2ResourceServerConfigurerTests-ValidMessageReadScope.token │ │ │ │ ├── OAuth2ResourceServerConfigurerTests-ValidMessageReadScp.token │ │ │ │ ├── OAuth2ResourceServerConfigurerTests-ValidMessageWriteScp.token │ │ │ │ ├── OAuth2ResourceServerConfigurerTests-ValidNoScopes.token │ │ │ │ ├── OAuth2ResourceServerConfigurerTests-WrongAlgorithm.token │ │ │ │ └── OAuth2ResourceServerConfigurerTests-WrongSignature.token │ │ │ ├── authentication/ │ │ │ │ ├── PasswordEncoderParserTests-bean.xml │ │ │ │ └── PasswordEncoderParserTests-default.xml │ │ │ ├── core/ │ │ │ │ └── GrantedAuthorityDefaultsXmlTests-context.xml │ │ │ ├── debug/ │ │ │ │ └── SecurityDebugBeanFactoryPostProcessorTests-context.xml │ │ │ ├── http/ │ │ │ │ ├── AccessDeniedConfigTests-AccessDeniedHandler.xml │ │ │ │ ├── AccessDeniedConfigTests-NoLeadingSlash.xml │ │ │ │ ├── AccessDeniedConfigTests-UsesPathAndRef.xml │ │ │ │ ├── CsrfBeanDefinitionParserTests-RegisterDataValueProcessorOnyIfNotRegistered.xml │ │ │ │ ├── CsrfConfigTests-AutoConfig.xml │ │ │ │ ├── CsrfConfigTests-CsrfDisabled.xml │ │ │ │ ├── CsrfConfigTests-CsrfEnabled.xml │ │ │ │ ├── CsrfConfigTests-WithAccessDeniedHandler.xml │ │ │ │ ├── CsrfConfigTests-WithRequestAttrName.xml │ │ │ │ ├── CsrfConfigTests-WithRequestMatcher.xml │ │ │ │ ├── CsrfConfigTests-WithSessionManagement.xml │ │ │ │ ├── CsrfConfigTests-WithXorCsrfTokenRequestAttributeHandler.xml │ │ │ │ ├── CsrfConfigTests-mock-csrf-token-repository.xml │ │ │ │ ├── CsrfConfigTests-mock-request-matcher.xml │ │ │ │ ├── CsrfConfigTests-shared-access-denied-handler.xml │ │ │ │ ├── CsrfConfigTests-shared-controllers.xml │ │ │ │ ├── CsrfConfigTests-shared-csrf-token-repository.xml │ │ │ │ ├── CsrfConfigTests-shared-userservice.xml │ │ │ │ ├── DeferHttpSessionTests-Explicit.xml │ │ │ │ ├── FormLoginBeanDefinitionParserTests-AutoConfig.xml │ │ │ │ ├── FormLoginBeanDefinitionParserTests-Simple.xml │ │ │ │ ├── FormLoginBeanDefinitionParserTests-WithAuthenticationFailureForwardUrl.xml │ │ │ │ ├── FormLoginBeanDefinitionParserTests-WithAuthenticationSuccessForwardUrl.xml │ │ │ │ ├── FormLoginBeanDefinitionParserTests-WithCustomAttributes.xml │ │ │ │ ├── FormLoginConfigTests-ForSec2919.xml │ │ │ │ ├── FormLoginConfigTests-ForSec3147.xml │ │ │ │ ├── FormLoginConfigTests-NoLeadingSlashDefaultTargetUrl.xml │ │ │ │ ├── FormLoginConfigTests-NoLeadingSlashLoginPage.xml │ │ │ │ ├── FormLoginConfigTests-UsingSpel.xml │ │ │ │ ├── FormLoginConfigTests-WithCsrfDisabled.xml │ │ │ │ ├── FormLoginConfigTests-WithCsrfEnabled.xml │ │ │ │ ├── FormLoginConfigTests-WithCustomSecurityContextHolderStrategy.xml │ │ │ │ ├── FormLoginConfigTests-WithDefaultTargetUrl.xml │ │ │ │ ├── FormLoginConfigTests-WithRequestMatcher.xml │ │ │ │ ├── FormLoginConfigTests-WithSuccessAndFailureHandlers.xml │ │ │ │ ├── FormLoginConfigTests-WithUsernameAndPasswordParameters.xml │ │ │ │ ├── HttpConfigTests-AuthorizationManager.xml │ │ │ │ ├── HttpConfigTests-Minimal.xml │ │ │ │ ├── HttpConfigTests-MinimalAuthorizationManager.xml │ │ │ │ ├── HttpConfigTests-WithObservationRegistry.xml │ │ │ │ ├── HttpCorsConfigTests-RequiresMvc.xml │ │ │ │ ├── HttpCorsConfigTests-WithCors.xml │ │ │ │ ├── HttpCorsConfigTests-WithCorsConfigurationSource.xml │ │ │ │ ├── HttpCorsConfigTests-WithCorsFilter.xml │ │ │ │ ├── HttpHeadersConfigTests-CacheControlDisabled.xml │ │ │ │ ├── HttpHeadersConfigTests-ContentSecurityPolicyWithEmptyDirectives.xml │ │ │ │ ├── HttpHeadersConfigTests-ContentSecurityPolicyWithPolicyDirectives.xml │ │ │ │ ├── HttpHeadersConfigTests-ContentSecurityPolicyWithReportOnly.xml │ │ │ │ ├── HttpHeadersConfigTests-ContentTypeOptionsDisabled.xml │ │ │ │ ├── HttpHeadersConfigTests-DefaultConfig.xml │ │ │ │ ├── HttpHeadersConfigTests-DefaultsDisabledWithCacheControl.xml │ │ │ │ ├── HttpHeadersConfigTests-DefaultsDisabledWithContentSecurityPolicy.xml │ │ │ │ ├── HttpHeadersConfigTests-DefaultsDisabledWithContentTypeOptions.xml │ │ │ │ ├── HttpHeadersConfigTests-DefaultsDisabledWithCrossOriginEmbedderPolicy.xml │ │ │ │ ├── HttpHeadersConfigTests-DefaultsDisabledWithCrossOriginOpenerPolicy.xml │ │ │ │ ├── HttpHeadersConfigTests-DefaultsDisabledWithCrossOriginPolicies.xml │ │ │ │ ├── HttpHeadersConfigTests-DefaultsDisabledWithCrossOriginResourcePolicy.xml │ │ │ │ ├── HttpHeadersConfigTests-DefaultsDisabledWithCustomHeader.xml │ │ │ │ ├── HttpHeadersConfigTests-DefaultsDisabledWithCustomHeaderWriter.xml │ │ │ │ ├── HttpHeadersConfigTests-DefaultsDisabledWithCustomHstsRequestMatcher.xml │ │ │ │ ├── HttpHeadersConfigTests-DefaultsDisabledWithEmptyHpkp.xml │ │ │ │ ├── HttpHeadersConfigTests-DefaultsDisabledWithEmptyPins.xml │ │ │ │ ├── HttpHeadersConfigTests-DefaultsDisabledWithFrameOptions.xml │ │ │ │ ├── HttpHeadersConfigTests-DefaultsDisabledWithFrameOptionsAllowFrom.xml │ │ │ │ ├── HttpHeadersConfigTests-DefaultsDisabledWithFrameOptionsAllowFromBlankOrigin.xml │ │ │ │ ├── HttpHeadersConfigTests-DefaultsDisabledWithFrameOptionsAllowFromNoOrigin.xml │ │ │ │ ├── HttpHeadersConfigTests-DefaultsDisabledWithFrameOptionsAllowFromWhitelist.xml │ │ │ │ ├── HttpHeadersConfigTests-DefaultsDisabledWithFrameOptionsDeny.xml │ │ │ │ ├── HttpHeadersConfigTests-DefaultsDisabledWithFrameOptionsSameOrigin.xml │ │ │ │ ├── HttpHeadersConfigTests-DefaultsDisabledWithHpkp.xml │ │ │ │ ├── HttpHeadersConfigTests-DefaultsDisabledWithHpkpDefaults.xml │ │ │ │ ├── HttpHeadersConfigTests-DefaultsDisabledWithHpkpIncludeSubdomains.xml │ │ │ │ ├── HttpHeadersConfigTests-DefaultsDisabledWithHpkpMaxAge.xml │ │ │ │ ├── HttpHeadersConfigTests-DefaultsDisabledWithHpkpReport.xml │ │ │ │ ├── HttpHeadersConfigTests-DefaultsDisabledWithHpkpReportUri.xml │ │ │ │ ├── HttpHeadersConfigTests-DefaultsDisabledWithHsts.xml │ │ │ │ ├── HttpHeadersConfigTests-DefaultsDisabledWithNoOverride.xml │ │ │ │ ├── HttpHeadersConfigTests-DefaultsDisabledWithOnlyHeaderName.xml │ │ │ │ ├── HttpHeadersConfigTests-DefaultsDisabledWithOnlyHeaderValue.xml │ │ │ │ ├── HttpHeadersConfigTests-DefaultsDisabledWithPermissionsPolicy.xml │ │ │ │ ├── HttpHeadersConfigTests-DefaultsDisabledWithPlaceholder.xml │ │ │ │ ├── HttpHeadersConfigTests-DefaultsDisabledWithReferrerPolicy.xml │ │ │ │ ├── HttpHeadersConfigTests-DefaultsDisabledWithReferrerPolicySameOrigin.xml │ │ │ │ ├── HttpHeadersConfigTests-DefaultsDisabledWithXssProtection.xml │ │ │ │ ├── HttpHeadersConfigTests-DefaultsDisabledWithXssProtectionHeaderValueOne.xml │ │ │ │ ├── HttpHeadersConfigTests-DefaultsDisabledWithXssProtectionHeaderValueOneModeBlock.xml │ │ │ │ ├── HttpHeadersConfigTests-DefaultsDisabledWithXssProtectionHeaderValueZero.xml │ │ │ │ ├── HttpHeadersConfigTests-DefaultsSessionManagementConcurrencyControlMaxSessions.xml │ │ │ │ ├── HttpHeadersConfigTests-DefaultsSessionManagementConcurrencyControlMaxSessionsRef.xml │ │ │ │ ├── HttpHeadersConfigTests-DefaultsSessionManagementConcurrencyControlWithInvalidMaxSessionsConfig.xml │ │ │ │ ├── HttpHeadersConfigTests-DisabledWithPlaceholder.xml │ │ │ │ ├── HttpHeadersConfigTests-FrameOptionsDisabled.xml │ │ │ │ ├── HttpHeadersConfigTests-FrameOptionsDisabledSpecifyingPolicy.xml │ │ │ │ ├── HttpHeadersConfigTests-HeadersDisabled.xml │ │ │ │ ├── HttpHeadersConfigTests-HeadersDisabledHavingChildElement.xml │ │ │ │ ├── HttpHeadersConfigTests-HeadersDisabledWithContentSecurityPolicy.xml │ │ │ │ ├── HttpHeadersConfigTests-HeadersEnabled.xml │ │ │ │ ├── HttpHeadersConfigTests-HpkpDisabled.xml │ │ │ │ ├── HttpHeadersConfigTests-HstsDisabled.xml │ │ │ │ ├── HttpHeadersConfigTests-HstsDisabledSpecifyingIncludeSubdomains.xml │ │ │ │ ├── HttpHeadersConfigTests-HstsDisabledSpecifyingMaxAge.xml │ │ │ │ ├── HttpHeadersConfigTests-HstsDisabledSpecifyingRequestMatcher.xml │ │ │ │ ├── HttpHeadersConfigTests-WithFrameOptions.xml │ │ │ │ ├── HttpHeadersConfigTests-XssProtectionDisabled.xml │ │ │ │ ├── HttpHeadersConfigTests-XssProtectionDisabledSpecifyingHeaderValue.xml │ │ │ │ ├── HttpInterceptUrlTests-interceptUrlWhenRequestMatcherRefThenWorks.xml │ │ │ │ ├── InterceptUrlConfigTests-AntMatcherServletPath.xml │ │ │ │ ├── InterceptUrlConfigTests-AntMatcherServletPathAuthorizationManager.xml │ │ │ │ ├── InterceptUrlConfigTests-AuthorizationManagerFilterAllDispatcherTypes.xml │ │ │ │ ├── InterceptUrlConfigTests-CamelCasePathVariables.xml │ │ │ │ ├── InterceptUrlConfigTests-CamelCasePathVariablesAuthorizationManager.xml │ │ │ │ ├── InterceptUrlConfigTests-CiRegexMatcherServletPath.xml │ │ │ │ ├── InterceptUrlConfigTests-CiRegexMatcherServletPathAuthorizationManager.xml │ │ │ │ ├── InterceptUrlConfigTests-DefaultMatcherNoIntrospectorBean.xml │ │ │ │ ├── InterceptUrlConfigTests-DefaultMatcherServletPath.xml │ │ │ │ ├── InterceptUrlConfigTests-DefaultMatcherServletPathAuthorizationManager.xml │ │ │ │ ├── InterceptUrlConfigTests-EmptyAccess.xml │ │ │ │ ├── InterceptUrlConfigTests-EmptyAccessLegacy.xml │ │ │ │ ├── InterceptUrlConfigTests-HasAnyRole.xml │ │ │ │ ├── InterceptUrlConfigTests-HasAnyRoleAuthorizationManager.xml │ │ │ │ ├── InterceptUrlConfigTests-MissingAccess.xml │ │ │ │ ├── InterceptUrlConfigTests-MissingAccessLegacy.xml │ │ │ │ ├── InterceptUrlConfigTests-MvcMatchers.xml │ │ │ │ ├── InterceptUrlConfigTests-MvcMatchersAuthorizationManager.xml │ │ │ │ ├── InterceptUrlConfigTests-MvcMatchersPathVariables.xml │ │ │ │ ├── InterceptUrlConfigTests-MvcMatchersPathVariablesAuthorizationManager.xml │ │ │ │ ├── InterceptUrlConfigTests-MvcMatchersServletPath.xml │ │ │ │ ├── InterceptUrlConfigTests-MvcMatchersServletPathAuthorizationManager.xml │ │ │ │ ├── InterceptUrlConfigTests-PatchMethod.xml │ │ │ │ ├── InterceptUrlConfigTests-PatchMethodAuthorizationManager.xml │ │ │ │ ├── InterceptUrlConfigTests-PathVariables.xml │ │ │ │ ├── InterceptUrlConfigTests-PathVariablesAuthorizationManager.xml │ │ │ │ ├── InterceptUrlConfigTests-RegexMatcherServletPath.xml │ │ │ │ ├── InterceptUrlConfigTests-RegexMatcherServletPathAuthorizationManager.xml │ │ │ │ ├── InterceptUrlConfigTests-Sec2256.xml │ │ │ │ ├── InterceptUrlConfigTests-Sec2256AuthorizationManager.xml │ │ │ │ ├── InterceptUrlConfigTests-TypeConversionPathVariables.xml │ │ │ │ ├── InterceptUrlConfigTests-TypeConversionPathVariablesAuthorizationManager.xml │ │ │ │ ├── InterceptUrlConfigTests-ValidAccess.xml │ │ │ │ ├── MiscHttpConfigTests-AnonymousCustomAttributes.xml │ │ │ │ ├── MiscHttpConfigTests-AnonymousDisabled.xml │ │ │ │ ├── MiscHttpConfigTests-AnonymousEndpoints.xml │ │ │ │ ├── MiscHttpConfigTests-AnonymousMultipleAuthorities.xml │ │ │ │ ├── MiscHttpConfigTests-AuthenticationManagerEraseCredentials.xml │ │ │ │ ├── MiscHttpConfigTests-AuthenticationManagerRefKeepCredentials.xml │ │ │ │ ├── MiscHttpConfigTests-AuthenticationManagerRefNotProviderManager.xml │ │ │ │ ├── MiscHttpConfigTests-AutoConfig.xml │ │ │ │ ├── MiscHttpConfigTests-CiRegexSecurityPattern.xml │ │ │ │ ├── MiscHttpConfigTests-CollidingFilters.xml │ │ │ │ ├── MiscHttpConfigTests-CustomAccessDecisionManager.xml │ │ │ │ ├── MiscHttpConfigTests-CustomAuthenticationDetailsSourceRef.xml │ │ │ │ ├── MiscHttpConfigTests-CustomFilters.xml │ │ │ │ ├── MiscHttpConfigTests-CustomHttpBasicEntryPointRef.xml │ │ │ │ ├── MiscHttpConfigTests-CustomRequestMatcher.xml │ │ │ │ ├── MiscHttpConfigTests-DeleteCookies.xml │ │ │ │ ├── MiscHttpConfigTests-DisableUrlRewriting-NullSecurityContextRepository.xml │ │ │ │ ├── MiscHttpConfigTests-DisableUrlRewriting.xml │ │ │ │ ├── MiscHttpConfigTests-EntryPoint.xml │ │ │ │ ├── MiscHttpConfigTests-ExplicitSave.xml │ │ │ │ ├── MiscHttpConfigTests-ExplicitSaveAndExplicitRepository.xml │ │ │ │ ├── MiscHttpConfigTests-ExpressionHandler.xml │ │ │ │ ├── MiscHttpConfigTests-HttpBasic.xml │ │ │ │ ├── MiscHttpConfigTests-HttpFirewall.xml │ │ │ │ ├── MiscHttpConfigTests-InterceptUrlExpressions.xml │ │ │ │ ├── MiscHttpConfigTests-InterceptUrlMethod.xml │ │ │ │ ├── MiscHttpConfigTests-InterceptUrlMethodRequiresHttps.xml │ │ │ │ ├── MiscHttpConfigTests-InterceptUrlMethodRequiresHttpsAny.xml │ │ │ │ ├── MiscHttpConfigTests-InvalidLogoutSuccessUrl.xml │ │ │ │ ├── MiscHttpConfigTests-Jaas.xml │ │ │ │ ├── MiscHttpConfigTests-JeeFilter.xml │ │ │ │ ├── MiscHttpConfigTests-JeeFilterWithSecurityContextHolderStrategy.xml │ │ │ │ ├── MiscHttpConfigTests-LogoutSuccessHandlerRef.xml │ │ │ │ ├── MiscHttpConfigTests-MinimalConfiguration.xml │ │ │ │ ├── MiscHttpConfigTests-MissingUserDetailsService.xml │ │ │ │ ├── MiscHttpConfigTests-NoAuthProviders.xml │ │ │ │ ├── MiscHttpConfigTests-NoInternalAuthenticationProviders.xml │ │ │ │ ├── MiscHttpConfigTests-NoSecurityForPattern.xml │ │ │ │ ├── MiscHttpConfigTests-OncePerRequest.xml │ │ │ │ ├── MiscHttpConfigTests-OncePerRequestTrue.xml │ │ │ │ ├── MiscHttpConfigTests-PortsMappedInterceptUrlMethodRequiresAny.xml │ │ │ │ ├── MiscHttpConfigTests-PortsMappedRequiresHttps.xml │ │ │ │ ├── MiscHttpConfigTests-ProtectedLoginPage.xml │ │ │ │ ├── MiscHttpConfigTests-ProtectedLoginPageAuthorizationManager.xml │ │ │ │ ├── MiscHttpConfigTests-RedirectToHttpsRequiresHttpsAny.xml │ │ │ │ ├── MiscHttpConfigTests-RegexSecurityPattern.xml │ │ │ │ ├── MiscHttpConfigTests-RequestCache.xml │ │ │ │ ├── MiscHttpConfigTests-RequestRejectedHandler.xml │ │ │ │ ├── MiscHttpConfigTests-Sec750.xml │ │ │ │ ├── MiscHttpConfigTests-Sec934.xml │ │ │ │ ├── MiscHttpConfigTests-SecurityContextRepository.xml │ │ │ │ ├── MiscHttpConfigTests-WithSecurityContextHolderStrategy.xml │ │ │ │ ├── MiscHttpConfigTests-X509.xml │ │ │ │ ├── MiscHttpConfigTests-X509PrincipalExtractorRef.xml │ │ │ │ ├── MiscHttpConfigTests-X509PrincipalExtractorRefAndSubjectPrincipalRegex.xml │ │ │ │ ├── MiscHttpConfigTests-X509WithSecurityContextHolderStrategy.xml │ │ │ │ ├── MiscHttpConfigTests-certificate.pem │ │ │ │ ├── MiscHttpConfigTests-controllers.xml │ │ │ │ ├── MultiHttpBlockConfigTests-DistinctHttpElements.xml │ │ │ │ ├── MultiHttpBlockConfigTests-IdenticalHttpElements.xml │ │ │ │ ├── MultiHttpBlockConfigTests-IdenticallyPatternedHttpElements.xml │ │ │ │ ├── MultiHttpBlockConfigTests-Sec1937.xml │ │ │ │ ├── OAuth2AuthorizedClientManagerRegistrarTests-clients.xml │ │ │ │ ├── OAuth2AuthorizedClientManagerRegistrarTests-minimal.xml │ │ │ │ ├── OAuth2AuthorizedClientManagerRegistrarTests-providers.xml │ │ │ │ ├── OAuth2ClientBeanDefinitionParserTests-AuthorizedClientArgumentResolver.xml │ │ │ │ ├── OAuth2ClientBeanDefinitionParserTests-CustomAuthorizationRedirectStrategy.xml │ │ │ │ ├── OAuth2ClientBeanDefinitionParserTests-CustomAuthorizedClientService.xml │ │ │ │ ├── OAuth2ClientBeanDefinitionParserTests-CustomClientRegistrationRepository.xml │ │ │ │ ├── OAuth2ClientBeanDefinitionParserTests-CustomConfiguration.xml │ │ │ │ ├── OAuth2ClientBeanDefinitionParserTests-Minimal.xml │ │ │ │ ├── OAuth2LoginBeanDefinitionParserTests-AuthorizedClientArgumentResolver.xml │ │ │ │ ├── OAuth2LoginBeanDefinitionParserTests-MultiClientRegistration-WithCustomConfiguration.xml │ │ │ │ ├── OAuth2LoginBeanDefinitionParserTests-MultiClientRegistration-WithCustomGrantedAuthorities.xml │ │ │ │ ├── OAuth2LoginBeanDefinitionParserTests-MultiClientRegistration-WithCustomLoginProcessingUrl.xml │ │ │ │ ├── OAuth2LoginBeanDefinitionParserTests-MultiClientRegistration.xml │ │ │ │ ├── OAuth2LoginBeanDefinitionParserTests-SingleClientRegistration-WithCustomAuthenticationFailureHandler.xml │ │ │ │ ├── OAuth2LoginBeanDefinitionParserTests-SingleClientRegistration-WithCustomAuthorizationRedirectStrategy.xml │ │ │ │ ├── OAuth2LoginBeanDefinitionParserTests-SingleClientRegistration-WithCustomAuthorizationRequestResolver.xml │ │ │ │ ├── OAuth2LoginBeanDefinitionParserTests-SingleClientRegistration-WithCustomLoginPage.xml │ │ │ │ ├── OAuth2LoginBeanDefinitionParserTests-SingleClientRegistration-WithFormLogin.xml │ │ │ │ ├── OAuth2LoginBeanDefinitionParserTests-SingleClientRegistration-WithJwtDecoderFactory.xml │ │ │ │ ├── OAuth2LoginBeanDefinitionParserTests-SingleClientRegistration-WithJwtDecoderFactoryAndDefaultSuccessHandler.xml │ │ │ │ ├── OAuth2LoginBeanDefinitionParserTests-SingleClientRegistration.xml │ │ │ │ ├── OAuth2LoginBeanDefinitionParserTests-WithCustomAuthorizedClientRepository.xml │ │ │ │ ├── OAuth2LoginBeanDefinitionParserTests-WithCustomAuthorizedClientService.xml │ │ │ │ ├── OAuth2LoginBeanDefinitionParserTests-WithCustomClientRegistrationRepository.xml │ │ │ │ ├── OAuth2LoginBeanDefinitionParserTests-WithCustomSecurityContextHolderStrategy.xml │ │ │ │ ├── OAuth2ResourceServerBeanDefinitionParserTests-AccessDeniedHandler.xml │ │ │ │ ├── OAuth2ResourceServerBeanDefinitionParserTests-Active.json │ │ │ │ ├── OAuth2ResourceServerBeanDefinitionParserTests-ActiveNoScopes.json │ │ │ │ ├── OAuth2ResourceServerBeanDefinitionParserTests-AllowBearerTokenInBody.xml │ │ │ │ ├── OAuth2ResourceServerBeanDefinitionParserTests-AllowBearerTokenInQuery.xml │ │ │ │ ├── OAuth2ResourceServerBeanDefinitionParserTests-AlwaysSessionCreation.xml │ │ │ │ ├── OAuth2ResourceServerBeanDefinitionParserTests-AuthenticationConverter.xml │ │ │ │ ├── OAuth2ResourceServerBeanDefinitionParserTests-AuthenticationConverterAndBearerTokenResolver.xml │ │ │ │ ├── OAuth2ResourceServerBeanDefinitionParserTests-AuthenticationEntryPoint.xml │ │ │ │ ├── OAuth2ResourceServerBeanDefinitionParserTests-AuthenticationManagerResolver.xml │ │ │ │ ├── OAuth2ResourceServerBeanDefinitionParserTests-AuthenticationManagerResolverPlusOtherConfig.xml │ │ │ │ ├── OAuth2ResourceServerBeanDefinitionParserTests-BasicAndResourceServer.xml │ │ │ │ ├── OAuth2ResourceServerBeanDefinitionParserTests-BearerTokenResolver.xml │ │ │ │ ├── OAuth2ResourceServerBeanDefinitionParserTests-Default.jwks │ │ │ │ ├── OAuth2ResourceServerBeanDefinitionParserTests-Empty.jwks │ │ │ │ ├── OAuth2ResourceServerBeanDefinitionParserTests-Expired.token │ │ │ │ ├── OAuth2ResourceServerBeanDefinitionParserTests-ExpiredJwtClockSkew.xml │ │ │ │ ├── OAuth2ResourceServerBeanDefinitionParserTests-ExpiresAt4687177990.token │ │ │ │ ├── OAuth2ResourceServerBeanDefinitionParserTests-FormAndResourceServer.xml │ │ │ │ ├── OAuth2ResourceServerBeanDefinitionParserTests-Inactive.json │ │ │ │ ├── OAuth2ResourceServerBeanDefinitionParserTests-IntrospectionUri.xml │ │ │ │ ├── OAuth2ResourceServerBeanDefinitionParserTests-JwkSetUri.xml │ │ │ │ ├── OAuth2ResourceServerBeanDefinitionParserTests-Jwt.xml │ │ │ │ ├── OAuth2ResourceServerBeanDefinitionParserTests-JwtAuthenticationConverter.xml │ │ │ │ ├── OAuth2ResourceServerBeanDefinitionParserTests-JwtCustomSecurityContextHolderStrategy.xml │ │ │ │ ├── OAuth2ResourceServerBeanDefinitionParserTests-JwtDecoderAndJwkSetUri.xml │ │ │ │ ├── OAuth2ResourceServerBeanDefinitionParserTests-JwtHalfConfigured.xml │ │ │ │ ├── OAuth2ResourceServerBeanDefinitionParserTests-JwtRestOperations.xml │ │ │ │ ├── OAuth2ResourceServerBeanDefinitionParserTests-Jwtless.xml │ │ │ │ ├── OAuth2ResourceServerBeanDefinitionParserTests-Kid.token │ │ │ │ ├── OAuth2ResourceServerBeanDefinitionParserTests-MalformedPayload.token │ │ │ │ ├── OAuth2ResourceServerBeanDefinitionParserTests-MockAuthenticationConverter.xml │ │ │ │ ├── OAuth2ResourceServerBeanDefinitionParserTests-MockBearerTokenResolver.xml │ │ │ │ ├── OAuth2ResourceServerBeanDefinitionParserTests-MockJwkSetUri.xml │ │ │ │ ├── OAuth2ResourceServerBeanDefinitionParserTests-MockJwtAuthenticationConverter.xml │ │ │ │ ├── OAuth2ResourceServerBeanDefinitionParserTests-MockJwtDecoder.xml │ │ │ │ ├── OAuth2ResourceServerBeanDefinitionParserTests-MockJwtValidator.xml │ │ │ │ ├── OAuth2ResourceServerBeanDefinitionParserTests-MockOpaqueTokenIntrospector.xml │ │ │ │ ├── OAuth2ResourceServerBeanDefinitionParserTests-MultipleIssuers.xml │ │ │ │ ├── OAuth2ResourceServerBeanDefinitionParserTests-OpaqueToken.xml │ │ │ │ ├── OAuth2ResourceServerBeanDefinitionParserTests-OpaqueTokenAndAuthenticationConverter.xml │ │ │ │ ├── OAuth2ResourceServerBeanDefinitionParserTests-OpaqueTokenAndIntrospectionUri.xml │ │ │ │ ├── OAuth2ResourceServerBeanDefinitionParserTests-OpaqueTokenHalfConfigured.xml │ │ │ │ ├── OAuth2ResourceServerBeanDefinitionParserTests-OpaqueTokenRestOperations.xml │ │ │ │ ├── OAuth2ResourceServerBeanDefinitionParserTests-SingleKey.pub │ │ │ │ ├── OAuth2ResourceServerBeanDefinitionParserTests-SingleKey.xml │ │ │ │ ├── OAuth2ResourceServerBeanDefinitionParserTests-TooEarly.token │ │ │ │ ├── OAuth2ResourceServerBeanDefinitionParserTests-TwoKeys.jwks │ │ │ │ ├── OAuth2ResourceServerBeanDefinitionParserTests-UnexpiredJwtClockSkew.xml │ │ │ │ ├── OAuth2ResourceServerBeanDefinitionParserTests-Unsigned.token │ │ │ │ ├── OAuth2ResourceServerBeanDefinitionParserTests-ValidMessageReadScope.token │ │ │ │ ├── OAuth2ResourceServerBeanDefinitionParserTests-ValidMessageWriteScp.token │ │ │ │ ├── OAuth2ResourceServerBeanDefinitionParserTests-ValidNoScopes.token │ │ │ │ ├── OAuth2ResourceServerBeanDefinitionParserTests-WebServer.xml │ │ │ │ ├── OAuth2ResourceServerBeanDefinitionParserTests-WrongAlgorithm.token │ │ │ │ ├── OAuth2ResourceServerBeanDefinitionParserTests-WrongSignature.token │ │ │ │ ├── PlaceHolderAndELConfigTests-AccessDeniedPage.xml │ │ │ │ ├── PlaceHolderAndELConfigTests-AccessDeniedPageWithSpEL.xml │ │ │ │ ├── PlaceHolderAndELConfigTests-InterceptUrlAndFormLogin.xml │ │ │ │ ├── PlaceHolderAndELConfigTests-InterceptUrlAndFormLoginWithSpEL.xml │ │ │ │ ├── PlaceHolderAndELConfigTests-PortMapping.xml │ │ │ │ ├── PlaceHolderAndELConfigTests-RequiresChannel.xml │ │ │ │ ├── PlaceHolderAndELConfigTests-UnsecuredPattern.xml │ │ │ │ ├── RememberMeConfigTests-DefaultConfig.xml │ │ │ │ ├── RememberMeConfigTests-NegativeTokenValidity.xml │ │ │ │ ├── RememberMeConfigTests-NegativeTokenValidityWithDataSource.xml │ │ │ │ ├── RememberMeConfigTests-NegativeTokenValidityWithPersistentRepository.xml │ │ │ │ ├── RememberMeConfigTests-Sec1827.xml │ │ │ │ ├── RememberMeConfigTests-Sec2165.xml │ │ │ │ ├── RememberMeConfigTests-Sec742.xml │ │ │ │ ├── RememberMeConfigTests-SecureCookie.xml │ │ │ │ ├── RememberMeConfigTests-TokenValidity.xml │ │ │ │ ├── RememberMeConfigTests-WithAuthenticationSuccessHandler.xml │ │ │ │ ├── RememberMeConfigTests-WithDataSource.xml │ │ │ │ ├── RememberMeConfigTests-WithRememberMeCookie.xml │ │ │ │ ├── RememberMeConfigTests-WithRememberMeCookieAndServicesRef.xml │ │ │ │ ├── RememberMeConfigTests-WithRememberMeParameter.xml │ │ │ │ ├── RememberMeConfigTests-WithRememberMeParameterAndServicesRef.xml │ │ │ │ ├── RememberMeConfigTests-WithSecurityContextHolderStrategy.xml │ │ │ │ ├── RememberMeConfigTests-WithServicesRef.xml │ │ │ │ ├── RememberMeConfigTests-WithTokenRepository.xml │ │ │ │ ├── RememberMeConfigTests-WithUserDetailsService.xml │ │ │ │ ├── Saml2LoginBeanDefinitionParserTests-MultiRelyingPartyRegistration.xml │ │ │ │ ├── Saml2LoginBeanDefinitionParserTests-SingleRelyingPartyRegistration-WithCustomAuthenticationFailureHandler.xml │ │ │ │ ├── Saml2LoginBeanDefinitionParserTests-SingleRelyingPartyRegistration.xml │ │ │ │ ├── Saml2LoginBeanDefinitionParserTests-WithCustomLoginProcessingUrl-WithCustomAuthenticationConverter.xml │ │ │ │ ├── Saml2LoginBeanDefinitionParserTests-WithCustomLoginProcessingUrl.xml │ │ │ │ ├── Saml2LoginBeanDefinitionParserTests-WithCustomRelyingPartyRepository-WithCustomAuthenticationConverter.xml │ │ │ │ ├── Saml2LoginBeanDefinitionParserTests-WithCustomRelyingPartyRepository-WithCustomAuthenticationManager.xml │ │ │ │ ├── Saml2LoginBeanDefinitionParserTests-WithCustomRelyingPartyRepository-WithCustomAuthenticationRequestResolver.xml │ │ │ │ ├── Saml2LoginBeanDefinitionParserTests-WithCustomRelyingPartyRepository-WithCustomAuthnRequestRepository.xml │ │ │ │ ├── Saml2LoginBeanDefinitionParserTests-WithCustomRelyingPartyRepository.xml │ │ │ │ ├── Saml2LoginBeanDefinitionParserTests-WithCustomSecurityContextHolderStrategy.xml │ │ │ │ ├── Saml2LogoutBeanDefinitionParserTests-CsrfDisabled-MockLogoutSuccessHandler.xml │ │ │ │ ├── Saml2LogoutBeanDefinitionParserTests-CustomComponents.xml │ │ │ │ ├── Saml2LogoutBeanDefinitionParserTests-Default.xml │ │ │ │ ├── Saml2LogoutBeanDefinitionParserTests-LogoutSuccessHandler.xml │ │ │ │ ├── Saml2LogoutBeanDefinitionParserTests-WithSecurityContextHolderStrategy.xml │ │ │ │ ├── SecurityContextHolderAwareRequestConfigTests-FormLogin.xml │ │ │ │ ├── SecurityContextHolderAwareRequestConfigTests-HttpBasic.xml │ │ │ │ ├── SecurityContextHolderAwareRequestConfigTests-Logout.xml │ │ │ │ ├── SecurityContextHolderAwareRequestConfigTests-MultiHttp.xml │ │ │ │ ├── SecurityContextHolderAwareRequestConfigTests-Simple.xml │ │ │ │ ├── SessionManagementConfigTests-ConcurrencyControlCustomLogoutHandler.xml │ │ │ │ ├── SessionManagementConfigTests-ConcurrencyControlExpiredUrl.xml │ │ │ │ ├── SessionManagementConfigTests-ConcurrencyControlLogoutAndRememberMeHandlers.xml │ │ │ │ ├── SessionManagementConfigTests-ConcurrencyControlMaxSessions.xml │ │ │ │ ├── SessionManagementConfigTests-ConcurrencyControlMaxSessionsPlaceHolder.xml │ │ │ │ ├── SessionManagementConfigTests-ConcurrencyControlRememberMeHandler.xml │ │ │ │ ├── SessionManagementConfigTests-ConcurrencyControlSessionRegistryAlias.xml │ │ │ │ ├── SessionManagementConfigTests-ConcurrencyControlSessionRegistryRef.xml │ │ │ │ ├── SessionManagementConfigTests-CreateSessionAlways.xml │ │ │ │ ├── SessionManagementConfigTests-CreateSessionIfRequired.xml │ │ │ │ ├── SessionManagementConfigTests-CreateSessionNever.xml │ │ │ │ ├── SessionManagementConfigTests-CreateSessionStateless.xml │ │ │ │ ├── SessionManagementConfigTests-NoSessionManagementFilter.xml │ │ │ │ ├── SessionManagementConfigTests-Sec1208.xml │ │ │ │ ├── SessionManagementConfigTests-Sec2137.xml │ │ │ │ ├── SessionManagementConfigTests-SessionAuthenticationStrategyRef.xml │ │ │ │ ├── SessionManagementConfigTests-SessionFixationProtectionMigrateSession.xml │ │ │ │ ├── SessionManagementConfigTests-SessionFixationProtectionNone.xml │ │ │ │ ├── SessionManagementConfigTests-SessionFixationProtectionNoneWithInvalidSessionUrl.xml │ │ │ │ ├── SessionManagementConfigTransientAuthenticationTests-CreateSessionAlwaysWithTransientAuthentication.xml │ │ │ │ ├── SessionManagementConfigTransientAuthenticationTests-WithTransientAuthentication.xml │ │ │ │ ├── WellKnownChangePasswordBeanDefinitionParserTests-CustomChangePasswordPage.xml │ │ │ │ ├── WellKnownChangePasswordBeanDefinitionParserTests-DefaultChangePasswordPage.xml │ │ │ │ ├── jaas-login.conf │ │ │ │ ├── key.pem │ │ │ │ └── userservice.xml │ │ │ ├── method/ │ │ │ │ ├── MethodSecurityBeanDefinitionParserTests-AspectJMethodSecurityServiceEnabled.xml │ │ │ │ ├── MethodSecurityBeanDefinitionParserTests-BusinessService.xml │ │ │ │ ├── MethodSecurityBeanDefinitionParserTests-CustomAuthorizationManagerAfterAdvice.xml │ │ │ │ ├── MethodSecurityBeanDefinitionParserTests-CustomAuthorizationManagerBeforeAdvice.xml │ │ │ │ ├── MethodSecurityBeanDefinitionParserTests-CustomGrantedAuthorityDefaults.xml │ │ │ │ ├── MethodSecurityBeanDefinitionParserTests-CustomPermissionEvaluator.xml │ │ │ │ ├── MethodSecurityBeanDefinitionParserTests-Jsr250.xml │ │ │ │ ├── MethodSecurityBeanDefinitionParserTests-MethodSecurityService.xml │ │ │ │ ├── MethodSecurityBeanDefinitionParserTests-MethodSecurityServiceEnabled.xml │ │ │ │ ├── MethodSecurityBeanDefinitionParserTests-MethodSecurityServiceEnabledCustomSecurityContextHolderStrategy.xml │ │ │ │ ├── MethodSecurityBeanDefinitionParserTests-ProtectPointcut.xml │ │ │ │ ├── MethodSecurityBeanDefinitionParserTests-ProtectPointcutBoolean.xml │ │ │ │ ├── MethodSecurityBeanDefinitionParserTests-Secured.xml │ │ │ │ ├── PreAuthorizeTests-context.xml │ │ │ │ ├── SecuredTests-context.xml │ │ │ │ ├── sec2136/ │ │ │ │ │ └── sec2136.xml │ │ │ │ └── sec2499/ │ │ │ │ ├── child.xml │ │ │ │ └── parent.xml │ │ │ ├── method-security.xml │ │ │ ├── oauth2/ │ │ │ │ └── client/ │ │ │ │ ├── ClientRegistrationsBeanDefinitionParserTests-ClientPlaceholders.xml │ │ │ │ ├── ClientRegistrationsBeanDefinitionParserTests-MultiClientRegistration.xml │ │ │ │ ├── google-github-registration.xml │ │ │ │ └── google-registration.xml │ │ │ ├── saml2/ │ │ │ │ ├── RelyingPartyRegistrationsBeanDefinitionParserTests-MultiRegistration.xml │ │ │ │ ├── RelyingPartyRegistrationsBeanDefinitionParserTests-PlaceholderRegistration.xml │ │ │ │ ├── RelyingPartyRegistrationsBeanDefinitionParserTests-RelayStateResolver.xml │ │ │ │ ├── RelyingPartyRegistrationsBeanDefinitionParserTests-SingleRegistration.xml │ │ │ │ ├── google-custom-registration.xml │ │ │ │ ├── google-registration.xml │ │ │ │ ├── idp-certificate.crt │ │ │ │ ├── logout-registrations.xml │ │ │ │ ├── rp-certificate.crt │ │ │ │ └── rp-private.key │ │ │ ├── users.properties │ │ │ ├── web/ │ │ │ │ └── server/ │ │ │ │ └── OAuth2ResourceServerSpecTests-simple.pub │ │ │ └── websocket/ │ │ │ ├── WebSocketMessageBrokerConfigTests-ConnectAckInterceptTypeConfig.xml │ │ │ ├── WebSocketMessageBrokerConfigTests-ConnectInterceptTypeConfig.xml │ │ │ ├── WebSocketMessageBrokerConfigTests-CustomAuthorizationManagerConfig.xml │ │ │ ├── WebSocketMessageBrokerConfigTests-CustomCsrfInterceptor.xml │ │ │ ├── WebSocketMessageBrokerConfigTests-CustomExpressionHandlerAuthorizationManager.xml │ │ │ ├── WebSocketMessageBrokerConfigTests-CustomExpressionHandlerConfig.xml │ │ │ ├── WebSocketMessageBrokerConfigTests-CustomInterceptorConfig.xml │ │ │ ├── WebSocketMessageBrokerConfigTests-CustomPathMatcherAuthorizationManager.xml │ │ │ ├── WebSocketMessageBrokerConfigTests-CustomPathMatcherConfig.xml │ │ │ ├── WebSocketMessageBrokerConfigTests-DisconnectAckInterceptTypeConfig.xml │ │ │ ├── WebSocketMessageBrokerConfigTests-DisconnectInterceptTypeConfig.xml │ │ │ ├── WebSocketMessageBrokerConfigTests-HeartbeatInterceptTypeConfig.xml │ │ │ ├── WebSocketMessageBrokerConfigTests-IdConfig.xml │ │ │ ├── WebSocketMessageBrokerConfigTests-IdIntegratedConfig.xml │ │ │ ├── WebSocketMessageBrokerConfigTests-MessageInterceptTypeAuthorizationManager.xml │ │ │ ├── WebSocketMessageBrokerConfigTests-MessageInterceptTypeConfig.xml │ │ │ ├── WebSocketMessageBrokerConfigTests-NoIdAuthorizationManager.xml │ │ │ ├── WebSocketMessageBrokerConfigTests-NoIdConfig.xml │ │ │ ├── WebSocketMessageBrokerConfigTests-OtherInterceptTypeConfig.xml │ │ │ ├── WebSocketMessageBrokerConfigTests-SubscribeInterceptTypeAuthorizationManager.xml │ │ │ ├── WebSocketMessageBrokerConfigTests-SubscribeInterceptTypeConfig.xml │ │ │ ├── WebSocketMessageBrokerConfigTests-SubscribeInterceptTypePathPattern.xml │ │ │ ├── WebSocketMessageBrokerConfigTests-SubscribeInterceptTypePathPatternParser.xml │ │ │ ├── WebSocketMessageBrokerConfigTests-SyncConfig.xml │ │ │ ├── WebSocketMessageBrokerConfigTests-SyncCustomArgumentResolverConfig.xml │ │ │ ├── WebSocketMessageBrokerConfigTests-SyncSameOriginDisabledConfig.xml │ │ │ ├── WebSocketMessageBrokerConfigTests-SyncSockJsConfig.xml │ │ │ ├── WebSocketMessageBrokerConfigTests-UnsubscribeInterceptTypeConfig.xml │ │ │ ├── WebSocketMessageBrokerConfigTests-WithSecurityContextHolderStrategy.xml │ │ │ ├── controllers.xml │ │ │ ├── sync.xml │ │ │ ├── websocket-sockjs.xml │ │ │ └── websocket.xml │ │ └── util/ │ │ └── filtertest-valid.xml │ ├── resources/ │ │ └── file.js │ ├── rod.cer │ ├── rodatexampledotcom.cer │ ├── serialized/ │ │ ├── 6.2.x/ │ │ │ ├── org.springframework.security.access.intercept.RunAsUserToken.serialized │ │ │ ├── org.springframework.security.authentication.RememberMeAuthenticationToken.serialized │ │ │ ├── org.springframework.security.authentication.UsernamePasswordAuthenticationToken.serialized │ │ │ ├── org.springframework.security.authentication.jaas.JaasAuthenticationToken.serialized │ │ │ ├── org.springframework.security.authentication.jaas.JaasGrantedAuthority.serialized │ │ │ ├── org.springframework.security.cas.authentication.CasAssertionAuthenticationToken.serialized │ │ │ ├── org.springframework.security.cas.authentication.CasAuthenticationToken.serialized │ │ │ ├── org.springframework.security.cas.authentication.CasServiceTicketAuthenticationToken.serialized │ │ │ ├── org.springframework.security.core.authority.SimpleGrantedAuthority.serialized │ │ │ ├── org.springframework.security.core.context.SecurityContextImpl.serialized │ │ │ ├── org.springframework.security.core.session.SessionInformation.serialized │ │ │ ├── org.springframework.security.core.userdetails.User$AuthorityComparator.serialized │ │ │ ├── org.springframework.security.core.userdetails.User.serialized │ │ │ ├── org.springframework.security.ldap.userdetails.InetOrgPerson.serialized │ │ │ ├── org.springframework.security.ldap.userdetails.LdapUserDetailsImpl.serialized │ │ │ ├── org.springframework.security.ldap.userdetails.Person.serialized │ │ │ ├── org.springframework.security.oauth2.client.OAuth2AuthorizedClient.serialized │ │ │ ├── org.springframework.security.oauth2.client.OAuth2AuthorizedClientId.serialized │ │ │ ├── org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken.serialized │ │ │ ├── org.springframework.security.oauth2.client.authentication.OAuth2AuthorizationCodeAuthenticationToken.serialized │ │ │ ├── org.springframework.security.oauth2.client.authentication.OAuth2LoginAuthenticationToken.serialized │ │ │ ├── org.springframework.security.oauth2.client.registration.ClientRegistration$Builder.serialized │ │ │ ├── org.springframework.security.oauth2.client.registration.ClientRegistration.serialized │ │ │ ├── org.springframework.security.oauth2.core.AuthenticationMethod.serialized │ │ │ ├── org.springframework.security.oauth2.core.AuthorizationGrantType.serialized │ │ │ ├── org.springframework.security.oauth2.core.ClientAuthenticationMethod.serialized │ │ │ ├── org.springframework.security.oauth2.core.OAuth2AccessToken$TokenType.serialized │ │ │ ├── org.springframework.security.oauth2.core.OAuth2Error.serialized │ │ │ ├── org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationExchange.serialized │ │ │ ├── org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest.serialized │ │ │ ├── org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponse.serialized │ │ │ ├── org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType.serialized │ │ │ ├── org.springframework.security.oauth2.core.oidc.OidcUserInfo.serialized │ │ │ ├── org.springframework.security.oauth2.core.user.DefaultOAuth2User.serialized │ │ │ ├── org.springframework.security.oauth2.core.user.OAuth2UserAuthority.serialized │ │ │ ├── org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken.serialized │ │ │ ├── org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthentication.serialized │ │ │ ├── org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthenticationToken.serialized │ │ │ ├── org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken.serialized │ │ │ ├── org.springframework.security.provisioning.MutableUser.serialized │ │ │ ├── org.springframework.security.saml2.core.Saml2Error.serialized │ │ │ ├── org.springframework.security.web.authentication.WebAuthenticationDetails.serialized │ │ │ ├── org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken.serialized │ │ │ ├── org.springframework.security.web.authentication.preauth.PreAuthenticatedGrantedAuthoritiesWebAuthenticationDetails.serialized │ │ │ ├── org.springframework.security.web.authentication.switchuser.SwitchUserGrantedAuthority.serialized │ │ │ ├── org.springframework.security.web.savedrequest.DefaultSavedRequest.serialized │ │ │ └── org.springframework.security.web.savedrequest.SavedCookie.serialized │ │ ├── 6.3.x/ │ │ │ ├── org.springframework.security.access.intercept.RunAsUserToken.serialized │ │ │ ├── org.springframework.security.authentication.RememberMeAuthenticationToken.serialized │ │ │ ├── org.springframework.security.authentication.UsernamePasswordAuthenticationToken.serialized │ │ │ ├── org.springframework.security.authentication.jaas.JaasAuthenticationToken.serialized │ │ │ ├── org.springframework.security.authentication.jaas.JaasGrantedAuthority.serialized │ │ │ ├── org.springframework.security.cas.authentication.CasAssertionAuthenticationToken.serialized │ │ │ ├── org.springframework.security.cas.authentication.CasAuthenticationToken.serialized │ │ │ ├── org.springframework.security.cas.authentication.CasServiceTicketAuthenticationToken.serialized │ │ │ ├── org.springframework.security.core.authority.SimpleGrantedAuthority.serialized │ │ │ ├── org.springframework.security.core.context.SecurityContextImpl.serialized │ │ │ ├── org.springframework.security.core.session.ReactiveSessionInformation.serialized │ │ │ ├── org.springframework.security.core.session.SessionInformation.serialized │ │ │ ├── org.springframework.security.core.userdetails.User$AuthorityComparator.serialized │ │ │ ├── org.springframework.security.core.userdetails.User.serialized │ │ │ ├── org.springframework.security.ldap.userdetails.InetOrgPerson.serialized │ │ │ ├── org.springframework.security.ldap.userdetails.LdapUserDetailsImpl.serialized │ │ │ ├── org.springframework.security.ldap.userdetails.Person.serialized │ │ │ ├── org.springframework.security.oauth2.client.OAuth2AuthorizedClient.serialized │ │ │ ├── org.springframework.security.oauth2.client.OAuth2AuthorizedClientId.serialized │ │ │ ├── org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken.serialized │ │ │ ├── org.springframework.security.oauth2.client.authentication.OAuth2AuthorizationCodeAuthenticationToken.serialized │ │ │ ├── org.springframework.security.oauth2.client.authentication.OAuth2LoginAuthenticationToken.serialized │ │ │ ├── org.springframework.security.oauth2.client.registration.ClientRegistration$Builder.serialized │ │ │ ├── org.springframework.security.oauth2.client.registration.ClientRegistration.serialized │ │ │ ├── org.springframework.security.oauth2.core.AuthenticationMethod.serialized │ │ │ ├── org.springframework.security.oauth2.core.AuthorizationGrantType.serialized │ │ │ ├── org.springframework.security.oauth2.core.ClientAuthenticationMethod.serialized │ │ │ ├── org.springframework.security.oauth2.core.OAuth2AccessToken$TokenType.serialized │ │ │ ├── org.springframework.security.oauth2.core.OAuth2Error.serialized │ │ │ ├── org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationExchange.serialized │ │ │ ├── org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest.serialized │ │ │ ├── org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponse.serialized │ │ │ ├── org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType.serialized │ │ │ ├── org.springframework.security.oauth2.core.oidc.OidcUserInfo.serialized │ │ │ ├── org.springframework.security.oauth2.core.user.DefaultOAuth2User.serialized │ │ │ ├── org.springframework.security.oauth2.core.user.OAuth2UserAuthority.serialized │ │ │ ├── org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken.serialized │ │ │ ├── org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthentication.serialized │ │ │ ├── org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthenticationToken.serialized │ │ │ ├── org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken.serialized │ │ │ ├── org.springframework.security.provisioning.MutableUser.serialized │ │ │ ├── org.springframework.security.saml2.core.Saml2Error.serialized │ │ │ ├── org.springframework.security.web.authentication.WebAuthenticationDetails.serialized │ │ │ ├── org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken.serialized │ │ │ ├── org.springframework.security.web.authentication.preauth.PreAuthenticatedGrantedAuthoritiesWebAuthenticationDetails.serialized │ │ │ ├── org.springframework.security.web.authentication.switchuser.SwitchUserGrantedAuthority.serialized │ │ │ ├── org.springframework.security.web.savedrequest.DefaultSavedRequest.serialized │ │ │ └── org.springframework.security.web.savedrequest.SavedCookie.serialized │ │ ├── 6.4.x/ │ │ │ ├── org.springframework.security.access.AccessDeniedException.serialized │ │ │ ├── org.springframework.security.access.AuthorizationServiceException.serialized │ │ │ ├── org.springframework.security.access.SecurityConfig.serialized │ │ │ ├── org.springframework.security.access.hierarchicalroles.CycleInRoleHierarchyException.serialized │ │ │ ├── org.springframework.security.access.intercept.RunAsUserToken.serialized │ │ │ ├── org.springframework.security.authentication.AccountExpiredException.serialized │ │ │ ├── org.springframework.security.authentication.AnonymousAuthenticationToken.serialized │ │ │ ├── org.springframework.security.authentication.AuthenticationCredentialsNotFoundException.serialized │ │ │ ├── org.springframework.security.authentication.AuthenticationServiceException.serialized │ │ │ ├── org.springframework.security.authentication.BadCredentialsException.serialized │ │ │ ├── org.springframework.security.authentication.CredentialsExpiredException.serialized │ │ │ ├── org.springframework.security.authentication.DisabledException.serialized │ │ │ ├── org.springframework.security.authentication.InsufficientAuthenticationException.serialized │ │ │ ├── org.springframework.security.authentication.InternalAuthenticationServiceException.serialized │ │ │ ├── org.springframework.security.authentication.LockedException.serialized │ │ │ ├── org.springframework.security.authentication.ProviderNotFoundException.serialized │ │ │ ├── org.springframework.security.authentication.RememberMeAuthenticationToken.serialized │ │ │ ├── org.springframework.security.authentication.TestingAuthenticationToken.serialized │ │ │ ├── org.springframework.security.authentication.UsernamePasswordAuthenticationToken.serialized │ │ │ ├── org.springframework.security.authentication.event.AuthenticationFailureBadCredentialsEvent.serialized │ │ │ ├── org.springframework.security.authentication.event.AuthenticationFailureCredentialsExpiredEvent.serialized │ │ │ ├── org.springframework.security.authentication.event.AuthenticationFailureDisabledEvent.serialized │ │ │ ├── org.springframework.security.authentication.event.AuthenticationFailureExpiredEvent.serialized │ │ │ ├── org.springframework.security.authentication.event.AuthenticationFailureLockedEvent.serialized │ │ │ ├── org.springframework.security.authentication.event.AuthenticationFailureProviderNotFoundEvent.serialized │ │ │ ├── org.springframework.security.authentication.event.AuthenticationFailureProxyUntrustedEvent.serialized │ │ │ ├── org.springframework.security.authentication.event.AuthenticationFailureServiceExceptionEvent.serialized │ │ │ ├── org.springframework.security.authentication.event.AuthenticationSuccessEvent.serialized │ │ │ ├── org.springframework.security.authentication.event.InteractiveAuthenticationSuccessEvent.serialized │ │ │ ├── org.springframework.security.authentication.event.LogoutSuccessEvent.serialized │ │ │ ├── org.springframework.security.authentication.jaas.JaasAuthenticationToken.serialized │ │ │ ├── org.springframework.security.authentication.jaas.JaasGrantedAuthority.serialized │ │ │ ├── org.springframework.security.authentication.jaas.event.JaasAuthenticationFailedEvent.serialized │ │ │ ├── org.springframework.security.authentication.jaas.event.JaasAuthenticationSuccessEvent.serialized │ │ │ ├── org.springframework.security.authentication.ott.InvalidOneTimeTokenException.serialized │ │ │ ├── org.springframework.security.authentication.ott.OneTimeTokenAuthenticationToken.serialized │ │ │ ├── org.springframework.security.authentication.password.CompromisedPasswordException.serialized │ │ │ ├── org.springframework.security.authorization.AuthorityAuthorizationDecision.serialized │ │ │ ├── org.springframework.security.authorization.AuthorizationDecision.serialized │ │ │ ├── org.springframework.security.authorization.AuthorizationDeniedException.serialized │ │ │ ├── org.springframework.security.authorization.event.AuthorizationEvent.serialized │ │ │ ├── org.springframework.security.authorization.event.AuthorizationGrantedEvent.serialized │ │ │ ├── org.springframework.security.cas.authentication.CasAssertionAuthenticationToken.serialized │ │ │ ├── org.springframework.security.cas.authentication.CasAuthenticationToken.serialized │ │ │ ├── org.springframework.security.cas.authentication.CasServiceTicketAuthenticationToken.serialized │ │ │ ├── org.springframework.security.config.annotation.AlreadyBuiltException.serialized │ │ │ ├── org.springframework.security.core.authority.SimpleGrantedAuthority.serialized │ │ │ ├── org.springframework.security.core.context.SecurityContextImpl.serialized │ │ │ ├── org.springframework.security.core.context.TransientSecurityContext.serialized │ │ │ ├── org.springframework.security.core.session.AbstractSessionEvent.serialized │ │ │ ├── org.springframework.security.core.session.ReactiveSessionInformation.serialized │ │ │ ├── org.springframework.security.core.session.SessionInformation.serialized │ │ │ ├── org.springframework.security.core.userdetails.User$AuthorityComparator.serialized │ │ │ ├── org.springframework.security.core.userdetails.User.serialized │ │ │ ├── org.springframework.security.core.userdetails.UsernameNotFoundException.serialized │ │ │ ├── org.springframework.security.ldap.ppolicy.PasswordPolicyControl.serialized │ │ │ ├── org.springframework.security.ldap.ppolicy.PasswordPolicyException.serialized │ │ │ ├── org.springframework.security.ldap.ppolicy.PasswordPolicyResponseControl.serialized │ │ │ ├── org.springframework.security.ldap.userdetails.InetOrgPerson.serialized │ │ │ ├── org.springframework.security.ldap.userdetails.LdapAuthority.serialized │ │ │ ├── org.springframework.security.ldap.userdetails.LdapUserDetailsImpl.serialized │ │ │ ├── org.springframework.security.ldap.userdetails.Person.serialized │ │ │ ├── org.springframework.security.oauth2.client.ClientAuthorizationException.serialized │ │ │ ├── org.springframework.security.oauth2.client.ClientAuthorizationRequiredException.serialized │ │ │ ├── org.springframework.security.oauth2.client.OAuth2AuthorizedClient.serialized │ │ │ ├── org.springframework.security.oauth2.client.OAuth2AuthorizedClientId.serialized │ │ │ ├── org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken.serialized │ │ │ ├── org.springframework.security.oauth2.client.authentication.OAuth2AuthorizationCodeAuthenticationToken.serialized │ │ │ ├── org.springframework.security.oauth2.client.authentication.OAuth2LoginAuthenticationToken.serialized │ │ │ ├── org.springframework.security.oauth2.client.oidc.authentication.logout.OidcLogoutToken.serialized │ │ │ ├── org.springframework.security.oauth2.client.oidc.session.OidcSessionInformation.serialized │ │ │ ├── org.springframework.security.oauth2.client.registration.ClientRegistration$Builder.serialized │ │ │ ├── org.springframework.security.oauth2.client.registration.ClientRegistration.serialized │ │ │ ├── org.springframework.security.oauth2.core.AuthenticationMethod.serialized │ │ │ ├── org.springframework.security.oauth2.core.AuthorizationGrantType.serialized │ │ │ ├── org.springframework.security.oauth2.core.ClientAuthenticationMethod.serialized │ │ │ ├── org.springframework.security.oauth2.core.DefaultOAuth2AuthenticatedPrincipal.serialized │ │ │ ├── org.springframework.security.oauth2.core.OAuth2AccessToken$TokenType.serialized │ │ │ ├── org.springframework.security.oauth2.core.OAuth2AccessToken.serialized │ │ │ ├── org.springframework.security.oauth2.core.OAuth2AuthenticationException.serialized │ │ │ ├── org.springframework.security.oauth2.core.OAuth2AuthorizationException.serialized │ │ │ ├── org.springframework.security.oauth2.core.OAuth2DeviceCode.serialized │ │ │ ├── org.springframework.security.oauth2.core.OAuth2Error.serialized │ │ │ ├── org.springframework.security.oauth2.core.OAuth2RefreshToken.serialized │ │ │ ├── org.springframework.security.oauth2.core.OAuth2UserCode.serialized │ │ │ ├── org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationExchange.serialized │ │ │ ├── org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest.serialized │ │ │ ├── org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponse.serialized │ │ │ ├── org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType.serialized │ │ │ ├── org.springframework.security.oauth2.core.oidc.OidcIdToken.serialized │ │ │ ├── org.springframework.security.oauth2.core.oidc.OidcUserInfo.serialized │ │ │ ├── org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser.serialized │ │ │ ├── org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority.serialized │ │ │ ├── org.springframework.security.oauth2.core.user.DefaultOAuth2User.serialized │ │ │ ├── org.springframework.security.oauth2.core.user.OAuth2UserAuthority.serialized │ │ │ ├── org.springframework.security.oauth2.jwt.BadJwtException.serialized │ │ │ ├── org.springframework.security.oauth2.jwt.Jwt.serialized │ │ │ ├── org.springframework.security.oauth2.jwt.JwtDecoderInitializationException.serialized │ │ │ ├── org.springframework.security.oauth2.jwt.JwtEncodingException.serialized │ │ │ ├── org.springframework.security.oauth2.jwt.JwtException.serialized │ │ │ ├── org.springframework.security.oauth2.jwt.JwtValidationException.serialized │ │ │ ├── org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken.serialized │ │ │ ├── org.springframework.security.oauth2.server.resource.BearerTokenError.serialized │ │ │ ├── org.springframework.security.oauth2.server.resource.InvalidBearerTokenException.serialized │ │ │ ├── org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthentication.serialized │ │ │ ├── org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthenticationToken.serialized │ │ │ ├── org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken.serialized │ │ │ ├── org.springframework.security.oauth2.server.resource.introspection.BadOpaqueTokenException.serialized │ │ │ ├── org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionAuthenticatedPrincipal.serialized │ │ │ ├── org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionException.serialized │ │ │ ├── org.springframework.security.provisioning.MutableUser.serialized │ │ │ ├── org.springframework.security.saml2.Saml2Exception.serialized │ │ │ ├── org.springframework.security.saml2.core.Saml2Error.serialized │ │ │ ├── org.springframework.security.saml2.core.Saml2X509Credential.serialized │ │ │ ├── org.springframework.security.saml2.provider.service.authentication.DefaultSaml2AuthenticatedPrincipal.serialized │ │ │ ├── org.springframework.security.saml2.provider.service.authentication.Saml2Authentication.serialized │ │ │ ├── org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException.serialized │ │ │ ├── org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationToken.serialized │ │ │ ├── org.springframework.security.saml2.provider.service.authentication.Saml2PostAuthenticationRequest.serialized │ │ │ ├── org.springframework.security.saml2.provider.service.authentication.Saml2RedirectAuthenticationRequest.serialized │ │ │ ├── org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutRequest.serialized │ │ │ ├── org.springframework.security.saml2.provider.service.registration.OpenSamlAssertingPartyDetails.serialized │ │ │ ├── org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration$AssertingPartyDetails.serialized │ │ │ ├── org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration.serialized │ │ │ ├── org.springframework.security.web.authentication.WebAuthenticationDetails.serialized │ │ │ ├── org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken.serialized │ │ │ ├── org.springframework.security.web.authentication.preauth.PreAuthenticatedCredentialsNotFoundException.serialized │ │ │ ├── org.springframework.security.web.authentication.preauth.PreAuthenticatedGrantedAuthoritiesWebAuthenticationDetails.serialized │ │ │ ├── org.springframework.security.web.authentication.rememberme.CookieTheftException.serialized │ │ │ ├── org.springframework.security.web.authentication.rememberme.InvalidCookieException.serialized │ │ │ ├── org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationException.serialized │ │ │ ├── org.springframework.security.web.authentication.session.SessionAuthenticationException.serialized │ │ │ ├── org.springframework.security.web.authentication.session.SessionFixationProtectionEvent.serialized │ │ │ ├── org.springframework.security.web.authentication.switchuser.AuthenticationSwitchUserEvent.serialized │ │ │ ├── org.springframework.security.web.authentication.switchuser.SwitchUserGrantedAuthority.serialized │ │ │ ├── org.springframework.security.web.authentication.www.NonceExpiredException.serialized │ │ │ ├── org.springframework.security.web.csrf.CsrfException.serialized │ │ │ ├── org.springframework.security.web.csrf.DefaultCsrfToken.serialized │ │ │ ├── org.springframework.security.web.csrf.InvalidCsrfTokenException.serialized │ │ │ ├── org.springframework.security.web.csrf.MissingCsrfTokenException.serialized │ │ │ ├── org.springframework.security.web.firewall.RequestRejectedException.serialized │ │ │ ├── org.springframework.security.web.savedrequest.DefaultSavedRequest.serialized │ │ │ ├── org.springframework.security.web.savedrequest.SavedCookie.serialized │ │ │ ├── org.springframework.security.web.savedrequest.SimpleSavedRequest.serialized │ │ │ ├── org.springframework.security.web.server.csrf.CsrfException.serialized │ │ │ ├── org.springframework.security.web.server.csrf.DefaultCsrfToken.serialized │ │ │ ├── org.springframework.security.web.server.firewall.ServerExchangeRejectedException.serialized │ │ │ ├── org.springframework.security.web.session.HttpSessionCreatedEvent.serialized │ │ │ ├── org.springframework.security.web.session.HttpSessionIdChangedEvent.serialized │ │ │ ├── org.springframework.security.web.webauthn.api.AuthenticatorAssertionResponse.serialized │ │ │ ├── org.springframework.security.web.webauthn.api.AuthenticatorAttachment.serialized │ │ │ ├── org.springframework.security.web.webauthn.api.AuthenticatorTransport.serialized │ │ │ ├── org.springframework.security.web.webauthn.api.Bytes.serialized │ │ │ ├── org.springframework.security.web.webauthn.api.CredProtectAuthenticationExtensionsClientInput$CredProtect.serialized │ │ │ ├── org.springframework.security.web.webauthn.api.CredProtectAuthenticationExtensionsClientInput.serialized │ │ │ ├── org.springframework.security.web.webauthn.api.CredentialPropertiesOutput$ExtensionOutput.serialized │ │ │ ├── org.springframework.security.web.webauthn.api.CredentialPropertiesOutput.serialized │ │ │ ├── org.springframework.security.web.webauthn.api.ImmutableAuthenticationExtensionsClientInput.serialized │ │ │ ├── org.springframework.security.web.webauthn.api.ImmutableAuthenticationExtensionsClientInputs.serialized │ │ │ ├── org.springframework.security.web.webauthn.api.ImmutableAuthenticationExtensionsClientOutputs.serialized │ │ │ ├── org.springframework.security.web.webauthn.api.ImmutablePublicKeyCredentialUserEntity.serialized │ │ │ ├── org.springframework.security.web.webauthn.api.PublicKeyCredential.serialized │ │ │ ├── org.springframework.security.web.webauthn.api.PublicKeyCredentialDescriptor.serialized │ │ │ ├── org.springframework.security.web.webauthn.api.PublicKeyCredentialRequestOptions.serialized │ │ │ ├── org.springframework.security.web.webauthn.api.PublicKeyCredentialType.serialized │ │ │ ├── org.springframework.security.web.webauthn.api.UserVerificationRequirement.serialized │ │ │ ├── org.springframework.security.web.webauthn.authentication.WebAuthnAuthentication.serialized │ │ │ ├── org.springframework.security.web.webauthn.authentication.WebAuthnAuthenticationRequestToken.serialized │ │ │ └── org.springframework.security.web.webauthn.management.RelyingPartyAuthenticationRequest.serialized │ │ ├── 6.5.x/ │ │ │ ├── org.springframework.security.access.AccessDeniedException.serialized │ │ │ ├── org.springframework.security.access.AuthorizationServiceException.serialized │ │ │ ├── org.springframework.security.access.SecurityConfig.serialized │ │ │ ├── org.springframework.security.access.hierarchicalroles.CycleInRoleHierarchyException.serialized │ │ │ ├── org.springframework.security.access.intercept.RunAsUserToken.serialized │ │ │ ├── org.springframework.security.authentication.AccountExpiredException.serialized │ │ │ ├── org.springframework.security.authentication.AnonymousAuthenticationToken.serialized │ │ │ ├── org.springframework.security.authentication.AuthenticationCredentialsNotFoundException.serialized │ │ │ ├── org.springframework.security.authentication.AuthenticationServiceException.serialized │ │ │ ├── org.springframework.security.authentication.BadCredentialsException.serialized │ │ │ ├── org.springframework.security.authentication.CredentialsExpiredException.serialized │ │ │ ├── org.springframework.security.authentication.DisabledException.serialized │ │ │ ├── org.springframework.security.authentication.InsufficientAuthenticationException.serialized │ │ │ ├── org.springframework.security.authentication.InternalAuthenticationServiceException.serialized │ │ │ ├── org.springframework.security.authentication.LockedException.serialized │ │ │ ├── org.springframework.security.authentication.ProviderNotFoundException.serialized │ │ │ ├── org.springframework.security.authentication.RememberMeAuthenticationToken.serialized │ │ │ ├── org.springframework.security.authentication.TestingAuthenticationToken.serialized │ │ │ ├── org.springframework.security.authentication.UsernamePasswordAuthenticationToken.serialized │ │ │ ├── org.springframework.security.authentication.event.AuthenticationFailureBadCredentialsEvent.serialized │ │ │ ├── org.springframework.security.authentication.event.AuthenticationFailureCredentialsExpiredEvent.serialized │ │ │ ├── org.springframework.security.authentication.event.AuthenticationFailureDisabledEvent.serialized │ │ │ ├── org.springframework.security.authentication.event.AuthenticationFailureExpiredEvent.serialized │ │ │ ├── org.springframework.security.authentication.event.AuthenticationFailureLockedEvent.serialized │ │ │ ├── org.springframework.security.authentication.event.AuthenticationFailureProviderNotFoundEvent.serialized │ │ │ ├── org.springframework.security.authentication.event.AuthenticationFailureProxyUntrustedEvent.serialized │ │ │ ├── org.springframework.security.authentication.event.AuthenticationFailureServiceExceptionEvent.serialized │ │ │ ├── org.springframework.security.authentication.event.AuthenticationSuccessEvent.serialized │ │ │ ├── org.springframework.security.authentication.event.InteractiveAuthenticationSuccessEvent.serialized │ │ │ ├── org.springframework.security.authentication.event.LogoutSuccessEvent.serialized │ │ │ ├── org.springframework.security.authentication.jaas.JaasAuthenticationToken.serialized │ │ │ ├── org.springframework.security.authentication.jaas.JaasGrantedAuthority.serialized │ │ │ ├── org.springframework.security.authentication.jaas.event.JaasAuthenticationFailedEvent.serialized │ │ │ ├── org.springframework.security.authentication.jaas.event.JaasAuthenticationSuccessEvent.serialized │ │ │ ├── org.springframework.security.authentication.ott.DefaultOneTimeToken.serialized │ │ │ ├── org.springframework.security.authentication.ott.InvalidOneTimeTokenException.serialized │ │ │ ├── org.springframework.security.authentication.ott.OneTimeTokenAuthenticationToken.serialized │ │ │ ├── org.springframework.security.authentication.password.CompromisedPasswordException.serialized │ │ │ ├── org.springframework.security.authorization.AuthorityAuthorizationDecision.serialized │ │ │ ├── org.springframework.security.authorization.AuthorizationDecision.serialized │ │ │ ├── org.springframework.security.authorization.AuthorizationDeniedException.serialized │ │ │ ├── org.springframework.security.authorization.event.AuthorizationEvent.serialized │ │ │ ├── org.springframework.security.authorization.event.AuthorizationGrantedEvent.serialized │ │ │ ├── org.springframework.security.cas.authentication.CasAssertionAuthenticationToken.serialized │ │ │ ├── org.springframework.security.cas.authentication.CasAuthenticationToken.serialized │ │ │ ├── org.springframework.security.cas.authentication.CasServiceTicketAuthenticationToken.serialized │ │ │ ├── org.springframework.security.config.annotation.AlreadyBuiltException.serialized │ │ │ ├── org.springframework.security.core.authority.SimpleGrantedAuthority.serialized │ │ │ ├── org.springframework.security.core.context.SecurityContextImpl.serialized │ │ │ ├── org.springframework.security.core.context.TransientSecurityContext.serialized │ │ │ ├── org.springframework.security.core.session.AbstractSessionEvent.serialized │ │ │ ├── org.springframework.security.core.session.ReactiveSessionInformation.serialized │ │ │ ├── org.springframework.security.core.session.SessionInformation.serialized │ │ │ ├── org.springframework.security.core.userdetails.User$AuthorityComparator.serialized │ │ │ ├── org.springframework.security.core.userdetails.User.serialized │ │ │ ├── org.springframework.security.core.userdetails.UsernameNotFoundException.serialized │ │ │ ├── org.springframework.security.ldap.ppolicy.PasswordPolicyControl.serialized │ │ │ ├── org.springframework.security.ldap.ppolicy.PasswordPolicyException.serialized │ │ │ ├── org.springframework.security.ldap.ppolicy.PasswordPolicyResponseControl.serialized │ │ │ ├── org.springframework.security.ldap.userdetails.InetOrgPerson.serialized │ │ │ ├── org.springframework.security.ldap.userdetails.LdapAuthority.serialized │ │ │ ├── org.springframework.security.ldap.userdetails.LdapUserDetailsImpl.serialized │ │ │ ├── org.springframework.security.ldap.userdetails.Person.serialized │ │ │ ├── org.springframework.security.oauth2.client.ClientAuthorizationException.serialized │ │ │ ├── org.springframework.security.oauth2.client.ClientAuthorizationRequiredException.serialized │ │ │ ├── org.springframework.security.oauth2.client.OAuth2AuthorizedClient.serialized │ │ │ ├── org.springframework.security.oauth2.client.OAuth2AuthorizedClientId.serialized │ │ │ ├── org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken.serialized │ │ │ ├── org.springframework.security.oauth2.client.authentication.OAuth2AuthorizationCodeAuthenticationToken.serialized │ │ │ ├── org.springframework.security.oauth2.client.authentication.OAuth2LoginAuthenticationToken.serialized │ │ │ ├── org.springframework.security.oauth2.client.event.OAuth2AuthorizedClientRefreshedEvent.serialized │ │ │ ├── org.springframework.security.oauth2.client.oidc.authentication.event.OidcUserRefreshedEvent.serialized │ │ │ ├── org.springframework.security.oauth2.client.oidc.authentication.logout.OidcLogoutToken.serialized │ │ │ ├── org.springframework.security.oauth2.client.oidc.session.OidcSessionInformation.serialized │ │ │ ├── org.springframework.security.oauth2.client.registration.ClientRegistration$Builder.serialized │ │ │ ├── org.springframework.security.oauth2.client.registration.ClientRegistration$ClientSettings.serialized │ │ │ ├── org.springframework.security.oauth2.client.registration.ClientRegistration.serialized │ │ │ ├── org.springframework.security.oauth2.core.AuthenticationMethod.serialized │ │ │ ├── org.springframework.security.oauth2.core.AuthorizationGrantType.serialized │ │ │ ├── org.springframework.security.oauth2.core.ClientAuthenticationMethod.serialized │ │ │ ├── org.springframework.security.oauth2.core.DefaultOAuth2AuthenticatedPrincipal.serialized │ │ │ ├── org.springframework.security.oauth2.core.OAuth2AccessToken$TokenType.serialized │ │ │ ├── org.springframework.security.oauth2.core.OAuth2AccessToken.serialized │ │ │ ├── org.springframework.security.oauth2.core.OAuth2AuthenticationException.serialized │ │ │ ├── org.springframework.security.oauth2.core.OAuth2AuthorizationException.serialized │ │ │ ├── org.springframework.security.oauth2.core.OAuth2DeviceCode.serialized │ │ │ ├── org.springframework.security.oauth2.core.OAuth2Error.serialized │ │ │ ├── org.springframework.security.oauth2.core.OAuth2RefreshToken.serialized │ │ │ ├── org.springframework.security.oauth2.core.OAuth2UserCode.serialized │ │ │ ├── org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationExchange.serialized │ │ │ ├── org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest.serialized │ │ │ ├── org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponse.serialized │ │ │ ├── org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType.serialized │ │ │ ├── org.springframework.security.oauth2.core.oidc.OidcIdToken.serialized │ │ │ ├── org.springframework.security.oauth2.core.oidc.OidcUserInfo.serialized │ │ │ ├── org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser.serialized │ │ │ ├── org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority.serialized │ │ │ ├── org.springframework.security.oauth2.core.user.DefaultOAuth2User.serialized │ │ │ ├── org.springframework.security.oauth2.core.user.OAuth2UserAuthority.serialized │ │ │ ├── org.springframework.security.oauth2.jwt.BadJwtException.serialized │ │ │ ├── org.springframework.security.oauth2.jwt.Jwt.serialized │ │ │ ├── org.springframework.security.oauth2.jwt.JwtDecoderInitializationException.serialized │ │ │ ├── org.springframework.security.oauth2.jwt.JwtEncodingException.serialized │ │ │ ├── org.springframework.security.oauth2.jwt.JwtException.serialized │ │ │ ├── org.springframework.security.oauth2.jwt.JwtValidationException.serialized │ │ │ ├── org.springframework.security.oauth2.server.resource.BearerTokenError.serialized │ │ │ ├── org.springframework.security.oauth2.server.resource.InvalidBearerTokenException.serialized │ │ │ ├── org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthentication.serialized │ │ │ ├── org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthenticationToken.serialized │ │ │ ├── org.springframework.security.oauth2.server.resource.authentication.DPoPAuthenticationToken.serialized │ │ │ ├── org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken.serialized │ │ │ ├── org.springframework.security.oauth2.server.resource.introspection.BadOpaqueTokenException.serialized │ │ │ ├── org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionAuthenticatedPrincipal.serialized │ │ │ ├── org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionException.serialized │ │ │ ├── org.springframework.security.provisioning.MutableUser.serialized │ │ │ ├── org.springframework.security.saml2.Saml2Exception.serialized │ │ │ ├── org.springframework.security.saml2.core.Saml2Error.serialized │ │ │ ├── org.springframework.security.saml2.core.Saml2X509Credential.serialized │ │ │ ├── org.springframework.security.saml2.provider.service.authentication.DefaultSaml2AuthenticatedPrincipal.serialized │ │ │ ├── org.springframework.security.saml2.provider.service.authentication.Saml2Authentication.serialized │ │ │ ├── org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException.serialized │ │ │ ├── org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationToken.serialized │ │ │ ├── org.springframework.security.saml2.provider.service.authentication.Saml2PostAuthenticationRequest.serialized │ │ │ ├── org.springframework.security.saml2.provider.service.authentication.Saml2RedirectAuthenticationRequest.serialized │ │ │ ├── org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutRequest.serialized │ │ │ ├── org.springframework.security.saml2.provider.service.registration.OpenSamlAssertingPartyDetails.serialized │ │ │ ├── org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration$AssertingPartyDetails.serialized │ │ │ ├── org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration.serialized │ │ │ ├── org.springframework.security.web.UnreachableFilterChainException.serialized │ │ │ ├── org.springframework.security.web.authentication.WebAuthenticationDetails.serialized │ │ │ ├── org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken.serialized │ │ │ ├── org.springframework.security.web.authentication.preauth.PreAuthenticatedCredentialsNotFoundException.serialized │ │ │ ├── org.springframework.security.web.authentication.preauth.PreAuthenticatedGrantedAuthoritiesWebAuthenticationDetails.serialized │ │ │ ├── org.springframework.security.web.authentication.rememberme.CookieTheftException.serialized │ │ │ ├── org.springframework.security.web.authentication.rememberme.InvalidCookieException.serialized │ │ │ ├── org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationException.serialized │ │ │ ├── org.springframework.security.web.authentication.session.SessionAuthenticationException.serialized │ │ │ ├── org.springframework.security.web.authentication.session.SessionFixationProtectionEvent.serialized │ │ │ ├── org.springframework.security.web.authentication.switchuser.AuthenticationSwitchUserEvent.serialized │ │ │ ├── org.springframework.security.web.authentication.switchuser.SwitchUserGrantedAuthority.serialized │ │ │ ├── org.springframework.security.web.authentication.www.NonceExpiredException.serialized │ │ │ ├── org.springframework.security.web.csrf.CsrfException.serialized │ │ │ ├── org.springframework.security.web.csrf.DefaultCsrfToken.serialized │ │ │ ├── org.springframework.security.web.csrf.InvalidCsrfTokenException.serialized │ │ │ ├── org.springframework.security.web.csrf.MissingCsrfTokenException.serialized │ │ │ ├── org.springframework.security.web.firewall.RequestRejectedException.serialized │ │ │ ├── org.springframework.security.web.savedrequest.DefaultSavedRequest.serialized │ │ │ ├── org.springframework.security.web.savedrequest.SavedCookie.serialized │ │ │ ├── org.springframework.security.web.savedrequest.SimpleSavedRequest.serialized │ │ │ ├── org.springframework.security.web.server.csrf.CsrfException.serialized │ │ │ ├── org.springframework.security.web.server.csrf.DefaultCsrfToken.serialized │ │ │ ├── org.springframework.security.web.server.firewall.ServerExchangeRejectedException.serialized │ │ │ ├── org.springframework.security.web.session.HttpSessionCreatedEvent.serialized │ │ │ ├── org.springframework.security.web.session.HttpSessionIdChangedEvent.serialized │ │ │ ├── org.springframework.security.web.webauthn.api.AuthenticatorAssertionResponse.serialized │ │ │ ├── org.springframework.security.web.webauthn.api.AuthenticatorAttachment.serialized │ │ │ ├── org.springframework.security.web.webauthn.api.AuthenticatorTransport.serialized │ │ │ ├── org.springframework.security.web.webauthn.api.Bytes.serialized │ │ │ ├── org.springframework.security.web.webauthn.api.CredProtectAuthenticationExtensionsClientInput$CredProtect.serialized │ │ │ ├── org.springframework.security.web.webauthn.api.CredProtectAuthenticationExtensionsClientInput.serialized │ │ │ ├── org.springframework.security.web.webauthn.api.CredentialPropertiesOutput$ExtensionOutput.serialized │ │ │ ├── org.springframework.security.web.webauthn.api.CredentialPropertiesOutput.serialized │ │ │ ├── org.springframework.security.web.webauthn.api.ImmutableAuthenticationExtensionsClientInput.serialized │ │ │ ├── org.springframework.security.web.webauthn.api.ImmutableAuthenticationExtensionsClientInputs.serialized │ │ │ ├── org.springframework.security.web.webauthn.api.ImmutableAuthenticationExtensionsClientOutputs.serialized │ │ │ ├── org.springframework.security.web.webauthn.api.ImmutablePublicKeyCredentialUserEntity.serialized │ │ │ ├── org.springframework.security.web.webauthn.api.PublicKeyCredential.serialized │ │ │ ├── org.springframework.security.web.webauthn.api.PublicKeyCredentialDescriptor.serialized │ │ │ ├── org.springframework.security.web.webauthn.api.PublicKeyCredentialRequestOptions.serialized │ │ │ ├── org.springframework.security.web.webauthn.api.PublicKeyCredentialType.serialized │ │ │ ├── org.springframework.security.web.webauthn.api.UserVerificationRequirement.serialized │ │ │ ├── org.springframework.security.web.webauthn.authentication.WebAuthnAuthentication.serialized │ │ │ ├── org.springframework.security.web.webauthn.authentication.WebAuthnAuthenticationRequestToken.serialized │ │ │ └── org.springframework.security.web.webauthn.management.RelyingPartyAuthenticationRequest.serialized │ │ ├── 7.0.x/ │ │ │ ├── org.springframework.security.access.AccessDeniedException.serialized │ │ │ ├── org.springframework.security.access.AuthorizationServiceException.serialized │ │ │ ├── org.springframework.security.access.SecurityConfig.serialized │ │ │ ├── org.springframework.security.access.hierarchicalroles.CycleInRoleHierarchyException.serialized │ │ │ ├── org.springframework.security.access.intercept.RunAsUserToken.serialized │ │ │ ├── org.springframework.security.authentication.AccountExpiredException.serialized │ │ │ ├── org.springframework.security.authentication.AnonymousAuthenticationToken.serialized │ │ │ ├── org.springframework.security.authentication.AuthenticationCredentialsNotFoundException.serialized │ │ │ ├── org.springframework.security.authentication.AuthenticationServiceException.serialized │ │ │ ├── org.springframework.security.authentication.BadCredentialsException.serialized │ │ │ ├── org.springframework.security.authentication.CredentialsExpiredException.serialized │ │ │ ├── org.springframework.security.authentication.DisabledException.serialized │ │ │ ├── org.springframework.security.authentication.InsufficientAuthenticationException.serialized │ │ │ ├── org.springframework.security.authentication.InternalAuthenticationServiceException.serialized │ │ │ ├── org.springframework.security.authentication.LockedException.serialized │ │ │ ├── org.springframework.security.authentication.ProviderNotFoundException.serialized │ │ │ ├── org.springframework.security.authentication.RememberMeAuthenticationToken.serialized │ │ │ ├── org.springframework.security.authentication.TestingAuthenticationToken.serialized │ │ │ ├── org.springframework.security.authentication.UsernamePasswordAuthenticationToken.serialized │ │ │ ├── org.springframework.security.authentication.event.AuthenticationFailureBadCredentialsEvent.serialized │ │ │ ├── org.springframework.security.authentication.event.AuthenticationFailureCredentialsExpiredEvent.serialized │ │ │ ├── org.springframework.security.authentication.event.AuthenticationFailureDisabledEvent.serialized │ │ │ ├── org.springframework.security.authentication.event.AuthenticationFailureExpiredEvent.serialized │ │ │ ├── org.springframework.security.authentication.event.AuthenticationFailureLockedEvent.serialized │ │ │ ├── org.springframework.security.authentication.event.AuthenticationFailureProviderNotFoundEvent.serialized │ │ │ ├── org.springframework.security.authentication.event.AuthenticationFailureProxyUntrustedEvent.serialized │ │ │ ├── org.springframework.security.authentication.event.AuthenticationFailureServiceExceptionEvent.serialized │ │ │ ├── org.springframework.security.authentication.event.AuthenticationSuccessEvent.serialized │ │ │ ├── org.springframework.security.authentication.event.InteractiveAuthenticationSuccessEvent.serialized │ │ │ ├── org.springframework.security.authentication.event.LogoutSuccessEvent.serialized │ │ │ ├── org.springframework.security.authentication.jaas.JaasAuthenticationToken.serialized │ │ │ ├── org.springframework.security.authentication.jaas.JaasGrantedAuthority.serialized │ │ │ ├── org.springframework.security.authentication.jaas.event.JaasAuthenticationFailedEvent.serialized │ │ │ ├── org.springframework.security.authentication.jaas.event.JaasAuthenticationSuccessEvent.serialized │ │ │ ├── org.springframework.security.authentication.ott.DefaultOneTimeToken.serialized │ │ │ ├── org.springframework.security.authentication.ott.InvalidOneTimeTokenException.serialized │ │ │ ├── org.springframework.security.authentication.ott.OneTimeTokenAuthentication.serialized │ │ │ ├── org.springframework.security.authentication.ott.OneTimeTokenAuthenticationToken.serialized │ │ │ ├── org.springframework.security.authentication.password.CompromisedPasswordException.serialized │ │ │ ├── org.springframework.security.authorization.AuthorityAuthorizationDecision.serialized │ │ │ ├── org.springframework.security.authorization.AuthorizationDecision.serialized │ │ │ ├── org.springframework.security.authorization.AuthorizationDeniedException.serialized │ │ │ ├── org.springframework.security.authorization.event.AuthorizationEvent.serialized │ │ │ ├── org.springframework.security.authorization.event.AuthorizationGrantedEvent.serialized │ │ │ ├── org.springframework.security.cas.authentication.CasAssertionAuthenticationToken.serialized │ │ │ ├── org.springframework.security.cas.authentication.CasAuthenticationToken.serialized │ │ │ ├── org.springframework.security.cas.authentication.CasServiceTicketAuthenticationToken.serialized │ │ │ ├── org.springframework.security.config.annotation.AlreadyBuiltException.serialized │ │ │ ├── org.springframework.security.core.authority.FactorGrantedAuthority.serialized │ │ │ ├── org.springframework.security.core.authority.SimpleGrantedAuthority.serialized │ │ │ ├── org.springframework.security.core.context.SecurityContextImpl.serialized │ │ │ ├── org.springframework.security.core.context.TransientSecurityContext.serialized │ │ │ ├── org.springframework.security.core.session.AbstractSessionEvent.serialized │ │ │ ├── org.springframework.security.core.session.ReactiveSessionInformation.serialized │ │ │ ├── org.springframework.security.core.session.SessionInformation.serialized │ │ │ ├── org.springframework.security.core.userdetails.User$AuthorityComparator.serialized │ │ │ ├── org.springframework.security.core.userdetails.User.serialized │ │ │ ├── org.springframework.security.core.userdetails.UsernameNotFoundException.serialized │ │ │ ├── org.springframework.security.ldap.ppolicy.PasswordPolicyControl.serialized │ │ │ ├── org.springframework.security.ldap.ppolicy.PasswordPolicyException.serialized │ │ │ ├── org.springframework.security.ldap.ppolicy.PasswordPolicyResponseControl.serialized │ │ │ ├── org.springframework.security.ldap.userdetails.InetOrgPerson.serialized │ │ │ ├── org.springframework.security.ldap.userdetails.LdapAuthority.serialized │ │ │ ├── org.springframework.security.ldap.userdetails.LdapUserDetailsImpl.serialized │ │ │ ├── org.springframework.security.ldap.userdetails.Person.serialized │ │ │ ├── org.springframework.security.oauth2.client.ClientAuthorizationException.serialized │ │ │ ├── org.springframework.security.oauth2.client.ClientAuthorizationRequiredException.serialized │ │ │ ├── org.springframework.security.oauth2.client.OAuth2AuthorizedClient.serialized │ │ │ ├── org.springframework.security.oauth2.client.OAuth2AuthorizedClientId.serialized │ │ │ ├── org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken.serialized │ │ │ ├── org.springframework.security.oauth2.client.authentication.OAuth2AuthorizationCodeAuthenticationToken.serialized │ │ │ ├── org.springframework.security.oauth2.client.authentication.OAuth2LoginAuthenticationToken.serialized │ │ │ ├── org.springframework.security.oauth2.client.event.OAuth2AuthorizedClientRefreshedEvent.serialized │ │ │ ├── org.springframework.security.oauth2.client.oidc.authentication.event.OidcUserRefreshedEvent.serialized │ │ │ ├── org.springframework.security.oauth2.client.oidc.authentication.logout.OidcLogoutToken.serialized │ │ │ ├── org.springframework.security.oauth2.client.oidc.session.OidcSessionInformation.serialized │ │ │ ├── org.springframework.security.oauth2.client.registration.ClientRegistration$Builder.serialized │ │ │ ├── org.springframework.security.oauth2.client.registration.ClientRegistration$ClientSettings.serialized │ │ │ ├── org.springframework.security.oauth2.client.registration.ClientRegistration.serialized │ │ │ ├── org.springframework.security.oauth2.core.AuthenticationMethod.serialized │ │ │ ├── org.springframework.security.oauth2.core.AuthorizationGrantType.serialized │ │ │ ├── org.springframework.security.oauth2.core.ClientAuthenticationMethod.serialized │ │ │ ├── org.springframework.security.oauth2.core.DefaultOAuth2AuthenticatedPrincipal.serialized │ │ │ ├── org.springframework.security.oauth2.core.OAuth2AccessToken$TokenType.serialized │ │ │ ├── org.springframework.security.oauth2.core.OAuth2AccessToken.serialized │ │ │ ├── org.springframework.security.oauth2.core.OAuth2AuthenticationException.serialized │ │ │ ├── org.springframework.security.oauth2.core.OAuth2AuthorizationException.serialized │ │ │ ├── org.springframework.security.oauth2.core.OAuth2DeviceCode.serialized │ │ │ ├── org.springframework.security.oauth2.core.OAuth2Error.serialized │ │ │ ├── org.springframework.security.oauth2.core.OAuth2RefreshToken.serialized │ │ │ ├── org.springframework.security.oauth2.core.OAuth2UserCode.serialized │ │ │ ├── org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationExchange.serialized │ │ │ ├── org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest.serialized │ │ │ ├── org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponse.serialized │ │ │ ├── org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType.serialized │ │ │ ├── org.springframework.security.oauth2.core.oidc.OidcIdToken.serialized │ │ │ ├── org.springframework.security.oauth2.core.oidc.OidcUserInfo.serialized │ │ │ ├── org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser.serialized │ │ │ ├── org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority.serialized │ │ │ ├── org.springframework.security.oauth2.core.user.DefaultOAuth2User.serialized │ │ │ ├── org.springframework.security.oauth2.core.user.OAuth2UserAuthority.serialized │ │ │ ├── org.springframework.security.oauth2.jwt.BadJwtException.serialized │ │ │ ├── org.springframework.security.oauth2.jwt.Jwt.serialized │ │ │ ├── org.springframework.security.oauth2.jwt.JwtDecoderInitializationException.serialized │ │ │ ├── org.springframework.security.oauth2.jwt.JwtEncodingException.serialized │ │ │ ├── org.springframework.security.oauth2.jwt.JwtException.serialized │ │ │ ├── org.springframework.security.oauth2.jwt.JwtValidationException.serialized │ │ │ ├── org.springframework.security.oauth2.server.authorization.OAuth2Authorization$Token.serialized │ │ │ ├── org.springframework.security.oauth2.server.authorization.OAuth2Authorization.serialized │ │ │ ├── org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsent.serialized │ │ │ ├── org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationServerMetadata.serialized │ │ │ ├── org.springframework.security.oauth2.server.authorization.OAuth2ClientRegistration.serialized │ │ │ ├── org.springframework.security.oauth2.server.authorization.OAuth2TokenIntrospection.serialized │ │ │ ├── org.springframework.security.oauth2.server.authorization.OAuth2TokenType.serialized │ │ │ ├── org.springframework.security.oauth2.server.authorization.authentication.OAuth2AccessTokenAuthenticationToken.serialized │ │ │ ├── org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationToken.serialized │ │ │ ├── org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationConsentAuthenticationToken.serialized │ │ │ ├── org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationGrantAuthenticationToken.serialized │ │ │ ├── org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken.serialized │ │ │ ├── org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientRegistrationAuthenticationToken.serialized │ │ │ ├── org.springframework.security.oauth2.server.authorization.authentication.OAuth2DeviceAuthorizationConsentAuthenticationToken.serialized │ │ │ ├── org.springframework.security.oauth2.server.authorization.authentication.OAuth2DeviceAuthorizationRequestAuthenticationToken.serialized │ │ │ ├── org.springframework.security.oauth2.server.authorization.authentication.OAuth2DeviceVerificationAuthenticationToken.serialized │ │ │ ├── org.springframework.security.oauth2.server.authorization.authentication.OAuth2PushedAuthorizationRequestAuthenticationToken.serialized │ │ │ ├── org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenIntrospectionAuthenticationToken.serialized │ │ │ ├── org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenRevocationAuthenticationToken.serialized │ │ │ ├── org.springframework.security.oauth2.server.authorization.client.RegisteredClient.serialized │ │ │ ├── org.springframework.security.oauth2.server.authorization.oidc.OidcClientRegistration.serialized │ │ │ ├── org.springframework.security.oauth2.server.authorization.oidc.OidcProviderConfiguration.serialized │ │ │ ├── org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcClientRegistrationAuthenticationToken.serialized │ │ │ ├── org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcLogoutAuthenticationToken.serialized │ │ │ ├── org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcUserInfoAuthenticationToken.serialized │ │ │ ├── org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings.serialized │ │ │ ├── org.springframework.security.oauth2.server.authorization.settings.ClientSettings.serialized │ │ │ ├── org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat.serialized │ │ │ ├── org.springframework.security.oauth2.server.authorization.settings.TokenSettings.serialized │ │ │ ├── org.springframework.security.oauth2.server.resource.BearerTokenError.serialized │ │ │ ├── org.springframework.security.oauth2.server.resource.InvalidBearerTokenException.serialized │ │ │ ├── org.springframework.security.oauth2.server.resource.OAuth2ProtectedResourceMetadata.serialized │ │ │ ├── org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthentication.serialized │ │ │ ├── org.springframework.security.oauth2.server.resource.authentication.DPoPAuthenticationToken.serialized │ │ │ ├── org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken.serialized │ │ │ ├── org.springframework.security.oauth2.server.resource.introspection.BadOpaqueTokenException.serialized │ │ │ ├── org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionAuthenticatedPrincipal.serialized │ │ │ ├── org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionException.serialized │ │ │ ├── org.springframework.security.provisioning.MutableUser.serialized │ │ │ ├── org.springframework.security.saml2.Saml2Exception.serialized │ │ │ ├── org.springframework.security.saml2.core.Saml2Error.serialized │ │ │ ├── org.springframework.security.saml2.core.Saml2X509Credential.serialized │ │ │ ├── org.springframework.security.saml2.provider.service.authentication.DefaultSaml2AuthenticatedPrincipal.serialized │ │ │ ├── org.springframework.security.saml2.provider.service.authentication.Saml2AssertionAuthentication.serialized │ │ │ ├── org.springframework.security.saml2.provider.service.authentication.Saml2Authentication.serialized │ │ │ ├── org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException.serialized │ │ │ ├── org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationToken.serialized │ │ │ ├── org.springframework.security.saml2.provider.service.authentication.Saml2PostAuthenticationRequest.serialized │ │ │ ├── org.springframework.security.saml2.provider.service.authentication.Saml2RedirectAuthenticationRequest.serialized │ │ │ ├── org.springframework.security.saml2.provider.service.authentication.Saml2ResponseAssertion.serialized │ │ │ ├── org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutRequest.serialized │ │ │ ├── org.springframework.security.saml2.provider.service.registration.OpenSamlAssertingPartyDetails.serialized │ │ │ ├── org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration$AssertingPartyDetails.serialized │ │ │ ├── org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration.serialized │ │ │ ├── org.springframework.security.web.UnreachableFilterChainException.serialized │ │ │ ├── org.springframework.security.web.authentication.WebAuthenticationDetails.serialized │ │ │ ├── org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken.serialized │ │ │ ├── org.springframework.security.web.authentication.preauth.PreAuthenticatedCredentialsNotFoundException.serialized │ │ │ ├── org.springframework.security.web.authentication.preauth.PreAuthenticatedGrantedAuthoritiesWebAuthenticationDetails.serialized │ │ │ ├── org.springframework.security.web.authentication.rememberme.CookieTheftException.serialized │ │ │ ├── org.springframework.security.web.authentication.rememberme.InvalidCookieException.serialized │ │ │ ├── org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationException.serialized │ │ │ ├── org.springframework.security.web.authentication.session.SessionAuthenticationException.serialized │ │ │ ├── org.springframework.security.web.authentication.session.SessionFixationProtectionEvent.serialized │ │ │ ├── org.springframework.security.web.authentication.switchuser.AuthenticationSwitchUserEvent.serialized │ │ │ ├── org.springframework.security.web.authentication.switchuser.SwitchUserGrantedAuthority.serialized │ │ │ ├── org.springframework.security.web.authentication.www.NonceExpiredException.serialized │ │ │ ├── org.springframework.security.web.csrf.CsrfException.serialized │ │ │ ├── org.springframework.security.web.csrf.DefaultCsrfToken.serialized │ │ │ ├── org.springframework.security.web.csrf.InvalidCsrfTokenException.serialized │ │ │ ├── org.springframework.security.web.csrf.MissingCsrfTokenException.serialized │ │ │ ├── org.springframework.security.web.firewall.RequestRejectedException.serialized │ │ │ ├── org.springframework.security.web.savedrequest.DefaultSavedRequest.serialized │ │ │ ├── org.springframework.security.web.savedrequest.SavedCookie.serialized │ │ │ ├── org.springframework.security.web.savedrequest.SimpleSavedRequest.serialized │ │ │ ├── org.springframework.security.web.server.csrf.CsrfException.serialized │ │ │ ├── org.springframework.security.web.server.csrf.DefaultCsrfToken.serialized │ │ │ ├── org.springframework.security.web.server.firewall.ServerExchangeRejectedException.serialized │ │ │ ├── org.springframework.security.web.session.HttpSessionCreatedEvent.serialized │ │ │ ├── org.springframework.security.web.session.HttpSessionIdChangedEvent.serialized │ │ │ ├── org.springframework.security.web.webauthn.api.AuthenticatorAssertionResponse.serialized │ │ │ ├── org.springframework.security.web.webauthn.api.AuthenticatorAttachment.serialized │ │ │ ├── org.springframework.security.web.webauthn.api.AuthenticatorTransport.serialized │ │ │ ├── org.springframework.security.web.webauthn.api.Bytes.serialized │ │ │ ├── org.springframework.security.web.webauthn.api.CredProtectAuthenticationExtensionsClientInput$CredProtect.serialized │ │ │ ├── org.springframework.security.web.webauthn.api.CredProtectAuthenticationExtensionsClientInput.serialized │ │ │ ├── org.springframework.security.web.webauthn.api.CredentialPropertiesOutput$ExtensionOutput.serialized │ │ │ ├── org.springframework.security.web.webauthn.api.CredentialPropertiesOutput.serialized │ │ │ ├── org.springframework.security.web.webauthn.api.ImmutableAuthenticationExtensionsClientInput.serialized │ │ │ ├── org.springframework.security.web.webauthn.api.ImmutableAuthenticationExtensionsClientInputs.serialized │ │ │ ├── org.springframework.security.web.webauthn.api.ImmutableAuthenticationExtensionsClientOutputs.serialized │ │ │ ├── org.springframework.security.web.webauthn.api.ImmutablePublicKeyCredentialUserEntity.serialized │ │ │ ├── org.springframework.security.web.webauthn.api.PublicKeyCredential.serialized │ │ │ ├── org.springframework.security.web.webauthn.api.PublicKeyCredentialDescriptor.serialized │ │ │ ├── org.springframework.security.web.webauthn.api.PublicKeyCredentialRequestOptions.serialized │ │ │ ├── org.springframework.security.web.webauthn.api.PublicKeyCredentialType.serialized │ │ │ ├── org.springframework.security.web.webauthn.api.UserVerificationRequirement.serialized │ │ │ ├── org.springframework.security.web.webauthn.authentication.WebAuthnAuthentication.serialized │ │ │ ├── org.springframework.security.web.webauthn.authentication.WebAuthnAuthenticationRequestToken.serialized │ │ │ └── org.springframework.security.web.webauthn.management.RelyingPartyAuthenticationRequest.serialized │ │ └── 7.1.x/ │ │ ├── org.springframework.security.access.AccessDeniedException.serialized │ │ ├── org.springframework.security.access.AuthorizationServiceException.serialized │ │ ├── org.springframework.security.access.SecurityConfig.serialized │ │ ├── org.springframework.security.access.hierarchicalroles.CycleInRoleHierarchyException.serialized │ │ ├── org.springframework.security.access.intercept.RunAsUserToken.serialized │ │ ├── org.springframework.security.authentication.AccountExpiredException.serialized │ │ ├── org.springframework.security.authentication.AnonymousAuthenticationToken.serialized │ │ ├── org.springframework.security.authentication.AuthenticationCredentialsNotFoundException.serialized │ │ ├── org.springframework.security.authentication.AuthenticationServiceException.serialized │ │ ├── org.springframework.security.authentication.BadCredentialsException.serialized │ │ ├── org.springframework.security.authentication.CredentialsExpiredException.serialized │ │ ├── org.springframework.security.authentication.DisabledException.serialized │ │ ├── org.springframework.security.authentication.InsufficientAuthenticationException.serialized │ │ ├── org.springframework.security.authentication.InternalAuthenticationServiceException.serialized │ │ ├── org.springframework.security.authentication.LockedException.serialized │ │ ├── org.springframework.security.authentication.ProviderNotFoundException.serialized │ │ ├── org.springframework.security.authentication.RememberMeAuthenticationToken.serialized │ │ ├── org.springframework.security.authentication.TestingAuthenticationToken.serialized │ │ ├── org.springframework.security.authentication.UsernamePasswordAuthenticationToken.serialized │ │ ├── org.springframework.security.authentication.event.AuthenticationFailureBadCredentialsEvent.serialized │ │ ├── org.springframework.security.authentication.event.AuthenticationFailureCredentialsExpiredEvent.serialized │ │ ├── org.springframework.security.authentication.event.AuthenticationFailureDisabledEvent.serialized │ │ ├── org.springframework.security.authentication.event.AuthenticationFailureExpiredEvent.serialized │ │ ├── org.springframework.security.authentication.event.AuthenticationFailureLockedEvent.serialized │ │ ├── org.springframework.security.authentication.event.AuthenticationFailureProviderNotFoundEvent.serialized │ │ ├── org.springframework.security.authentication.event.AuthenticationFailureProxyUntrustedEvent.serialized │ │ ├── org.springframework.security.authentication.event.AuthenticationFailureServiceExceptionEvent.serialized │ │ ├── org.springframework.security.authentication.event.AuthenticationSuccessEvent.serialized │ │ ├── org.springframework.security.authentication.event.InteractiveAuthenticationSuccessEvent.serialized │ │ ├── org.springframework.security.authentication.event.LogoutSuccessEvent.serialized │ │ ├── org.springframework.security.authentication.jaas.JaasAuthenticationToken.serialized │ │ ├── org.springframework.security.authentication.jaas.JaasGrantedAuthority.serialized │ │ ├── org.springframework.security.authentication.jaas.event.JaasAuthenticationFailedEvent.serialized │ │ ├── org.springframework.security.authentication.jaas.event.JaasAuthenticationSuccessEvent.serialized │ │ ├── org.springframework.security.authentication.ott.DefaultOneTimeToken.serialized │ │ ├── org.springframework.security.authentication.ott.InvalidOneTimeTokenException.serialized │ │ ├── org.springframework.security.authentication.ott.OneTimeTokenAuthentication.serialized │ │ ├── org.springframework.security.authentication.ott.OneTimeTokenAuthenticationToken.serialized │ │ ├── org.springframework.security.authentication.password.CompromisedPasswordException.serialized │ │ ├── org.springframework.security.authorization.AuthorityAuthorizationDecision.serialized │ │ ├── org.springframework.security.authorization.AuthorizationDecision.serialized │ │ ├── org.springframework.security.authorization.AuthorizationDeniedException.serialized │ │ ├── org.springframework.security.authorization.event.AuthorizationEvent.serialized │ │ ├── org.springframework.security.authorization.event.AuthorizationGrantedEvent.serialized │ │ ├── org.springframework.security.cas.authentication.CasAssertionAuthenticationToken.serialized │ │ ├── org.springframework.security.cas.authentication.CasAuthenticationToken.serialized │ │ ├── org.springframework.security.cas.authentication.CasServiceTicketAuthenticationToken.serialized │ │ ├── org.springframework.security.config.annotation.AlreadyBuiltException.serialized │ │ ├── org.springframework.security.core.authority.FactorGrantedAuthority.serialized │ │ ├── org.springframework.security.core.authority.SimpleGrantedAuthority.serialized │ │ ├── org.springframework.security.core.context.SecurityContextImpl.serialized │ │ ├── org.springframework.security.core.context.TransientSecurityContext.serialized │ │ ├── org.springframework.security.core.session.AbstractSessionEvent.serialized │ │ ├── org.springframework.security.core.session.ReactiveSessionInformation.serialized │ │ ├── org.springframework.security.core.session.SessionInformation.serialized │ │ ├── org.springframework.security.core.userdetails.User$AuthorityComparator.serialized │ │ ├── org.springframework.security.core.userdetails.User.serialized │ │ ├── org.springframework.security.core.userdetails.UsernameNotFoundException.serialized │ │ ├── org.springframework.security.ldap.ppolicy.PasswordPolicyControl.serialized │ │ ├── org.springframework.security.ldap.ppolicy.PasswordPolicyException.serialized │ │ ├── org.springframework.security.ldap.ppolicy.PasswordPolicyResponseControl.serialized │ │ ├── org.springframework.security.ldap.userdetails.InetOrgPerson.serialized │ │ ├── org.springframework.security.ldap.userdetails.LdapAuthority.serialized │ │ ├── org.springframework.security.ldap.userdetails.LdapUserDetailsImpl.serialized │ │ ├── org.springframework.security.ldap.userdetails.Person.serialized │ │ ├── org.springframework.security.oauth2.client.ClientAuthorizationException.serialized │ │ ├── org.springframework.security.oauth2.client.ClientAuthorizationRequiredException.serialized │ │ ├── org.springframework.security.oauth2.client.OAuth2AuthorizedClient.serialized │ │ ├── org.springframework.security.oauth2.client.OAuth2AuthorizedClientId.serialized │ │ ├── org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken.serialized │ │ ├── org.springframework.security.oauth2.client.authentication.OAuth2AuthorizationCodeAuthenticationToken.serialized │ │ ├── org.springframework.security.oauth2.client.authentication.OAuth2LoginAuthenticationToken.serialized │ │ ├── org.springframework.security.oauth2.client.event.OAuth2AuthorizedClientRefreshedEvent.serialized │ │ ├── org.springframework.security.oauth2.client.oidc.authentication.event.OidcUserRefreshedEvent.serialized │ │ ├── org.springframework.security.oauth2.client.oidc.authentication.logout.OidcLogoutToken.serialized │ │ ├── org.springframework.security.oauth2.client.oidc.session.OidcSessionInformation.serialized │ │ ├── org.springframework.security.oauth2.client.registration.ClientRegistration$Builder.serialized │ │ ├── org.springframework.security.oauth2.client.registration.ClientRegistration$ClientSettings.serialized │ │ ├── org.springframework.security.oauth2.client.registration.ClientRegistration.serialized │ │ ├── org.springframework.security.oauth2.core.AuthenticationMethod.serialized │ │ ├── org.springframework.security.oauth2.core.AuthorizationGrantType.serialized │ │ ├── org.springframework.security.oauth2.core.ClientAuthenticationMethod.serialized │ │ ├── org.springframework.security.oauth2.core.DefaultOAuth2AuthenticatedPrincipal.serialized │ │ ├── org.springframework.security.oauth2.core.OAuth2AccessToken$TokenType.serialized │ │ ├── org.springframework.security.oauth2.core.OAuth2AccessToken.serialized │ │ ├── org.springframework.security.oauth2.core.OAuth2AuthenticationException.serialized │ │ ├── org.springframework.security.oauth2.core.OAuth2AuthorizationException.serialized │ │ ├── org.springframework.security.oauth2.core.OAuth2DeviceCode.serialized │ │ ├── org.springframework.security.oauth2.core.OAuth2Error.serialized │ │ ├── org.springframework.security.oauth2.core.OAuth2RefreshToken.serialized │ │ ├── org.springframework.security.oauth2.core.OAuth2UserCode.serialized │ │ ├── org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationExchange.serialized │ │ ├── org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest.serialized │ │ ├── org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponse.serialized │ │ ├── org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType.serialized │ │ ├── org.springframework.security.oauth2.core.oidc.OidcIdToken.serialized │ │ ├── org.springframework.security.oauth2.core.oidc.OidcUserInfo.serialized │ │ ├── org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser.serialized │ │ ├── org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority.serialized │ │ ├── org.springframework.security.oauth2.core.user.DefaultOAuth2User.serialized │ │ ├── org.springframework.security.oauth2.core.user.OAuth2UserAuthority.serialized │ │ ├── org.springframework.security.oauth2.jwt.BadJwtException.serialized │ │ ├── org.springframework.security.oauth2.jwt.Jwt.serialized │ │ ├── org.springframework.security.oauth2.jwt.JwtDecoderInitializationException.serialized │ │ ├── org.springframework.security.oauth2.jwt.JwtEncodingException.serialized │ │ ├── org.springframework.security.oauth2.jwt.JwtException.serialized │ │ ├── org.springframework.security.oauth2.jwt.JwtValidationException.serialized │ │ ├── org.springframework.security.oauth2.server.authorization.OAuth2Authorization$Token.serialized │ │ ├── org.springframework.security.oauth2.server.authorization.OAuth2Authorization.serialized │ │ ├── org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsent.serialized │ │ ├── org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationServerMetadata.serialized │ │ ├── org.springframework.security.oauth2.server.authorization.OAuth2ClientRegistration.serialized │ │ ├── org.springframework.security.oauth2.server.authorization.OAuth2TokenIntrospection.serialized │ │ ├── org.springframework.security.oauth2.server.authorization.OAuth2TokenType.serialized │ │ ├── org.springframework.security.oauth2.server.authorization.authentication.OAuth2AccessTokenAuthenticationToken.serialized │ │ ├── org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationToken.serialized │ │ ├── org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationConsentAuthenticationToken.serialized │ │ ├── org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationGrantAuthenticationToken.serialized │ │ ├── org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken.serialized │ │ ├── org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientRegistrationAuthenticationToken.serialized │ │ ├── org.springframework.security.oauth2.server.authorization.authentication.OAuth2DeviceAuthorizationConsentAuthenticationToken.serialized │ │ ├── org.springframework.security.oauth2.server.authorization.authentication.OAuth2DeviceAuthorizationRequestAuthenticationToken.serialized │ │ ├── org.springframework.security.oauth2.server.authorization.authentication.OAuth2DeviceVerificationAuthenticationToken.serialized │ │ ├── org.springframework.security.oauth2.server.authorization.authentication.OAuth2PushedAuthorizationRequestAuthenticationToken.serialized │ │ ├── org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenIntrospectionAuthenticationToken.serialized │ │ ├── org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenRevocationAuthenticationToken.serialized │ │ ├── org.springframework.security.oauth2.server.authorization.client.RegisteredClient.serialized │ │ ├── org.springframework.security.oauth2.server.authorization.oidc.OidcClientRegistration.serialized │ │ ├── org.springframework.security.oauth2.server.authorization.oidc.OidcProviderConfiguration.serialized │ │ ├── org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcClientRegistrationAuthenticationToken.serialized │ │ ├── org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcLogoutAuthenticationToken.serialized │ │ ├── org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcUserInfoAuthenticationToken.serialized │ │ ├── org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings.serialized │ │ ├── org.springframework.security.oauth2.server.authorization.settings.ClientSettings.serialized │ │ ├── org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat.serialized │ │ ├── org.springframework.security.oauth2.server.authorization.settings.TokenSettings.serialized │ │ ├── org.springframework.security.oauth2.server.resource.BearerTokenError.serialized │ │ ├── org.springframework.security.oauth2.server.resource.InvalidBearerTokenException.serialized │ │ ├── org.springframework.security.oauth2.server.resource.OAuth2ProtectedResourceMetadata.serialized │ │ ├── org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthentication.serialized │ │ ├── org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthenticationToken.serialized │ │ ├── org.springframework.security.oauth2.server.resource.authentication.DPoPAuthenticationToken.serialized │ │ ├── org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken.serialized │ │ ├── org.springframework.security.oauth2.server.resource.introspection.BadOpaqueTokenException.serialized │ │ ├── org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionAuthenticatedPrincipal.serialized │ │ ├── org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionException.serialized │ │ ├── org.springframework.security.provisioning.MutableUser.serialized │ │ ├── org.springframework.security.saml2.Saml2Exception.serialized │ │ ├── org.springframework.security.saml2.core.Saml2Error.serialized │ │ ├── org.springframework.security.saml2.core.Saml2X509Credential.serialized │ │ ├── org.springframework.security.saml2.provider.service.authentication.DefaultSaml2AuthenticatedPrincipal.serialized │ │ ├── org.springframework.security.saml2.provider.service.authentication.Saml2AssertionAuthentication.serialized │ │ ├── org.springframework.security.saml2.provider.service.authentication.Saml2Authentication.serialized │ │ ├── org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException.serialized │ │ ├── org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationToken.serialized │ │ ├── org.springframework.security.saml2.provider.service.authentication.Saml2PostAuthenticationRequest.serialized │ │ ├── org.springframework.security.saml2.provider.service.authentication.Saml2RedirectAuthenticationRequest.serialized │ │ ├── org.springframework.security.saml2.provider.service.authentication.Saml2ResponseAssertion.serialized │ │ ├── org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutRequest.serialized │ │ ├── org.springframework.security.saml2.provider.service.registration.OpenSamlAssertingPartyDetails.serialized │ │ ├── org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration$AssertingPartyDetails.serialized │ │ ├── org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration.serialized │ │ ├── org.springframework.security.web.UnreachableFilterChainException.serialized │ │ ├── org.springframework.security.web.authentication.WebAuthenticationDetails.serialized │ │ ├── org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken.serialized │ │ ├── org.springframework.security.web.authentication.preauth.PreAuthenticatedCredentialsNotFoundException.serialized │ │ ├── org.springframework.security.web.authentication.preauth.PreAuthenticatedGrantedAuthoritiesWebAuthenticationDetails.serialized │ │ ├── org.springframework.security.web.authentication.rememberme.CookieTheftException.serialized │ │ ├── org.springframework.security.web.authentication.rememberme.InvalidCookieException.serialized │ │ ├── org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationException.serialized │ │ ├── org.springframework.security.web.authentication.session.SessionAuthenticationException.serialized │ │ ├── org.springframework.security.web.authentication.session.SessionFixationProtectionEvent.serialized │ │ ├── org.springframework.security.web.authentication.switchuser.AuthenticationSwitchUserEvent.serialized │ │ ├── org.springframework.security.web.authentication.switchuser.SwitchUserGrantedAuthority.serialized │ │ ├── org.springframework.security.web.authentication.www.NonceExpiredException.serialized │ │ ├── org.springframework.security.web.csrf.CsrfException.serialized │ │ ├── org.springframework.security.web.csrf.DefaultCsrfToken.serialized │ │ ├── org.springframework.security.web.csrf.InvalidCsrfTokenException.serialized │ │ ├── org.springframework.security.web.csrf.MissingCsrfTokenException.serialized │ │ ├── org.springframework.security.web.firewall.RequestRejectedException.serialized │ │ ├── org.springframework.security.web.savedrequest.DefaultSavedRequest.serialized │ │ ├── org.springframework.security.web.savedrequest.SavedCookie.serialized │ │ ├── org.springframework.security.web.savedrequest.SimpleSavedRequest.serialized │ │ ├── org.springframework.security.web.server.csrf.CsrfException.serialized │ │ ├── org.springframework.security.web.server.csrf.DefaultCsrfToken.serialized │ │ ├── org.springframework.security.web.server.firewall.ServerExchangeRejectedException.serialized │ │ ├── org.springframework.security.web.session.HttpSessionCreatedEvent.serialized │ │ ├── org.springframework.security.web.session.HttpSessionIdChangedEvent.serialized │ │ ├── org.springframework.security.web.webauthn.api.AttestationConveyancePreference.serialized │ │ ├── org.springframework.security.web.webauthn.api.AuthenticatorAssertionResponse.serialized │ │ ├── org.springframework.security.web.webauthn.api.AuthenticatorAttachment.serialized │ │ ├── org.springframework.security.web.webauthn.api.AuthenticatorSelectionCriteria.serialized │ │ ├── org.springframework.security.web.webauthn.api.AuthenticatorTransport.serialized │ │ ├── org.springframework.security.web.webauthn.api.Bytes.serialized │ │ ├── org.springframework.security.web.webauthn.api.COSEAlgorithmIdentifier.serialized │ │ ├── org.springframework.security.web.webauthn.api.CredProtectAuthenticationExtensionsClientInput$CredProtect.serialized │ │ ├── org.springframework.security.web.webauthn.api.CredProtectAuthenticationExtensionsClientInput.serialized │ │ ├── org.springframework.security.web.webauthn.api.CredentialPropertiesOutput$ExtensionOutput.serialized │ │ ├── org.springframework.security.web.webauthn.api.CredentialPropertiesOutput.serialized │ │ ├── org.springframework.security.web.webauthn.api.ImmutableAuthenticationExtensionsClientInput.serialized │ │ ├── org.springframework.security.web.webauthn.api.ImmutableAuthenticationExtensionsClientInputs.serialized │ │ ├── org.springframework.security.web.webauthn.api.ImmutableAuthenticationExtensionsClientOutputs.serialized │ │ ├── org.springframework.security.web.webauthn.api.ImmutablePublicKeyCredentialUserEntity.serialized │ │ ├── org.springframework.security.web.webauthn.api.PublicKeyCredential.serialized │ │ ├── org.springframework.security.web.webauthn.api.PublicKeyCredentialCreationOptions.serialized │ │ ├── org.springframework.security.web.webauthn.api.PublicKeyCredentialDescriptor.serialized │ │ ├── org.springframework.security.web.webauthn.api.PublicKeyCredentialParameters.serialized │ │ ├── org.springframework.security.web.webauthn.api.PublicKeyCredentialRequestOptions.serialized │ │ ├── org.springframework.security.web.webauthn.api.PublicKeyCredentialRpEntity.serialized │ │ ├── org.springframework.security.web.webauthn.api.PublicKeyCredentialType.serialized │ │ ├── org.springframework.security.web.webauthn.api.ResidentKeyRequirement.serialized │ │ ├── org.springframework.security.web.webauthn.api.UserVerificationRequirement.serialized │ │ ├── org.springframework.security.web.webauthn.authentication.WebAuthnAuthentication.serialized │ │ ├── org.springframework.security.web.webauthn.authentication.WebAuthnAuthenticationRequestToken.serialized │ │ └── org.springframework.security.web.webauthn.management.RelyingPartyAuthenticationRequest.serialized │ └── users.properties ├── core/ │ ├── spring-security-core.gradle │ └── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── security/ │ │ │ ├── access/ │ │ │ │ ├── AccessDeniedException.java │ │ │ │ ├── AuthorizationServiceException.java │ │ │ │ ├── PermissionCacheOptimizer.java │ │ │ │ ├── PermissionEvaluator.java │ │ │ │ ├── annotation/ │ │ │ │ │ ├── Secured.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── expression/ │ │ │ │ │ ├── AbstractSecurityExpressionHandler.java │ │ │ │ │ ├── DenyAllPermissionEvaluator.java │ │ │ │ │ ├── ExpressionUtils.java │ │ │ │ │ ├── SecurityExpressionHandler.java │ │ │ │ │ ├── SecurityExpressionOperations.java │ │ │ │ │ ├── SecurityExpressionRoot.java │ │ │ │ │ ├── method/ │ │ │ │ │ │ ├── DefaultMethodSecurityExpressionHandler.java │ │ │ │ │ │ ├── MethodSecurityEvaluationContext.java │ │ │ │ │ │ ├── MethodSecurityExpressionHandler.java │ │ │ │ │ │ ├── MethodSecurityExpressionOperations.java │ │ │ │ │ │ ├── MethodSecurityExpressionRoot.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── hierarchicalroles/ │ │ │ │ │ ├── CycleInRoleHierarchyException.java │ │ │ │ │ ├── NullRoleHierarchy.java │ │ │ │ │ ├── RoleHierarchy.java │ │ │ │ │ ├── RoleHierarchyAuthoritiesMapper.java │ │ │ │ │ ├── RoleHierarchyImpl.java │ │ │ │ │ ├── RoleHierarchyUtils.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── package-info.java │ │ │ │ └── prepost/ │ │ │ │ ├── PostAuthorize.java │ │ │ │ ├── PostFilter.java │ │ │ │ ├── PreAuthorize.java │ │ │ │ ├── PreFilter.java │ │ │ │ └── package-info.java │ │ │ ├── aot/ │ │ │ │ └── hint/ │ │ │ │ ├── AuthorizeReturnObjectCoreHintsRegistrar.java │ │ │ │ ├── AuthorizeReturnObjectHintsRegistrar.java │ │ │ │ ├── CoreSecurityRuntimeHints.java │ │ │ │ ├── OneTimeTokenRuntimeHints.java │ │ │ │ ├── PrePostAuthorizeExpressionBeanHintsRegistrar.java │ │ │ │ ├── PrePostAuthorizeHintsRegistrar.java │ │ │ │ ├── SecurityHintsAotProcessor.java │ │ │ │ ├── SecurityHintsRegistrar.java │ │ │ │ └── package-info.java │ │ │ ├── authentication/ │ │ │ │ ├── AbstractAuthenticationToken.java │ │ │ │ ├── AbstractUserDetailsReactiveAuthenticationManager.java │ │ │ │ ├── AccountExpiredException.java │ │ │ │ ├── AccountStatusException.java │ │ │ │ ├── AccountStatusUserDetailsChecker.java │ │ │ │ ├── AnonymousAuthenticationProvider.java │ │ │ │ ├── AnonymousAuthenticationToken.java │ │ │ │ ├── AuthenticationCredentialsNotFoundException.java │ │ │ │ ├── AuthenticationDetailsSource.java │ │ │ │ ├── AuthenticationEventPublisher.java │ │ │ │ ├── AuthenticationManager.java │ │ │ │ ├── AuthenticationManagerResolver.java │ │ │ │ ├── AuthenticationObservationContext.java │ │ │ │ ├── AuthenticationObservationConvention.java │ │ │ │ ├── AuthenticationProvider.java │ │ │ │ ├── AuthenticationServiceException.java │ │ │ │ ├── AuthenticationTrustResolver.java │ │ │ │ ├── AuthenticationTrustResolverImpl.java │ │ │ │ ├── BadCredentialsException.java │ │ │ │ ├── CachingUserDetailsService.java │ │ │ │ ├── CredentialsExpiredException.java │ │ │ │ ├── DefaultAuthenticationEventPublisher.java │ │ │ │ ├── DelegatingReactiveAuthenticationManager.java │ │ │ │ ├── DisabledException.java │ │ │ │ ├── InsufficientAuthenticationException.java │ │ │ │ ├── InternalAuthenticationServiceException.java │ │ │ │ ├── LockedException.java │ │ │ │ ├── ObservationAuthenticationManager.java │ │ │ │ ├── ObservationReactiveAuthenticationManager.java │ │ │ │ ├── ProviderManager.java │ │ │ │ ├── ProviderNotFoundException.java │ │ │ │ ├── ReactiveAuthenticationManager.java │ │ │ │ ├── ReactiveAuthenticationManagerAdapter.java │ │ │ │ ├── ReactiveAuthenticationManagerResolver.java │ │ │ │ ├── RememberMeAuthenticationProvider.java │ │ │ │ ├── RememberMeAuthenticationToken.java │ │ │ │ ├── TestingAuthenticationProvider.java │ │ │ │ ├── TestingAuthenticationToken.java │ │ │ │ ├── UserDetailsRepositoryReactiveAuthenticationManager.java │ │ │ │ ├── UsernamePasswordAuthenticationToken.java │ │ │ │ ├── dao/ │ │ │ │ │ ├── AbstractUserDetailsAuthenticationProvider.java │ │ │ │ │ ├── DaoAuthenticationProvider.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── event/ │ │ │ │ │ ├── AbstractAuthenticationEvent.java │ │ │ │ │ ├── AbstractAuthenticationFailureEvent.java │ │ │ │ │ ├── AuthenticationFailureBadCredentialsEvent.java │ │ │ │ │ ├── AuthenticationFailureCredentialsExpiredEvent.java │ │ │ │ │ ├── AuthenticationFailureDisabledEvent.java │ │ │ │ │ ├── AuthenticationFailureExpiredEvent.java │ │ │ │ │ ├── AuthenticationFailureLockedEvent.java │ │ │ │ │ ├── AuthenticationFailureProviderNotFoundEvent.java │ │ │ │ │ ├── AuthenticationFailureProxyUntrustedEvent.java │ │ │ │ │ ├── AuthenticationFailureServiceExceptionEvent.java │ │ │ │ │ ├── AuthenticationSuccessEvent.java │ │ │ │ │ ├── InteractiveAuthenticationSuccessEvent.java │ │ │ │ │ ├── LoggerListener.java │ │ │ │ │ ├── LogoutSuccessEvent.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── jaas/ │ │ │ │ │ ├── AbstractJaasAuthenticationProvider.java │ │ │ │ │ ├── AuthorityGranter.java │ │ │ │ │ ├── DefaultJaasAuthenticationProvider.java │ │ │ │ │ ├── DefaultLoginExceptionResolver.java │ │ │ │ │ ├── JaasAuthenticationCallbackHandler.java │ │ │ │ │ ├── JaasAuthenticationProvider.java │ │ │ │ │ ├── JaasAuthenticationToken.java │ │ │ │ │ ├── JaasGrantedAuthority.java │ │ │ │ │ ├── JaasNameCallbackHandler.java │ │ │ │ │ ├── JaasPasswordCallbackHandler.java │ │ │ │ │ ├── LoginExceptionResolver.java │ │ │ │ │ ├── SecurityContextLoginModule.java │ │ │ │ │ ├── event/ │ │ │ │ │ │ ├── JaasAuthenticationEvent.java │ │ │ │ │ │ ├── JaasAuthenticationFailedEvent.java │ │ │ │ │ │ ├── JaasAuthenticationSuccessEvent.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ ├── memory/ │ │ │ │ │ │ ├── InMemoryConfiguration.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── ott/ │ │ │ │ │ ├── DefaultOneTimeToken.java │ │ │ │ │ ├── GenerateOneTimeTokenRequest.java │ │ │ │ │ ├── InMemoryOneTimeTokenService.java │ │ │ │ │ ├── InvalidOneTimeTokenException.java │ │ │ │ │ ├── JdbcOneTimeTokenService.java │ │ │ │ │ ├── OneTimeToken.java │ │ │ │ │ ├── OneTimeTokenAuthentication.java │ │ │ │ │ ├── OneTimeTokenAuthenticationProvider.java │ │ │ │ │ ├── OneTimeTokenAuthenticationToken.java │ │ │ │ │ ├── OneTimeTokenService.java │ │ │ │ │ ├── package-info.java │ │ │ │ │ └── reactive/ │ │ │ │ │ ├── InMemoryReactiveOneTimeTokenService.java │ │ │ │ │ ├── OneTimeTokenReactiveAuthenticationManager.java │ │ │ │ │ ├── ReactiveOneTimeTokenService.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── package-info.java │ │ │ │ └── password/ │ │ │ │ ├── CompromisedPasswordChecker.java │ │ │ │ ├── CompromisedPasswordDecision.java │ │ │ │ ├── CompromisedPasswordException.java │ │ │ │ ├── ReactiveCompromisedPasswordChecker.java │ │ │ │ └── package-info.java │ │ │ ├── authorization/ │ │ │ │ ├── AllAuthoritiesAuthorizationManager.java │ │ │ │ ├── AllAuthoritiesReactiveAuthorizationManager.java │ │ │ │ ├── AllRequiredFactorsAuthorizationManager.java │ │ │ │ ├── AuthenticatedAuthorizationManager.java │ │ │ │ ├── AuthenticatedReactiveAuthorizationManager.java │ │ │ │ ├── AuthoritiesAuthorizationManager.java │ │ │ │ ├── AuthorityAuthorizationDecision.java │ │ │ │ ├── AuthorityAuthorizationManager.java │ │ │ │ ├── AuthorityReactiveAuthorizationManager.java │ │ │ │ ├── AuthorizationDecision.java │ │ │ │ ├── AuthorizationDeniedException.java │ │ │ │ ├── AuthorizationEventPublisher.java │ │ │ │ ├── AuthorizationManager.java │ │ │ │ ├── AuthorizationManagerFactories.java │ │ │ │ ├── AuthorizationManagerFactory.java │ │ │ │ ├── AuthorizationManagers.java │ │ │ │ ├── AuthorizationObservationContext.java │ │ │ │ ├── AuthorizationObservationConvention.java │ │ │ │ ├── AuthorizationProxyFactory.java │ │ │ │ ├── AuthorizationResult.java │ │ │ │ ├── ConditionalAuthorizationManager.java │ │ │ │ ├── DefaultAuthorizationManagerFactory.java │ │ │ │ ├── ExpressionAuthorizationDecision.java │ │ │ │ ├── FactorAuthorizationDecision.java │ │ │ │ ├── MapRequiredAuthoritiesRepository.java │ │ │ │ ├── ObservationAuthorizationManager.java │ │ │ │ ├── ObservationReactiveAuthorizationManager.java │ │ │ │ ├── ReactiveAuthorizationManager.java │ │ │ │ ├── RequiredAuthoritiesAuthorizationManager.java │ │ │ │ ├── RequiredAuthoritiesRepository.java │ │ │ │ ├── RequiredFactor.java │ │ │ │ ├── RequiredFactorError.java │ │ │ │ ├── SingleResultAuthorizationManager.java │ │ │ │ ├── SpringAuthorizationEventPublisher.java │ │ │ │ ├── event/ │ │ │ │ │ ├── AuthorizationDeniedEvent.java │ │ │ │ │ ├── AuthorizationEvent.java │ │ │ │ │ ├── AuthorizationGrantedEvent.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── method/ │ │ │ │ │ ├── AbstractAuthorizationManagerRegistry.java │ │ │ │ │ ├── AbstractExpressionAttributeRegistry.java │ │ │ │ │ ├── AuthorizationAdvisor.java │ │ │ │ │ ├── AuthorizationAdvisorProxyFactory.java │ │ │ │ │ ├── AuthorizationInterceptorsOrder.java │ │ │ │ │ ├── AuthorizationManagerAfterMethodInterceptor.java │ │ │ │ │ ├── AuthorizationManagerAfterReactiveMethodInterceptor.java │ │ │ │ │ ├── AuthorizationManagerBeforeMethodInterceptor.java │ │ │ │ │ ├── AuthorizationManagerBeforeReactiveMethodInterceptor.java │ │ │ │ │ ├── AuthorizationMethodPointcuts.java │ │ │ │ │ ├── AuthorizationProxy.java │ │ │ │ │ ├── AuthorizeReturnObject.java │ │ │ │ │ ├── AuthorizeReturnObjectMethodInterceptor.java │ │ │ │ │ ├── ExpressionAttribute.java │ │ │ │ │ ├── ExpressionUtils.java │ │ │ │ │ ├── HandleAuthorizationDenied.java │ │ │ │ │ ├── Jsr250AuthorizationManager.java │ │ │ │ │ ├── MethodAuthorizationDeniedHandler.java │ │ │ │ │ ├── MethodExpressionAuthorizationManager.java │ │ │ │ │ ├── MethodInvocationResult.java │ │ │ │ │ ├── NoOpAuthorizationEventPublisher.java │ │ │ │ │ ├── NullReturningMethodAuthorizationDeniedHandler.java │ │ │ │ │ ├── PostAuthorizeAuthorizationManager.java │ │ │ │ │ ├── PostAuthorizeExpressionAttribute.java │ │ │ │ │ ├── PostAuthorizeExpressionAttributeRegistry.java │ │ │ │ │ ├── PostAuthorizeReactiveAuthorizationManager.java │ │ │ │ │ ├── PostFilterAuthorizationMethodInterceptor.java │ │ │ │ │ ├── PostFilterAuthorizationReactiveMethodInterceptor.java │ │ │ │ │ ├── PostFilterExpressionAttributeRegistry.java │ │ │ │ │ ├── PreAuthorizeAuthorizationManager.java │ │ │ │ │ ├── PreAuthorizeExpressionAttribute.java │ │ │ │ │ ├── PreAuthorizeExpressionAttributeRegistry.java │ │ │ │ │ ├── PreAuthorizeReactiveAuthorizationManager.java │ │ │ │ │ ├── PreFilterAuthorizationMethodInterceptor.java │ │ │ │ │ ├── PreFilterAuthorizationReactiveMethodInterceptor.java │ │ │ │ │ ├── PreFilterExpressionAttributeRegistry.java │ │ │ │ │ ├── ReactiveAuthenticationUtils.java │ │ │ │ │ ├── ReactiveExpressionUtils.java │ │ │ │ │ ├── ReactiveMethodInvocationUtils.java │ │ │ │ │ ├── ReflectiveMethodAuthorizationDeniedHandler.java │ │ │ │ │ ├── SecuredAuthorizationManager.java │ │ │ │ │ ├── ThrowingMethodAuthorizationDeniedHandler.java │ │ │ │ │ └── package-info.java │ │ │ │ └── package-info.java │ │ │ ├── concurrent/ │ │ │ │ ├── AbstractDelegatingSecurityContextSupport.java │ │ │ │ ├── DelegatingSecurityContextCallable.java │ │ │ │ ├── DelegatingSecurityContextExecutor.java │ │ │ │ ├── DelegatingSecurityContextExecutorService.java │ │ │ │ ├── DelegatingSecurityContextRunnable.java │ │ │ │ ├── DelegatingSecurityContextScheduledExecutorService.java │ │ │ │ └── package-info.java │ │ │ ├── context/ │ │ │ │ ├── DelegatingApplicationListener.java │ │ │ │ └── package-info.java │ │ │ ├── converter/ │ │ │ │ ├── RsaKeyConverters.java │ │ │ │ └── package-info.java │ │ │ ├── core/ │ │ │ │ ├── AuthenticatedPrincipal.java │ │ │ │ ├── Authentication.java │ │ │ │ ├── AuthenticationException.java │ │ │ │ ├── ComparableVersion.java │ │ │ │ ├── CredentialsContainer.java │ │ │ │ ├── GrantedAuthority.java │ │ │ │ ├── SimpleAuthentication.java │ │ │ │ ├── SpringSecurityCoreVersion.java │ │ │ │ ├── SpringSecurityMessageSource.java │ │ │ │ ├── Transient.java │ │ │ │ ├── annotation/ │ │ │ │ │ ├── AbstractSecurityAnnotationScanner.java │ │ │ │ │ ├── AnnotationTemplateExpressionDefaults.java │ │ │ │ │ ├── AuthenticationPrincipal.java │ │ │ │ │ ├── CurrentSecurityContext.java │ │ │ │ │ ├── ExpressionTemplateSecurityAnnotationScanner.java │ │ │ │ │ ├── ExpressionTemplateValueProvider.java │ │ │ │ │ ├── SecurityAnnotationScanner.java │ │ │ │ │ ├── SecurityAnnotationScanners.java │ │ │ │ │ ├── UniqueSecurityAnnotationScanner.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── authority/ │ │ │ │ │ ├── AuthorityUtils.java │ │ │ │ │ ├── FactorGrantedAuthority.java │ │ │ │ │ ├── GrantedAuthoritiesContainer.java │ │ │ │ │ ├── SimpleGrantedAuthority.java │ │ │ │ │ ├── mapping/ │ │ │ │ │ │ ├── Attributes2GrantedAuthoritiesMapper.java │ │ │ │ │ │ ├── GrantedAuthoritiesMapper.java │ │ │ │ │ │ ├── MapBasedAttributes2GrantedAuthoritiesMapper.java │ │ │ │ │ │ ├── MappableAttributesRetriever.java │ │ │ │ │ │ ├── NullAuthoritiesMapper.java │ │ │ │ │ │ ├── SimpleAttributes2GrantedAuthoritiesMapper.java │ │ │ │ │ │ ├── SimpleAuthorityMapper.java │ │ │ │ │ │ ├── SimpleMappableAttributesRetriever.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── context/ │ │ │ │ │ ├── DeferredSecurityContext.java │ │ │ │ │ ├── GlobalSecurityContextHolderStrategy.java │ │ │ │ │ ├── InheritableThreadLocalSecurityContextHolderStrategy.java │ │ │ │ │ ├── ListeningSecurityContextHolderStrategy.java │ │ │ │ │ ├── ObservationSecurityContextChangedListener.java │ │ │ │ │ ├── ReactiveSecurityContextHolder.java │ │ │ │ │ ├── ReactiveSecurityContextHolderThreadLocalAccessor.java │ │ │ │ │ ├── SecurityContext.java │ │ │ │ │ ├── SecurityContextChangedEvent.java │ │ │ │ │ ├── SecurityContextChangedListener.java │ │ │ │ │ ├── SecurityContextHolder.java │ │ │ │ │ ├── SecurityContextHolderStrategy.java │ │ │ │ │ ├── SecurityContextHolderThreadLocalAccessor.java │ │ │ │ │ ├── SecurityContextImpl.java │ │ │ │ │ ├── ThreadLocalSecurityContextHolderStrategy.java │ │ │ │ │ ├── TransientSecurityContext.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── package-info.java │ │ │ │ ├── parameters/ │ │ │ │ │ ├── AnnotationParameterNameDiscoverer.java │ │ │ │ │ ├── DefaultSecurityParameterNameDiscoverer.java │ │ │ │ │ ├── P.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── session/ │ │ │ │ │ ├── AbstractSessionEvent.java │ │ │ │ │ ├── InMemoryReactiveSessionRegistry.java │ │ │ │ │ ├── ReactiveSessionInformation.java │ │ │ │ │ ├── ReactiveSessionRegistry.java │ │ │ │ │ ├── SessionCreationEvent.java │ │ │ │ │ ├── SessionDestroyedEvent.java │ │ │ │ │ ├── SessionIdChangedEvent.java │ │ │ │ │ ├── SessionInformation.java │ │ │ │ │ ├── SessionRegistry.java │ │ │ │ │ ├── SessionRegistryImpl.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── token/ │ │ │ │ │ ├── DefaultToken.java │ │ │ │ │ ├── KeyBasedPersistenceTokenService.java │ │ │ │ │ ├── SecureRandomFactoryBean.java │ │ │ │ │ ├── Sha512DigestUtils.java │ │ │ │ │ ├── Token.java │ │ │ │ │ ├── TokenService.java │ │ │ │ │ └── package-info.java │ │ │ │ └── userdetails/ │ │ │ │ ├── AuthenticationUserDetailsService.java │ │ │ │ ├── MapReactiveUserDetailsService.java │ │ │ │ ├── ReactiveUserDetailsPasswordService.java │ │ │ │ ├── ReactiveUserDetailsService.java │ │ │ │ ├── User.java │ │ │ │ ├── UserCache.java │ │ │ │ ├── UserDetails.java │ │ │ │ ├── UserDetailsByNameServiceWrapper.java │ │ │ │ ├── UserDetailsChecker.java │ │ │ │ ├── UserDetailsPasswordService.java │ │ │ │ ├── UserDetailsService.java │ │ │ │ ├── UsernameNotFoundException.java │ │ │ │ ├── cache/ │ │ │ │ │ ├── NullUserCache.java │ │ │ │ │ ├── SpringCacheBasedUserCache.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── jdbc/ │ │ │ │ │ ├── JdbcDaoImpl.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── memory/ │ │ │ │ │ ├── UserAttribute.java │ │ │ │ │ ├── UserAttributeEditor.java │ │ │ │ │ └── package-info.java │ │ │ │ └── package-info.java │ │ │ ├── jackson/ │ │ │ │ ├── AnonymousAuthenticationTokenMixin.java │ │ │ │ ├── BadCredentialsExceptionMixin.java │ │ │ │ ├── CoreJacksonModule.java │ │ │ │ ├── FactorGrantedAuthorityMixin.java │ │ │ │ ├── OneTimeTokenAuthenticationMixin.java │ │ │ │ ├── RememberMeAuthenticationTokenMixin.java │ │ │ │ ├── SecurityJacksonModule.java │ │ │ │ ├── SecurityJacksonModules.java │ │ │ │ ├── SimpleGrantedAuthorityMixin.java │ │ │ │ ├── TestingAuthenticationTokenMixin.java │ │ │ │ ├── UserDeserializer.java │ │ │ │ ├── UserMixin.java │ │ │ │ ├── UsernamePasswordAuthenticationTokenDeserializer.java │ │ │ │ ├── UsernamePasswordAuthenticationTokenMixin.java │ │ │ │ └── package-info.java │ │ │ ├── jackson2/ │ │ │ │ ├── AbstractUnmodifiableCollectionDeserializer.java │ │ │ │ ├── AnonymousAuthenticationTokenMixin.java │ │ │ │ ├── BadCredentialsExceptionMixin.java │ │ │ │ ├── CoreJackson2Module.java │ │ │ │ ├── FactorGrantedAuthorityMixin.java │ │ │ │ ├── OneTimeTokenAuthenticationTokenMixin.java │ │ │ │ ├── RememberMeAuthenticationTokenMixin.java │ │ │ │ ├── SecurityJackson2Modules.java │ │ │ │ ├── SimpleGrantedAuthorityMixin.java │ │ │ │ ├── UnmodifiableListDeserializer.java │ │ │ │ ├── UnmodifiableListMixin.java │ │ │ │ ├── UnmodifiableMapDeserializer.java │ │ │ │ ├── UnmodifiableMapMixin.java │ │ │ │ ├── UnmodifiableSetDeserializer.java │ │ │ │ ├── UnmodifiableSetMixin.java │ │ │ │ ├── UserDeserializer.java │ │ │ │ ├── UserMixin.java │ │ │ │ ├── UsernamePasswordAuthenticationTokenDeserializer.java │ │ │ │ ├── UsernamePasswordAuthenticationTokenMixin.java │ │ │ │ └── package-info.java │ │ │ ├── provisioning/ │ │ │ │ ├── GroupManager.java │ │ │ │ ├── InMemoryUserDetailsManager.java │ │ │ │ ├── JdbcUserDetailsManager.java │ │ │ │ ├── MutableUser.java │ │ │ │ ├── MutableUserDetails.java │ │ │ │ ├── UserDetailsManager.java │ │ │ │ └── package-info.java │ │ │ ├── scheduling/ │ │ │ │ ├── DelegatingSecurityContextSchedulingTaskExecutor.java │ │ │ │ ├── DelegatingSecurityContextTaskScheduler.java │ │ │ │ └── package-info.java │ │ │ ├── task/ │ │ │ │ ├── DelegatingSecurityContextAsyncTaskExecutor.java │ │ │ │ ├── DelegatingSecurityContextTaskExecutor.java │ │ │ │ └── package-info.java │ │ │ └── util/ │ │ │ ├── FieldUtils.java │ │ │ ├── InMemoryResource.java │ │ │ ├── MethodInvocationUtils.java │ │ │ ├── SimpleMethodInvocation.java │ │ │ └── package-info.java │ │ └── resources/ │ │ ├── META-INF/ │ │ │ ├── services/ │ │ │ │ └── io.micrometer.context.ThreadLocalAccessor │ │ │ └── spring/ │ │ │ └── aot.factories │ │ └── org/ │ │ └── springframework/ │ │ └── security/ │ │ ├── core/ │ │ │ ├── ott/ │ │ │ │ └── jdbc/ │ │ │ │ └── one-time-tokens-schema.sql │ │ │ └── userdetails/ │ │ │ └── jdbc/ │ │ │ └── users.ddl │ │ ├── messages.properties │ │ ├── messages_ca.properties │ │ ├── messages_cs_CZ.properties │ │ ├── messages_de.properties │ │ ├── messages_en.properties │ │ ├── messages_es_ES.properties │ │ ├── messages_fr.properties │ │ ├── messages_it.properties │ │ ├── messages_ja.properties │ │ ├── messages_ko_KR.properties │ │ ├── messages_lt.properties │ │ ├── messages_mn_MN.properties │ │ ├── messages_nl.properties │ │ ├── messages_pl.properties │ │ ├── messages_pt_BR.properties │ │ ├── messages_pt_PT.properties │ │ ├── messages_ru.properties │ │ ├── messages_uk_UA.properties │ │ ├── messages_zh_CN.properties │ │ └── messages_zh_TW.properties │ ├── site/ │ │ └── site.xml │ └── test/ │ ├── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── security/ │ │ ├── DelegatingSecurityContextTestUtils.java │ │ ├── ITargetObject.java │ │ ├── OtherTargetObject.java │ │ ├── PopulatedDatabase.java │ │ ├── TargetObject.java │ │ ├── TestDataSource.java │ │ ├── access/ │ │ │ ├── expression/ │ │ │ │ ├── AbstractSecurityExpressionHandlerTests.java │ │ │ │ ├── ExpressionUtilsTests.java │ │ │ │ └── SecurityExpressionRootTests.java │ │ │ └── hierarchicalroles/ │ │ │ ├── HierarchicalRolesTestHelper.java │ │ │ ├── RoleHierarchyAuthoritiesMapperTests.java │ │ │ ├── RoleHierarchyImplTests.java │ │ │ ├── RoleHierarchyUtilsTests.java │ │ │ └── TestHelperTests.java │ │ ├── aot/ │ │ │ └── hint/ │ │ │ ├── AuthorizeReturnObjectCoreHintsRegistrarTests.java │ │ │ ├── AuthorizeReturnObjectHintsRegistrarTests.java │ │ │ ├── CoreSecurityRuntimeHintsTests.java │ │ │ ├── OneTimeTokenRuntimeHintsTests.java │ │ │ ├── PrePostAuthorizeHintsRegistrarTests.java │ │ │ └── SecurityHintsAotProcessorTests.java │ │ ├── authentication/ │ │ │ ├── AbstractAuthenticationBuilderTests.java │ │ │ ├── AbstractAuthenticationTokenTests.java │ │ │ ├── AuthenticationTrustResolverImplTests.java │ │ │ ├── DefaultAuthenticationEventPublisherTests.java │ │ │ ├── DelegatingReactiveAuthenticationManagerTests.java │ │ │ ├── NonBuildableAuthenticationToken.java │ │ │ ├── ObservationAuthenticationManagerTests.java │ │ │ ├── ObservationReactiveAuthenticationManagerTests.java │ │ │ ├── ProviderManagerTests.java │ │ │ ├── ReactiveAuthenticationManagerAdapterTests.java │ │ │ ├── ReactiveUserDetailsServiceAuthenticationManagerTests.java │ │ │ ├── SecurityAssertions.java │ │ │ ├── TestAuthentication.java │ │ │ ├── TestingAuthenticationProviderTests.java │ │ │ ├── TestingAuthenticationTokenTests.java │ │ │ ├── UserDetailsRepositoryReactiveAuthenticationManagerTests.java │ │ │ ├── UsernamePasswordAuthenticationTokenTests.java │ │ │ ├── anonymous/ │ │ │ │ ├── AnonymousAuthenticationProviderTests.java │ │ │ │ └── AnonymousAuthenticationTokenTests.java │ │ │ ├── dao/ │ │ │ │ ├── DaoAuthenticationProviderTests.java │ │ │ │ └── MockUserCache.java │ │ │ ├── event/ │ │ │ │ ├── AuthenticationEventTests.java │ │ │ │ └── LoggerListenerTests.java │ │ │ ├── jaas/ │ │ │ │ ├── DefaultJaasAuthenticationProviderTests.java │ │ │ │ ├── JaasAuthenticationProviderTests.java │ │ │ │ ├── JaasAuthenticationTokenTests.java │ │ │ │ ├── JaasEventCheck.java │ │ │ │ ├── JaasGrantedAuthorityTests.java │ │ │ │ ├── Sec760Tests.java │ │ │ │ ├── SecurityContextLoginModuleTests.java │ │ │ │ ├── TestAuthorityGranter.java │ │ │ │ ├── TestCallbackHandler.java │ │ │ │ ├── TestLoginModule.java │ │ │ │ └── memory/ │ │ │ │ └── InMemoryConfigurationTests.java │ │ │ ├── ott/ │ │ │ │ ├── InMemoryOneTimeTokenServiceTests.java │ │ │ │ ├── JdbcOneTimeTokenServiceTests.java │ │ │ │ ├── OneTimeTokenAuthenticationProviderTests.java │ │ │ │ ├── OneTimeTokenAuthenticationTests.java │ │ │ │ ├── OneTimeTokenAuthenticationTokenTests.java │ │ │ │ └── reactive/ │ │ │ │ ├── InMemoryReactiveOneTimeTokenServiceTests.java │ │ │ │ └── OneTimeTokenReactiveAuthenticationManagerTests.java │ │ │ └── rememberme/ │ │ │ ├── RememberMeAuthenticationProviderTests.java │ │ │ └── RememberMeAuthenticationTokenTests.java │ │ ├── authorization/ │ │ │ ├── AllAuthoritiesAuthorizationManagerTests.java │ │ │ ├── AllAuthoritiesReactiveAuthorizationManagerTests.java │ │ │ ├── AllRequiredFactorsAuthorizationManagerTests.java │ │ │ ├── AuthenticatedAuthorizationManagerTests.java │ │ │ ├── AuthenticatedReactiveAuthorizationManagerTests.java │ │ │ ├── AuthoritiesAuthorizationManagerTests.java │ │ │ ├── AuthorityAuthorizationManagerTests.java │ │ │ ├── AuthorityReactiveAuthorizationManagerTests.java │ │ │ ├── AuthorizationAdvisorProxyFactoryTests.java │ │ │ ├── AuthorizationManagerFactoryTests.java │ │ │ ├── AuthorizationManagerTests.java │ │ │ ├── AuthorizationManagersTests.java │ │ │ ├── ConditionalAuthorizationManagerTests.java │ │ │ ├── FactorAuthorizationDecisionTests.java │ │ │ ├── MapRequiredAuthoritiesRepositoryTests.java │ │ │ ├── ObservationAuthorizationManagerTests.java │ │ │ ├── ObservationReactiveAuthorizationManagerTests.java │ │ │ ├── ReactiveAuthorizationAdvisorProxyFactoryTests.java │ │ │ ├── RequiredAuthoritiesAuthorizationManagerTests.java │ │ │ ├── RequiredFactorErrorTests.java │ │ │ ├── RequiredFactorTests.java │ │ │ ├── SingleResultAuthorizationManagerTests.java │ │ │ ├── SpringAuthorizationEventPublisherTests.java │ │ │ └── method/ │ │ │ ├── AuthorizationManagerAfterMethodInterceptorTests.java │ │ │ ├── AuthorizationManagerAfterReactiveMethodInterceptorTests.java │ │ │ ├── AuthorizationManagerBeforeMethodInterceptorTests.java │ │ │ ├── AuthorizationManagerBeforeReactiveMethodInterceptorTests.java │ │ │ ├── AuthorizationMethodPointcutsTests.java │ │ │ ├── BusinessService.java │ │ │ ├── ExpressionUtilsTests.java │ │ │ ├── Jsr250AuthorizationManagerTests.java │ │ │ ├── MethodExpressionAuthorizationManagerTests.java │ │ │ ├── MockMethodInvocation.java │ │ │ ├── NullReturningMethodAuthorizationDeniedHandlerTests.java │ │ │ ├── PostAuthorizeAuthorizationManagerTests.java │ │ │ ├── PostAuthorizeReactiveAuthorizationManagerTests.java │ │ │ ├── PostFilterAuthorizationMethodInterceptorTests.java │ │ │ ├── PostFilterAuthorizationReactiveMethodInterceptorTests.java │ │ │ ├── PreAuthorizeAuthorizationManagerTests.java │ │ │ ├── PreAuthorizeReactiveAuthorizationManagerTests.java │ │ │ ├── PreFilterAuthorizationMethodInterceptorTests.java │ │ │ ├── PreFilterAuthorizationReactiveMethodInterceptorTests.java │ │ │ ├── RequireAdminRole.java │ │ │ ├── RequireUserRole.java │ │ │ └── SecuredAuthorizationManagerTests.java │ │ ├── concurrent/ │ │ │ ├── AbstractDelegatingSecurityContextExecutorServiceTests.java │ │ │ ├── AbstractDelegatingSecurityContextExecutorTests.java │ │ │ ├── AbstractDelegatingSecurityContextScheduledExecutorServiceTests.java │ │ │ ├── AbstractDelegatingSecurityContextTestSupport.java │ │ │ ├── CurrentDelegatingSecurityContextExecutorServiceTests.java │ │ │ ├── CurrentDelegatingSecurityContextExecutorTests.java │ │ │ ├── CurrentDelegatingSecurityContextScheduledExecutorServiceTests.java │ │ │ ├── DelegatingSecurityContextCallableTests.java │ │ │ ├── DelegatingSecurityContextExecutorIntegrationTests.java │ │ │ ├── DelegatingSecurityContextExecutorServiceIntegrationTests.java │ │ │ ├── DelegatingSecurityContextRunnableTests.java │ │ │ ├── DelegatingSecurityContextScheduledExecutorServiceIntegrationTests.java │ │ │ ├── DelegatingSecurityContextSupportTests.java │ │ │ ├── ExplicitDelegatingSecurityContextExecutorServiceTests.java │ │ │ ├── ExplicitDelegatingSecurityContextExecutorTests.java │ │ │ └── ExplicitDelegatingSecurityContextScheduledExecutorServiceTests.java │ │ ├── context/ │ │ │ └── DelegatingApplicationListenerTests.java │ │ ├── converter/ │ │ │ └── RsaKeyConvertersTests.java │ │ ├── core/ │ │ │ ├── JavaVersionTests.java │ │ │ ├── SpringSecurityCoreVersionTests.java │ │ │ ├── SpringSecurityMessageSourceTests.java │ │ │ ├── StaticFinalReflectionUtils.java │ │ │ ├── annotation/ │ │ │ │ ├── ExpressionTemplateSecurityAnnotationScannerTests.java │ │ │ │ └── UniqueSecurityAnnotationScannerTests.java │ │ │ ├── authority/ │ │ │ │ ├── AuthorityUtilsTests.java │ │ │ │ ├── FactorGrantedAuthorityTests.java │ │ │ │ ├── SimpleGrantedAuthorityTests.java │ │ │ │ └── mapping/ │ │ │ │ ├── MapBasedAttributes2GrantedAuthoritiesMapperTests.java │ │ │ │ ├── SimpleAuthoritiesMapperTests.java │ │ │ │ ├── SimpleMappableRolesRetrieverTests.java │ │ │ │ └── SimpleRoles2GrantedAuthoritiesMapperTests.java │ │ │ ├── context/ │ │ │ │ ├── InheritableThreadLocalSecurityContextHolderStrategyTests.java │ │ │ │ ├── ListeningSecurityContextHolderStrategyTests.java │ │ │ │ ├── MockSecurityContextHolderStrategy.java │ │ │ │ ├── ObservationSecurityContextChangedListenerTests.java │ │ │ │ ├── ReactiveSecurityContextHolderTests.java │ │ │ │ ├── ReactiveSecurityContextHolderThreadLocalAccessorTests.java │ │ │ │ ├── SecurityContextHolderTests.java │ │ │ │ ├── SecurityContextHolderThreadLocalAccessorTests.java │ │ │ │ ├── SecurityContextImplTests.java │ │ │ │ └── ThreadLocalSecurityContextHolderStrategyTests.java │ │ │ ├── parameters/ │ │ │ │ ├── AnnotationParameterNameDiscovererTests.java │ │ │ │ └── DefaultSecurityParameterNameDiscovererTests.java │ │ │ ├── session/ │ │ │ │ ├── SessionInformationTests.java │ │ │ │ └── SessionRegistryImplTests.java │ │ │ ├── token/ │ │ │ │ ├── DefaultTokenTests.java │ │ │ │ ├── KeyBasedPersistenceTokenServiceTests.java │ │ │ │ └── SecureRandomFactoryBeanTests.java │ │ │ └── userdetails/ │ │ │ ├── MapReactiveUserDetailsServiceTests.java │ │ │ ├── MockUserDetailsService.java │ │ │ ├── PasswordEncodedUser.java │ │ │ ├── UserDetailsByNameServiceWrapperTests.java │ │ │ ├── UserTests.java │ │ │ ├── cache/ │ │ │ │ ├── NullUserCacheTests.java │ │ │ │ └── SpringCacheBasedUserCacheTests.java │ │ │ ├── jdbc/ │ │ │ │ └── JdbcDaoImplTests.java │ │ │ └── memory/ │ │ │ └── UserAttributeEditorTests.java │ │ ├── jackson/ │ │ │ ├── AbstractMixinTests.java │ │ │ ├── AnonymousAuthenticationTokenMixinTests.java │ │ │ ├── BadCredentialsExceptionMixinTests.java │ │ │ ├── FactorGrantedAuthorityMixinTests.java │ │ │ ├── RememberMeAuthenticationTokenMixinTests.java │ │ │ ├── SecurityContextMixinTests.java │ │ │ ├── SecurityJacksonModulesTests.java │ │ │ ├── SimpleGrantedAuthorityMixinTests.java │ │ │ ├── TestingAuthenticationTokenMixinTests.java │ │ │ ├── UnmodifiableMapTests.java │ │ │ ├── UserDeserializerTests.java │ │ │ └── UsernamePasswordAuthenticationTokenMixinTests.java │ │ ├── jackson2/ │ │ │ ├── AbstractMixinTests.java │ │ │ ├── AnonymousAuthenticationTokenMixinTests.java │ │ │ ├── BadCredentialsExceptionMixinTests.java │ │ │ ├── FactorGrantedAuthorityMixinTests.java │ │ │ ├── RememberMeAuthenticationTokenMixinTests.java │ │ │ ├── SecurityContextMixinTests.java │ │ │ ├── SecurityJackson2ModulesTests.java │ │ │ ├── SimpleGrantedAuthorityMixinTests.java │ │ │ ├── UnmodifiableMapDeserializerTests.java │ │ │ ├── UserDeserializerTests.java │ │ │ └── UsernamePasswordAuthenticationTokenMixinTests.java │ │ ├── provisioning/ │ │ │ ├── InMemoryUserDetailsManagerTests.java │ │ │ └── JdbcUserDetailsManagerTests.java │ │ ├── scheduling/ │ │ │ ├── AbstractSecurityContextSchedulingTaskExecutorTests.java │ │ │ ├── CurrentSecurityContextSchedulingTaskExecutorTests.java │ │ │ ├── DelegatingSecurityContextSchedulingTaskExecutorIntegrationTests.java │ │ │ ├── DelegatingSecurityContextTaskSchedulerIntegrationTests.java │ │ │ ├── DelegatingSecurityContextTaskSchedulerTests.java │ │ │ └── ExplicitSecurityContextSchedulingTaskExecutorTests.java │ │ ├── task/ │ │ │ ├── AbstractDelegatingSecurityContextAsyncTaskExecutorTests.java │ │ │ ├── CurrentDelegatingSecurityContextAsyncTaskExecutorTests.java │ │ │ ├── CurrentDelegatingSecurityContextTaskExecutorTests.java │ │ │ ├── DelegatingSecurityContextAsyncTaskExecutorIntegrationTests.java │ │ │ ├── DelegatingSecurityContextTaskExecutorIntegrationTests.java │ │ │ ├── ExplicitDelegatingSecurityContextAsyncTaskExecutorTests.java │ │ │ └── ExplicitDelegatingSecurityContextTaskExecutorTests.java │ │ └── util/ │ │ ├── BusinessService.java │ │ ├── BusinessServiceImpl.java │ │ ├── Entity.java │ │ ├── FieldUtilsTests.java │ │ ├── InMemoryResourceTests.java │ │ ├── MethodInvocationUtilsTests.java │ │ ├── RequireAdminRole.java │ │ └── RequireUserRole.java │ ├── kotlin/ │ │ └── org/ │ │ └── springframework/ │ │ └── security/ │ │ └── access/ │ │ └── expression/ │ │ └── method/ │ │ └── DefaultMethodSecurityExpressionHandlerKotlinTests.kt │ └── resources/ │ ├── logback-test.xml │ └── org/ │ └── springframework/ │ └── security/ │ ├── authentication/ │ │ └── jaas/ │ │ ├── DefaultJaasAuthenticationProviderTests.xml │ │ ├── JaasAuthenticationProviderTests.conf │ │ ├── JaasAuthenticationProviderTests.xml │ │ ├── login.conf │ │ ├── test1.conf │ │ └── test2.conf │ └── converter/ │ └── simple.priv ├── crypto/ │ ├── spring-security-crypto.gradle │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── security/ │ │ └── crypto/ │ │ ├── argon2/ │ │ │ ├── Argon2EncodingUtils.java │ │ │ ├── Argon2PasswordEncoder.java │ │ │ └── package-info.java │ │ ├── bcrypt/ │ │ │ ├── BCrypt.java │ │ │ ├── BCryptPasswordEncoder.java │ │ │ └── package-info.java │ │ ├── codec/ │ │ │ ├── Hex.java │ │ │ ├── Utf8.java │ │ │ └── package-info.java │ │ ├── encrypt/ │ │ │ ├── AesBytesEncryptor.java │ │ │ ├── BouncyCastleAesBytesEncryptor.java │ │ │ ├── BouncyCastleAesCbcBytesEncryptor.java │ │ │ ├── BouncyCastleAesGcmBytesEncryptor.java │ │ │ ├── BytesEncryptor.java │ │ │ ├── CipherUtils.java │ │ │ ├── Encryptors.java │ │ │ ├── HexEncodingTextEncryptor.java │ │ │ ├── KeyStoreKeyFactory.java │ │ │ ├── RsaAlgorithm.java │ │ │ ├── RsaKeyHelper.java │ │ │ ├── RsaKeyHolder.java │ │ │ ├── RsaRawEncryptor.java │ │ │ ├── RsaSecretEncryptor.java │ │ │ ├── TextEncryptor.java │ │ │ └── package-info.java │ │ ├── factory/ │ │ │ ├── PasswordEncoderFactories.java │ │ │ └── package-info.java │ │ ├── keygen/ │ │ │ ├── Base64StringKeyGenerator.java │ │ │ ├── BytesKeyGenerator.java │ │ │ ├── HexEncodingStringKeyGenerator.java │ │ │ ├── KeyGenerators.java │ │ │ ├── SecureRandomBytesKeyGenerator.java │ │ │ ├── SharedKeyGenerator.java │ │ │ ├── StringKeyGenerator.java │ │ │ └── package-info.java │ │ ├── password/ │ │ │ ├── AbstractPasswordEncoder.java │ │ │ ├── AbstractValidatingPasswordEncoder.java │ │ │ ├── DelegatingPasswordEncoder.java │ │ │ ├── Digester.java │ │ │ ├── LdapShaPasswordEncoder.java │ │ │ ├── Md4.java │ │ │ ├── Md4PasswordEncoder.java │ │ │ ├── MessageDigestPasswordEncoder.java │ │ │ ├── NoOpPasswordEncoder.java │ │ │ ├── PasswordEncoder.java │ │ │ ├── PasswordEncoderUtils.java │ │ │ ├── Pbkdf2PasswordEncoder.java │ │ │ ├── StandardPasswordEncoder.java │ │ │ └── package-info.java │ │ ├── password4j/ │ │ │ ├── Argon2Password4jPasswordEncoder.java │ │ │ ├── BalloonHashingPassword4jPasswordEncoder.java │ │ │ ├── BcryptPassword4jPasswordEncoder.java │ │ │ ├── Password4jPasswordEncoder.java │ │ │ ├── Pbkdf2Password4jPasswordEncoder.java │ │ │ ├── ScryptPassword4jPasswordEncoder.java │ │ │ └── package-info.java │ │ ├── scrypt/ │ │ │ ├── SCryptPasswordEncoder.java │ │ │ └── package-info.java │ │ └── util/ │ │ ├── EncodingUtils.java │ │ └── package-info.java │ ├── test/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── security/ │ │ │ └── crypto/ │ │ │ ├── argon2/ │ │ │ │ ├── Argon2EncodingUtilsTests.java │ │ │ │ └── Argon2PasswordEncoderTests.java │ │ │ ├── assertions/ │ │ │ │ └── CryptoAssertionsTests.java │ │ │ ├── bcrypt/ │ │ │ │ ├── BCryptPasswordEncoderTests.java │ │ │ │ └── BCryptTests.java │ │ │ ├── codec/ │ │ │ │ ├── HexTests.java │ │ │ │ └── Utf8Tests.java │ │ │ ├── encrypt/ │ │ │ │ ├── AesBytesEncryptorTests.java │ │ │ │ ├── BouncyCastleAesBytesEncryptorEquivalencyTests.java │ │ │ │ ├── BouncyCastleAesBytesEncryptorTests.java │ │ │ │ ├── CryptoAssumptions.java │ │ │ │ ├── EncryptorsTests.java │ │ │ │ ├── KeyStoreKeyFactoryTests.java │ │ │ │ ├── RsaKeyHelperTests.java │ │ │ │ ├── RsaRawEncryptorTests.java │ │ │ │ └── RsaSecretEncryptorTests.java │ │ │ ├── factory/ │ │ │ │ └── PasswordEncoderFactoriesTests.java │ │ │ ├── keygen/ │ │ │ │ ├── Base64StringKeyGeneratorTests.java │ │ │ │ └── KeyGeneratorsTests.java │ │ │ ├── password/ │ │ │ │ ├── AbstractPasswordEncoderTests.java │ │ │ │ ├── AbstractPasswordEncoderValidationTests.java │ │ │ │ ├── AbstractValidatingPasswordEncoderTests.java │ │ │ │ ├── DelegatingPasswordEncoderTests.java │ │ │ │ ├── DigesterTests.java │ │ │ │ ├── LdapShaPasswordEncoderTests.java │ │ │ │ ├── Md4PasswordEncoderTests.java │ │ │ │ ├── MessageDigestPasswordEncoderTests.java │ │ │ │ ├── NoOpPasswordEncoderTests.java │ │ │ │ ├── PasswordEncoderUtilsTests.java │ │ │ │ ├── Pbkdf2PasswordEncoderTests.java │ │ │ │ └── StandardPasswordEncoderTests.java │ │ │ ├── password4j/ │ │ │ │ ├── Argon2Password4jPasswordEncoderTests.java │ │ │ │ ├── BalloonHashingPassword4jPasswordEncoderTests.java │ │ │ │ ├── BcryptPassword4jPasswordEncoderTests.java │ │ │ │ ├── Password4jPasswordEncoderTests.java │ │ │ │ ├── PasswordCompatibilityTests.java │ │ │ │ ├── Pbkdf2Password4jPasswordEncoderTests.java │ │ │ │ └── ScryptPassword4jPasswordEncoderTests.java │ │ │ ├── scrypt/ │ │ │ │ └── SCryptPasswordEncoderTests.java │ │ │ └── util/ │ │ │ └── EncodingUtilsTests.java │ │ └── resources/ │ │ ├── bad.pem │ │ ├── fake.pem │ │ ├── keystore.jks │ │ ├── keystore.pkcs12 │ │ ├── logback-test.xml │ │ └── spacey.pem │ └── testFixtures/ │ └── java/ │ └── org/ │ └── springframework/ │ └── security/ │ └── crypto/ │ └── assertions/ │ ├── CryptoAssertions.java │ └── CryptoStringAssert.java ├── data/ │ ├── spring-security-data.gradle │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── security/ │ │ └── data/ │ │ ├── aot/ │ │ │ └── hint/ │ │ │ ├── AuthorizeReturnObjectDataHintsRegistrar.java │ │ │ └── package-info.java │ │ └── repository/ │ │ └── query/ │ │ ├── SecurityEvaluationContextExtension.java │ │ └── package-info.java │ └── test/ │ ├── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── security/ │ │ └── data/ │ │ ├── aot/ │ │ │ └── hint/ │ │ │ └── AuthorizeReturnObjectDataHintsRegistrarTests.java │ │ └── repository/ │ │ └── query/ │ │ └── SecurityEvaluationContextExtensionTests.java │ └── resources/ │ └── logback-test.xml ├── dependencies/ │ └── spring-security-dependencies.gradle ├── docs/ │ ├── .gitignore │ ├── antora-playbook.yml │ ├── antora.yml │ ├── articles/ │ │ └── src/ │ │ └── docbook/ │ │ └── codebase-structure.xml │ ├── modules/ │ │ └── ROOT/ │ │ ├── assets/ │ │ │ └── images/ │ │ │ ├── icons/ │ │ │ │ └── numbers.odg │ │ │ ├── palette.otg │ │ │ ├── security-interception.graffle │ │ │ └── servlet/ │ │ │ ├── architecture/ │ │ │ │ ├── delegatingfilterproxy.odg │ │ │ │ ├── exceptiontranslationfilter.odg │ │ │ │ ├── filterchain.odg │ │ │ │ ├── filterchainproxy.odg │ │ │ │ ├── multi-securityfilterchain.odg │ │ │ │ └── securityfilterchain.odg │ │ │ ├── authentication/ │ │ │ │ ├── architecture/ │ │ │ │ │ ├── abstractauthenticationprocessingfilter.odg │ │ │ │ │ ├── providermanager-parent.odg │ │ │ │ │ ├── providermanager.odg │ │ │ │ │ ├── providermanagers-parent.odg │ │ │ │ │ └── securitycontextholder.odg │ │ │ │ ├── securitycontextholderfilter.odg │ │ │ │ ├── securitycontextpersistencefilter.odg │ │ │ │ └── unpwd/ │ │ │ │ ├── basicauthenticationentrypoint.odg │ │ │ │ ├── basicauthenticationfilter.odg │ │ │ │ ├── daoauthenticationprovider.odg │ │ │ │ ├── loginurlauthenticationentrypoint.odg │ │ │ │ └── usernamepasswordauthenticationfilter.odg │ │ │ ├── authorization/ │ │ │ │ ├── access-decision-voting.graffle │ │ │ │ ├── after-invocation.graffle │ │ │ │ ├── authorizationfilter.odg │ │ │ │ ├── authorizationhierarchy.odg │ │ │ │ ├── filtersecurityinterceptor.odg │ │ │ │ └── methodsecurity.odg │ │ │ ├── exploits/ │ │ │ │ ├── csrf-processing.odg │ │ │ │ └── csrf.odg │ │ │ ├── oauth2/ │ │ │ │ ├── beareraccessdeniedhandler.odg │ │ │ │ ├── bearerauthenticationentrypoint.odg │ │ │ │ ├── bearertokenauthenticationfilter.odg │ │ │ │ ├── jwtauthenticationprovider.odg │ │ │ │ └── opaquetokenauthenticationprovider.odg │ │ │ └── saml2/ │ │ │ ├── opensamlauthenticationprovider.odg │ │ │ ├── saml2webssoauthenticationfilter.odg │ │ │ └── saml2webssoauthenticationrequestfilter.odg │ │ ├── examples/ │ │ │ └── kerberos/ │ │ │ ├── AuthProviderConfig.java │ │ │ ├── AuthProviderConfigTest.java │ │ │ ├── DummyUserDetailsService.java │ │ │ ├── KerberosLdapContextSourceConfig.java │ │ │ ├── KerberosRestTemplateConfig.java │ │ │ └── SpnegoConfig.java │ │ ├── nav.adoc │ │ ├── pages/ │ │ │ ├── community.adoc │ │ │ ├── features/ │ │ │ │ ├── authentication/ │ │ │ │ │ ├── index.adoc │ │ │ │ │ └── password-storage.adoc │ │ │ │ ├── authorization/ │ │ │ │ │ └── index.adoc │ │ │ │ ├── exploits/ │ │ │ │ │ ├── csrf.adoc │ │ │ │ │ ├── headers.adoc │ │ │ │ │ ├── http.adoc │ │ │ │ │ └── index.adoc │ │ │ │ ├── index.adoc │ │ │ │ └── integrations/ │ │ │ │ ├── concurrency.adoc │ │ │ │ ├── cryptography.adoc │ │ │ │ ├── data.adoc │ │ │ │ ├── index.adoc │ │ │ │ ├── jackson.adoc │ │ │ │ ├── localization.adoc │ │ │ │ └── rest/ │ │ │ │ └── http-service-client.adoc │ │ │ ├── getting-spring-security.adoc │ │ │ ├── index.adoc │ │ │ ├── migration/ │ │ │ │ ├── index.adoc │ │ │ │ ├── reactive.adoc │ │ │ │ └── servlet/ │ │ │ │ ├── authorization.adoc │ │ │ │ ├── index.adoc │ │ │ │ ├── kerberos.adoc │ │ │ │ ├── oauth2.adoc │ │ │ │ └── saml2.adoc │ │ │ ├── migration-8/ │ │ │ │ └── index.adoc │ │ │ ├── modules.adoc │ │ │ ├── native-image/ │ │ │ │ ├── index.adoc │ │ │ │ └── method-security.adoc │ │ │ ├── prerequisites.adoc │ │ │ ├── reactive/ │ │ │ │ ├── authentication/ │ │ │ │ │ ├── concurrent-sessions-control.adoc │ │ │ │ │ ├── index.adoc │ │ │ │ │ ├── logout.adoc │ │ │ │ │ ├── onetimetoken.adoc │ │ │ │ │ └── x509.adoc │ │ │ │ ├── authorization/ │ │ │ │ │ ├── authorize-http-requests.adoc │ │ │ │ │ └── method.adoc │ │ │ │ ├── configuration/ │ │ │ │ │ └── webflux.adoc │ │ │ │ ├── exploits/ │ │ │ │ │ ├── csrf.adoc │ │ │ │ │ ├── firewall.adoc │ │ │ │ │ ├── headers.adoc │ │ │ │ │ ├── http.adoc │ │ │ │ │ └── index.adoc │ │ │ │ ├── getting-started.adoc │ │ │ │ ├── index.adoc │ │ │ │ ├── integrations/ │ │ │ │ │ ├── cors.adoc │ │ │ │ │ ├── observability.adoc │ │ │ │ │ └── rsocket.adoc │ │ │ │ ├── oauth2/ │ │ │ │ │ ├── client/ │ │ │ │ │ │ ├── authorization-grants.adoc │ │ │ │ │ │ ├── authorized-clients.adoc │ │ │ │ │ │ ├── client-authentication.adoc │ │ │ │ │ │ ├── core.adoc │ │ │ │ │ │ └── index.adoc │ │ │ │ │ ├── index.adoc │ │ │ │ │ ├── login/ │ │ │ │ │ │ ├── advanced.adoc │ │ │ │ │ │ ├── core.adoc │ │ │ │ │ │ ├── index.adoc │ │ │ │ │ │ └── logout.adoc │ │ │ │ │ └── resource-server/ │ │ │ │ │ ├── bearer-tokens.adoc │ │ │ │ │ ├── index.adoc │ │ │ │ │ ├── jwt.adoc │ │ │ │ │ ├── multitenancy.adoc │ │ │ │ │ └── opaque-token.adoc │ │ │ │ └── test/ │ │ │ │ ├── index.adoc │ │ │ │ ├── method.adoc │ │ │ │ └── web/ │ │ │ │ ├── authentication.adoc │ │ │ │ ├── csrf.adoc │ │ │ │ ├── index.adoc │ │ │ │ ├── oauth2.adoc │ │ │ │ ├── setup.adoc │ │ │ │ └── x509.adoc │ │ │ ├── samples.adoc │ │ │ ├── servlet/ │ │ │ │ ├── appendix/ │ │ │ │ │ ├── database-schema.adoc │ │ │ │ │ ├── faq.adoc │ │ │ │ │ ├── index.adoc │ │ │ │ │ ├── namespace/ │ │ │ │ │ │ ├── authentication-manager.adoc │ │ │ │ │ │ ├── http.adoc │ │ │ │ │ │ ├── index.adoc │ │ │ │ │ │ ├── ldap.adoc │ │ │ │ │ │ ├── method-security.adoc │ │ │ │ │ │ └── websocket.adoc │ │ │ │ │ └── proxy-server.adoc │ │ │ │ ├── architecture.adoc │ │ │ │ ├── authentication/ │ │ │ │ │ ├── anonymous.adoc │ │ │ │ │ ├── architecture.adoc │ │ │ │ │ ├── cas.adoc │ │ │ │ │ ├── events.adoc │ │ │ │ │ ├── index.adoc │ │ │ │ │ ├── jaas.adoc │ │ │ │ │ ├── kerberos/ │ │ │ │ │ │ ├── appendix.adoc │ │ │ │ │ │ ├── index.adoc │ │ │ │ │ │ ├── introduction.adoc │ │ │ │ │ │ ├── samples.adoc │ │ │ │ │ │ └── ssk.adoc │ │ │ │ │ ├── logout.adoc │ │ │ │ │ ├── mfa.adoc │ │ │ │ │ ├── onetimetoken.adoc │ │ │ │ │ ├── passkeys.adoc │ │ │ │ │ ├── passwords/ │ │ │ │ │ │ ├── basic.adoc │ │ │ │ │ │ ├── caching.adoc │ │ │ │ │ │ ├── credentials-container.adoc │ │ │ │ │ │ ├── dao-authentication-provider.adoc │ │ │ │ │ │ ├── digest.adoc │ │ │ │ │ │ ├── erasure.adoc │ │ │ │ │ │ ├── form.adoc │ │ │ │ │ │ ├── in-memory.adoc │ │ │ │ │ │ ├── index.adoc │ │ │ │ │ │ ├── input.adoc │ │ │ │ │ │ ├── jdbc.adoc │ │ │ │ │ │ ├── ldap.adoc │ │ │ │ │ │ ├── password-encoder.adoc │ │ │ │ │ │ ├── storage.adoc │ │ │ │ │ │ ├── user-details-service.adoc │ │ │ │ │ │ └── user-details.adoc │ │ │ │ │ ├── persistence.adoc │ │ │ │ │ ├── preauth.adoc │ │ │ │ │ ├── rememberme.adoc │ │ │ │ │ ├── runas.adoc │ │ │ │ │ ├── session-management.adoc │ │ │ │ │ └── x509.adoc │ │ │ │ ├── authorization/ │ │ │ │ │ ├── acls.adoc │ │ │ │ │ ├── architecture.adoc │ │ │ │ │ ├── authorize-http-requests.adoc │ │ │ │ │ ├── events.adoc │ │ │ │ │ ├── index.adoc │ │ │ │ │ └── method-security.adoc │ │ │ │ ├── configuration/ │ │ │ │ │ ├── java.adoc │ │ │ │ │ ├── kotlin.adoc │ │ │ │ │ └── xml-namespace.adoc │ │ │ │ ├── exploits/ │ │ │ │ │ ├── csrf.adoc │ │ │ │ │ ├── firewall.adoc │ │ │ │ │ ├── headers.adoc │ │ │ │ │ ├── http.adoc │ │ │ │ │ └── index.adoc │ │ │ │ ├── getting-started.adoc │ │ │ │ ├── index.adoc │ │ │ │ ├── integrations/ │ │ │ │ │ ├── concurrency.adoc │ │ │ │ │ ├── cors.adoc │ │ │ │ │ ├── data.adoc │ │ │ │ │ ├── index.adoc │ │ │ │ │ ├── jsp-taglibs.adoc │ │ │ │ │ ├── localization.adoc │ │ │ │ │ ├── mvc.adoc │ │ │ │ │ ├── observability.adoc │ │ │ │ │ ├── servlet-api.adoc │ │ │ │ │ └── websocket.adoc │ │ │ │ ├── oauth2/ │ │ │ │ │ ├── authorization-server/ │ │ │ │ │ │ ├── configuration-model.adoc │ │ │ │ │ │ ├── core-model-components.adoc │ │ │ │ │ │ ├── getting-started.adoc │ │ │ │ │ │ ├── index.adoc │ │ │ │ │ │ └── protocol-endpoints.adoc │ │ │ │ │ ├── client/ │ │ │ │ │ │ ├── authorization-grants.adoc │ │ │ │ │ │ ├── authorized-clients.adoc │ │ │ │ │ │ ├── client-authentication.adoc │ │ │ │ │ │ ├── core.adoc │ │ │ │ │ │ └── index.adoc │ │ │ │ │ ├── index.adoc │ │ │ │ │ ├── login/ │ │ │ │ │ │ ├── advanced.adoc │ │ │ │ │ │ ├── core.adoc │ │ │ │ │ │ ├── index.adoc │ │ │ │ │ │ └── logout.adoc │ │ │ │ │ └── resource-server/ │ │ │ │ │ ├── bearer-tokens.adoc │ │ │ │ │ ├── dpop-tokens.adoc │ │ │ │ │ ├── index.adoc │ │ │ │ │ ├── jwt.adoc │ │ │ │ │ ├── multitenancy.adoc │ │ │ │ │ ├── opaque-token.adoc │ │ │ │ │ └── protected-resource-metadata.adoc │ │ │ │ ├── saml2/ │ │ │ │ │ ├── index.adoc │ │ │ │ │ ├── login/ │ │ │ │ │ │ ├── authentication-requests.adoc │ │ │ │ │ │ ├── authentication.adoc │ │ │ │ │ │ ├── index.adoc │ │ │ │ │ │ └── overview.adoc │ │ │ │ │ ├── logout.adoc │ │ │ │ │ ├── metadata.adoc │ │ │ │ │ └── saml-extension-migration.adoc │ │ │ │ └── test/ │ │ │ │ ├── index.adoc │ │ │ │ ├── method.adoc │ │ │ │ └── mockmvc/ │ │ │ │ ├── authentication.adoc │ │ │ │ ├── csrf.adoc │ │ │ │ ├── form-login.adoc │ │ │ │ ├── http-basic.adoc │ │ │ │ ├── index.adoc │ │ │ │ ├── logout.adoc │ │ │ │ ├── oauth2.adoc │ │ │ │ ├── request-builders.adoc │ │ │ │ ├── request-post-processors.adoc │ │ │ │ ├── result-handlers.adoc │ │ │ │ ├── result-matchers.adoc │ │ │ │ └── setup.adoc │ │ │ └── whats-new.adoc │ │ └── partials/ │ │ ├── reactive/ │ │ │ └── oauth2/ │ │ │ └── client/ │ │ │ └── web-client-access-token-response-client.adoc │ │ └── servlet/ │ │ ├── architecture/ │ │ │ ├── request-cache-continue.adoc │ │ │ └── security-context-explicit.adoc │ │ └── oauth2/ │ │ └── client/ │ │ └── rest-client-access-token-response-client.adoc │ ├── package.json │ ├── spring-security-docs.gradle │ └── src/ │ └── test/ │ ├── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── security/ │ │ └── docs/ │ │ ├── features/ │ │ │ ├── authentication/ │ │ │ │ ├── authenticationcompromisedpasswordcheck/ │ │ │ │ │ └── CompromisedPasswordCheckerUsage.java │ │ │ │ ├── authenticationpasswordstorageargon2/ │ │ │ │ │ └── Argon2PasswordEncoderUsage.java │ │ │ │ ├── authenticationpasswordstoragebcrypt/ │ │ │ │ │ └── BCryptPasswordEncoderUsage.java │ │ │ │ ├── authenticationpasswordstoragedepgettingstarted/ │ │ │ │ │ └── WithDefaultPasswordEncoderUsage.java │ │ │ │ ├── authenticationpasswordstoragedpe/ │ │ │ │ │ └── DelegatingPasswordEncoderUsage.java │ │ │ │ ├── authenticationpasswordstoragepbkdf2/ │ │ │ │ │ └── Pbkdf2PasswordEncoderUsage.java │ │ │ │ ├── authenticationpasswordstoragescrypt/ │ │ │ │ │ └── SCryptPasswordEncoderUsage.java │ │ │ │ ├── password4jargon2/ │ │ │ │ │ └── Argon2UsageTests.java │ │ │ │ ├── password4jballooning/ │ │ │ │ │ └── BallooningHashingUsageTests.java │ │ │ │ ├── password4jbcrypt/ │ │ │ │ │ └── BcryptUsageTests.java │ │ │ │ ├── password4jpbkdf2/ │ │ │ │ │ └── Pbkdf2UsageTests.java │ │ │ │ └── password4jscrypt/ │ │ │ │ └── ScryptUsageTests.java │ │ │ └── integrations/ │ │ │ └── rest/ │ │ │ ├── clientregistrationid/ │ │ │ │ ├── User.java │ │ │ │ └── UserService.java │ │ │ ├── configurationrestclient/ │ │ │ │ ├── RestClientHttpInterfaceIntegrationConfiguration.java │ │ │ │ └── RestClientHttpInterfaceIntegrationConfigurationTests.java │ │ │ ├── configurationwebclient/ │ │ │ │ ├── ServerRestClientHttpInterfaceIntegrationConfigurationTests.java │ │ │ │ └── ServerWebClientHttpInterfaceIntegrationConfiguration.java │ │ │ └── type/ │ │ │ ├── Hovercard.java │ │ │ └── UserService.java │ │ ├── reactive/ │ │ │ ├── authentication/ │ │ │ │ └── reactivex509/ │ │ │ │ ├── CustomX509Configuration.java │ │ │ │ ├── DefaultX509Configuration.java │ │ │ │ └── X509ConfigurationTests.java │ │ │ └── configuration/ │ │ │ ├── customizerbeanordering/ │ │ │ │ ├── CustomizerBeanOrderingConfiguration.java │ │ │ │ └── CustomizerBeanOrderingTests.java │ │ │ ├── serverhttpsecuritycustomizerbean/ │ │ │ │ ├── ServerHttpSecurityCustomizerBeanConfiguration.java │ │ │ │ └── ServerHttpSecurityCustomizerBeanTests.java │ │ │ └── toplevelcustomizerbean/ │ │ │ ├── TopLevelCustomizerBeanConfiguration.java │ │ │ └── TopLevelCustomizerBeanTests.java │ │ └── servlet/ │ │ ├── addingcustomfilter/ │ │ │ ├── CustomFilterTests.java │ │ │ ├── SecurityConfig.java │ │ │ └── TenantFilter.java │ │ ├── authentication/ │ │ │ ├── authorizationmanagerfactory/ │ │ │ │ ├── AuthorizationManagerFactoryTests.java │ │ │ │ └── UseAuthorizationManagerFactoryConfiguration.java │ │ │ ├── emfa/ │ │ │ │ ├── EnableMultiFactorAuthenticationConfiguration.java │ │ │ │ └── EnableMultiFactorAuthenticationTests.java │ │ │ ├── hasallauthorities/ │ │ │ │ ├── ListAuthoritiesConfiguration.java │ │ │ │ ├── MultiFactorAuthenticationTests.java │ │ │ │ ├── MultipleAuthorizationRulesConfiguration.java │ │ │ │ └── MultipleAuthorizationRulesConfigurationTests.java │ │ │ ├── mfawhencustomconditions/ │ │ │ │ └── CustomizerAuthorizationManagerFactoryConfiguration.java │ │ │ ├── mfawhenwebauthnregistered/ │ │ │ │ └── WebAuthnConditionConfiguration.java │ │ │ ├── obtainingmoreauthorization/ │ │ │ │ ├── MissingAuthorityConfiguration.java │ │ │ │ ├── ObtainingMoreAuthorizationTests.java │ │ │ │ └── ScopeConfiguration.java │ │ │ ├── programmaticmfa/ │ │ │ │ ├── AdminMfaAuthorizationManagerConfiguration.java │ │ │ │ └── AdminMfaAuthorizationManagerConfigurationTests.java │ │ │ ├── raammfa/ │ │ │ │ ├── RequiredAuthoritiesAuthorizationManagerConfiguration.java │ │ │ │ └── RequiredAuthoritiesAuthorizationManagerConfigurationTests.java │ │ │ ├── reauthentication/ │ │ │ │ ├── ReauthenticationTests.java │ │ │ │ ├── RequireOttConfiguration.java │ │ │ │ └── SimpleConfiguration.java │ │ │ ├── selectivemfa/ │ │ │ │ ├── SelectiveMfaConfiguration.java │ │ │ │ └── SelectiveMfaConfigurationTests.java │ │ │ ├── servletauthenticationauthentication/ │ │ │ │ └── CopyAuthoritiesTests.java │ │ │ ├── servletx509config/ │ │ │ │ ├── CustomX509Configuration.java │ │ │ │ ├── DefaultX509Configuration.java │ │ │ │ └── X509ConfigurationTests.java │ │ │ ├── tokenbasedremembermeservices/ │ │ │ │ ├── CustomAlgorithmRememberMeServicesConfiguration.java │ │ │ │ └── DefaultAlgorithmRememberMeServicesConfiguration.java │ │ │ └── validduration/ │ │ │ ├── ValidDurationConfiguration.java │ │ │ └── ValidDurationConfigurationTests.java │ │ ├── authorization/ │ │ │ ├── authzauthorizationmanagerfactory/ │ │ │ │ ├── AuthorizationManagerFactoryConfiguration.java │ │ │ │ └── AuthorizationManagerFactoryConfigurationTests.java │ │ │ ├── authzconditionalauthorizationmanager/ │ │ │ │ └── ConditionalAuthorizationManagerExample.java │ │ │ └── customizingauthorizationmanagers/ │ │ │ ├── CustomHttpRequestsAuthorizationManagerFactory.java │ │ │ ├── CustomHttpRequestsAuthorizationManagerFactoryTests.java │ │ │ ├── CustomMethodInvocationAuthorizationManagerFactory.java │ │ │ └── CustomMethodInvocationAuthorizationManagerFactoryTests.java │ │ ├── configuration/ │ │ │ ├── customizerbeanordering/ │ │ │ │ ├── CustomizerBeanOrderingConfiguration.java │ │ │ │ └── CustomizerBeanOrderingTests.java │ │ │ ├── httpsecuritycustomizerbean/ │ │ │ │ ├── HttpSecurityCustomizerBeanConfiguration.java │ │ │ │ └── HttpSecurityCustomizerBeanTests.java │ │ │ └── toplevelcustomizerbean/ │ │ │ ├── TopLevelCustomizerBeanConfiguration.java │ │ │ └── TopLevelCustomizerBeanTests.java │ │ ├── customizingfilter/ │ │ │ └── CustomizingFilterTests.java │ │ ├── integrations/ │ │ │ ├── customauthorization/ │ │ │ │ ├── AdvancedWebSocketSecurityConfig.java │ │ │ │ └── WebSocketSecurityConfig.java │ │ │ ├── migratingspelexpressions/ │ │ │ │ └── WebSocketSecurityConfig.java │ │ │ ├── websocketauthorization/ │ │ │ │ └── WebSocketSecurityConfig.java │ │ │ ├── websocketsameorigindisable/ │ │ │ │ └── WebSocketSecurityConfig.java │ │ │ ├── websocketsockjscsrf/ │ │ │ │ └── WebSecurityConfig.java │ │ │ └── websocketsockjssameorigin/ │ │ │ └── WebSecurityConfig.java │ │ ├── oauth2/ │ │ │ └── resourceserver/ │ │ │ ├── customuserdetailsservice/ │ │ │ │ ├── UserDetailsJwtPrincipalConverter.java │ │ │ │ ├── UserDetailsJwtPrincipalConverterConfiguration.java │ │ │ │ └── UserDetailsJwtPrincipalConverterTests.java │ │ │ ├── jwtgrantedauthoritiesspelexpression/ │ │ │ │ └── ExpressionJwtGrantedAuthoritiesConverterTests.java │ │ │ ├── methodsecurityhasscope/ │ │ │ │ ├── MessageService.java │ │ │ │ ├── MethodSecurityHasScopeConfiguration.java │ │ │ │ ├── MethodSecurityHasScopeConfigurationTests.java │ │ │ │ └── MethodSecurityHasScopeMfaConfiguration.java │ │ │ └── opaquetokentimeoutsrestclient/ │ │ │ ├── RestClientOpaqueTokenIntrospectorConfiguration.java │ │ │ └── RestClientOpaqueTokenIntrospectorConfigurationTests.java │ │ ├── requestcachepreventsavedrequest/ │ │ │ └── SecurityConfig.java │ │ ├── servletdelegatingfilterproxy/ │ │ │ ├── SampleDelegatingFilterProxy.java │ │ │ └── SampleDelegatingFilterProxyTests.java │ │ ├── servletfiltersreview/ │ │ │ └── FilterChainUsage.java │ │ ├── servletsecurityfilters/ │ │ │ ├── SampleSecurityConfigTests.java │ │ │ └── SecurityConfig.java │ │ └── test/ │ │ ├── testmethod/ │ │ │ ├── HelloMessageService.java │ │ │ └── HelloServiceTests.java │ │ ├── testmethodmetaannotations/ │ │ │ ├── WithMockAdmin.java │ │ │ ├── WithMockAdminTests.java │ │ │ └── WithMockUserTests.java │ │ ├── testmethodsetup/ │ │ │ ├── WithMockUserSampleTests.java │ │ │ └── WithMockUserTests.java │ │ ├── testmethodwithanonymoususer/ │ │ │ └── WithUserClassLevelAuthenticationTests.java │ │ ├── testmethodwithmockuser/ │ │ │ ├── WithMockUserClassTests.java │ │ │ ├── WithMockUserNestedTests.java │ │ │ └── WithMockUserTests.java │ │ ├── testmethodwithsecuritycontext/ │ │ │ ├── CustomUserDetails.java │ │ │ ├── WithMockCustomUser.java │ │ │ ├── WithMockCustomUserSecurityContextFactory.java │ │ │ └── WithUserDetailsSecurityContextFactory.java │ │ └── testmethodwithuserdetails/ │ │ ├── WithCustomUserDetailsTests.java │ │ └── WithUserDetailsTests.java │ ├── kotlin/ │ │ └── org/ │ │ └── springframework/ │ │ └── security/ │ │ └── kt/ │ │ └── docs/ │ │ ├── features/ │ │ │ ├── authentication/ │ │ │ │ ├── authenticationcompromisedpasswordcheck/ │ │ │ │ │ └── CompromisedPasswordCheckerUsage.kt │ │ │ │ ├── authenticationpasswordstorageargon2/ │ │ │ │ │ └── Argon2PasswordEncoderUsage.kt │ │ │ │ ├── authenticationpasswordstoragebcrypt/ │ │ │ │ │ └── BCryptPasswordEncoderUsage.kt │ │ │ │ ├── authenticationpasswordstoragedepgettingstarted/ │ │ │ │ │ └── WithDefaultPasswordEncoderUsage.kt │ │ │ │ ├── authenticationpasswordstoragedpe/ │ │ │ │ │ └── DelegatingPasswordEncoderUsage.kt │ │ │ │ ├── authenticationpasswordstoragepbkdf2/ │ │ │ │ │ └── Pbkdf2PasswordEncoderUsage.kt │ │ │ │ ├── authenticationpasswordstoragescrypt/ │ │ │ │ │ └── SCryptPasswordEncoderUsage.kt │ │ │ │ ├── password4jargon2/ │ │ │ │ │ └── Argon2UsageTests.kt │ │ │ │ ├── password4jballooning/ │ │ │ │ │ └── BallooningHashingUsageTests.kt │ │ │ │ ├── password4jbcrypt/ │ │ │ │ │ └── BcryptUsageTests.kt │ │ │ │ ├── password4jpbkdf2/ │ │ │ │ │ └── Pbkdf2UsageTests.kt │ │ │ │ └── password4jscrypt/ │ │ │ │ └── ScryptUsageTests.kt │ │ │ └── integrations/ │ │ │ └── rest/ │ │ │ ├── clientregistrationid/ │ │ │ │ ├── User.kt │ │ │ │ └── UserService.kt │ │ │ ├── configurationrestclient/ │ │ │ │ ├── RestClientHttpInterfaceIntegrationConfiguration.kt │ │ │ │ └── RestClientHttpInterfaceIntegrationConfigurationTests.kt │ │ │ ├── configurationwebclient/ │ │ │ │ ├── ServerRestClientHttpInterfaceIntegrationConfigurationTests.kt │ │ │ │ └── ServerWebClientHttpInterfaceIntegrationConfiguration.kt │ │ │ └── type/ │ │ │ ├── Hovercard.kt │ │ │ └── UserService.kt │ │ ├── reactive/ │ │ │ ├── authentication/ │ │ │ │ └── reactivex509/ │ │ │ │ ├── CustomX509Configuration.kt │ │ │ │ ├── DefaultX509Configuration.kt │ │ │ │ └── X509ConfigurationTests.kt │ │ │ └── configuration/ │ │ │ ├── customizerbeanordering/ │ │ │ │ ├── CustomizerBeanOrderingConfiguration.kt │ │ │ │ └── CustomizerBeanOrderingTests.kt │ │ │ ├── dslbeanordering/ │ │ │ │ ├── DslBeanOrderingConfiguration.kt │ │ │ │ └── DslBeanOrderingTests.kt │ │ │ ├── serverhttpsecuritycustomizerbean/ │ │ │ │ ├── ServerHttpSecurityCustomizerBeanConfiguration.kt │ │ │ │ └── ServerHttpSecurityCustomizerBeanTests.kt │ │ │ ├── serverhttpsecuritydslbean/ │ │ │ │ ├── ServerHttpSecurityDslBeanConfiguration.kt │ │ │ │ └── ServerHttpSecurityDslBeanTests.kt │ │ │ ├── toplevelcustomizerbean/ │ │ │ │ ├── TopLevelCustomizerBeanConfiguration.kt │ │ │ │ └── TopLevelCustomizerBeanTests.kt │ │ │ └── topleveldslbean/ │ │ │ ├── TopLevelDslBeanConfiguration.kt │ │ │ └── TopLevelDslBeanTests.kt │ │ └── servlet/ │ │ ├── addingcustomfilter/ │ │ │ ├── CustomFilterTests.kt │ │ │ ├── SecurityConfig.kt │ │ │ └── TenantFilter.kt │ │ ├── authentication/ │ │ │ ├── authorizationmanagerfactory/ │ │ │ │ ├── AuthorizationManagerFactoryTests.kt │ │ │ │ └── UseAuthorizationManagerFactoryConfiguration.kt │ │ │ ├── emfa/ │ │ │ │ ├── EnableMultiFactorAuthenticationConfiguration.kt │ │ │ │ └── EnableMultiFactorAuthenticationConfigurationTests.kt │ │ │ ├── hasallauthorities/ │ │ │ │ ├── ListAuthoritiesConfiguration.kt │ │ │ │ ├── MultiFactorAuthenticationTests.kt │ │ │ │ ├── MultipleAuthorizationRulesConfiguration.kt │ │ │ │ └── MultipleAuthorizationRulesConfigurationTests.kt │ │ │ ├── mfawhencustomconditions/ │ │ │ │ └── CustomizerAuthorizationManagerFactoryConfiguration.kt │ │ │ ├── mfawhenwebauthnregistered/ │ │ │ │ └── WebAuthnConditionConfiguration.kt │ │ │ ├── obtainingmoreauthorization/ │ │ │ │ ├── MissingAuthorityConfiguration.kt │ │ │ │ ├── ObtainingMoreAuthorizationTests.kt │ │ │ │ └── ScopeConfiguration.kt │ │ │ ├── programmaticmfa/ │ │ │ │ ├── AdminMfaAuthorizationManagerConfiguration.kt │ │ │ │ └── AdminMfaAuthorizationManagerConfigurationTests.kt │ │ │ ├── raammfa/ │ │ │ │ ├── RequiredAuthoritiesAuthorizationManagerConfiguration.kt │ │ │ │ └── RequiredAuthoritiesAuthorizationManagerConfigurationTests.kt │ │ │ ├── reauthentication/ │ │ │ │ ├── ReauthenticationTests.kt │ │ │ │ ├── RequireOttConfiguration.kt │ │ │ │ └── SimpleConfiguration.kt │ │ │ ├── selectivemfa/ │ │ │ │ ├── SelectiveMfaConfiguration.kt │ │ │ │ └── SelectiveMfaConfigurationTests.kt │ │ │ ├── servletauthenticationauthentication/ │ │ │ │ └── CopyAuthoritiesTests.kt │ │ │ ├── servletx509config/ │ │ │ │ ├── CustomX509Configuration.kt │ │ │ │ ├── DefaultX509Configuration.kt │ │ │ │ └── X509ConfigurationTests.kt │ │ │ ├── tokenbasedremembermeservices/ │ │ │ │ ├── CustomAlgorithmRememberMeServicesConfiguration.kt │ │ │ │ └── DefaultAlgorithmRememberMeServicesConfiguration.kt │ │ │ └── validduration/ │ │ │ ├── ValidDurationConfiguration.kt │ │ │ └── ValidDurationConfigurationTests.kt │ │ ├── authorization/ │ │ │ ├── authzauthorizationmanagerfactory/ │ │ │ │ ├── AuthorizationManagerFactoryConfiguration.kt │ │ │ │ └── AuthorizationManagerFactoryConfigurationTests.kt │ │ │ ├── authzconditionalauthorizationmanager/ │ │ │ │ └── ConditionalAuthorizationManagerExample.kt │ │ │ └── customizingauthorizationmanagers/ │ │ │ ├── CustomHttpRequestsAuthorizationManagerFactory.kt │ │ │ ├── CustomHttpRequestsAuthorizationManagerFactoryTests.kt │ │ │ ├── CustomMethodInvocationAuthorizationManagerFactory.kt │ │ │ └── CustomMethodInvocationAuthorizationManagerFactoryTests.kt │ │ ├── configuration/ │ │ │ ├── customizerbeanordering/ │ │ │ │ ├── CustomizerBeanOrderingConfiguration.kt │ │ │ │ └── CustomizerBeanOrderingTests.kt │ │ │ ├── dslbeanordering/ │ │ │ │ ├── DslBeanOrderingConfiguration.kt │ │ │ │ └── DslBeanOrderingTests.kt │ │ │ ├── httpsecuritycustomizerbean/ │ │ │ │ ├── HttpSecurityCustomizerBeanConfiguration.kt │ │ │ │ └── HttpSecurityCustomizerBeanTests.kt │ │ │ ├── httpsecuritydslbean/ │ │ │ │ ├── HttpSecurityDslBeanConfiguration.kt │ │ │ │ └── HttpSecurityDslBeanTests.kt │ │ │ ├── toplevelcustomizerbean/ │ │ │ │ ├── TopLevelCustomizerBeanConfiguration.kt │ │ │ │ └── TopLevelCustomizerBeanTests.kt │ │ │ └── topleveldslbean/ │ │ │ ├── TopLevelDslBeanConfiguration.kt │ │ │ └── TopLevelDslBeanTests.kt │ │ ├── customizingfilter/ │ │ │ └── CustomizingFilterTests.kt │ │ ├── integrations/ │ │ │ ├── customauthorization/ │ │ │ │ ├── AdvancedWebSocketSecurityConfig.kt │ │ │ │ └── WebSocketSecurityConfig.kt │ │ │ ├── migratingspelexpressions/ │ │ │ │ └── WebSocketSecurityConfig.kt │ │ │ ├── websocketauthorization/ │ │ │ │ └── WebSocketSecurityConfig.kt │ │ │ ├── websocketsameorigindisable/ │ │ │ │ └── WebSocketSecurityConfig.kt │ │ │ ├── websocketsockjscsrf/ │ │ │ │ └── WebSecurityConfig.kt │ │ │ └── websocketsockjssameorigin/ │ │ │ └── WebSecurityConfig.kt │ │ ├── oauth2/ │ │ │ └── resourceserver/ │ │ │ ├── customuserdetailsservice/ │ │ │ │ ├── UserDetailsJwtPrincipalConverter.kt │ │ │ │ ├── UserDetailsJwtPrincipalConverterConfiguration.kt │ │ │ │ └── UserDetailsJwtPrincipalConverterTests.kt │ │ │ ├── jwtgrantedauthoritiesspelexpression/ │ │ │ │ └── ExpressionJwtGrantedAuthoritiesConverterTests.kt │ │ │ ├── methodsecurityhasscope/ │ │ │ │ ├── MessageService.kt │ │ │ │ ├── MethodSecurityHasScopeConfiguration.kt │ │ │ │ ├── MethodSecurityHasScopeConfigurationTests.kt │ │ │ │ └── MethodSecurityHasScopeMfaConfiguration.kt │ │ │ └── opaquetokentimeoutsrestclient/ │ │ │ ├── RestClientOpaqueTokenIntrospectorConfiguration.kt │ │ │ └── RestClientOpaqueTokenIntrospectorConfigurationTests.kt │ │ ├── requestcachepreventsavedrequest/ │ │ │ └── SecurityConfig.kt │ │ ├── servletdelegatingfilterproxy/ │ │ │ ├── SampleDelegatingFilterProxy.kt │ │ │ └── SampleDelegatingFilterProxyTests.kt │ │ ├── servletfiltersreview/ │ │ │ └── FilterChainUsage.kt │ │ ├── servletsecurityfilters/ │ │ │ └── SecurityConfig.kt │ │ └── test/ │ │ ├── testmethod/ │ │ │ ├── HelloMessageService.kt │ │ │ └── HelloServiceTests.kt │ │ ├── testmethodmetaannotations/ │ │ │ ├── WithMockAdmin.kt │ │ │ ├── WithMockAdminTests.kt │ │ │ └── WithMockUserTests.kt │ │ ├── testmethodsetup/ │ │ │ ├── WithMockUserSampleTests.kt │ │ │ └── WithMockUserTests.kt │ │ ├── testmethodwithanonymoususer/ │ │ │ └── WithUserClassLevelAuthenticationTests.kt │ │ ├── testmethodwithmockuser/ │ │ │ ├── WithMockUserClassTests.kt │ │ │ ├── WithMockUserNestedTests.kt │ │ │ └── WithMockUserTests.kt │ │ ├── testmethodwithsecuritycontext/ │ │ │ ├── CustomUserDetails.kt │ │ │ ├── WithMockCustomUser.kt │ │ │ ├── WithMockCustomUserSecurityContextFactory.kt │ │ │ └── WithUserDetailsSecurityContextFactory.kt │ │ └── testmethodwithuserdetails/ │ │ ├── WithCustomUserDetailsTests.kt │ │ └── WithUserDetailsTests.kt │ └── resources/ │ └── org/ │ └── springframework/ │ └── security/ │ └── docs/ │ └── servlet/ │ ├── authentication/ │ │ ├── servletx509config/ │ │ │ ├── CustomX509Configuration.xml │ │ │ └── DefaultX509Configuration.xml │ │ └── tokenbasedremembermeservices/ │ │ ├── CustomAlgorithmRememberMeServicesConfiguration.xml │ │ └── DefaultAlgorithmRememberMeServicesConfiguration.xml │ ├── authorization/ │ │ └── authzauthorizationmanagerfactory/ │ │ └── AuthorizationManagerFactoryConfiguration.xml │ ├── integrations/ │ │ ├── customauthorization/ │ │ │ ├── AdvancedWebSocketSecurityConfig.xml │ │ │ └── WebSocketSecurityConfig.xml │ │ ├── websocketauthorization/ │ │ │ └── WebSocketSecurityConfig.xml │ │ ├── websocketsameorigindisable/ │ │ │ └── WebSocketSecurityConfig.xml │ │ └── websocketsockjscsrf/ │ │ └── WebSocketSecurityConfig.xml │ └── requestcachepreventsavedrequest/ │ └── SecurityConfig.xml ├── etc/ │ ├── checkstyle/ │ │ ├── checkstyle-suppressions.xml │ │ ├── checkstyle.xml │ │ └── header.txt │ ├── eclipse/ │ │ ├── eclipse-code-formatter.xml │ │ ├── org.eclipse.jdt.core.prefs │ │ └── org.eclipse.jdt.ui.prefs │ ├── nohttp/ │ │ ├── allowlist.lines │ │ └── checkstyle.xml │ └── s101/ │ ├── config.xml │ ├── project.java.hsp │ └── repository/ │ ├── repository.xml │ └── snapshots/ │ └── baseline/ │ ├── actions.hsx │ ├── arch.hsx │ ├── key-measures.xml │ ├── package-slice.hsx │ ├── settings.hsx │ ├── spec.hsx │ ├── summary.hsx │ ├── violations.xml │ ├── xb.hsx │ ├── xblite.hsx │ └── xs-offenders.hsx ├── git/ │ └── hooks/ │ ├── .forward-merge.swp │ ├── forward-merge │ ├── pre-push │ └── prepare-forward-merge ├── gradle/ │ ├── libs.versions.toml │ └── wrapper/ │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle.properties ├── gradlew ├── gradlew.bat ├── itest/ │ ├── context/ │ │ ├── spring-security-itest-context.gradle │ │ └── src/ │ │ ├── integration-test/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── security/ │ │ │ │ ├── integration/ │ │ │ │ │ ├── HttpNamespaceWithMultipleInterceptorsTests.java │ │ │ │ │ ├── HttpPathParameterStrippingTests.java │ │ │ │ │ ├── MultiAnnotationTests.java │ │ │ │ │ ├── SEC933ApplicationContextTests.java │ │ │ │ │ ├── StubUserRepository.java │ │ │ │ │ └── python/ │ │ │ │ │ └── PythonInterpreterBasedSecurityTests.java │ │ │ │ └── performance/ │ │ │ │ ├── FilterChainPerformanceTests.java │ │ │ │ └── ProtectPointcutPerformanceTests.java │ │ │ └── resources/ │ │ │ ├── filter-chain-performance-app-context.xml │ │ │ ├── http-extra-fsi-app-context.xml │ │ │ ├── http-path-param-stripping-app-context.xml │ │ │ ├── logback-test.xml │ │ │ ├── multi-sec-annotation-app-context.xml │ │ │ ├── protect-pointcut-performance-app-context.xml │ │ │ ├── python-method-access-app-context.xml │ │ │ ├── sec-933-app-context.xml │ │ │ ├── sec-936-app-context.xml │ │ │ └── someMethod.py │ │ └── main/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── security/ │ │ └── integration/ │ │ ├── UserDetailsServiceImpl.java │ │ ├── UserRepository.java │ │ ├── multiannotation/ │ │ │ ├── MultiAnnotationService.java │ │ │ ├── MultiAnnotationServiceImpl.java │ │ │ ├── PreAuthorizeService.java │ │ │ ├── PreAuthorizeServiceImpl.java │ │ │ ├── SecuredService.java │ │ │ └── SecuredServiceImpl.java │ │ └── python/ │ │ ├── PythonInterpreterPostInvocationAdvice.java │ │ ├── PythonInterpreterPreInvocationAdvice.java │ │ ├── PythonInterpreterPreInvocationAttribute.java │ │ ├── PythonInterpreterPrePostInvocationAttributeFactory.java │ │ ├── TestService.java │ │ └── TestServiceImpl.java │ ├── ldap/ │ │ ├── embedded-ldap-mode-unboundid/ │ │ │ ├── spring-security-itest-ldap-embedded-mode-unboundid.gradle │ │ │ └── src/ │ │ │ └── integration-test/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── security/ │ │ │ │ └── LdapServerBeanDefinitionParserTests.java │ │ │ └── resources/ │ │ │ ├── applicationContext-security.xml │ │ │ └── users.ldif │ │ ├── embedded-ldap-none/ │ │ │ ├── spring-security-itest-ldap-embedded-none.gradle │ │ │ └── src/ │ │ │ └── integration-test/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── security/ │ │ │ │ └── LdapServerBeanDefinitionParserTests.java │ │ │ └── resources/ │ │ │ ├── applicationContext-security.xml │ │ │ └── users.ldif │ │ └── embedded-ldap-unboundid-default/ │ │ ├── spring-security-itest-ldap-embedded-unboundid-default.gradle │ │ └── src/ │ │ └── integration-test/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── security/ │ │ │ └── LdapServerBeanDefinitionParserTests.java │ │ └── resources/ │ │ ├── applicationContext-security.xml │ │ └── users.ldif │ ├── misc/ │ │ └── src/ │ │ └── integration-test/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── security/ │ │ ├── concurrent/ │ │ │ └── SessionRegistryImplMTTests.java │ │ └── context/ │ │ └── SecurityContextHolderMTTests.java │ └── web/ │ ├── spring-security-itest-web.gradle │ └── src/ │ ├── integration-test/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── security/ │ │ │ └── integration/ │ │ │ ├── AbstractWebServerIntegrationTests.java │ │ │ ├── BasicAuthenticationTests.java │ │ │ └── ConcurrentSessionManagementTests.java │ │ └── resources/ │ │ ├── logback-test.xml │ │ └── spring/ │ │ ├── http-security-basic.xml │ │ ├── http-security-concurrency.xml │ │ ├── http-security.xml │ │ ├── in-memory-provider.xml │ │ └── testapp-servlet.xml │ └── main/ │ ├── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── security/ │ │ └── itest/ │ │ └── web/ │ │ └── TestController.java │ └── resources/ │ └── test-server.ldif ├── javascript/ │ ├── .gitignore │ ├── .prettierrc │ ├── eslint.config.js │ ├── lib/ │ │ ├── abort-controller.js │ │ ├── base64url.js │ │ ├── http.js │ │ ├── index.js │ │ ├── webauthn-core.js │ │ ├── webauthn-login.js │ │ └── webauthn-registration.js │ ├── package.json │ ├── spring-security-javascript.gradle │ └── test/ │ ├── abort-controller.test.js │ ├── base64.test.js │ ├── bootstrap.js │ ├── http.test.js │ ├── webauthn-core.test.js │ ├── webauthn-login.test.js │ └── webauthn-registration.test.js ├── kerberos/ │ ├── kerberos-client/ │ │ ├── spring-security-kerberos-client.gradle │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── security/ │ │ │ └── kerberos/ │ │ │ └── client/ │ │ │ ├── KerberosRestTemplate.java │ │ │ ├── config/ │ │ │ │ ├── SunJaasKrb5LoginConfig.java │ │ │ │ └── package-info.java │ │ │ ├── ldap/ │ │ │ │ ├── KerberosLdapContextSource.java │ │ │ │ └── package-info.java │ │ │ └── package-info.java │ │ └── test/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── security/ │ │ │ └── kerberos/ │ │ │ └── client/ │ │ │ └── KerberosRestTemplateTests.java │ │ └── resources/ │ │ ├── log4j.properties │ │ ├── minikdc-krb5.conf │ │ └── minikdc.ldiff │ ├── kerberos-core/ │ │ ├── spring-security-kerberos-core.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── security/ │ │ │ │ └── kerberos/ │ │ │ │ ├── aot/ │ │ │ │ │ └── hint/ │ │ │ │ │ ├── KerberosRuntimeHints.java │ │ │ │ │ └── package-info.java │ │ │ │ └── authentication/ │ │ │ │ ├── JaasSubjectHolder.java │ │ │ │ ├── KerberosAuthentication.java │ │ │ │ ├── KerberosAuthenticationProvider.java │ │ │ │ ├── KerberosClient.java │ │ │ │ ├── KerberosMultiTier.java │ │ │ │ ├── KerberosServiceAuthenticationProvider.java │ │ │ │ ├── KerberosServiceRequestToken.java │ │ │ │ ├── KerberosTicketValidation.java │ │ │ │ ├── KerberosTicketValidator.java │ │ │ │ ├── KerberosUsernamePasswordAuthenticationToken.java │ │ │ │ ├── package-info.java │ │ │ │ └── sun/ │ │ │ │ ├── GlobalSunJaasKerberosConfig.java │ │ │ │ ├── JaasUtil.java │ │ │ │ ├── SunJaasKerberosClient.java │ │ │ │ ├── SunJaasKerberosTicketValidator.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ └── spring/ │ │ │ └── aot.factories │ │ └── test/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── security/ │ │ └── kerberos/ │ │ └── authentication/ │ │ ├── KerberosAuthenticationProviderTests.java │ │ ├── KerberosServiceAuthenticationProviderTests.java │ │ ├── KerberosTicketValidationTests.java │ │ └── sun/ │ │ └── SunJaasKerberosTicketValidatorTests.java │ ├── kerberos-test/ │ │ ├── spring-security-kerberos-test.gradle │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── security/ │ │ │ └── kerberos/ │ │ │ └── test/ │ │ │ ├── KerberosSecurityTestcase.java │ │ │ ├── MiniKdc.java │ │ │ └── package-info.java │ │ └── test/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── security/ │ │ │ └── kerberos/ │ │ │ └── test/ │ │ │ └── TestMiniKdc.java │ │ └── resources/ │ │ ├── log4j.properties │ │ ├── minikdc-krb5.conf │ │ └── minikdc.ldiff │ └── kerberos-web/ │ ├── spring-security-kerberos-web.gradle │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── security/ │ │ └── kerberos/ │ │ └── web/ │ │ └── authentication/ │ │ ├── ResponseHeaderSettingKerberosAuthenticationSuccessHandler.java │ │ ├── SpnegoAuthenticationProcessingFilter.java │ │ ├── SpnegoEntryPoint.java │ │ └── package-info.java │ └── test/ │ ├── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── security/ │ │ └── kerberos/ │ │ ├── docs/ │ │ │ ├── AuthProviderConfig.java │ │ │ ├── AuthProviderConfigTests.java │ │ │ ├── DummyUserDetailsService.java │ │ │ └── SpnegoConfig.java │ │ └── web/ │ │ ├── SpnegoAuthenticationProcessingFilterTests.java │ │ └── SpnegoEntryPointTests.java │ └── resources/ │ └── org/ │ └── springframework/ │ └── security/ │ └── kerberos/ │ └── docs/ │ ├── AuthProviderConfig.xml │ ├── SpnegoConfig.xml │ └── appproperties.xml ├── ldap/ │ ├── openldaptest.ldif │ ├── run_slapd.sh │ ├── slapd.conf │ ├── spring-security-ldap.gradle │ └── src/ │ ├── integration-test/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── security/ │ │ │ └── ldap/ │ │ │ ├── DefaultSpringSecurityContextSourceTests.java │ │ │ ├── SpringSecurityLdapTemplateITests.java │ │ │ ├── UnboundIdContainerConfig.java │ │ │ ├── authentication/ │ │ │ │ ├── BindAuthenticatorTests.java │ │ │ │ └── PasswordComparisonAuthenticatorTests.java │ │ │ ├── search/ │ │ │ │ ├── FilterBasedLdapUserSearchTests.java │ │ │ │ └── FilterBasedLdapUserSearchWithSpacesTests.java │ │ │ ├── server/ │ │ │ │ ├── UnboundIdContainerLdifTests.java │ │ │ │ └── UnboundIdContainerTests.java │ │ │ └── userdetails/ │ │ │ ├── DefaultLdapAuthoritiesPopulatorGetGrantedAuthoritiesTests.java │ │ │ ├── DefaultLdapAuthoritiesPopulatorTests.java │ │ │ ├── LdapUserDetailsManagerModifyPasswordTests.java │ │ │ ├── LdapUserDetailsManagerTests.java │ │ │ └── NestedLdapAuthoritiesPopulatorTests.java │ │ └── resources/ │ │ ├── logback-test.xml │ │ ├── org/ │ │ │ └── springframework/ │ │ │ └── security/ │ │ │ └── ldap/ │ │ │ └── server/ │ │ │ └── spring.keystore │ │ ├── test-server-custom-attribute-types.ldif │ │ ├── test-server-malformed.txt │ │ ├── test-server-with-spaces.ldif │ │ ├── test-server-with-undefined-group-role-attributes.ldif │ │ └── test-server.ldif │ ├── main/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── security/ │ │ │ └── ldap/ │ │ │ ├── DefaultLdapUsernameToDnMapper.java │ │ │ ├── DefaultSpringSecurityContextSource.java │ │ │ ├── LdapEncoder.java │ │ │ ├── LdapUsernameToDnMapper.java │ │ │ ├── LdapUtils.java │ │ │ ├── SpringSecurityLdapTemplate.java │ │ │ ├── aot/ │ │ │ │ └── hint/ │ │ │ │ ├── LdapSecurityRuntimeHints.java │ │ │ │ └── package-info.java │ │ │ ├── authentication/ │ │ │ │ ├── AbstractLdapAuthenticationProvider.java │ │ │ │ ├── AbstractLdapAuthenticator.java │ │ │ │ ├── BindAuthenticator.java │ │ │ │ ├── LdapAuthenticationProvider.java │ │ │ │ ├── LdapAuthenticator.java │ │ │ │ ├── LdapEncoder.java │ │ │ │ ├── NullLdapAuthoritiesPopulator.java │ │ │ │ ├── PasswordComparisonAuthenticator.java │ │ │ │ ├── SpringSecurityAuthenticationSource.java │ │ │ │ ├── UserDetailsServiceLdapAuthoritiesPopulator.java │ │ │ │ ├── ad/ │ │ │ │ │ ├── ActiveDirectoryAuthenticationException.java │ │ │ │ │ ├── ActiveDirectoryLdapAuthenticationProvider.java │ │ │ │ │ ├── DefaultActiveDirectoryAuthoritiesPopulator.java │ │ │ │ │ └── package-info.java │ │ │ │ └── package-info.java │ │ │ ├── jackson/ │ │ │ │ ├── InetOrgPersonMixin.java │ │ │ │ ├── LdapAuthorityMixin.java │ │ │ │ ├── LdapJacksonModule.java │ │ │ │ ├── LdapUserDetailsImplMixin.java │ │ │ │ ├── PersonMixin.java │ │ │ │ └── package-info.java │ │ │ ├── jackson2/ │ │ │ │ ├── InetOrgPersonMixin.java │ │ │ │ ├── LdapAuthorityMixin.java │ │ │ │ ├── LdapJackson2Module.java │ │ │ │ ├── LdapUserDetailsImplMixin.java │ │ │ │ ├── PersonMixin.java │ │ │ │ └── package-info.java │ │ │ ├── package-info.java │ │ │ ├── ppolicy/ │ │ │ │ ├── PasswordPolicyAwareContextSource.java │ │ │ │ ├── PasswordPolicyControl.java │ │ │ │ ├── PasswordPolicyControlExtractor.java │ │ │ │ ├── PasswordPolicyControlFactory.java │ │ │ │ ├── PasswordPolicyData.java │ │ │ │ ├── PasswordPolicyErrorStatus.java │ │ │ │ ├── PasswordPolicyException.java │ │ │ │ ├── PasswordPolicyResponseControl.java │ │ │ │ └── package-info.java │ │ │ ├── search/ │ │ │ │ ├── FilterBasedLdapUserSearch.java │ │ │ │ ├── LdapUserSearch.java │ │ │ │ └── package-info.java │ │ │ ├── server/ │ │ │ │ ├── EmbeddedLdapServerContainer.java │ │ │ │ ├── UnboundIdContainer.java │ │ │ │ └── package-info.java │ │ │ └── userdetails/ │ │ │ ├── DefaultLdapAuthoritiesPopulator.java │ │ │ ├── InetOrgPerson.java │ │ │ ├── InetOrgPersonContextMapper.java │ │ │ ├── LdapAuthoritiesPopulator.java │ │ │ ├── LdapAuthority.java │ │ │ ├── LdapUserDetails.java │ │ │ ├── LdapUserDetailsImpl.java │ │ │ ├── LdapUserDetailsManager.java │ │ │ ├── LdapUserDetailsMapper.java │ │ │ ├── LdapUserDetailsService.java │ │ │ ├── NestedLdapAuthoritiesPopulator.java │ │ │ ├── Person.java │ │ │ ├── PersonContextMapper.java │ │ │ ├── UserDetailsContextMapper.java │ │ │ └── package-info.java │ │ └── resources/ │ │ └── META-INF/ │ │ └── spring/ │ │ └── aot.factories │ └── test/ │ ├── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── security/ │ │ └── ldap/ │ │ ├── LdapUtilsTests.java │ │ ├── SpringSecurityAuthenticationSourceTests.java │ │ ├── SpringSecurityLdapTemplateTests.java │ │ ├── aot/ │ │ │ └── hint/ │ │ │ └── LdapSecurityRuntimeHintsTests.java │ │ ├── authentication/ │ │ │ ├── LdapAuthenticationProviderTests.java │ │ │ ├── MockUserSearch.java │ │ │ ├── PasswordComparisonAuthenticatorMockTests.java │ │ │ └── ad/ │ │ │ └── ActiveDirectoryLdapAuthenticationProviderTests.java │ │ ├── jackson/ │ │ │ ├── InetOrgPersonMixinTests.java │ │ │ ├── LdapUserDetailsImplMixinTests.java │ │ │ └── PersonMixinTests.java │ │ ├── jackson2/ │ │ │ ├── InetOrgPersonMixinTests.java │ │ │ ├── LdapUserDetailsImplMixinTests.java │ │ │ └── PersonMixinTests.java │ │ ├── ppolicy/ │ │ │ ├── OpenLDAPIntegrationTestSuite.java │ │ │ ├── PasswordPolicyAwareContextSourceTests.java │ │ │ ├── PasswordPolicyControlFactoryTests.java │ │ │ └── PasswordPolicyResponseControlTests.java │ │ └── userdetails/ │ │ ├── InetOrgPersonTests.java │ │ ├── LdapAuthorityTests.java │ │ ├── LdapUserDetailsImplTests.java │ │ ├── LdapUserDetailsMapperTests.java │ │ ├── LdapUserDetailsServiceTests.java │ │ └── UserDetailsServiceLdapAuthoritiesPopulatorTests.java │ └── resources/ │ └── logback-test.xml ├── messaging/ │ ├── spring-security-messaging.gradle │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── security/ │ │ └── messaging/ │ │ ├── access/ │ │ │ ├── expression/ │ │ │ │ ├── DefaultMessageSecurityExpressionHandler.java │ │ │ │ ├── MessageAuthorizationContextSecurityExpressionHandler.java │ │ │ │ ├── MessageExpressionAuthorizationManager.java │ │ │ │ ├── MessageSecurityExpressionRoot.java │ │ │ │ └── package-info.java │ │ │ └── intercept/ │ │ │ ├── AuthorizationChannelInterceptor.java │ │ │ ├── MessageAuthorizationContext.java │ │ │ ├── MessageMatcherDelegatingAuthorizationManager.java │ │ │ └── package-info.java │ │ ├── context/ │ │ │ ├── AuthenticationPrincipalArgumentResolver.java │ │ │ ├── SecurityContextChannelInterceptor.java │ │ │ ├── SecurityContextPropagationChannelInterceptor.java │ │ │ └── package-info.java │ │ ├── handler/ │ │ │ └── invocation/ │ │ │ └── reactive/ │ │ │ ├── AuthenticationPrincipalArgumentResolver.java │ │ │ ├── CurrentSecurityContextArgumentResolver.java │ │ │ └── package-info.java │ │ ├── util/ │ │ │ └── matcher/ │ │ │ ├── AbstractMessageMatcherComposite.java │ │ │ ├── AndMessageMatcher.java │ │ │ ├── MessageMatcher.java │ │ │ ├── OrMessageMatcher.java │ │ │ ├── PathPatternMessageMatcher.java │ │ │ ├── SimpMessageTypeMatcher.java │ │ │ └── package-info.java │ │ └── web/ │ │ ├── csrf/ │ │ │ ├── CsrfChannelInterceptor.java │ │ │ ├── XorCsrfChannelInterceptor.java │ │ │ ├── XorCsrfTokenUtils.java │ │ │ └── package-info.java │ │ └── socket/ │ │ └── server/ │ │ ├── CsrfTokenHandshakeInterceptor.java │ │ └── package-info.java │ └── test/ │ ├── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── security/ │ │ └── messaging/ │ │ ├── access/ │ │ │ ├── expression/ │ │ │ │ ├── DefaultMessageSecurityExpressionHandlerTests.java │ │ │ │ └── MessageExpressionAuthorizationManagerTests.java │ │ │ └── intercept/ │ │ │ ├── AuthorizationChannelInterceptorTests.java │ │ │ └── MessageMatcherDelegatingAuthorizationManagerTests.java │ │ ├── context/ │ │ │ ├── AuthenticationPrincipalArgumentResolverTests.java │ │ │ ├── SecurityContextChannelInterceptorTests.java │ │ │ └── SecurityContextPropagationChannelInterceptorTests.java │ │ ├── handler/ │ │ │ └── invocation/ │ │ │ ├── ResolvableMethod.java │ │ │ └── reactive/ │ │ │ ├── AuthenticationPrincipalArgumentResolverTests.java │ │ │ └── CurrentSecurityContextArgumentResolverTests.java │ │ ├── util/ │ │ │ └── matcher/ │ │ │ ├── AndMessageMatcherTests.java │ │ │ ├── OrMessageMatcherTests.java │ │ │ ├── PathPatternMessageMatcherTests.java │ │ │ └── SimpMessageTypeMatcherTests.java │ │ └── web/ │ │ ├── csrf/ │ │ │ ├── CsrfChannelInterceptorTests.java │ │ │ └── XorCsrfChannelInterceptorTests.java │ │ └── socket/ │ │ └── server/ │ │ └── CsrfTokenHandshakeInterceptorTests.java │ └── resources/ │ └── logback-test.xml ├── notice.txt ├── oauth2/ │ ├── oauth2-authorization-server/ │ │ ├── spring-security-oauth2-authorization-server.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── security/ │ │ │ │ └── oauth2/ │ │ │ │ └── server/ │ │ │ │ └── authorization/ │ │ │ │ ├── AbstractOAuth2AuthorizationServerMetadata.java │ │ │ │ ├── AbstractOAuth2ClientRegistration.java │ │ │ │ ├── InMemoryOAuth2AuthorizationConsentService.java │ │ │ │ ├── InMemoryOAuth2AuthorizationService.java │ │ │ │ ├── JdbcOAuth2AuthorizationConsentService.java │ │ │ │ ├── JdbcOAuth2AuthorizationService.java │ │ │ │ ├── OAuth2Authorization.java │ │ │ │ ├── OAuth2AuthorizationCode.java │ │ │ │ ├── OAuth2AuthorizationConsent.java │ │ │ │ ├── OAuth2AuthorizationConsentService.java │ │ │ │ ├── OAuth2AuthorizationServerMetadata.java │ │ │ │ ├── OAuth2AuthorizationServerMetadataClaimAccessor.java │ │ │ │ ├── OAuth2AuthorizationServerMetadataClaimNames.java │ │ │ │ ├── OAuth2AuthorizationService.java │ │ │ │ ├── OAuth2ClientMetadataClaimAccessor.java │ │ │ │ ├── OAuth2ClientMetadataClaimNames.java │ │ │ │ ├── OAuth2ClientRegistration.java │ │ │ │ ├── OAuth2TokenIntrospection.java │ │ │ │ ├── OAuth2TokenType.java │ │ │ │ ├── aot/ │ │ │ │ │ └── hint/ │ │ │ │ │ ├── OAuth2AuthorizationServerBeanRegistrationAotProcessor.java │ │ │ │ │ └── OAuth2AuthorizationServerRuntimeHints.java │ │ │ │ ├── authentication/ │ │ │ │ │ ├── AbstractOAuth2AuthorizationCodeRequestAuthenticationToken.java │ │ │ │ │ ├── ClientSecretAuthenticationProvider.java │ │ │ │ │ ├── CodeVerifierAuthenticator.java │ │ │ │ │ ├── DPoPProofVerifier.java │ │ │ │ │ ├── JwtClientAssertionAuthenticationProvider.java │ │ │ │ │ ├── JwtClientAssertionDecoderFactory.java │ │ │ │ │ ├── OAuth2AccessTokenAuthenticationContext.java │ │ │ │ │ ├── OAuth2AccessTokenAuthenticationToken.java │ │ │ │ │ ├── OAuth2AuthenticationContext.java │ │ │ │ │ ├── OAuth2AuthenticationProviderUtils.java │ │ │ │ │ ├── OAuth2AuthorizationCodeAuthenticationProvider.java │ │ │ │ │ ├── OAuth2AuthorizationCodeAuthenticationToken.java │ │ │ │ │ ├── OAuth2AuthorizationCodeGenerator.java │ │ │ │ │ ├── OAuth2AuthorizationCodeRequestAuthenticationContext.java │ │ │ │ │ ├── OAuth2AuthorizationCodeRequestAuthenticationException.java │ │ │ │ │ ├── OAuth2AuthorizationCodeRequestAuthenticationProvider.java │ │ │ │ │ ├── OAuth2AuthorizationCodeRequestAuthenticationToken.java │ │ │ │ │ ├── OAuth2AuthorizationCodeRequestAuthenticationValidator.java │ │ │ │ │ ├── OAuth2AuthorizationConsentAuthenticationContext.java │ │ │ │ │ ├── OAuth2AuthorizationConsentAuthenticationProvider.java │ │ │ │ │ ├── OAuth2AuthorizationConsentAuthenticationToken.java │ │ │ │ │ ├── OAuth2AuthorizationGrantAuthenticationToken.java │ │ │ │ │ ├── OAuth2ClientAuthenticationContext.java │ │ │ │ │ ├── OAuth2ClientAuthenticationToken.java │ │ │ │ │ ├── OAuth2ClientCredentialsAuthenticationContext.java │ │ │ │ │ ├── OAuth2ClientCredentialsAuthenticationProvider.java │ │ │ │ │ ├── OAuth2ClientCredentialsAuthenticationToken.java │ │ │ │ │ ├── OAuth2ClientCredentialsAuthenticationValidator.java │ │ │ │ │ ├── OAuth2ClientRegistrationAuthenticationProvider.java │ │ │ │ │ ├── OAuth2ClientRegistrationAuthenticationToken.java │ │ │ │ │ ├── OAuth2DeviceAuthorizationConsentAuthenticationProvider.java │ │ │ │ │ ├── OAuth2DeviceAuthorizationConsentAuthenticationToken.java │ │ │ │ │ ├── OAuth2DeviceAuthorizationRequestAuthenticationProvider.java │ │ │ │ │ ├── OAuth2DeviceAuthorizationRequestAuthenticationToken.java │ │ │ │ │ ├── OAuth2DeviceCodeAuthenticationProvider.java │ │ │ │ │ ├── OAuth2DeviceCodeAuthenticationToken.java │ │ │ │ │ ├── OAuth2DeviceVerificationAuthenticationContext.java │ │ │ │ │ ├── OAuth2DeviceVerificationAuthenticationProvider.java │ │ │ │ │ ├── OAuth2DeviceVerificationAuthenticationToken.java │ │ │ │ │ ├── OAuth2PushedAuthorizationRequestAuthenticationProvider.java │ │ │ │ │ ├── OAuth2PushedAuthorizationRequestAuthenticationToken.java │ │ │ │ │ ├── OAuth2PushedAuthorizationRequestUri.java │ │ │ │ │ ├── OAuth2RefreshTokenAuthenticationProvider.java │ │ │ │ │ ├── OAuth2RefreshTokenAuthenticationToken.java │ │ │ │ │ ├── OAuth2TokenExchangeActor.java │ │ │ │ │ ├── OAuth2TokenExchangeAuthenticationProvider.java │ │ │ │ │ ├── OAuth2TokenExchangeAuthenticationToken.java │ │ │ │ │ ├── OAuth2TokenExchangeCompositeAuthenticationToken.java │ │ │ │ │ ├── OAuth2TokenIntrospectionAuthenticationProvider.java │ │ │ │ │ ├── OAuth2TokenIntrospectionAuthenticationToken.java │ │ │ │ │ ├── OAuth2TokenRevocationAuthenticationProvider.java │ │ │ │ │ ├── OAuth2TokenRevocationAuthenticationToken.java │ │ │ │ │ ├── OidcPrompt.java │ │ │ │ │ ├── PublicClientAuthenticationProvider.java │ │ │ │ │ ├── X509ClientCertificateAuthenticationProvider.java │ │ │ │ │ └── X509SelfSignedCertificateVerifier.java │ │ │ │ ├── client/ │ │ │ │ │ ├── InMemoryRegisteredClientRepository.java │ │ │ │ │ ├── JdbcRegisteredClientRepository.java │ │ │ │ │ ├── RegisteredClient.java │ │ │ │ │ └── RegisteredClientRepository.java │ │ │ │ ├── context/ │ │ │ │ │ ├── AuthorizationServerContext.java │ │ │ │ │ ├── AuthorizationServerContextHolder.java │ │ │ │ │ └── Context.java │ │ │ │ ├── converter/ │ │ │ │ │ ├── OAuth2ClientRegistrationRegisteredClientConverter.java │ │ │ │ │ └── RegisteredClientOAuth2ClientRegistrationConverter.java │ │ │ │ ├── http/ │ │ │ │ │ └── converter/ │ │ │ │ │ ├── GenericHttpMessageConverterAdapter.java │ │ │ │ │ ├── HttpMessageConverters.java │ │ │ │ │ ├── OAuth2AuthorizationServerMetadataHttpMessageConverter.java │ │ │ │ │ ├── OAuth2ClientRegistrationHttpMessageConverter.java │ │ │ │ │ └── OAuth2TokenIntrospectionHttpMessageConverter.java │ │ │ │ ├── jackson/ │ │ │ │ │ ├── JsonNodeUtils.java │ │ │ │ │ ├── JwsAlgorithmMixin.java │ │ │ │ │ ├── OAuth2AuthorizationRequestDeserializer.java │ │ │ │ │ ├── OAuth2AuthorizationRequestMixin.java │ │ │ │ │ ├── OAuth2AuthorizationServerJacksonModule.java │ │ │ │ │ ├── OAuth2TokenExchangeActorMixin.java │ │ │ │ │ ├── OAuth2TokenExchangeCompositeAuthenticationTokenMixin.java │ │ │ │ │ └── OAuth2TokenFormatMixin.java │ │ │ │ ├── jackson2/ │ │ │ │ │ ├── DurationMixin.java │ │ │ │ │ ├── HashSetMixin.java │ │ │ │ │ ├── JsonNodeUtils.java │ │ │ │ │ ├── JwsAlgorithmMixin.java │ │ │ │ │ ├── OAuth2AuthorizationRequestDeserializer.java │ │ │ │ │ ├── OAuth2AuthorizationRequestMixin.java │ │ │ │ │ ├── OAuth2AuthorizationServerJackson2Module.java │ │ │ │ │ ├── OAuth2TokenExchangeActorMixin.java │ │ │ │ │ ├── OAuth2TokenExchangeCompositeAuthenticationTokenMixin.java │ │ │ │ │ ├── OAuth2TokenFormatMixin.java │ │ │ │ │ ├── StringArrayMixin.java │ │ │ │ │ ├── UnmodifiableMapDeserializer.java │ │ │ │ │ └── UnmodifiableMapMixin.java │ │ │ │ ├── oidc/ │ │ │ │ │ ├── OidcClientMetadataClaimAccessor.java │ │ │ │ │ ├── OidcClientMetadataClaimNames.java │ │ │ │ │ ├── OidcClientRegistration.java │ │ │ │ │ ├── OidcProviderConfiguration.java │ │ │ │ │ ├── OidcProviderMetadataClaimAccessor.java │ │ │ │ │ ├── OidcProviderMetadataClaimNames.java │ │ │ │ │ ├── authentication/ │ │ │ │ │ │ ├── OidcAuthenticationProviderUtils.java │ │ │ │ │ │ ├── OidcClientConfigurationAuthenticationProvider.java │ │ │ │ │ │ ├── OidcClientRegistrationAuthenticationProvider.java │ │ │ │ │ │ ├── OidcClientRegistrationAuthenticationToken.java │ │ │ │ │ │ ├── OidcLogoutAuthenticationContext.java │ │ │ │ │ │ ├── OidcLogoutAuthenticationProvider.java │ │ │ │ │ │ ├── OidcLogoutAuthenticationToken.java │ │ │ │ │ │ ├── OidcLogoutAuthenticationValidator.java │ │ │ │ │ │ ├── OidcUserInfoAuthenticationContext.java │ │ │ │ │ │ ├── OidcUserInfoAuthenticationProvider.java │ │ │ │ │ │ └── OidcUserInfoAuthenticationToken.java │ │ │ │ │ ├── converter/ │ │ │ │ │ │ ├── OidcClientRegistrationRegisteredClientConverter.java │ │ │ │ │ │ └── RegisteredClientOidcClientRegistrationConverter.java │ │ │ │ │ ├── http/ │ │ │ │ │ │ └── converter/ │ │ │ │ │ │ ├── GenericHttpMessageConverterAdapter.java │ │ │ │ │ │ ├── HttpMessageConverters.java │ │ │ │ │ │ ├── OidcClientRegistrationHttpMessageConverter.java │ │ │ │ │ │ ├── OidcProviderConfigurationHttpMessageConverter.java │ │ │ │ │ │ └── OidcUserInfoHttpMessageConverter.java │ │ │ │ │ └── web/ │ │ │ │ │ ├── OidcClientRegistrationEndpointFilter.java │ │ │ │ │ ├── OidcLogoutEndpointFilter.java │ │ │ │ │ ├── OidcProviderConfigurationEndpointFilter.java │ │ │ │ │ ├── OidcUserInfoEndpointFilter.java │ │ │ │ │ └── authentication/ │ │ │ │ │ ├── OAuth2EndpointUtils.java │ │ │ │ │ ├── OidcClientRegistrationAuthenticationConverter.java │ │ │ │ │ ├── OidcLogoutAuthenticationConverter.java │ │ │ │ │ └── OidcLogoutAuthenticationSuccessHandler.java │ │ │ │ ├── settings/ │ │ │ │ │ ├── AbstractSettings.java │ │ │ │ │ ├── AuthorizationServerSettings.java │ │ │ │ │ ├── ClientSettings.java │ │ │ │ │ ├── ConfigurationSettingNames.java │ │ │ │ │ ├── OAuth2TokenFormat.java │ │ │ │ │ └── TokenSettings.java │ │ │ │ ├── token/ │ │ │ │ │ ├── DefaultOAuth2TokenContext.java │ │ │ │ │ ├── DelegatingOAuth2TokenGenerator.java │ │ │ │ │ ├── JwtEncodingContext.java │ │ │ │ │ ├── JwtGenerator.java │ │ │ │ │ ├── OAuth2AccessTokenGenerator.java │ │ │ │ │ ├── OAuth2RefreshTokenGenerator.java │ │ │ │ │ ├── OAuth2TokenClaimAccessor.java │ │ │ │ │ ├── OAuth2TokenClaimNames.java │ │ │ │ │ ├── OAuth2TokenClaimsContext.java │ │ │ │ │ ├── OAuth2TokenClaimsSet.java │ │ │ │ │ ├── OAuth2TokenContext.java │ │ │ │ │ ├── OAuth2TokenCustomizer.java │ │ │ │ │ └── OAuth2TokenGenerator.java │ │ │ │ └── web/ │ │ │ │ ├── DefaultConsentPage.java │ │ │ │ ├── GenericHttpMessageConverterAdapter.java │ │ │ │ ├── HttpMessageConverters.java │ │ │ │ ├── NimbusJwkSetEndpointFilter.java │ │ │ │ ├── OAuth2AuthorizationEndpointFilter.java │ │ │ │ ├── OAuth2AuthorizationServerMetadataEndpointFilter.java │ │ │ │ ├── OAuth2ClientAuthenticationFilter.java │ │ │ │ ├── OAuth2ClientRegistrationEndpointFilter.java │ │ │ │ ├── OAuth2DeviceAuthorizationEndpointFilter.java │ │ │ │ ├── OAuth2DeviceVerificationEndpointFilter.java │ │ │ │ ├── OAuth2PushedAuthorizationRequestEndpointFilter.java │ │ │ │ ├── OAuth2TokenEndpointFilter.java │ │ │ │ ├── OAuth2TokenIntrospectionEndpointFilter.java │ │ │ │ ├── OAuth2TokenRevocationEndpointFilter.java │ │ │ │ └── authentication/ │ │ │ │ ├── ClientSecretBasicAuthenticationConverter.java │ │ │ │ ├── ClientSecretPostAuthenticationConverter.java │ │ │ │ ├── JwtClientAssertionAuthenticationConverter.java │ │ │ │ ├── OAuth2AccessTokenResponseAuthenticationSuccessHandler.java │ │ │ │ ├── OAuth2AuthorizationCodeAuthenticationConverter.java │ │ │ │ ├── OAuth2AuthorizationCodeRequestAuthenticationConverter.java │ │ │ │ ├── OAuth2AuthorizationConsentAuthenticationConverter.java │ │ │ │ ├── OAuth2ClientCredentialsAuthenticationConverter.java │ │ │ │ ├── OAuth2ClientRegistrationAuthenticationConverter.java │ │ │ │ ├── OAuth2DeviceAuthorizationConsentAuthenticationConverter.java │ │ │ │ ├── OAuth2DeviceAuthorizationRequestAuthenticationConverter.java │ │ │ │ ├── OAuth2DeviceCodeAuthenticationConverter.java │ │ │ │ ├── OAuth2DeviceVerificationAuthenticationConverter.java │ │ │ │ ├── OAuth2EndpointUtils.java │ │ │ │ ├── OAuth2ErrorAuthenticationFailureHandler.java │ │ │ │ ├── OAuth2RefreshTokenAuthenticationConverter.java │ │ │ │ ├── OAuth2TokenExchangeAuthenticationConverter.java │ │ │ │ ├── OAuth2TokenIntrospectionAuthenticationConverter.java │ │ │ │ ├── OAuth2TokenRevocationAuthenticationConverter.java │ │ │ │ ├── PublicClientAuthenticationConverter.java │ │ │ │ └── X509ClientCertificateAuthenticationConverter.java │ │ │ └── resources/ │ │ │ ├── META-INF/ │ │ │ │ └── spring/ │ │ │ │ └── aot.factories │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── security/ │ │ │ └── oauth2/ │ │ │ └── server/ │ │ │ └── authorization/ │ │ │ ├── client/ │ │ │ │ └── oauth2-registered-client-schema.sql │ │ │ ├── oauth2-authorization-consent-schema.sql │ │ │ └── oauth2-authorization-schema.sql │ │ └── test/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── security/ │ │ │ └── oauth2/ │ │ │ └── server/ │ │ │ └── authorization/ │ │ │ ├── InMemoryOAuth2AuthorizationConsentServiceTests.java │ │ │ ├── InMemoryOAuth2AuthorizationServiceTests.java │ │ │ ├── JdbcOAuth2AuthorizationConsentServiceTests.java │ │ │ ├── JdbcOAuth2AuthorizationServiceTests.java │ │ │ ├── OAuth2AuthorizationConsentTests.java │ │ │ ├── OAuth2AuthorizationServerMetadataTests.java │ │ │ ├── OAuth2AuthorizationTests.java │ │ │ ├── OAuth2ClientRegistrationTests.java │ │ │ ├── TestOAuth2Authorizations.java │ │ │ ├── aot/ │ │ │ │ └── hint/ │ │ │ │ └── OAuth2AuthorizationServerBeanRegistrationAotProcessorTests.java │ │ │ ├── authentication/ │ │ │ │ ├── ClientSecretAuthenticationProviderTests.java │ │ │ │ ├── JwtClientAssertionAuthenticationProviderTests.java │ │ │ │ ├── JwtClientAssertionDecoderFactoryTests.java │ │ │ │ ├── OAuth2AccessTokenAuthenticationContextTests.java │ │ │ │ ├── OAuth2AccessTokenAuthenticationTokenTests.java │ │ │ │ ├── OAuth2AuthorizationCodeAuthenticationProviderTests.java │ │ │ │ ├── OAuth2AuthorizationCodeAuthenticationTokenTests.java │ │ │ │ ├── OAuth2AuthorizationCodeRequestAuthenticationProviderTests.java │ │ │ │ ├── OAuth2AuthorizationCodeRequestAuthenticationTokenTests.java │ │ │ │ ├── OAuth2AuthorizationConsentAuthenticationContextTests.java │ │ │ │ ├── OAuth2AuthorizationConsentAuthenticationProviderTests.java │ │ │ │ ├── OAuth2ClientAuthenticationTokenTests.java │ │ │ │ ├── OAuth2ClientCredentialsAuthenticationProviderTests.java │ │ │ │ ├── OAuth2ClientCredentialsAuthenticationTokenTests.java │ │ │ │ ├── OAuth2ClientRegistrationAuthenticationProviderTests.java │ │ │ │ ├── OAuth2DeviceAuthorizationConsentAuthenticationProviderTests.java │ │ │ │ ├── OAuth2DeviceAuthorizationRequestAuthenticationProviderTests.java │ │ │ │ ├── OAuth2DeviceCodeAuthenticationProviderTests.java │ │ │ │ ├── OAuth2DeviceVerificationAuthenticationProviderTests.java │ │ │ │ ├── OAuth2PushedAuthorizationRequestAuthenticationProviderTests.java │ │ │ │ ├── OAuth2RefreshTokenAuthenticationProviderTests.java │ │ │ │ ├── OAuth2RefreshTokenAuthenticationTokenTests.java │ │ │ │ ├── OAuth2TokenExchangeActorTests.java │ │ │ │ ├── OAuth2TokenExchangeAuthenticationProviderTests.java │ │ │ │ ├── OAuth2TokenExchangeAuthenticationTokenTests.java │ │ │ │ ├── OAuth2TokenExchangeCompositeAuthenticationTokenTests.java │ │ │ │ ├── OAuth2TokenIntrospectionAuthenticationProviderTests.java │ │ │ │ ├── OAuth2TokenIntrospectionAuthenticationTokenTests.java │ │ │ │ ├── OAuth2TokenRevocationAuthenticationProviderTests.java │ │ │ │ ├── OAuth2TokenRevocationAuthenticationTokenTests.java │ │ │ │ ├── PublicClientAuthenticationProviderTests.java │ │ │ │ └── X509ClientCertificateAuthenticationProviderTests.java │ │ │ ├── client/ │ │ │ │ ├── InMemoryRegisteredClientRepositoryTests.java │ │ │ │ ├── JdbcRegisteredClientRepositoryTests.java │ │ │ │ ├── RegisteredClientTests.java │ │ │ │ └── TestRegisteredClients.java │ │ │ ├── context/ │ │ │ │ └── TestAuthorizationServerContext.java │ │ │ ├── http/ │ │ │ │ └── converter/ │ │ │ │ ├── OAuth2AuthorizationServerMetadataHttpMessageConverterTests.java │ │ │ │ ├── OAuth2ClientRegistrationHttpMessageConverterTests.java │ │ │ │ └── OAuth2TokenIntrospectionHttpMessageConverterTests.java │ │ │ ├── jackson/ │ │ │ │ └── OAuth2AuthorizationServerJacksonModuleTests.java │ │ │ ├── jackson2/ │ │ │ │ ├── OAuth2AuthorizationServerJackson2ModuleTests.java │ │ │ │ └── TestingAuthenticationTokenMixin.java │ │ │ ├── oidc/ │ │ │ │ ├── OidcClientRegistrationTests.java │ │ │ │ ├── OidcProviderConfigurationTests.java │ │ │ │ ├── authentication/ │ │ │ │ │ ├── OidcClientConfigurationAuthenticationProviderTests.java │ │ │ │ │ ├── OidcClientRegistrationAuthenticationProviderTests.java │ │ │ │ │ ├── OidcClientRegistrationAuthenticationTokenTests.java │ │ │ │ │ ├── OidcLogoutAuthenticationProviderTests.java │ │ │ │ │ ├── OidcLogoutAuthenticationTokenTests.java │ │ │ │ │ ├── OidcUserInfoAuthenticationProviderTests.java │ │ │ │ │ └── OidcUserInfoAuthenticationTokenTests.java │ │ │ │ ├── http/ │ │ │ │ │ └── converter/ │ │ │ │ │ ├── OidcClientRegistrationHttpMessageConverterTests.java │ │ │ │ │ ├── OidcProviderConfigurationHttpMessageConverterTests.java │ │ │ │ │ └── OidcUserInfoHttpMessageConverterTests.java │ │ │ │ └── web/ │ │ │ │ ├── OidcClientRegistrationEndpointFilterTests.java │ │ │ │ ├── OidcLogoutEndpointFilterTests.java │ │ │ │ ├── OidcProviderConfigurationEndpointFilterTests.java │ │ │ │ ├── OidcUserInfoEndpointFilterTests.java │ │ │ │ └── authentication/ │ │ │ │ └── OidcLogoutAuthenticationSuccessHandlerTests.java │ │ │ ├── settings/ │ │ │ │ ├── AuthorizationServerSettingsTests.java │ │ │ │ ├── ClientSettingsTests.java │ │ │ │ └── TokenSettingsTests.java │ │ │ ├── token/ │ │ │ │ ├── DelegatingOAuth2TokenGeneratorTests.java │ │ │ │ ├── JwtEncodingContextTests.java │ │ │ │ ├── JwtGeneratorTests.java │ │ │ │ ├── OAuth2AccessTokenGeneratorTests.java │ │ │ │ ├── OAuth2RefreshTokenGeneratorTests.java │ │ │ │ ├── OAuth2TokenClaimsContextTests.java │ │ │ │ └── OAuth2TokenClaimsSetTests.java │ │ │ ├── util/ │ │ │ │ ├── TestX509Certificates.java │ │ │ │ └── X509CertificateUtils.java │ │ │ └── web/ │ │ │ ├── NimbusJwkSetEndpointFilterTests.java │ │ │ ├── OAuth2AuthorizationEndpointFilterTests.java │ │ │ ├── OAuth2AuthorizationServerMetadataEndpointFilterTests.java │ │ │ ├── OAuth2ClientAuthenticationFilterTests.java │ │ │ ├── OAuth2ClientRegistrationEndpointFilterTests.java │ │ │ ├── OAuth2DeviceAuthorizationEndpointFilterTests.java │ │ │ ├── OAuth2DeviceVerificationEndpointFilterTests.java │ │ │ ├── OAuth2PushedAuthorizationRequestEndpointFilterTests.java │ │ │ ├── OAuth2TokenEndpointFilterTests.java │ │ │ ├── OAuth2TokenIntrospectionEndpointFilterTests.java │ │ │ ├── OAuth2TokenRevocationEndpointFilterTests.java │ │ │ └── authentication/ │ │ │ ├── ClientSecretBasicAuthenticationConverterTests.java │ │ │ ├── ClientSecretPostAuthenticationConverterTests.java │ │ │ ├── JwtClientAssertionAuthenticationConverterTests.java │ │ │ ├── OAuth2AccessTokenResponseAuthenticationSuccessHandlerTests.java │ │ │ ├── OAuth2DeviceAuthorizationConsentAuthenticationConverterTests.java │ │ │ ├── OAuth2DeviceAuthorizationRequestAuthenticationConverterTests.java │ │ │ ├── OAuth2DeviceCodeAuthenticationConverterTests.java │ │ │ ├── OAuth2DeviceVerificationAuthenticationConverterTests.java │ │ │ ├── OAuth2ErrorAuthenticationFailureHandlerTests.java │ │ │ ├── OAuth2TokenExchangeAuthenticationConverterTests.java │ │ │ ├── PublicClientAuthenticationConverterTests.java │ │ │ └── X509ClientCertificateAuthenticationConverterTests.java │ │ └── resources/ │ │ └── org/ │ │ └── springframework/ │ │ └── security/ │ │ └── oauth2/ │ │ └── server/ │ │ └── authorization/ │ │ ├── client/ │ │ │ └── custom-oauth2-registered-client-schema.sql │ │ ├── custom-oauth2-authorization-consent-schema.sql │ │ ├── custom-oauth2-authorization-schema-clob-data-type.sql │ │ └── custom-oauth2-authorization-schema.sql │ ├── oauth2-client/ │ │ ├── spring-security-oauth2-client.gradle │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── security/ │ │ │ │ └── oauth2/ │ │ │ │ └── client/ │ │ │ │ ├── AuthorizationCodeOAuth2AuthorizedClientProvider.java │ │ │ │ ├── AuthorizationCodeReactiveOAuth2AuthorizedClientProvider.java │ │ │ │ ├── AuthorizedClientServiceOAuth2AuthorizedClientManager.java │ │ │ │ ├── AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager.java │ │ │ │ ├── ClientAuthorizationException.java │ │ │ │ ├── ClientAuthorizationRequiredException.java │ │ │ │ ├── ClientCredentialsOAuth2AuthorizedClientProvider.java │ │ │ │ ├── ClientCredentialsReactiveOAuth2AuthorizedClientProvider.java │ │ │ │ ├── DelegatingOAuth2AuthorizedClientProvider.java │ │ │ │ ├── DelegatingReactiveOAuth2AuthorizedClientProvider.java │ │ │ │ ├── InMemoryOAuth2AuthorizedClientService.java │ │ │ │ ├── InMemoryReactiveOAuth2AuthorizedClientService.java │ │ │ │ ├── JdbcOAuth2AuthorizedClientService.java │ │ │ │ ├── JwtBearerOAuth2AuthorizedClientProvider.java │ │ │ │ ├── JwtBearerReactiveOAuth2AuthorizedClientProvider.java │ │ │ │ ├── OAuth2AuthorizationContext.java │ │ │ │ ├── OAuth2AuthorizationFailureHandler.java │ │ │ │ ├── OAuth2AuthorizationSuccessHandler.java │ │ │ │ ├── OAuth2AuthorizeRequest.java │ │ │ │ ├── OAuth2AuthorizedClient.java │ │ │ │ ├── OAuth2AuthorizedClientId.java │ │ │ │ ├── OAuth2AuthorizedClientManager.java │ │ │ │ ├── OAuth2AuthorizedClientProvider.java │ │ │ │ ├── OAuth2AuthorizedClientProviderBuilder.java │ │ │ │ ├── OAuth2AuthorizedClientService.java │ │ │ │ ├── R2dbcReactiveOAuth2AuthorizedClientService.java │ │ │ │ ├── ReactiveOAuth2AuthorizationFailureHandler.java │ │ │ │ ├── ReactiveOAuth2AuthorizationSuccessHandler.java │ │ │ │ ├── ReactiveOAuth2AuthorizedClientManager.java │ │ │ │ ├── ReactiveOAuth2AuthorizedClientProvider.java │ │ │ │ ├── ReactiveOAuth2AuthorizedClientProviderBuilder.java │ │ │ │ ├── ReactiveOAuth2AuthorizedClientService.java │ │ │ │ ├── RefreshOidcUserReactiveOAuth2AuthorizationSuccessHandler.java │ │ │ │ ├── RefreshTokenOAuth2AuthorizedClientProvider.java │ │ │ │ ├── RefreshTokenReactiveOAuth2AuthorizedClientProvider.java │ │ │ │ ├── RemoveAuthorizedClientOAuth2AuthorizationFailureHandler.java │ │ │ │ ├── RemoveAuthorizedClientReactiveOAuth2AuthorizationFailureHandler.java │ │ │ │ ├── TokenExchangeOAuth2AuthorizedClientProvider.java │ │ │ │ ├── TokenExchangeReactiveOAuth2AuthorizedClientProvider.java │ │ │ │ ├── annotation/ │ │ │ │ │ ├── ClientRegistrationId.java │ │ │ │ │ ├── RegisteredOAuth2AuthorizedClient.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── aot/ │ │ │ │ │ └── hint/ │ │ │ │ │ ├── OAuth2ClientRuntimeHints.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── authentication/ │ │ │ │ │ ├── OAuth2AuthenticationToken.java │ │ │ │ │ ├── OAuth2AuthorizationCodeAuthenticationProvider.java │ │ │ │ │ ├── OAuth2AuthorizationCodeAuthenticationToken.java │ │ │ │ │ ├── OAuth2AuthorizationCodeReactiveAuthenticationManager.java │ │ │ │ │ ├── OAuth2LoginAuthenticationProvider.java │ │ │ │ │ ├── OAuth2LoginAuthenticationToken.java │ │ │ │ │ ├── OAuth2LoginReactiveAuthenticationManager.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── endpoint/ │ │ │ │ │ ├── AbstractOAuth2AuthorizationGrantRequest.java │ │ │ │ │ ├── AbstractRestClientOAuth2AccessTokenResponseClient.java │ │ │ │ │ ├── AbstractWebClientReactiveOAuth2AccessTokenResponseClient.java │ │ │ │ │ ├── DefaultOAuth2TokenRequestHeadersConverter.java │ │ │ │ │ ├── DefaultOAuth2TokenRequestParametersConverter.java │ │ │ │ │ ├── JwtBearerGrantRequest.java │ │ │ │ │ ├── NimbusJwtClientAuthenticationParametersConverter.java │ │ │ │ │ ├── OAuth2AccessTokenResponseClient.java │ │ │ │ │ ├── OAuth2AuthorizationCodeGrantRequest.java │ │ │ │ │ ├── OAuth2ClientCredentialsGrantRequest.java │ │ │ │ │ ├── OAuth2RefreshTokenGrantRequest.java │ │ │ │ │ ├── ReactiveOAuth2AccessTokenResponseClient.java │ │ │ │ │ ├── RestClientAuthorizationCodeTokenResponseClient.java │ │ │ │ │ ├── RestClientClientCredentialsTokenResponseClient.java │ │ │ │ │ ├── RestClientJwtBearerTokenResponseClient.java │ │ │ │ │ ├── RestClientRefreshTokenTokenResponseClient.java │ │ │ │ │ ├── RestClientTokenExchangeTokenResponseClient.java │ │ │ │ │ ├── TokenExchangeGrantRequest.java │ │ │ │ │ ├── WebClientReactiveAuthorizationCodeTokenResponseClient.java │ │ │ │ │ ├── WebClientReactiveClientCredentialsTokenResponseClient.java │ │ │ │ │ ├── WebClientReactiveJwtBearerTokenResponseClient.java │ │ │ │ │ ├── WebClientReactiveRefreshTokenTokenResponseClient.java │ │ │ │ │ ├── WebClientReactiveTokenExchangeTokenResponseClient.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── event/ │ │ │ │ │ ├── OAuth2AuthorizedClientRefreshedEvent.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── http/ │ │ │ │ │ ├── OAuth2ErrorResponseErrorHandler.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── jackson/ │ │ │ │ │ ├── ClientRegistrationDeserializer.java │ │ │ │ │ ├── ClientRegistrationMixin.java │ │ │ │ │ ├── DefaultOAuth2UserMixin.java │ │ │ │ │ ├── DefaultOidcUserMixin.java │ │ │ │ │ ├── JsonNodeUtils.java │ │ │ │ │ ├── OAuth2AccessTokenMixin.java │ │ │ │ │ ├── OAuth2AuthenticationExceptionMixin.java │ │ │ │ │ ├── OAuth2AuthenticationTokenMixin.java │ │ │ │ │ ├── OAuth2AuthorizationRequestDeserializer.java │ │ │ │ │ ├── OAuth2AuthorizationRequestMixin.java │ │ │ │ │ ├── OAuth2AuthorizedClientMixin.java │ │ │ │ │ ├── OAuth2ClientJacksonModule.java │ │ │ │ │ ├── OAuth2ErrorMixin.java │ │ │ │ │ ├── OAuth2RefreshTokenMixin.java │ │ │ │ │ ├── OAuth2UserAuthorityMixin.java │ │ │ │ │ ├── OidcIdTokenMixin.java │ │ │ │ │ ├── OidcUserAuthorityMixin.java │ │ │ │ │ ├── OidcUserInfoMixin.java │ │ │ │ │ ├── StdConverters.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── jackson2/ │ │ │ │ │ ├── ClientRegistrationDeserializer.java │ │ │ │ │ ├── ClientRegistrationMixin.java │ │ │ │ │ ├── DefaultOAuth2UserMixin.java │ │ │ │ │ ├── DefaultOidcUserMixin.java │ │ │ │ │ ├── JsonNodeUtils.java │ │ │ │ │ ├── OAuth2AccessTokenMixin.java │ │ │ │ │ ├── OAuth2AuthenticationExceptionMixin.java │ │ │ │ │ ├── OAuth2AuthenticationTokenMixin.java │ │ │ │ │ ├── OAuth2AuthorizationRequestDeserializer.java │ │ │ │ │ ├── OAuth2AuthorizationRequestMixin.java │ │ │ │ │ ├── OAuth2AuthorizedClientMixin.java │ │ │ │ │ ├── OAuth2ClientJackson2Module.java │ │ │ │ │ ├── OAuth2ErrorMixin.java │ │ │ │ │ ├── OAuth2RefreshTokenMixin.java │ │ │ │ │ ├── OAuth2UserAuthorityMixin.java │ │ │ │ │ ├── OidcIdTokenMixin.java │ │ │ │ │ ├── OidcUserAuthorityMixin.java │ │ │ │ │ ├── OidcUserInfoMixin.java │ │ │ │ │ ├── StdConverters.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── oidc/ │ │ │ │ │ ├── authentication/ │ │ │ │ │ │ ├── DefaultOidcIdTokenValidatorFactory.java │ │ │ │ │ │ ├── OidcAuthorizationCodeAuthenticationProvider.java │ │ │ │ │ │ ├── OidcAuthorizationCodeReactiveAuthenticationManager.java │ │ │ │ │ │ ├── OidcAuthorizedClientRefreshedEventListener.java │ │ │ │ │ │ ├── OidcIdTokenDecoderFactory.java │ │ │ │ │ │ ├── OidcIdTokenValidator.java │ │ │ │ │ │ ├── ReactiveOidcIdTokenDecoderFactory.java │ │ │ │ │ │ ├── event/ │ │ │ │ │ │ │ ├── OidcUserRefreshedEvent.java │ │ │ │ │ │ │ └── package-info.java │ │ │ │ │ │ ├── logout/ │ │ │ │ │ │ │ ├── LogoutTokenClaimAccessor.java │ │ │ │ │ │ │ ├── LogoutTokenClaimNames.java │ │ │ │ │ │ │ ├── OidcLogoutToken.java │ │ │ │ │ │ │ └── package-info.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ ├── server/ │ │ │ │ │ │ └── session/ │ │ │ │ │ │ ├── InMemoryReactiveOidcSessionRegistry.java │ │ │ │ │ │ ├── ReactiveOidcSessionRegistry.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ ├── session/ │ │ │ │ │ │ ├── InMemoryOidcSessionRegistry.java │ │ │ │ │ │ ├── OidcSessionInformation.java │ │ │ │ │ │ ├── OidcSessionRegistry.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ ├── userinfo/ │ │ │ │ │ │ ├── OidcReactiveOAuth2UserService.java │ │ │ │ │ │ ├── OidcUserRequest.java │ │ │ │ │ │ ├── OidcUserRequestUtils.java │ │ │ │ │ │ ├── OidcUserService.java │ │ │ │ │ │ ├── OidcUserSource.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ └── web/ │ │ │ │ │ ├── logout/ │ │ │ │ │ │ ├── OidcClientInitiatedLogoutSuccessHandler.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ └── server/ │ │ │ │ │ └── logout/ │ │ │ │ │ ├── OidcClientInitiatedServerLogoutSuccessHandler.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── package-info.java │ │ │ │ ├── registration/ │ │ │ │ │ ├── ClientRegistration.java │ │ │ │ │ ├── ClientRegistrationRepository.java │ │ │ │ │ ├── ClientRegistrations.java │ │ │ │ │ ├── InMemoryClientRegistrationRepository.java │ │ │ │ │ ├── InMemoryReactiveClientRegistrationRepository.java │ │ │ │ │ ├── ReactiveClientRegistrationRepository.java │ │ │ │ │ ├── SupplierClientRegistrationRepository.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── userinfo/ │ │ │ │ │ ├── DefaultOAuth2UserService.java │ │ │ │ │ ├── DefaultReactiveOAuth2UserService.java │ │ │ │ │ ├── DelegatingOAuth2UserService.java │ │ │ │ │ ├── OAuth2UserRequest.java │ │ │ │ │ ├── OAuth2UserRequestEntityConverter.java │ │ │ │ │ ├── OAuth2UserService.java │ │ │ │ │ ├── ReactiveOAuth2UserService.java │ │ │ │ │ └── package-info.java │ │ │ │ └── web/ │ │ │ │ ├── AuthenticatedPrincipalOAuth2AuthorizedClientRepository.java │ │ │ │ ├── AuthorizationRequestRepository.java │ │ │ │ ├── ClientAttributes.java │ │ │ │ ├── DefaultOAuth2AuthorizationRequestResolver.java │ │ │ │ ├── DefaultOAuth2AuthorizedClientManager.java │ │ │ │ ├── DefaultReactiveOAuth2AuthorizedClientManager.java │ │ │ │ ├── HttpSessionOAuth2AuthorizationRequestRepository.java │ │ │ │ ├── HttpSessionOAuth2AuthorizedClientRepository.java │ │ │ │ ├── InvalidClientRegistrationIdException.java │ │ │ │ ├── OAuth2AuthorizationCodeGrantFilter.java │ │ │ │ ├── OAuth2AuthorizationRequestCustomizers.java │ │ │ │ ├── OAuth2AuthorizationRequestRedirectFilter.java │ │ │ │ ├── OAuth2AuthorizationRequestResolver.java │ │ │ │ ├── OAuth2AuthorizationResponseUtils.java │ │ │ │ ├── OAuth2AuthorizedClientRepository.java │ │ │ │ ├── OAuth2LoginAuthenticationFilter.java │ │ │ │ ├── client/ │ │ │ │ │ ├── ClientRegistrationIdProcessor.java │ │ │ │ │ ├── OAuth2ClientHttpRequestInterceptor.java │ │ │ │ │ ├── RequestAttributeClientRegistrationIdResolver.java │ │ │ │ │ ├── RequestAttributePrincipalResolver.java │ │ │ │ │ ├── SecurityContextHolderPrincipalResolver.java │ │ │ │ │ ├── package-info.java │ │ │ │ │ └── support/ │ │ │ │ │ ├── OAuth2RestClientHttpServiceGroupConfigurer.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── method/ │ │ │ │ │ └── annotation/ │ │ │ │ │ ├── OAuth2AuthorizedClientArgumentResolver.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── package-info.java │ │ │ │ ├── reactive/ │ │ │ │ │ ├── function/ │ │ │ │ │ │ └── client/ │ │ │ │ │ │ ├── ServerOAuth2AuthorizedClientExchangeFilterFunction.java │ │ │ │ │ │ ├── ServletOAuth2AuthorizedClientExchangeFilterFunction.java │ │ │ │ │ │ ├── package-info.java │ │ │ │ │ │ └── support/ │ │ │ │ │ │ ├── OAuth2WebClientHttpServiceGroupConfigurer.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ └── result/ │ │ │ │ │ └── method/ │ │ │ │ │ └── annotation/ │ │ │ │ │ ├── OAuth2AuthorizedClientArgumentResolver.java │ │ │ │ │ └── package-info.java │ │ │ │ └── server/ │ │ │ │ ├── AuthenticatedPrincipalServerOAuth2AuthorizedClientRepository.java │ │ │ │ ├── DefaultServerOAuth2AuthorizationRequestResolver.java │ │ │ │ ├── OAuth2AuthorizationCodeGrantWebFilter.java │ │ │ │ ├── OAuth2AuthorizationRequestRedirectWebFilter.java │ │ │ │ ├── OAuth2AuthorizationResponseUtils.java │ │ │ │ ├── ServerAuthorizationRequestRepository.java │ │ │ │ ├── ServerOAuth2AuthorizationCodeAuthenticationTokenConverter.java │ │ │ │ ├── ServerOAuth2AuthorizationRequestResolver.java │ │ │ │ ├── ServerOAuth2AuthorizedClientRepository.java │ │ │ │ ├── WebSessionOAuth2ServerAuthorizationRequestRepository.java │ │ │ │ ├── WebSessionServerOAuth2AuthorizedClientRepository.java │ │ │ │ ├── authentication/ │ │ │ │ │ ├── OAuth2LoginAuthenticationWebFilter.java │ │ │ │ │ └── package-info.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ ├── META-INF/ │ │ │ │ └── spring/ │ │ │ │ └── aot.factories │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── security/ │ │ │ └── oauth2/ │ │ │ └── client/ │ │ │ ├── oauth2-client-schema-postgres.sql │ │ │ └── oauth2-client-schema.sql │ │ └── test/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── security/ │ │ │ └── oauth2/ │ │ │ └── client/ │ │ │ ├── AuthorizationCodeOAuth2AuthorizedClientProviderTests.java │ │ │ ├── AuthorizationCodeReactiveOAuth2AuthorizedClientProviderTests.java │ │ │ ├── AuthorizedClientServiceOAuth2AuthorizedClientManagerTests.java │ │ │ ├── AuthorizedClientServiceReactiveOAuth2AuthorizedClientManagerTests.java │ │ │ ├── ClientCredentialsOAuth2AuthorizedClientProviderTests.java │ │ │ ├── ClientCredentialsReactiveOAuth2AuthorizedClientProviderTests.java │ │ │ ├── DelegatingOAuth2AuthorizedClientProviderTests.java │ │ │ ├── DelegatingReactiveOAuth2AuthorizedClientProviderTests.java │ │ │ ├── InMemoryOAuth2AuthorizedClientServiceTests.java │ │ │ ├── InMemoryReactiveOAuth2AuthorizedClientServiceTests.java │ │ │ ├── JdbcOAuth2AuthorizedClientServiceTests.java │ │ │ ├── JwtBearerOAuth2AuthorizedClientProviderTests.java │ │ │ ├── JwtBearerReactiveOAuth2AuthorizedClientProviderTests.java │ │ │ ├── MockResponses.java │ │ │ ├── OAuth2AuthorizationContextTests.java │ │ │ ├── OAuth2AuthorizeRequestTests.java │ │ │ ├── OAuth2AuthorizedClientIdTests.java │ │ │ ├── OAuth2AuthorizedClientProviderBuilderTests.java │ │ │ ├── OAuth2AuthorizedClientTests.java │ │ │ ├── R2dbcReactiveOAuth2AuthorizedClientServiceTests.java │ │ │ ├── ReactiveOAuth2AuthorizedClientProviderBuilderTests.java │ │ │ ├── RefreshOidcUserReactiveOAuth2AuthorizationSuccessHandlerTests.java │ │ │ ├── RefreshTokenOAuth2AuthorizedClientProviderTests.java │ │ │ ├── RefreshTokenReactiveOAuth2AuthorizedClientProviderTests.java │ │ │ ├── TokenExchangeOAuth2AuthorizedClientProviderTests.java │ │ │ ├── TokenExchangeReactiveOAuth2AuthorizedClientProviderTests.java │ │ │ ├── aot/ │ │ │ │ └── hint/ │ │ │ │ └── OAuth2ClientRuntimeHintsTests.java │ │ │ ├── authentication/ │ │ │ │ ├── OAuth2AuthenticationTokenTests.java │ │ │ │ ├── OAuth2AuthorizationCodeAuthenticationProviderTests.java │ │ │ │ ├── OAuth2AuthorizationCodeAuthenticationTokenTests.java │ │ │ │ ├── OAuth2AuthorizationCodeReactiveAuthenticationManagerTests.java │ │ │ │ ├── OAuth2LoginAuthenticationProviderTests.java │ │ │ │ ├── OAuth2LoginAuthenticationTokenTests.java │ │ │ │ ├── OAuth2LoginReactiveAuthenticationManagerTests.java │ │ │ │ ├── TestOAuth2AuthenticationTokens.java │ │ │ │ └── TestOAuth2AuthorizationCodeAuthenticationTokens.java │ │ │ ├── endpoint/ │ │ │ │ ├── DefaultOAuth2TokenRequestHeadersConverterTests.java │ │ │ │ ├── DefaultOAuth2TokenRequestParametersConverterTests.java │ │ │ │ ├── JwtBearerGrantRequestTests.java │ │ │ │ ├── NimbusJwtClientAuthenticationParametersConverterTests.java │ │ │ │ ├── OAuth2AuthorizationCodeGrantRequestTests.java │ │ │ │ ├── OAuth2ClientCredentialsGrantRequestTests.java │ │ │ │ ├── OAuth2RefreshTokenGrantRequestTests.java │ │ │ │ ├── RestClientAuthorizationCodeTokenResponseClientTests.java │ │ │ │ ├── RestClientClientCredentialsTokenResponseClientTests.java │ │ │ │ ├── RestClientJwtBearerTokenResponseClientTests.java │ │ │ │ ├── RestClientRefreshTokenTokenResponseClientTests.java │ │ │ │ ├── RestClientTokenExchangeTokenResponseClientTests.java │ │ │ │ ├── TokenExchangeGrantRequestTests.java │ │ │ │ ├── WebClientReactiveAuthorizationCodeTokenResponseClientTests.java │ │ │ │ ├── WebClientReactiveClientCredentialsTokenResponseClientTests.java │ │ │ │ ├── WebClientReactiveJwtBearerTokenResponseClientTests.java │ │ │ │ ├── WebClientReactiveRefreshTokenTokenResponseClientTests.java │ │ │ │ └── WebClientReactiveTokenExchangeTokenResponseClientTests.java │ │ │ ├── http/ │ │ │ │ └── OAuth2ErrorResponseErrorHandlerTests.java │ │ │ ├── jackson/ │ │ │ │ ├── OAuth2AuthenticationExceptionMixinTests.java │ │ │ │ ├── OAuth2AuthenticationTokenMixinTests.java │ │ │ │ ├── OAuth2AuthorizationRequestMixinTests.java │ │ │ │ ├── OAuth2AuthorizedClientMixinTests.java │ │ │ │ └── StdConvertersTests.java │ │ │ ├── jackson2/ │ │ │ │ ├── OAuth2AuthenticationExceptionMixinTests.java │ │ │ │ ├── OAuth2AuthenticationTokenMixinTests.java │ │ │ │ ├── OAuth2AuthorizationRequestMixinTests.java │ │ │ │ ├── OAuth2AuthorizedClientMixinTests.java │ │ │ │ └── StdConvertersTests.java │ │ │ ├── oidc/ │ │ │ │ ├── authentication/ │ │ │ │ │ ├── OidcAuthorizationCodeAuthenticationProviderTests.java │ │ │ │ │ ├── OidcAuthorizationCodeReactiveAuthenticationManagerTests.java │ │ │ │ │ ├── OidcAuthorizedClientRefreshedEventListenerTests.java │ │ │ │ │ ├── OidcIdTokenDecoderFactoryTests.java │ │ │ │ │ ├── OidcIdTokenValidatorTests.java │ │ │ │ │ ├── ReactiveOidcIdTokenDecoderFactoryTests.java │ │ │ │ │ └── logout/ │ │ │ │ │ └── TestOidcLogoutTokens.java │ │ │ │ ├── session/ │ │ │ │ │ ├── InMemoryOidcSessionRegistryTests.java │ │ │ │ │ └── TestOidcSessionInformations.java │ │ │ │ ├── userinfo/ │ │ │ │ │ ├── OidcReactiveOAuth2UserServiceTests.java │ │ │ │ │ ├── OidcUserRequestTests.java │ │ │ │ │ ├── OidcUserRequestUtilsTests.java │ │ │ │ │ └── OidcUserServiceTests.java │ │ │ │ └── web/ │ │ │ │ ├── logout/ │ │ │ │ │ └── OidcClientInitiatedLogoutSuccessHandlerTests.java │ │ │ │ └── server/ │ │ │ │ └── logout/ │ │ │ │ └── OidcClientInitiatedServerLogoutSuccessHandlerTests.java │ │ │ ├── registration/ │ │ │ │ ├── ClientRegistrationTests.java │ │ │ │ ├── ClientRegistrationsTests.java │ │ │ │ ├── InMemoryClientRegistrationRepositoryTests.java │ │ │ │ ├── InMemoryReactiveClientRegistrationRepositoryTests.java │ │ │ │ ├── SupplierClientRegistrationRepositoryTests.java │ │ │ │ └── TestClientRegistrations.java │ │ │ ├── userinfo/ │ │ │ │ ├── DefaultOAuth2UserServiceTests.java │ │ │ │ ├── DefaultReactiveOAuth2UserServiceTests.java │ │ │ │ ├── DelegatingOAuth2UserServiceTests.java │ │ │ │ ├── OAuth2UserRequestEntityConverterTests.java │ │ │ │ └── OAuth2UserRequestTests.java │ │ │ └── web/ │ │ │ ├── AuthenticatedPrincipalOAuth2AuthorizedClientRepositoryTests.java │ │ │ ├── DefaultOAuth2AuthorizationRequestResolverTests.java │ │ │ ├── DefaultOAuth2AuthorizedClientManagerTests.java │ │ │ ├── DefaultReactiveOAuth2AuthorizedClientManagerTests.java │ │ │ ├── HttpSessionOAuth2AuthorizationRequestRepositoryTests.java │ │ │ ├── HttpSessionOAuth2AuthorizedClientRepositoryTests.java │ │ │ ├── OAuth2AuthorizationCodeGrantFilterTests.java │ │ │ ├── OAuth2AuthorizationRequestRedirectFilterTests.java │ │ │ ├── OAuth2LoginAuthenticationFilterTests.java │ │ │ ├── client/ │ │ │ │ ├── AbstractMockServerClientRegistrationIdProcessorTests.java │ │ │ │ ├── ClientRegistrationIdProcessorRestClientTests.java │ │ │ │ ├── ClientRegistrationIdProcessorTests.java │ │ │ │ ├── ClientRegistrationIdProcessorWebClientTests.java │ │ │ │ ├── OAuth2ClientHttpRequestInterceptorTests.java │ │ │ │ └── support/ │ │ │ │ └── OAuth2RestClientHttpServiceGroupConfigurerTests.java │ │ │ ├── method/ │ │ │ │ └── annotation/ │ │ │ │ └── OAuth2AuthorizedClientArgumentResolverTests.java │ │ │ ├── reactive/ │ │ │ │ ├── function/ │ │ │ │ │ └── client/ │ │ │ │ │ ├── MockExchangeFunction.java │ │ │ │ │ ├── ServerOAuth2AuthorizedClientExchangeFilterFunctionITests.java │ │ │ │ │ ├── ServerOAuth2AuthorizedClientExchangeFilterFunctionTests.java │ │ │ │ │ ├── ServletOAuth2AuthorizedClientExchangeFilterFunctionITests.java │ │ │ │ │ ├── ServletOAuth2AuthorizedClientExchangeFilterFunctionTests.java │ │ │ │ │ └── support/ │ │ │ │ │ └── OAuth2WebClientHttpServiceGroupConfigurerTests.java │ │ │ │ └── result/ │ │ │ │ └── method/ │ │ │ │ └── annotation/ │ │ │ │ └── OAuth2AuthorizedClientArgumentResolverTests.java │ │ │ └── server/ │ │ │ ├── AuthenticatedPrincipalServerOAuth2AuthorizedClientRepositoryTests.java │ │ │ ├── DefaultServerOAuth2AuthorizationRequestResolverTests.java │ │ │ ├── OAuth2AuthorizationCodeGrantWebFilterTests.java │ │ │ ├── OAuth2AuthorizationRequestRedirectWebFilterTests.java │ │ │ ├── ServerOAuth2AuthorizationCodeAuthenticationTokenConverterTests.java │ │ │ ├── WebSessionOAuth2ServerAuthorizationRequestRepositoryTests.java │ │ │ ├── WebSessionServerOAuth2AuthorizedClientRepositoryTests.java │ │ │ └── authentication/ │ │ │ └── OAuth2LoginAuthenticationWebFilterTests.java │ │ └── resources/ │ │ ├── access-token-response-1.json │ │ ├── access-token-response-create.json │ │ ├── access-token-response-openid-profile-2.json │ │ ├── access-token-response-openid-profile.json │ │ ├── access-token-response-read-write.json │ │ ├── access-token-response-read.json │ │ ├── access-token-response.json │ │ ├── custom-oauth2-client-schema.sql │ │ ├── invalid-grant-response.json │ │ ├── invalid-token-type-response.json │ │ ├── logback-test.xml │ │ ├── server-error-response.json │ │ └── unauthorized-client-response.json │ ├── oauth2-core/ │ │ ├── spring-security-oauth2-core.gradle │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── security/ │ │ │ └── oauth2/ │ │ │ └── core/ │ │ │ ├── AbstractOAuth2Token.java │ │ │ ├── AuthenticationMethod.java │ │ │ ├── AuthorizationGrantType.java │ │ │ ├── ClaimAccessor.java │ │ │ ├── ClientAuthenticationMethod.java │ │ │ ├── DefaultOAuth2AuthenticatedPrincipal.java │ │ │ ├── DelegatingOAuth2TokenValidator.java │ │ │ ├── OAuth2AccessToken.java │ │ │ ├── OAuth2AuthenticatedPrincipal.java │ │ │ ├── OAuth2AuthenticationException.java │ │ │ ├── OAuth2AuthorizationException.java │ │ │ ├── OAuth2DeviceCode.java │ │ │ ├── OAuth2Error.java │ │ │ ├── OAuth2ErrorCodes.java │ │ │ ├── OAuth2RefreshToken.java │ │ │ ├── OAuth2Token.java │ │ │ ├── OAuth2TokenIntrospectionClaimAccessor.java │ │ │ ├── OAuth2TokenIntrospectionClaimNames.java │ │ │ ├── OAuth2TokenValidator.java │ │ │ ├── OAuth2TokenValidatorResult.java │ │ │ ├── OAuth2UserCode.java │ │ │ ├── authorization/ │ │ │ │ ├── DefaultOAuth2AuthorizationManagerFactory.java │ │ │ │ ├── OAuth2AuthorizationManagerFactory.java │ │ │ │ ├── OAuth2AuthorizationManagers.java │ │ │ │ ├── OAuth2ReactiveAuthorizationManagers.java │ │ │ │ └── package-info.java │ │ │ ├── converter/ │ │ │ │ ├── ClaimConversionService.java │ │ │ │ ├── ClaimTypeConverter.java │ │ │ │ ├── ObjectToBooleanConverter.java │ │ │ │ ├── ObjectToInstantConverter.java │ │ │ │ ├── ObjectToListStringConverter.java │ │ │ │ ├── ObjectToMapStringObjectConverter.java │ │ │ │ ├── ObjectToStringConverter.java │ │ │ │ ├── ObjectToURLConverter.java │ │ │ │ └── package-info.java │ │ │ ├── endpoint/ │ │ │ │ ├── DefaultMapOAuth2AccessTokenResponseConverter.java │ │ │ │ ├── DefaultOAuth2AccessTokenResponseMapConverter.java │ │ │ │ ├── OAuth2AccessTokenResponse.java │ │ │ │ ├── OAuth2AuthorizationExchange.java │ │ │ │ ├── OAuth2AuthorizationRequest.java │ │ │ │ ├── OAuth2AuthorizationResponse.java │ │ │ │ ├── OAuth2AuthorizationResponseType.java │ │ │ │ ├── OAuth2DeviceAuthorizationResponse.java │ │ │ │ ├── OAuth2ParameterNames.java │ │ │ │ ├── PkceParameterNames.java │ │ │ │ └── package-info.java │ │ │ ├── http/ │ │ │ │ ├── converter/ │ │ │ │ │ ├── GenericHttpMessageConverterAdapter.java │ │ │ │ │ ├── HttpMessageConverters.java │ │ │ │ │ ├── OAuth2AccessTokenResponseHttpMessageConverter.java │ │ │ │ │ ├── OAuth2DeviceAuthorizationResponseHttpMessageConverter.java │ │ │ │ │ ├── OAuth2ErrorHttpMessageConverter.java │ │ │ │ │ └── package-info.java │ │ │ │ └── package-info.java │ │ │ ├── oidc/ │ │ │ │ ├── AddressStandardClaim.java │ │ │ │ ├── DefaultAddressStandardClaim.java │ │ │ │ ├── IdTokenClaimAccessor.java │ │ │ │ ├── IdTokenClaimNames.java │ │ │ │ ├── OidcIdToken.java │ │ │ │ ├── OidcScopes.java │ │ │ │ ├── OidcUserInfo.java │ │ │ │ ├── StandardClaimAccessor.java │ │ │ │ ├── StandardClaimNames.java │ │ │ │ ├── endpoint/ │ │ │ │ │ ├── OidcParameterNames.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── package-info.java │ │ │ │ └── user/ │ │ │ │ ├── DefaultOidcUser.java │ │ │ │ ├── OidcUser.java │ │ │ │ ├── OidcUserAuthority.java │ │ │ │ └── package-info.java │ │ │ ├── package-info.java │ │ │ ├── user/ │ │ │ │ ├── DefaultOAuth2User.java │ │ │ │ ├── OAuth2User.java │ │ │ │ ├── OAuth2UserAuthority.java │ │ │ │ └── package-info.java │ │ │ └── web/ │ │ │ ├── package-info.java │ │ │ └── reactive/ │ │ │ ├── function/ │ │ │ │ ├── OAuth2AccessTokenResponseBodyExtractor.java │ │ │ │ ├── OAuth2BodyExtractors.java │ │ │ │ └── package-info.java │ │ │ └── package-info.java │ │ └── test/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── security/ │ │ └── oauth2/ │ │ └── core/ │ │ ├── AuthenticationMethodTests.java │ │ ├── AuthorizationGrantTypeTests.java │ │ ├── ClaimAccessorTests.java │ │ ├── ClientAuthenticationMethodTests.java │ │ ├── DefaultOAuth2AuthenticatedPrincipalTests.java │ │ ├── DelegatingOAuth2TokenValidatorTests.java │ │ ├── OAuth2AccessTokenTests.java │ │ ├── OAuth2ErrorTests.java │ │ ├── OAuth2RefreshTokenTests.java │ │ ├── OAuth2TokenIntrospectionClaimAccessorTests.java │ │ ├── OAuth2TokenValidatorResultTests.java │ │ ├── TestOAuth2AccessTokens.java │ │ ├── TestOAuth2RefreshTokens.java │ │ ├── authorization/ │ │ │ ├── OAuth2AuthorizationManagerFactoryTests.java │ │ │ ├── OAuth2AuthorizationManagersTests.java │ │ │ └── OAuth2ReactiveAuthorizationManagersTests.java │ │ ├── converter/ │ │ │ ├── ClaimConversionServiceTests.java │ │ │ └── ClaimTypeConverterTests.java │ │ ├── endpoint/ │ │ │ ├── DefaultMapOAuth2AccessTokenResponseConverterTests.java │ │ │ ├── DefaultOAuth2AccessTokenResponseMapConverterTests.java │ │ │ ├── OAuth2AccessTokenResponseTests.java │ │ │ ├── OAuth2AuthorizationExchangeTests.java │ │ │ ├── OAuth2AuthorizationRequestTests.java │ │ │ ├── OAuth2AuthorizationResponseTests.java │ │ │ ├── OAuth2AuthorizationResponseTypeTests.java │ │ │ ├── TestOAuth2AccessTokenResponses.java │ │ │ ├── TestOAuth2AuthorizationExchanges.java │ │ │ ├── TestOAuth2AuthorizationRequests.java │ │ │ ├── TestOAuth2AuthorizationResponses.java │ │ │ └── TestOidcAuthorizationRequest.java │ │ ├── http/ │ │ │ └── converter/ │ │ │ ├── OAuth2AccessTokenResponseHttpMessageConverterTests.java │ │ │ ├── OAuth2DeviceAuthorizationResponseHttpMessageConverterTests.java │ │ │ └── OAuth2ErrorHttpMessageConverterTests.java │ │ ├── oidc/ │ │ │ ├── DefaultAddressStandardClaimTests.java │ │ │ ├── OidcIdTokenBuilderTests.java │ │ │ ├── OidcIdTokenTests.java │ │ │ ├── OidcUserInfoBuilderTests.java │ │ │ ├── OidcUserInfoTests.java │ │ │ ├── TestOidcIdTokens.java │ │ │ └── user/ │ │ │ ├── DefaultOidcUserTests.java │ │ │ ├── OidcUserAuthorityTests.java │ │ │ └── TestOidcUsers.java │ │ ├── user/ │ │ │ ├── DefaultOAuth2UserTests.java │ │ │ ├── OAuth2UserAuthorityTests.java │ │ │ └── TestOAuth2Users.java │ │ └── web/ │ │ └── reactive/ │ │ └── function/ │ │ └── OAuth2BodyExtractorsTests.java │ ├── oauth2-jose/ │ │ ├── spring-security-oauth2-jose.gradle │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── security/ │ │ │ └── oauth2/ │ │ │ ├── jose/ │ │ │ │ ├── JwaAlgorithm.java │ │ │ │ ├── jws/ │ │ │ │ │ ├── JwsAlgorithm.java │ │ │ │ │ ├── JwsAlgorithms.java │ │ │ │ │ ├── MacAlgorithm.java │ │ │ │ │ ├── SignatureAlgorithm.java │ │ │ │ │ └── package-info.java │ │ │ │ └── package-info.java │ │ │ └── jwt/ │ │ │ ├── BadJwtException.java │ │ │ ├── DPoPProofContext.java │ │ │ ├── DPoPProofJwtDecoderFactory.java │ │ │ ├── JWKS.java │ │ │ ├── JoseHeader.java │ │ │ ├── JoseHeaderNames.java │ │ │ ├── JwsHeader.java │ │ │ ├── Jwt.java │ │ │ ├── JwtAudienceValidator.java │ │ │ ├── JwtClaimAccessor.java │ │ │ ├── JwtClaimNames.java │ │ │ ├── JwtClaimValidator.java │ │ │ ├── JwtClaimsSet.java │ │ │ ├── JwtDecoder.java │ │ │ ├── JwtDecoderFactory.java │ │ │ ├── JwtDecoderInitializationException.java │ │ │ ├── JwtDecoderProviderConfigurationUtils.java │ │ │ ├── JwtDecoders.java │ │ │ ├── JwtEncoder.java │ │ │ ├── JwtEncoderParameters.java │ │ │ ├── JwtEncodingException.java │ │ │ ├── JwtException.java │ │ │ ├── JwtIssuedAtValidator.java │ │ │ ├── JwtIssuerValidator.java │ │ │ ├── JwtTimestampValidator.java │ │ │ ├── JwtTypeValidator.java │ │ │ ├── JwtValidationException.java │ │ │ ├── JwtValidators.java │ │ │ ├── MappedJwtClaimSetConverter.java │ │ │ ├── NimbusJwtDecoder.java │ │ │ ├── NimbusJwtEncoder.java │ │ │ ├── NimbusReactiveJwtDecoder.java │ │ │ ├── ReactiveJWKSource.java │ │ │ ├── ReactiveJWKSourceAdapter.java │ │ │ ├── ReactiveJwtDecoder.java │ │ │ ├── ReactiveJwtDecoderFactory.java │ │ │ ├── ReactiveJwtDecoderProviderConfigurationUtils.java │ │ │ ├── ReactiveJwtDecoders.java │ │ │ ├── ReactiveRemoteJWKSource.java │ │ │ ├── SupplierJwtDecoder.java │ │ │ ├── SupplierReactiveJwtDecoder.java │ │ │ ├── X509CertificateThumbprintValidator.java │ │ │ └── package-info.java │ │ └── test/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── security/ │ │ └── oauth2/ │ │ ├── jose/ │ │ │ ├── TestJwks.java │ │ │ ├── TestKeys.java │ │ │ ├── TestX509Certificates.java │ │ │ ├── X509CertificateUtils.java │ │ │ └── jws/ │ │ │ ├── MacAlgorithmTests.java │ │ │ └── SignatureAlgorithmTests.java │ │ └── jwt/ │ │ ├── DPoPProofJwtDecoderFactoryTests.java │ │ ├── JwsHeaderTests.java │ │ ├── JwtAudienceValidatorTests.java │ │ ├── JwtBuilderTests.java │ │ ├── JwtClaimValidatorTests.java │ │ ├── JwtClaimsSetTests.java │ │ ├── JwtDecoderProviderConfigurationUtilsTests.java │ │ ├── JwtDecodersTests.java │ │ ├── JwtIssuerValidatorTests.java │ │ ├── JwtTests.java │ │ ├── JwtTimestampValidatorTests.java │ │ ├── JwtTypeValidatorTests.java │ │ ├── JwtValidatorsTests.java │ │ ├── MappedJwtClaimSetConverterTests.java │ │ ├── NimbusJweEncoderTests.java │ │ ├── NimbusJwtDecoderTests.java │ │ ├── NimbusJwtEncoderDecoderTests.java │ │ ├── NimbusJwtEncoderTests.java │ │ ├── NimbusReactiveJwtDecoderTests.java │ │ ├── ReactiveJwtDecoderProviderConfigurationUtilsTests.java │ │ ├── ReactiveJwtDecodersTests.java │ │ ├── ReactiveRemoteJWKSourceTests.java │ │ ├── SupplierJwtDecoderTests.java │ │ ├── SupplierReactiveJwtDecoderTests.java │ │ ├── TestJwsHeaders.java │ │ ├── TestJwtClaimsSets.java │ │ ├── TestJwts.java │ │ └── X509CertificateThumbprintValidatorTests.java │ └── oauth2-resource-server/ │ ├── spring-security-oauth2-resource-server.gradle │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── security/ │ │ └── oauth2/ │ │ └── server/ │ │ └── resource/ │ │ ├── BearerTokenError.java │ │ ├── BearerTokenErrorCodes.java │ │ ├── BearerTokenErrors.java │ │ ├── InvalidBearerTokenException.java │ │ ├── OAuth2ProtectedResourceMetadata.java │ │ ├── OAuth2ProtectedResourceMetadataClaimAccessor.java │ │ ├── OAuth2ProtectedResourceMetadataClaimNames.java │ │ ├── authentication/ │ │ │ ├── AbstractOAuth2TokenAuthenticationToken.java │ │ │ ├── BearerTokenAuthentication.java │ │ │ ├── BearerTokenAuthenticationToken.java │ │ │ ├── DPoPAuthenticationProvider.java │ │ │ ├── DPoPAuthenticationToken.java │ │ │ ├── DelegatingJwtGrantedAuthoritiesConverter.java │ │ │ ├── ExpressionJwtGrantedAuthoritiesConverter.java │ │ │ ├── JwtAuthenticationConverter.java │ │ │ ├── JwtAuthenticationProvider.java │ │ │ ├── JwtAuthenticationToken.java │ │ │ ├── JwtBearerTokenAuthenticationConverter.java │ │ │ ├── JwtGrantedAuthoritiesConverter.java │ │ │ ├── JwtIssuerAuthenticationManagerResolver.java │ │ │ ├── JwtIssuerReactiveAuthenticationManagerResolver.java │ │ │ ├── JwtReactiveAuthenticationManager.java │ │ │ ├── OpaqueTokenAuthenticationProvider.java │ │ │ ├── OpaqueTokenReactiveAuthenticationManager.java │ │ │ ├── ReactiveJwtAuthenticationConverter.java │ │ │ ├── ReactiveJwtAuthenticationConverterAdapter.java │ │ │ ├── ReactiveJwtGrantedAuthoritiesConverterAdapter.java │ │ │ └── package-info.java │ │ ├── introspection/ │ │ │ ├── BadOpaqueTokenException.java │ │ │ ├── OAuth2IntrospectionAuthenticatedPrincipal.java │ │ │ ├── OAuth2IntrospectionException.java │ │ │ ├── OpaqueTokenAuthenticationConverter.java │ │ │ ├── OpaqueTokenIntrospector.java │ │ │ ├── ReactiveOpaqueTokenAuthenticationConverter.java │ │ │ ├── ReactiveOpaqueTokenIntrospector.java │ │ │ ├── RestClientOpaqueTokenIntrospector.java │ │ │ ├── SpringOpaqueTokenIntrospector.java │ │ │ ├── SpringReactiveOpaqueTokenIntrospector.java │ │ │ └── package-info.java │ │ ├── package-info.java │ │ └── web/ │ │ ├── BearerTokenAuthenticationEntryPoint.java │ │ ├── BearerTokenResolver.java │ │ ├── DefaultBearerTokenResolver.java │ │ ├── GenericHttpMessageConverterAdapter.java │ │ ├── HeaderBearerTokenResolver.java │ │ ├── OAuth2ProtectedResourceMetadataFilter.java │ │ ├── access/ │ │ │ ├── BearerTokenAccessDeniedHandler.java │ │ │ ├── package-info.java │ │ │ └── server/ │ │ │ ├── BearerTokenServerAccessDeniedHandler.java │ │ │ └── package-info.java │ │ ├── authentication/ │ │ │ ├── BearerTokenAuthenticationConverter.java │ │ │ ├── BearerTokenAuthenticationFilter.java │ │ │ └── package-info.java │ │ ├── package-info.java │ │ ├── reactive/ │ │ │ └── function/ │ │ │ └── client/ │ │ │ ├── ServerBearerExchangeFilterFunction.java │ │ │ ├── ServletBearerExchangeFilterFunction.java │ │ │ └── package-info.java │ │ └── server/ │ │ ├── BearerTokenServerAuthenticationEntryPoint.java │ │ ├── authentication/ │ │ │ ├── ServerBearerTokenAuthenticationConverter.java │ │ │ └── package-info.java │ │ └── package-info.java │ └── test/ │ └── java/ │ └── org/ │ └── springframework/ │ └── security/ │ └── oauth2/ │ ├── core/ │ │ └── TestOAuth2AuthenticatedPrincipals.java │ └── server/ │ └── resource/ │ ├── BearerTokenErrorTests.java │ ├── BearerTokenErrorsTests.java │ ├── DefaultAuthenticationEventPublisherBearerTokenTests.java │ ├── authentication/ │ │ ├── BearerTokenAuthenticationTests.java │ │ ├── BearerTokenAuthenticationTokenTests.java │ │ ├── DPoPAuthenticationProviderTests.java │ │ ├── DelegatingJwtGrantedAuthoritiesConverterTests.java │ │ ├── ExpressionJwtGrantedAuthoritiesConverterTests.java │ │ ├── JwtAuthenticationConverterTests.java │ │ ├── JwtAuthenticationProviderTests.java │ │ ├── JwtAuthenticationTokenTests.java │ │ ├── JwtBearerTokenAuthenticationConverterTests.java │ │ ├── JwtGrantedAuthoritiesConverterTests.java │ │ ├── JwtIssuerAuthenticationManagerResolverTests.java │ │ ├── JwtIssuerReactiveAuthenticationManagerResolverTests.java │ │ ├── JwtReactiveAuthenticationManagerTests.java │ │ ├── OpaqueTokenAuthenticationProviderTests.java │ │ ├── OpaqueTokenReactiveAuthenticationManagerTests.java │ │ ├── ReactiveJwtAuthenticationConverterAdapterTests.java │ │ ├── ReactiveJwtAuthenticationConverterTests.java │ │ ├── ReactiveJwtGrantedAuthoritiesConverterAdapterTests.java │ │ └── TestBearerTokenAuthentications.java │ ├── introspection/ │ │ ├── OAuth2IntrospectionAuthenticatedPrincipalTests.java │ │ ├── RestClientOpaqueTokenIntrospectorTests.java │ │ ├── SpringOpaqueTokenIntrospectorTests.java │ │ └── SpringReactiveOpaqueTokenIntrospectorTests.java │ └── web/ │ ├── BearerTokenAuthenticationEntryPointTests.java │ ├── DefaultBearerTokenResolverTests.java │ ├── HeaderBearerTokenResolverTests.java │ ├── MockExchangeFunction.java │ ├── OAuth2ProtectedResourceMetadataFilterTests.java │ ├── access/ │ │ ├── BearerTokenAccessDeniedHandlerTests.java │ │ └── server/ │ │ └── BearerTokenServerAccessDeniedHandlerTests.java │ ├── authentication/ │ │ ├── BearerTokenAuthenticationConverterTests.java │ │ └── BearerTokenAuthenticationFilterTests.java │ ├── reactive/ │ │ └── function/ │ │ └── client/ │ │ ├── ServerBearerExchangeFilterFunctionTests.java │ │ └── ServletBearerExchangeFilterFunctionTests.java │ └── server/ │ ├── BearerTokenServerAuthenticationEntryPointTests.java │ └── authentication/ │ └── ServerBearerTokenAuthenticationConverterTests.java ├── rsocket/ │ ├── spring-security-rsocket.gradle │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── security/ │ │ └── rsocket/ │ │ ├── api/ │ │ │ ├── PayloadExchange.java │ │ │ ├── PayloadExchangeType.java │ │ │ ├── PayloadInterceptor.java │ │ │ ├── PayloadInterceptorChain.java │ │ │ └── package-info.java │ │ ├── authentication/ │ │ │ ├── AnonymousPayloadInterceptor.java │ │ │ ├── AuthenticationPayloadExchangeConverter.java │ │ │ ├── AuthenticationPayloadInterceptor.java │ │ │ ├── BasicAuthenticationPayloadExchangeConverter.java │ │ │ ├── BearerPayloadExchangeConverter.java │ │ │ ├── PayloadExchangeAuthenticationConverter.java │ │ │ └── package-info.java │ │ ├── authorization/ │ │ │ ├── AuthorizationPayloadInterceptor.java │ │ │ ├── PayloadExchangeMatcherReactiveAuthorizationManager.java │ │ │ └── package-info.java │ │ ├── core/ │ │ │ ├── ContextPayloadInterceptorChain.java │ │ │ ├── DefaultPayloadExchange.java │ │ │ ├── PayloadInterceptorRSocket.java │ │ │ ├── PayloadSocketAcceptor.java │ │ │ ├── PayloadSocketAcceptorInterceptor.java │ │ │ ├── SecuritySocketAcceptorInterceptor.java │ │ │ └── package-info.java │ │ ├── metadata/ │ │ │ ├── BasicAuthenticationDecoder.java │ │ │ ├── BasicAuthenticationEncoder.java │ │ │ ├── BearerTokenAuthenticationEncoder.java │ │ │ ├── BearerTokenMetadata.java │ │ │ ├── SimpleAuthenticationEncoder.java │ │ │ ├── UsernamePasswordMetadata.java │ │ │ └── package-info.java │ │ └── util/ │ │ └── matcher/ │ │ ├── PayloadExchangeAuthorizationContext.java │ │ ├── PayloadExchangeMatcher.java │ │ ├── PayloadExchangeMatcherEntry.java │ │ ├── PayloadExchangeMatchers.java │ │ ├── RoutePayloadExchangeMatcher.java │ │ └── package-info.java │ └── test/ │ └── java/ │ └── org/ │ └── springframework/ │ └── security/ │ └── rsocket/ │ ├── authentication/ │ │ ├── AnonymousPayloadInterceptorTests.java │ │ ├── AuthenticationPayloadInterceptorChain.java │ │ └── AuthenticationPayloadInterceptorTests.java │ ├── authorization/ │ │ ├── AuthorizationPayloadInterceptorTests.java │ │ └── PayloadExchangeMatcherReactiveAuthorizationManagerTests.java │ ├── core/ │ │ ├── CaptureSecurityContextSocketAcceptor.java │ │ ├── PayloadInterceptorRSocketTests.java │ │ ├── PayloadSocketAcceptorInterceptorTests.java │ │ └── PayloadSocketAcceptorTests.java │ ├── metadata/ │ │ └── BasicAuthenticationDecoderTests.java │ └── util/ │ └── matcher/ │ └── RoutePayloadExchangeMatcherTests.java ├── saml2/ │ └── saml2-service-provider/ │ ├── spring-security-saml2-service-provider.gradle │ └── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── security/ │ │ │ └── saml2/ │ │ │ ├── Saml2Exception.java │ │ │ ├── aot/ │ │ │ │ └── hint/ │ │ │ │ ├── Saml2RuntimeHints.java │ │ │ │ └── package-info.java │ │ │ ├── core/ │ │ │ │ ├── OpenSamlInitializationService.java │ │ │ │ ├── Saml2Error.java │ │ │ │ ├── Saml2ErrorCodes.java │ │ │ │ ├── Saml2ParameterNames.java │ │ │ │ ├── Saml2ResponseValidatorResult.java │ │ │ │ ├── Saml2X509Credential.java │ │ │ │ └── package-info.java │ │ │ ├── internal/ │ │ │ │ ├── OpenSamlOperations.java │ │ │ │ ├── Saml2Utils.java │ │ │ │ └── package-info.java │ │ │ ├── jackson/ │ │ │ │ ├── DefaultSaml2AuthenticatedPrincipalMixin.java │ │ │ │ ├── Saml2AssertionAuthenticationMixin.java │ │ │ │ ├── Saml2AuthenticationExceptionMixin.java │ │ │ │ ├── Saml2AuthenticationMixin.java │ │ │ │ ├── Saml2ErrorMixin.java │ │ │ │ ├── Saml2JacksonModule.java │ │ │ │ ├── Saml2LogoutRequestMixin.java │ │ │ │ ├── Saml2PostAuthenticationRequestMixin.java │ │ │ │ ├── Saml2RedirectAuthenticationRequestMixin.java │ │ │ │ ├── SimpleSaml2ResponseAssertionAccessorMixin.java │ │ │ │ └── package-info.java │ │ │ ├── jackson2/ │ │ │ │ ├── DefaultSaml2AuthenticatedPrincipalMixin.java │ │ │ │ ├── Saml2AssertionAuthenticationMixin.java │ │ │ │ ├── Saml2AuthenticationExceptionMixin.java │ │ │ │ ├── Saml2AuthenticationMixin.java │ │ │ │ ├── Saml2ErrorMixin.java │ │ │ │ ├── Saml2Jackson2Module.java │ │ │ │ ├── Saml2LogoutRequestMixin.java │ │ │ │ ├── Saml2PostAuthenticationRequestMixin.java │ │ │ │ ├── Saml2RedirectAuthenticationRequestMixin.java │ │ │ │ ├── SimpleSaml2ResponseAssertionAccessorMixin.java │ │ │ │ └── package-info.java │ │ │ ├── package-info.java │ │ │ └── provider/ │ │ │ └── service/ │ │ │ ├── authentication/ │ │ │ │ ├── AbstractSaml2AuthenticationRequest.java │ │ │ │ ├── BaseOpenSamlAuthenticationProvider.java │ │ │ │ ├── DefaultSaml2AuthenticatedPrincipal.java │ │ │ │ ├── OpenSamlOperations.java │ │ │ │ ├── Saml2AssertionAuthentication.java │ │ │ │ ├── Saml2AuthenticatedPrincipal.java │ │ │ │ ├── Saml2Authentication.java │ │ │ │ ├── Saml2AuthenticationException.java │ │ │ │ ├── Saml2AuthenticationToken.java │ │ │ │ ├── Saml2PostAuthenticationRequest.java │ │ │ │ ├── Saml2RedirectAuthenticationRequest.java │ │ │ │ ├── Saml2ResponseAssertion.java │ │ │ │ ├── Saml2ResponseAssertionAccessor.java │ │ │ │ ├── Saml2Utils.java │ │ │ │ ├── logout/ │ │ │ │ │ ├── BaseOpenSamlLogoutRequestValidator.java │ │ │ │ │ ├── BaseOpenSamlLogoutResponseValidator.java │ │ │ │ │ ├── OpenSamlOperations.java │ │ │ │ │ ├── Saml2LogoutRequest.java │ │ │ │ │ ├── Saml2LogoutRequestValidator.java │ │ │ │ │ ├── Saml2LogoutRequestValidatorParameters.java │ │ │ │ │ ├── Saml2LogoutResponse.java │ │ │ │ │ ├── Saml2LogoutResponseValidator.java │ │ │ │ │ ├── Saml2LogoutResponseValidatorParameters.java │ │ │ │ │ ├── Saml2LogoutValidatorResult.java │ │ │ │ │ ├── Saml2Utils.java │ │ │ │ │ └── package-info.java │ │ │ │ └── package-info.java │ │ │ ├── metadata/ │ │ │ │ ├── BaseOpenSamlMetadataResolver.java │ │ │ │ ├── OpenSamlOperations.java │ │ │ │ ├── RequestMatcherMetadataResponseResolver.java │ │ │ │ ├── Saml2MetadataResolver.java │ │ │ │ ├── Saml2MetadataResponse.java │ │ │ │ ├── Saml2MetadataResponseResolver.java │ │ │ │ ├── Saml2Utils.java │ │ │ │ └── package-info.java │ │ │ ├── registration/ │ │ │ │ ├── AssertingPartyMetadata.java │ │ │ │ ├── AssertingPartyMetadataRepository.java │ │ │ │ ├── BaseOpenSamlAssertingPartyMetadataRepository.java │ │ │ │ ├── CachingRelyingPartyRegistrationRepository.java │ │ │ │ ├── InMemoryRelyingPartyRegistrationRepository.java │ │ │ │ ├── IterableRelyingPartyRegistrationRepository.java │ │ │ │ ├── JdbcAssertingPartyMetadataRepository.java │ │ │ │ ├── OpenSamlAssertingPartyDetails.java │ │ │ │ ├── OpenSamlMetadataUtils.java │ │ │ │ ├── OpenSamlOperations.java │ │ │ │ ├── OpenSamlRelyingPartyRegistrationBuilderHttpMessageConverter.java │ │ │ │ ├── RelyingPartyRegistration.java │ │ │ │ ├── RelyingPartyRegistrationRepository.java │ │ │ │ ├── RelyingPartyRegistrations.java │ │ │ │ ├── Saml2MessageBinding.java │ │ │ │ ├── Saml2Utils.java │ │ │ │ └── package-info.java │ │ │ └── web/ │ │ │ ├── BaseOpenSamlAuthenticationTokenConverter.java │ │ │ ├── CacheSaml2AuthenticationRequestRepository.java │ │ │ ├── DefaultRelyingPartyRegistrationResolver.java │ │ │ ├── HttpSessionSaml2AuthenticationRequestRepository.java │ │ │ ├── OpenSamlOperations.java │ │ │ ├── RelyingPartyRegistrationPlaceholderResolvers.java │ │ │ ├── RelyingPartyRegistrationResolver.java │ │ │ ├── Saml2AuthenticationRequestRepository.java │ │ │ ├── Saml2AuthenticationTokenConverter.java │ │ │ ├── Saml2MetadataFilter.java │ │ │ ├── Saml2Utils.java │ │ │ ├── Saml2WebSsoAuthenticationRequestFilter.java │ │ │ ├── authentication/ │ │ │ │ ├── BaseOpenSamlAuthenticationRequestResolver.java │ │ │ │ ├── OpenSamlOperations.java │ │ │ │ ├── Saml2AuthenticationRequestResolver.java │ │ │ │ ├── Saml2Utils.java │ │ │ │ ├── Saml2WebSsoAuthenticationFilter.java │ │ │ │ ├── logout/ │ │ │ │ │ ├── BaseOpenSamlLogoutRequestResolver.java │ │ │ │ │ ├── BaseOpenSamlLogoutRequestValidatorParametersResolver.java │ │ │ │ │ ├── BaseOpenSamlLogoutResponseResolver.java │ │ │ │ │ ├── HttpSessionLogoutRequestRepository.java │ │ │ │ │ ├── OpenSamlOperations.java │ │ │ │ │ ├── Saml2LogoutRequestFilter.java │ │ │ │ │ ├── Saml2LogoutRequestRepository.java │ │ │ │ │ ├── Saml2LogoutRequestResolver.java │ │ │ │ │ ├── Saml2LogoutRequestValidatorParametersResolver.java │ │ │ │ │ ├── Saml2LogoutResponseFilter.java │ │ │ │ │ ├── Saml2LogoutResponseResolver.java │ │ │ │ │ ├── Saml2MessageBindingUtils.java │ │ │ │ │ ├── Saml2RelyingPartyInitiatedLogoutSuccessHandler.java │ │ │ │ │ ├── Saml2Utils.java │ │ │ │ │ └── package-info.java │ │ │ │ └── package-info.java │ │ │ ├── metadata/ │ │ │ │ ├── RequestMatcherMetadataResponseResolver.java │ │ │ │ └── package-info.java │ │ │ └── package-info.java │ │ └── resources/ │ │ ├── META-INF/ │ │ │ └── spring/ │ │ │ └── aot.factories │ │ └── org/ │ │ └── springframework/ │ │ └── security/ │ │ └── saml2/ │ │ ├── saml2-asserting-party-metadata-schema-postgres.sql │ │ └── saml2-asserting-party-metadata-schema.sql │ ├── opensaml5Main/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── security/ │ │ └── saml2/ │ │ ├── internal/ │ │ │ └── OpenSaml5Template.java │ │ └── provider/ │ │ └── service/ │ │ ├── authentication/ │ │ │ ├── OpenSaml5AuthenticationProvider.java │ │ │ ├── OpenSaml5Template.java │ │ │ └── logout/ │ │ │ ├── OpenSaml5LogoutRequestValidator.java │ │ │ ├── OpenSaml5LogoutResponseValidator.java │ │ │ ├── OpenSaml5Template.java │ │ │ └── Saml2Utils.java │ │ ├── metadata/ │ │ │ ├── OpenSaml5MetadataResolver.java │ │ │ └── OpenSaml5Template.java │ │ ├── registration/ │ │ │ ├── OpenSaml5AssertingPartyMetadataRepository.java │ │ │ └── OpenSaml5Template.java │ │ └── web/ │ │ ├── OpenSaml5AuthenticationTokenConverter.java │ │ ├── OpenSaml5Template.java │ │ └── authentication/ │ │ ├── OpenSaml5AuthenticationRequestResolver.java │ │ ├── OpenSaml5Template.java │ │ └── logout/ │ │ ├── OpenSaml5LogoutRequestResolver.java │ │ ├── OpenSaml5LogoutRequestValidatorParametersResolver.java │ │ ├── OpenSaml5LogoutResponseResolver.java │ │ └── OpenSaml5Template.java │ ├── opensaml5Test/ │ │ └── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── security/ │ │ └── saml2/ │ │ └── provider/ │ │ └── service/ │ │ ├── authentication/ │ │ │ ├── OpenSaml5AuthenticationProviderTests.java │ │ │ ├── TestCustomOpenSaml5Objects.java │ │ │ └── logout/ │ │ │ ├── OpenSaml5LogoutRequestValidatorTests.java │ │ │ └── OpenSaml5LogoutResponseValidatorTests.java │ │ ├── metadata/ │ │ │ └── OpenSaml5MetadataResolverTests.java │ │ ├── registration/ │ │ │ └── OpenSaml5AssertingPartyMetadataRepositoryTests.java │ │ └── web/ │ │ ├── OpenSaml5AuthenticationTokenConverterTests.java │ │ └── authentication/ │ │ ├── OpenSaml5AuthenticationRequestResolverTests.java │ │ ├── OpenSaml5SigningUtilsTests.java │ │ └── logout/ │ │ ├── OpenSaml5LogoutRequestResolverTests.java │ │ ├── OpenSaml5LogoutRequestValidatorParametersResolverTests.java │ │ └── OpenSaml5LogoutResponseResolverTests.java │ └── test/ │ ├── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── security/ │ │ └── saml2/ │ │ ├── core/ │ │ │ ├── OpenSamlInitializationServiceTests.java │ │ │ ├── Saml2ResponseValidatorResultTests.java │ │ │ ├── Saml2Utils.java │ │ │ ├── Saml2X509CredentialTests.java │ │ │ └── TestSaml2X509Credentials.java │ │ ├── credentials/ │ │ │ ├── Saml2X509CredentialTests.java │ │ │ └── TestSaml2X509Credentials.java │ │ ├── jackson/ │ │ │ ├── DefaultSaml2AuthenticatedPrincipalMixinTests.java │ │ │ ├── Saml2AuthenticationExceptionMixinTests.java │ │ │ ├── Saml2AuthenticationMixinTests.java │ │ │ ├── Saml2LogoutRequestMixinTests.java │ │ │ ├── Saml2PostAuthenticationRequestMixinTests.java │ │ │ ├── Saml2RedirectAuthenticationRequestMixinTests.java │ │ │ └── TestSaml2JsonPayloads.java │ │ ├── jackson2/ │ │ │ ├── DefaultSaml2AuthenticatedPrincipalMixinTests.java │ │ │ ├── Saml2AuthenticationExceptionMixinTests.java │ │ │ ├── Saml2AuthenticationMixinTests.java │ │ │ ├── Saml2LogoutRequestMixinTests.java │ │ │ ├── Saml2PostAuthenticationRequestMixinTests.java │ │ │ ├── Saml2RedirectAuthenticationRequestMixinTests.java │ │ │ └── TestSaml2JsonPayloads.java │ │ └── provider/ │ │ └── service/ │ │ ├── authentication/ │ │ │ ├── DefaultSaml2AuthenticatedPrincipalTests.java │ │ │ ├── Saml2AssertionAuthenticationTests.java │ │ │ ├── Saml2PostAuthenticationRequestTests.java │ │ │ ├── Saml2RedirectAuthenticationRequestTests.java │ │ │ ├── TestOpenSamlObjects.java │ │ │ ├── TestSaml2AuthenticationTokens.java │ │ │ ├── TestSaml2Authentications.java │ │ │ ├── TestSaml2LogoutRequests.java │ │ │ ├── TestSaml2PostAuthenticationRequests.java │ │ │ └── TestSaml2RedirectAuthenticationRequests.java │ │ ├── metadata/ │ │ │ └── RequestMatcherMetadataResponseResolverTests.java │ │ ├── registration/ │ │ │ ├── CachingRelyingPartyRegistrationRepositoryTests.java │ │ │ ├── InMemoryRelyingPartyRegistrationRepositoryTests.java │ │ │ ├── JdbcAssertingPartyMetadataRepositoryTests.java │ │ │ ├── OpenSamlRelyingPartyRegistrationBuilderHttpMessageConverterTests.java │ │ │ ├── RelyingPartyRegistrationTests.java │ │ │ ├── RelyingPartyRegistrationsTests.java │ │ │ └── TestRelyingPartyRegistrations.java │ │ ├── servlet/ │ │ │ └── HttpSessionSaml2AuthenticationRequestRepositoryTests.java │ │ └── web/ │ │ ├── CacheSaml2AuthenticationRequestRepositoryTests.java │ │ ├── DefaultRelyingPartyRegistrationResolverTests.java │ │ ├── RelyingPartyRegistrationPlaceholderResolversTests.java │ │ ├── Saml2AuthenticationTokenConverterTests.java │ │ ├── Saml2MetadataFilterTests.java │ │ ├── Saml2WebSsoAuthenticationRequestFilterTests.java │ │ └── authentication/ │ │ ├── Saml2WebSsoAuthenticationFilterTests.java │ │ └── logout/ │ │ ├── HttpSessionLogoutRequestRepositoryTests.java │ │ ├── Saml2LogoutRequestFilterTests.java │ │ ├── Saml2LogoutResponseFilterTests.java │ │ └── Saml2RelyingPartyInitiatedLogoutSuccessHandlerTests.java │ └── resources/ │ ├── logback-test.xml │ ├── rsa.crt │ ├── saml2-response-sso-circle.encoded │ ├── test-entitiesdescriptor.xml │ ├── test-federated-metadata.xml │ ├── test-metadata-without-idp.xml │ └── test-metadata.xml ├── scripts/ │ ├── release/ │ │ ├── release-notes-sections.yml │ │ └── wait-for-done.sh │ ├── s101.sh │ └── update-dependencies.sh ├── settings.gradle ├── taglibs/ │ ├── spring-security-taglibs.gradle │ └── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── security/ │ │ │ └── taglibs/ │ │ │ ├── TagLibConfig.java │ │ │ ├── authz/ │ │ │ │ ├── AbstractAuthorizeTag.java │ │ │ │ ├── AccessControlListTag.java │ │ │ │ ├── AuthenticationTag.java │ │ │ │ ├── JspAuthorizeTag.java │ │ │ │ └── package-info.java │ │ │ ├── csrf/ │ │ │ │ ├── AbstractCsrfTag.java │ │ │ │ ├── CsrfInputTag.java │ │ │ │ ├── CsrfMetaTagsTag.java │ │ │ │ └── package-info.java │ │ │ └── package-info.java │ │ └── resources/ │ │ └── META-INF/ │ │ └── security.tld │ └── test/ │ ├── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── security/ │ │ └── taglibs/ │ │ ├── TldTests.java │ │ ├── authz/ │ │ │ ├── AbstractAuthorizeTagTests.java │ │ │ ├── AccessControlListTagTests.java │ │ │ ├── AuthenticationTagTests.java │ │ │ └── AuthorizeTagTests.java │ │ └── csrf/ │ │ ├── AbstractCsrfTagTests.java │ │ ├── CsrfInputTagTests.java │ │ └── CsrfMetaTagsTagTests.java │ └── resources/ │ └── logback-test.xml ├── test/ │ ├── spring-security-test.gradle │ ├── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── springframework/ │ │ │ │ └── security/ │ │ │ │ └── test/ │ │ │ │ ├── aot/ │ │ │ │ │ └── hint/ │ │ │ │ │ ├── WebTestUtilsRuntimeHints.java │ │ │ │ │ ├── WithSecurityContextTestRuntimeHints.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── context/ │ │ │ │ │ ├── TestSecurityContextHolder.java │ │ │ │ │ ├── TestSecurityContextHolderStrategyAdapter.java │ │ │ │ │ ├── annotation/ │ │ │ │ │ │ ├── SecurityTestExecutionListeners.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ ├── package-info.java │ │ │ │ │ └── support/ │ │ │ │ │ ├── DelegatingTestExecutionListener.java │ │ │ │ │ ├── ReactorContextTestExecutionListener.java │ │ │ │ │ ├── TestExecutionEvent.java │ │ │ │ │ ├── WithAnonymousUser.java │ │ │ │ │ ├── WithAnonymousUserSecurityContextFactory.java │ │ │ │ │ ├── WithMockUser.java │ │ │ │ │ ├── WithMockUserSecurityContextFactory.java │ │ │ │ │ ├── WithSecurityContext.java │ │ │ │ │ ├── WithSecurityContextFactory.java │ │ │ │ │ ├── WithSecurityContextTestExecutionListener.java │ │ │ │ │ ├── WithUserDetails.java │ │ │ │ │ ├── WithUserDetailsSecurityContextFactory.java │ │ │ │ │ └── package-info.java │ │ │ │ └── web/ │ │ │ │ ├── reactive/ │ │ │ │ │ └── server/ │ │ │ │ │ ├── SecurityMockServerConfigurers.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── servlet/ │ │ │ │ │ ├── request/ │ │ │ │ │ │ ├── SecurityMockMvcRequestBuilders.java │ │ │ │ │ │ ├── SecurityMockMvcRequestPostProcessors.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ ├── response/ │ │ │ │ │ │ ├── SecurityMockMvcResultHandlers.java │ │ │ │ │ │ ├── SecurityMockMvcResultMatchers.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ └── setup/ │ │ │ │ │ ├── SecurityMockMvcConfigurer.java │ │ │ │ │ ├── SecurityMockMvcConfigurers.java │ │ │ │ │ └── package-info.java │ │ │ │ └── support/ │ │ │ │ ├── WebTestUtils.java │ │ │ │ └── package-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── spring/ │ │ │ │ └── aot.factories │ │ │ └── spring.factories │ │ └── test/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── security/ │ │ │ └── test/ │ │ │ ├── aot/ │ │ │ │ └── hint/ │ │ │ │ ├── WebTestUtilsRuntimeHintsTests.java │ │ │ │ └── WithSecurityContextTestRuntimeHintsTests.java │ │ │ ├── context/ │ │ │ │ ├── TestSecurityContextHolderTests.java │ │ │ │ ├── annotation/ │ │ │ │ │ └── SecurityTestExecutionListenerTests.java │ │ │ │ ├── showcase/ │ │ │ │ │ ├── CustomUserDetails.java │ │ │ │ │ ├── WithMockCustomUser.java │ │ │ │ │ ├── WithMockCustomUserSecurityContextFactory.java │ │ │ │ │ ├── WithMockUserParent.java │ │ │ │ │ ├── WithMockUserParentTests.java │ │ │ │ │ ├── WithMockUserTests.java │ │ │ │ │ ├── WithUserDetailsTests.java │ │ │ │ │ └── service/ │ │ │ │ │ ├── HelloMessageService.java │ │ │ │ │ └── MessageService.java │ │ │ │ └── support/ │ │ │ │ ├── ReactorContextTestExecutionListenerTests.java │ │ │ │ ├── WithAnonymousUserTests.java │ │ │ │ ├── WithMockUserSecurityContextFactoryTests.java │ │ │ │ ├── WithMockUserTests.java │ │ │ │ ├── WithSecurityContextTestExcecutionListenerTests.java │ │ │ │ ├── WithSecurityContextTestExecutionListenerTests.java │ │ │ │ ├── WithUserDetailsSecurityContextFactoryTests.java │ │ │ │ └── WithUserDetailsTests.java │ │ │ └── web/ │ │ │ ├── reactive/ │ │ │ │ └── server/ │ │ │ │ ├── AbstractMockServerConfigurersTests.java │ │ │ │ ├── SecurityMockServerConfigurerOpaqueTokenTests.java │ │ │ │ ├── SecurityMockServerConfigurersAnnotatedTests.java │ │ │ │ ├── SecurityMockServerConfigurersClassAnnotatedTests.java │ │ │ │ ├── SecurityMockServerConfigurersJwtTests.java │ │ │ │ ├── SecurityMockServerConfigurersOAuth2ClientTests.java │ │ │ │ ├── SecurityMockServerConfigurersOAuth2LoginTests.java │ │ │ │ ├── SecurityMockServerConfigurersOidcLoginTests.java │ │ │ │ └── SecurityMockServerConfigurersTests.java │ │ │ ├── servlet/ │ │ │ │ ├── request/ │ │ │ │ │ ├── Sec2935Tests.java │ │ │ │ │ ├── SecurityMockMvcRequestBuildersFormLoginTests.java │ │ │ │ │ ├── SecurityMockMvcRequestBuildersFormLogoutTests.java │ │ │ │ │ ├── SecurityMockMvcRequestPostProcessorsAuthenticationStatelessTests.java │ │ │ │ │ ├── SecurityMockMvcRequestPostProcessorsAuthenticationTests.java │ │ │ │ │ ├── SecurityMockMvcRequestPostProcessorsCertificateTests.java │ │ │ │ │ ├── SecurityMockMvcRequestPostProcessorsCsrfDebugFilterTests.java │ │ │ │ │ ├── SecurityMockMvcRequestPostProcessorsCsrfTests.java │ │ │ │ │ ├── SecurityMockMvcRequestPostProcessorsDigestTests.java │ │ │ │ │ ├── SecurityMockMvcRequestPostProcessorsJwtTests.java │ │ │ │ │ ├── SecurityMockMvcRequestPostProcessorsOAuth2ClientTests.java │ │ │ │ │ ├── SecurityMockMvcRequestPostProcessorsOAuth2LoginTests.java │ │ │ │ │ ├── SecurityMockMvcRequestPostProcessorsOidcLoginTests.java │ │ │ │ │ ├── SecurityMockMvcRequestPostProcessorsOpaqueTokenTests.java │ │ │ │ │ ├── SecurityMockMvcRequestPostProcessorsSecurityContextTests.java │ │ │ │ │ ├── SecurityMockMvcRequestPostProcessorsTestSecurityContextStatelessTests.java │ │ │ │ │ ├── SecurityMockMvcRequestPostProcessorsTestSecurityContextTests.java │ │ │ │ │ ├── SecurityMockMvcRequestPostProcessorsUserDetailsTests.java │ │ │ │ │ └── SecurityMockMvcRequestPostProcessorsUserTests.java │ │ │ │ ├── response/ │ │ │ │ │ ├── Gh3409Tests.java │ │ │ │ │ ├── SecurityMockMvcResultHandlersTests.java │ │ │ │ │ ├── SecurityMockMvcResultMatchersTests.java │ │ │ │ │ └── SecurityMockWithAuthoritiesMvcResultMatchersTests.java │ │ │ │ ├── setup/ │ │ │ │ │ ├── SecurityMockMvcConfigurerTests.java │ │ │ │ │ └── SecurityMockMvcConfigurersTests.java │ │ │ │ └── showcase/ │ │ │ │ ├── csrf/ │ │ │ │ │ ├── CsrfShowcaseTests.java │ │ │ │ │ ├── CustomCsrfShowcaseTests.java │ │ │ │ │ └── DefaultCsrfShowcaseTests.java │ │ │ │ ├── login/ │ │ │ │ │ ├── AuthenticationTests.java │ │ │ │ │ ├── CustomConfigAuthenticationTests.java │ │ │ │ │ └── CustomLoginRequestBuilderAuthenticationTests.java │ │ │ │ └── secured/ │ │ │ │ ├── DefaultfSecurityRequestsTests.java │ │ │ │ ├── SecurityRequestsTests.java │ │ │ │ ├── WithAdminRob.java │ │ │ │ ├── WithUserAuthenticationTests.java │ │ │ │ ├── WithUserClassLevelAuthenticationTests.java │ │ │ │ ├── WithUserDetailsAuthenticationTests.java │ │ │ │ └── WithUserDetailsClassLevelAuthenticationTests.java │ │ │ └── support/ │ │ │ └── WebTestUtilsTests.java │ │ └── resources/ │ │ ├── logback-test.xml │ │ └── rod.cer │ └── template.mf ├── web/ │ ├── spring-security-web.gradle │ └── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── security/ │ │ │ └── web/ │ │ │ ├── AuthenticationEntryPoint.java │ │ │ ├── DefaultRedirectStrategy.java │ │ │ ├── DefaultSecurityFilterChain.java │ │ │ ├── FilterChainProxy.java │ │ │ ├── FilterInvocation.java │ │ │ ├── FormPostRedirectStrategy.java │ │ │ ├── ObservationFilterChainDecorator.java │ │ │ ├── PortMapper.java │ │ │ ├── PortMapperImpl.java │ │ │ ├── RedirectStrategy.java │ │ │ ├── RequestMatcherRedirectFilter.java │ │ │ ├── SecurityFilterChain.java │ │ │ ├── UnreachableFilterChainException.java │ │ │ ├── WebAttributes.java │ │ │ ├── access/ │ │ │ │ ├── AccessDeniedHandler.java │ │ │ │ ├── AccessDeniedHandlerImpl.java │ │ │ │ ├── AuthorizationManagerWebInvocationPrivilegeEvaluator.java │ │ │ │ ├── CompositeAccessDeniedHandler.java │ │ │ │ ├── DelegatingAccessDeniedHandler.java │ │ │ │ ├── DelegatingMissingAuthorityAccessDeniedHandler.java │ │ │ │ ├── ExceptionTranslationFilter.java │ │ │ │ ├── HttpStatusAccessDeniedHandler.java │ │ │ │ ├── IpAddressAuthorizationManager.java │ │ │ │ ├── NoOpAccessDeniedHandler.java │ │ │ │ ├── ObservationMarkingAccessDeniedHandler.java │ │ │ │ ├── PathPatternRequestTransformer.java │ │ │ │ ├── RequestMatcherDelegatingAccessDeniedHandler.java │ │ │ │ ├── RequestMatcherDelegatingWebInvocationPrivilegeEvaluator.java │ │ │ │ ├── WebInvocationPrivilegeEvaluator.java │ │ │ │ ├── expression/ │ │ │ │ │ ├── AbstractVariableEvaluationContextPostProcessor.java │ │ │ │ │ ├── DefaultHttpSecurityExpressionHandler.java │ │ │ │ │ ├── DelegatingEvaluationContext.java │ │ │ │ │ ├── EvaluationContextPostProcessor.java │ │ │ │ │ ├── FilterInvocationExpressionRoot.java │ │ │ │ │ ├── WebExpressionAuthorizationManager.java │ │ │ │ │ ├── WebSecurityExpressionRoot.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── intercept/ │ │ │ │ │ ├── AuthorizationFilter.java │ │ │ │ │ ├── RequestAuthorizationContext.java │ │ │ │ │ ├── RequestKey.java │ │ │ │ │ ├── RequestMatcherDelegatingAuthorizationManager.java │ │ │ │ │ └── package-info.java │ │ │ │ └── package-info.java │ │ │ ├── aot/ │ │ │ │ └── hint/ │ │ │ │ ├── WebMvcSecurityRuntimeHints.java │ │ │ │ └── package-info.java │ │ │ ├── authentication/ │ │ │ │ ├── AbstractAuthenticationProcessingFilter.java │ │ │ │ ├── AbstractAuthenticationTargetUrlRequestHandler.java │ │ │ │ ├── AnonymousAuthenticationFilter.java │ │ │ │ ├── AuthenticationConverter.java │ │ │ │ ├── AuthenticationEntryPointFailureHandler.java │ │ │ │ ├── AuthenticationFailureHandler.java │ │ │ │ ├── AuthenticationFilter.java │ │ │ │ ├── AuthenticationSuccessHandler.java │ │ │ │ ├── DelegatingAuthenticationConverter.java │ │ │ │ ├── DelegatingAuthenticationEntryPoint.java │ │ │ │ ├── DelegatingAuthenticationFailureHandler.java │ │ │ │ ├── ExceptionMappingAuthenticationFailureHandler.java │ │ │ │ ├── ForwardAuthenticationFailureHandler.java │ │ │ │ ├── ForwardAuthenticationSuccessHandler.java │ │ │ │ ├── GenericHttpMessageConverterAdapter.java │ │ │ │ ├── Http403ForbiddenEntryPoint.java │ │ │ │ ├── HttpMessageConverterAuthenticationSuccessHandler.java │ │ │ │ ├── HttpMessageConverters.java │ │ │ │ ├── HttpStatusEntryPoint.java │ │ │ │ ├── LoginUrlAuthenticationEntryPoint.java │ │ │ │ ├── NoOpAuthenticationEntryPoint.java │ │ │ │ ├── NullRememberMeServices.java │ │ │ │ ├── RememberMeServices.java │ │ │ │ ├── RequestMatcherDelegatingAuthenticationManagerResolver.java │ │ │ │ ├── SavedRequestAwareAuthenticationSuccessHandler.java │ │ │ │ ├── SimpleUrlAuthenticationFailureHandler.java │ │ │ │ ├── SimpleUrlAuthenticationSuccessHandler.java │ │ │ │ ├── UsernamePasswordAuthenticationFilter.java │ │ │ │ ├── WebAuthenticationDetails.java │ │ │ │ ├── WebAuthenticationDetailsSource.java │ │ │ │ ├── logout/ │ │ │ │ │ ├── CompositeLogoutHandler.java │ │ │ │ │ ├── CookieClearingLogoutHandler.java │ │ │ │ │ ├── DelegatingLogoutSuccessHandler.java │ │ │ │ │ ├── ForwardLogoutSuccessHandler.java │ │ │ │ │ ├── HeaderWriterLogoutHandler.java │ │ │ │ │ ├── HttpStatusReturningLogoutSuccessHandler.java │ │ │ │ │ ├── LogoutFilter.java │ │ │ │ │ ├── LogoutHandler.java │ │ │ │ │ ├── LogoutSuccessEventPublishingLogoutHandler.java │ │ │ │ │ ├── LogoutSuccessHandler.java │ │ │ │ │ ├── SecurityContextLogoutHandler.java │ │ │ │ │ ├── SimpleUrlLogoutSuccessHandler.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── ott/ │ │ │ │ │ ├── DefaultGenerateOneTimeTokenRequestResolver.java │ │ │ │ │ ├── GenerateOneTimeTokenFilter.java │ │ │ │ │ ├── GenerateOneTimeTokenRequestResolver.java │ │ │ │ │ ├── OneTimeTokenAuthenticationConverter.java │ │ │ │ │ ├── OneTimeTokenAuthenticationFilter.java │ │ │ │ │ ├── OneTimeTokenGenerationSuccessHandler.java │ │ │ │ │ ├── RedirectOneTimeTokenGenerationSuccessHandler.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── package-info.java │ │ │ │ ├── password/ │ │ │ │ │ ├── HaveIBeenPwnedRestApiPasswordChecker.java │ │ │ │ │ ├── HaveIBeenPwnedRestApiReactivePasswordChecker.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── preauth/ │ │ │ │ │ ├── AbstractPreAuthenticatedProcessingFilter.java │ │ │ │ │ ├── PreAuthenticatedAuthenticationProvider.java │ │ │ │ │ ├── PreAuthenticatedAuthenticationToken.java │ │ │ │ │ ├── PreAuthenticatedCredentialsNotFoundException.java │ │ │ │ │ ├── PreAuthenticatedGrantedAuthoritiesUserDetailsService.java │ │ │ │ │ ├── PreAuthenticatedGrantedAuthoritiesWebAuthenticationDetails.java │ │ │ │ │ ├── RequestAttributeAuthenticationFilter.java │ │ │ │ │ ├── RequestHeaderAuthenticationFilter.java │ │ │ │ │ ├── j2ee/ │ │ │ │ │ │ ├── J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource.java │ │ │ │ │ │ ├── J2eePreAuthenticatedProcessingFilter.java │ │ │ │ │ │ ├── WebXmlMappableAttributesRetriever.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ ├── package-info.java │ │ │ │ │ ├── websphere/ │ │ │ │ │ │ ├── DefaultWASUsernameAndGroupsExtractor.java │ │ │ │ │ │ ├── WASUsernameAndGroupsExtractor.java │ │ │ │ │ │ ├── WebSpherePreAuthenticatedProcessingFilter.java │ │ │ │ │ │ ├── WebSpherePreAuthenticatedWebAuthenticationDetailsSource.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ └── x509/ │ │ │ │ │ ├── SubjectDnX509PrincipalExtractor.java │ │ │ │ │ ├── SubjectX500PrincipalExtractor.java │ │ │ │ │ ├── X509AuthenticationFilter.java │ │ │ │ │ ├── X509PrincipalExtractor.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── rememberme/ │ │ │ │ │ ├── AbstractRememberMeServices.java │ │ │ │ │ ├── CookieTheftException.java │ │ │ │ │ ├── InMemoryTokenRepositoryImpl.java │ │ │ │ │ ├── InvalidCookieException.java │ │ │ │ │ ├── JdbcTokenRepositoryImpl.java │ │ │ │ │ ├── PersistentRememberMeToken.java │ │ │ │ │ ├── PersistentTokenBasedRememberMeServices.java │ │ │ │ │ ├── PersistentTokenRepository.java │ │ │ │ │ ├── RememberMeAuthenticationException.java │ │ │ │ │ ├── RememberMeAuthenticationFilter.java │ │ │ │ │ ├── TokenBasedRememberMeServices.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── session/ │ │ │ │ │ ├── AbstractSessionFixationProtectionStrategy.java │ │ │ │ │ ├── ChangeSessionIdAuthenticationStrategy.java │ │ │ │ │ ├── CompositeSessionAuthenticationStrategy.java │ │ │ │ │ ├── ConcurrentSessionControlAuthenticationStrategy.java │ │ │ │ │ ├── NullAuthenticatedSessionStrategy.java │ │ │ │ │ ├── RegisterSessionAuthenticationStrategy.java │ │ │ │ │ ├── SessionAuthenticationException.java │ │ │ │ │ ├── SessionAuthenticationStrategy.java │ │ │ │ │ ├── SessionFixationProtectionEvent.java │ │ │ │ │ ├── SessionFixationProtectionStrategy.java │ │ │ │ │ ├── SessionLimit.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── switchuser/ │ │ │ │ │ ├── AuthenticationSwitchUserEvent.java │ │ │ │ │ ├── SwitchUserAuthorityChanger.java │ │ │ │ │ ├── SwitchUserFilter.java │ │ │ │ │ ├── SwitchUserGrantedAuthority.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── ui/ │ │ │ │ │ ├── DefaultLoginPageGeneratingFilter.java │ │ │ │ │ ├── DefaultLogoutPageGeneratingFilter.java │ │ │ │ │ ├── DefaultOneTimeTokenSubmitPageGeneratingFilter.java │ │ │ │ │ ├── DefaultResourcesFilter.java │ │ │ │ │ ├── HtmlTemplates.java │ │ │ │ │ └── package-info.java │ │ │ │ └── www/ │ │ │ │ ├── BasicAuthenticationConverter.java │ │ │ │ ├── BasicAuthenticationEntryPoint.java │ │ │ │ ├── BasicAuthenticationFilter.java │ │ │ │ ├── DigestAuthUtils.java │ │ │ │ ├── DigestAuthenticationEntryPoint.java │ │ │ │ ├── DigestAuthenticationFilter.java │ │ │ │ ├── NonceExpiredException.java │ │ │ │ └── package-info.java │ │ │ ├── bind/ │ │ │ │ ├── annotation/ │ │ │ │ │ ├── AuthenticationPrincipal.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── package-info.java │ │ │ │ └── support/ │ │ │ │ ├── AuthenticationPrincipalArgumentResolver.java │ │ │ │ └── package-info.java │ │ │ ├── context/ │ │ │ │ ├── AbstractSecurityWebApplicationInitializer.java │ │ │ │ ├── DelegatingSecurityContextRepository.java │ │ │ │ ├── HttpRequestResponseHolder.java │ │ │ │ ├── HttpSessionSecurityContextRepository.java │ │ │ │ ├── NullSecurityContextRepository.java │ │ │ │ ├── RequestAttributeSecurityContextRepository.java │ │ │ │ ├── SaveContextOnUpdateOrErrorResponseWrapper.java │ │ │ │ ├── SecurityContextHolderFilter.java │ │ │ │ ├── SecurityContextPersistenceFilter.java │ │ │ │ ├── SecurityContextRepository.java │ │ │ │ ├── SupplierDeferredSecurityContext.java │ │ │ │ ├── package-info.java │ │ │ │ ├── request/ │ │ │ │ │ └── async/ │ │ │ │ │ ├── SecurityContextCallableProcessingInterceptor.java │ │ │ │ │ ├── WebAsyncManagerIntegrationFilter.java │ │ │ │ │ └── package-info.java │ │ │ │ └── support/ │ │ │ │ ├── SecurityWebApplicationContextUtils.java │ │ │ │ └── package-info.java │ │ │ ├── csrf/ │ │ │ │ ├── CookieCsrfTokenRepository.java │ │ │ │ ├── CsrfAuthenticationStrategy.java │ │ │ │ ├── CsrfException.java │ │ │ │ ├── CsrfFilter.java │ │ │ │ ├── CsrfLogoutHandler.java │ │ │ │ ├── CsrfToken.java │ │ │ │ ├── CsrfTokenRepository.java │ │ │ │ ├── CsrfTokenRequestAttributeHandler.java │ │ │ │ ├── CsrfTokenRequestHandler.java │ │ │ │ ├── CsrfTokenRequestHandlerLoggerHolder.java │ │ │ │ ├── CsrfTokenRequestResolver.java │ │ │ │ ├── DefaultCsrfToken.java │ │ │ │ ├── DeferredCsrfToken.java │ │ │ │ ├── HttpSessionCsrfTokenRepository.java │ │ │ │ ├── InvalidCsrfTokenException.java │ │ │ │ ├── MissingCsrfTokenException.java │ │ │ │ ├── RepositoryDeferredCsrfToken.java │ │ │ │ ├── XorCsrfTokenRequestAttributeHandler.java │ │ │ │ └── package-info.java │ │ │ ├── debug/ │ │ │ │ ├── DebugFilter.java │ │ │ │ ├── Logger.java │ │ │ │ └── package-info.java │ │ │ ├── firewall/ │ │ │ │ ├── CompositeRequestRejectedHandler.java │ │ │ │ ├── DefaultHttpFirewall.java │ │ │ │ ├── DefaultRequestRejectedHandler.java │ │ │ │ ├── FirewalledRequest.java │ │ │ │ ├── FirewalledResponse.java │ │ │ │ ├── HttpFirewall.java │ │ │ │ ├── HttpStatusRequestRejectedHandler.java │ │ │ │ ├── ObservationMarkingRequestRejectedHandler.java │ │ │ │ ├── RequestRejectedException.java │ │ │ │ ├── RequestRejectedHandler.java │ │ │ │ ├── RequestWrapper.java │ │ │ │ ├── StrictHttpFirewall.java │ │ │ │ └── package-info.java │ │ │ ├── header/ │ │ │ │ ├── Header.java │ │ │ │ ├── HeaderWriter.java │ │ │ │ ├── HeaderWriterFilter.java │ │ │ │ ├── package-info.java │ │ │ │ └── writers/ │ │ │ │ ├── CacheControlHeadersWriter.java │ │ │ │ ├── ClearSiteDataHeaderWriter.java │ │ │ │ ├── CompositeHeaderWriter.java │ │ │ │ ├── ContentSecurityPolicyHeaderWriter.java │ │ │ │ ├── CrossOriginEmbedderPolicyHeaderWriter.java │ │ │ │ ├── CrossOriginOpenerPolicyHeaderWriter.java │ │ │ │ ├── CrossOriginResourcePolicyHeaderWriter.java │ │ │ │ ├── DelegatingRequestMatcherHeaderWriter.java │ │ │ │ ├── FeaturePolicyHeaderWriter.java │ │ │ │ ├── HpkpHeaderWriter.java │ │ │ │ ├── HstsHeaderWriter.java │ │ │ │ ├── PermissionsPolicyHeaderWriter.java │ │ │ │ ├── ReferrerPolicyHeaderWriter.java │ │ │ │ ├── StaticHeadersWriter.java │ │ │ │ ├── XContentTypeOptionsHeaderWriter.java │ │ │ │ ├── XXssProtectionHeaderWriter.java │ │ │ │ ├── frameoptions/ │ │ │ │ │ ├── AbstractRequestParameterAllowFromStrategy.java │ │ │ │ │ ├── AllowFromStrategy.java │ │ │ │ │ ├── RegExpAllowFromStrategy.java │ │ │ │ │ ├── StaticAllowFromStrategy.java │ │ │ │ │ ├── WhiteListedAllowFromStrategy.java │ │ │ │ │ ├── XFrameOptionsHeaderWriter.java │ │ │ │ │ └── package-info.java │ │ │ │ └── package-info.java │ │ │ ├── http/ │ │ │ │ ├── SecurityHeaders.java │ │ │ │ └── package-info.java │ │ │ ├── jaasapi/ │ │ │ │ ├── JaasApiIntegrationFilter.java │ │ │ │ └── package-info.java │ │ │ ├── jackson/ │ │ │ │ ├── CookieDeserializer.java │ │ │ │ ├── CookieMixin.java │ │ │ │ ├── DefaultCsrfTokenMixin.java │ │ │ │ ├── DefaultSavedRequestMixin.java │ │ │ │ ├── PreAuthenticatedAuthenticationTokenDeserializer.java │ │ │ │ ├── PreAuthenticatedAuthenticationTokenMixin.java │ │ │ │ ├── SavedCookieMixin.java │ │ │ │ ├── SwitchUserGrantedAuthorityMixIn.java │ │ │ │ ├── WebAuthenticationDetailsMixin.java │ │ │ │ ├── WebJacksonModule.java │ │ │ │ ├── WebServletJacksonModule.java │ │ │ │ └── package-info.java │ │ │ ├── jackson2/ │ │ │ │ ├── CookieDeserializer.java │ │ │ │ ├── CookieMixin.java │ │ │ │ ├── DefaultCsrfTokenMixin.java │ │ │ │ ├── DefaultSavedRequestMixin.java │ │ │ │ ├── PreAuthenticatedAuthenticationTokenDeserializer.java │ │ │ │ ├── PreAuthenticatedAuthenticationTokenMixin.java │ │ │ │ ├── SavedCookieMixin.java │ │ │ │ ├── SwitchUserGrantedAuthorityMixIn.java │ │ │ │ ├── WebAuthenticationDetailsMixin.java │ │ │ │ ├── WebJackson2Module.java │ │ │ │ ├── WebServletJackson2Module.java │ │ │ │ └── package-info.java │ │ │ ├── method/ │ │ │ │ └── annotation/ │ │ │ │ ├── AuthenticationPrincipalArgumentResolver.java │ │ │ │ ├── CsrfTokenArgumentResolver.java │ │ │ │ ├── CurrentSecurityContextArgumentResolver.java │ │ │ │ └── package-info.java │ │ │ ├── package-info.java │ │ │ ├── reactive/ │ │ │ │ └── result/ │ │ │ │ ├── method/ │ │ │ │ │ └── annotation/ │ │ │ │ │ ├── AuthenticationPrincipalArgumentResolver.java │ │ │ │ │ ├── CurrentSecurityContextArgumentResolver.java │ │ │ │ │ └── package-info.java │ │ │ │ └── view/ │ │ │ │ ├── CsrfRequestDataValueProcessor.java │ │ │ │ └── package-info.java │ │ │ ├── savedrequest/ │ │ │ │ ├── CookieRequestCache.java │ │ │ │ ├── DefaultSavedRequest.java │ │ │ │ ├── Enumerator.java │ │ │ │ ├── FastHttpDateFormat.java │ │ │ │ ├── HttpSessionRequestCache.java │ │ │ │ ├── NullRequestCache.java │ │ │ │ ├── RequestCache.java │ │ │ │ ├── RequestCacheAwareFilter.java │ │ │ │ ├── SavedCookie.java │ │ │ │ ├── SavedRequest.java │ │ │ │ ├── SavedRequestAwareWrapper.java │ │ │ │ ├── SimpleSavedRequest.java │ │ │ │ └── package-info.java │ │ │ ├── server/ │ │ │ │ ├── DefaultServerRedirectStrategy.java │ │ │ │ ├── DelegatingServerAuthenticationEntryPoint.java │ │ │ │ ├── ExchangeMatcherRedirectWebFilter.java │ │ │ │ ├── FormPostServerRedirectStrategy.java │ │ │ │ ├── MatcherSecurityWebFilterChain.java │ │ │ │ ├── ObservationWebFilterChainDecorator.java │ │ │ │ ├── SecurityWebFilterChain.java │ │ │ │ ├── ServerAuthenticationEntryPoint.java │ │ │ │ ├── ServerFormLoginAuthenticationConverter.java │ │ │ │ ├── ServerHttpBasicAuthenticationConverter.java │ │ │ │ ├── ServerRedirectStrategy.java │ │ │ │ ├── ServerWebExchangeThreadLocalAccessor.java │ │ │ │ ├── WebFilterChainProxy.java │ │ │ │ ├── WebFilterExchange.java │ │ │ │ ├── authentication/ │ │ │ │ │ ├── AnonymousAuthenticationWebFilter.java │ │ │ │ │ ├── AuthenticationConverterServerWebExchangeMatcher.java │ │ │ │ │ ├── AuthenticationWebFilter.java │ │ │ │ │ ├── ConcurrentSessionControlServerAuthenticationSuccessHandler.java │ │ │ │ │ ├── DelegatingServerAuthenticationConverter.java │ │ │ │ │ ├── DelegatingServerAuthenticationSuccessHandler.java │ │ │ │ │ ├── HttpBasicServerAuthenticationEntryPoint.java │ │ │ │ │ ├── HttpStatusServerEntryPoint.java │ │ │ │ │ ├── InvalidateLeastUsedServerMaximumSessionsExceededHandler.java │ │ │ │ │ ├── MaximumSessionsContext.java │ │ │ │ │ ├── PreventLoginServerMaximumSessionsExceededHandler.java │ │ │ │ │ ├── ReactivePreAuthenticatedAuthenticationManager.java │ │ │ │ │ ├── RedirectServerAuthenticationEntryPoint.java │ │ │ │ │ ├── RedirectServerAuthenticationFailureHandler.java │ │ │ │ │ ├── RedirectServerAuthenticationSuccessHandler.java │ │ │ │ │ ├── RegisterSessionServerAuthenticationSuccessHandler.java │ │ │ │ │ ├── ServerAuthenticationConverter.java │ │ │ │ │ ├── ServerAuthenticationEntryPointFailureHandler.java │ │ │ │ │ ├── ServerAuthenticationFailureHandler.java │ │ │ │ │ ├── ServerAuthenticationSuccessHandler.java │ │ │ │ │ ├── ServerFormLoginAuthenticationConverter.java │ │ │ │ │ ├── ServerHttpBasicAuthenticationConverter.java │ │ │ │ │ ├── ServerMaximumSessionsExceededHandler.java │ │ │ │ │ ├── ServerWebExchangeDelegatingReactiveAuthenticationManagerResolver.java │ │ │ │ │ ├── ServerX509AuthenticationConverter.java │ │ │ │ │ ├── SessionLimit.java │ │ │ │ │ ├── SwitchUserWebFilter.java │ │ │ │ │ ├── WebFilterChainServerAuthenticationSuccessHandler.java │ │ │ │ │ ├── logout/ │ │ │ │ │ │ ├── DelegatingServerLogoutHandler.java │ │ │ │ │ │ ├── HeaderWriterServerLogoutHandler.java │ │ │ │ │ │ ├── HttpStatusReturningServerLogoutSuccessHandler.java │ │ │ │ │ │ ├── LogoutWebFilter.java │ │ │ │ │ │ ├── RedirectServerLogoutSuccessHandler.java │ │ │ │ │ │ ├── SecurityContextServerLogoutHandler.java │ │ │ │ │ │ ├── ServerLogoutHandler.java │ │ │ │ │ │ ├── ServerLogoutSuccessHandler.java │ │ │ │ │ │ ├── WebSessionServerLogoutHandler.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ ├── ott/ │ │ │ │ │ │ ├── DefaultServerGenerateOneTimeTokenRequestResolver.java │ │ │ │ │ │ ├── GenerateOneTimeTokenWebFilter.java │ │ │ │ │ │ ├── ServerGenerateOneTimeTokenRequestResolver.java │ │ │ │ │ │ ├── ServerOneTimeTokenAuthenticationConverter.java │ │ │ │ │ │ ├── ServerOneTimeTokenGenerationSuccessHandler.java │ │ │ │ │ │ ├── ServerRedirectOneTimeTokenGenerationSuccessHandler.java │ │ │ │ │ │ └── package-info.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── authorization/ │ │ │ │ │ ├── AuthorizationContext.java │ │ │ │ │ ├── AuthorizationWebFilter.java │ │ │ │ │ ├── DelegatingReactiveAuthorizationManager.java │ │ │ │ │ ├── ExceptionTranslationWebFilter.java │ │ │ │ │ ├── HttpStatusServerAccessDeniedHandler.java │ │ │ │ │ ├── IpAddressReactiveAuthorizationManager.java │ │ │ │ │ ├── ServerAccessDeniedHandler.java │ │ │ │ │ ├── ServerWebExchangeDelegatingServerAccessDeniedHandler.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── context/ │ │ │ │ │ ├── NoOpServerSecurityContextRepository.java │ │ │ │ │ ├── ReactorContextWebFilter.java │ │ │ │ │ ├── SecurityContextServerWebExchange.java │ │ │ │ │ ├── SecurityContextServerWebExchangeWebFilter.java │ │ │ │ │ ├── ServerSecurityContextRepository.java │ │ │ │ │ ├── WebSessionServerSecurityContextRepository.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── csrf/ │ │ │ │ │ ├── CookieServerCsrfTokenRepository.java │ │ │ │ │ ├── CsrfException.java │ │ │ │ │ ├── CsrfServerLogoutHandler.java │ │ │ │ │ ├── CsrfToken.java │ │ │ │ │ ├── CsrfWebFilter.java │ │ │ │ │ ├── DefaultCsrfToken.java │ │ │ │ │ ├── ServerCsrfTokenRepository.java │ │ │ │ │ ├── ServerCsrfTokenRequestAttributeHandler.java │ │ │ │ │ ├── ServerCsrfTokenRequestHandler.java │ │ │ │ │ ├── ServerCsrfTokenRequestHandlerLoggerHolder.java │ │ │ │ │ ├── ServerCsrfTokenRequestResolver.java │ │ │ │ │ ├── WebSessionServerCsrfTokenRepository.java │ │ │ │ │ ├── XorServerCsrfTokenRequestAttributeHandler.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── firewall/ │ │ │ │ │ ├── HttpStatusExchangeRejectedHandler.java │ │ │ │ │ ├── ServerExchangeRejectedException.java │ │ │ │ │ ├── ServerExchangeRejectedHandler.java │ │ │ │ │ ├── ServerWebExchangeFirewall.java │ │ │ │ │ ├── StrictServerWebExchangeFirewall.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── header/ │ │ │ │ │ ├── CacheControlServerHttpHeadersWriter.java │ │ │ │ │ ├── ClearSiteDataServerHttpHeadersWriter.java │ │ │ │ │ ├── CompositeServerHttpHeadersWriter.java │ │ │ │ │ ├── ContentSecurityPolicyServerHttpHeadersWriter.java │ │ │ │ │ ├── ContentTypeOptionsServerHttpHeadersWriter.java │ │ │ │ │ ├── CrossOriginEmbedderPolicyServerHttpHeadersWriter.java │ │ │ │ │ ├── CrossOriginOpenerPolicyServerHttpHeadersWriter.java │ │ │ │ │ ├── CrossOriginResourcePolicyServerHttpHeadersWriter.java │ │ │ │ │ ├── FeaturePolicyServerHttpHeadersWriter.java │ │ │ │ │ ├── HttpHeaderWriterWebFilter.java │ │ │ │ │ ├── PermissionsPolicyServerHttpHeadersWriter.java │ │ │ │ │ ├── ReferrerPolicyServerHttpHeadersWriter.java │ │ │ │ │ ├── ServerHttpHeadersWriter.java │ │ │ │ │ ├── ServerWebExchangeDelegatingServerHttpHeadersWriter.java │ │ │ │ │ ├── StaticServerHttpHeadersWriter.java │ │ │ │ │ ├── StrictTransportSecurityServerHttpHeadersWriter.java │ │ │ │ │ ├── XContentTypeOptionsServerHttpHeadersWriter.java │ │ │ │ │ ├── XFrameOptionsServerHttpHeadersWriter.java │ │ │ │ │ ├── XXssProtectionServerHttpHeadersWriter.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── jackson/ │ │ │ │ │ ├── DefaultCsrfServerTokenMixin.java │ │ │ │ │ ├── WebServerJacksonModule.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── jackson2/ │ │ │ │ │ ├── DefaultCsrfServerTokenMixin.java │ │ │ │ │ ├── WebServerJackson2Module.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── package-info.java │ │ │ │ ├── savedrequest/ │ │ │ │ │ ├── CookieServerRequestCache.java │ │ │ │ │ ├── NoOpServerRequestCache.java │ │ │ │ │ ├── ServerRequestCache.java │ │ │ │ │ ├── ServerRequestCacheWebFilter.java │ │ │ │ │ ├── WebSessionServerRequestCache.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── transport/ │ │ │ │ │ ├── HttpsRedirectWebFilter.java │ │ │ │ │ └── package-info.java │ │ │ │ ├── ui/ │ │ │ │ │ ├── DefaultResourcesWebFilter.java │ │ │ │ │ ├── HtmlTemplates.java │ │ │ │ │ ├── LoginPageGeneratingWebFilter.java │ │ │ │ │ ├── LogoutPageGeneratingWebFilter.java │ │ │ │ │ ├── OneTimeTokenSubmitPageGeneratingWebFilter.java │ │ │ │ │ └── package-info.java │ │ │ │ └── util/ │ │ │ │ └── matcher/ │ │ │ │ ├── AndServerWebExchangeMatcher.java │ │ │ │ ├── IpAddressServerWebExchangeMatcher.java │ │ │ │ ├── MediaTypeServerWebExchangeMatcher.java │ │ │ │ ├── NegatedServerWebExchangeMatcher.java │ │ │ │ ├── OrServerWebExchangeMatcher.java │ │ │ │ ├── PathPatternParserServerWebExchangeMatcher.java │ │ │ │ ├── ServerWebExchangeMatcher.java │ │ │ │ ├── ServerWebExchangeMatcherEntry.java │ │ │ │ ├── ServerWebExchangeMatchers.java │ │ │ │ └── package-info.java │ │ │ ├── servlet/ │ │ │ │ ├── support/ │ │ │ │ │ └── csrf/ │ │ │ │ │ ├── CsrfRequestDataValueProcessor.java │ │ │ │ │ └── package-info.java │ │ │ │ └── util/ │ │ │ │ └── matcher/ │ │ │ │ ├── PathPatternRequestMatcher.java │ │ │ │ └── package-info.java │ │ │ ├── servletapi/ │ │ │ │ ├── HttpServlet3RequestFactory.java │ │ │ │ ├── HttpServletRequestFactory.java │ │ │ │ ├── SecurityContextHolderAwareRequestFilter.java │ │ │ │ ├── SecurityContextHolderAwareRequestWrapper.java │ │ │ │ └── package-info.java │ │ │ ├── session/ │ │ │ │ ├── ConcurrentSessionFilter.java │ │ │ │ ├── DisableEncodeUrlFilter.java │ │ │ │ ├── ForceEagerSessionCreationFilter.java │ │ │ │ ├── HttpSessionCreatedEvent.java │ │ │ │ ├── HttpSessionDestroyedEvent.java │ │ │ │ ├── HttpSessionEventPublisher.java │ │ │ │ ├── HttpSessionIdChangedEvent.java │ │ │ │ ├── InvalidSessionAccessDeniedHandler.java │ │ │ │ ├── InvalidSessionStrategy.java │ │ │ │ ├── RequestedUrlRedirectInvalidSessionStrategy.java │ │ │ │ ├── SessionInformationExpiredEvent.java │ │ │ │ ├── SessionInformationExpiredStrategy.java │ │ │ │ ├── SessionManagementFilter.java │ │ │ │ ├── SimpleRedirectInvalidSessionStrategy.java │ │ │ │ ├── SimpleRedirectSessionInformationExpiredStrategy.java │ │ │ │ └── package-info.java │ │ │ ├── transport/ │ │ │ │ ├── HttpsRedirectFilter.java │ │ │ │ └── package-info.java │ │ │ └── util/ │ │ │ ├── OnCommittedResponseWrapper.java │ │ │ ├── RedirectUrlBuilder.java │ │ │ ├── TextEscapeUtils.java │ │ │ ├── ThrowableAnalyzer.java │ │ │ ├── ThrowableCauseExtractor.java │ │ │ ├── UrlUtils.java │ │ │ ├── matcher/ │ │ │ │ ├── AndRequestMatcher.java │ │ │ │ ├── AnyRequestMatcher.java │ │ │ │ ├── DispatcherTypeRequestMatcher.java │ │ │ │ ├── ELRequestMatcher.java │ │ │ │ ├── ELRequestMatcherContext.java │ │ │ │ ├── InetAddressMatcher.java │ │ │ │ ├── InetAddressMatchers.java │ │ │ │ ├── InetAddressParser.java │ │ │ │ ├── IpAddressMatcher.java │ │ │ │ ├── IpInetAddressMatcher.java │ │ │ │ ├── MediaTypeRequestMatcher.java │ │ │ │ ├── NegatedRequestMatcher.java │ │ │ │ ├── OrRequestMatcher.java │ │ │ │ ├── ParameterRequestMatcher.java │ │ │ │ ├── RegexRequestMatcher.java │ │ │ │ ├── RequestHeaderRequestMatcher.java │ │ │ │ ├── RequestMatcher.java │ │ │ │ ├── RequestMatcherEditor.java │ │ │ │ ├── RequestMatcherEntry.java │ │ │ │ ├── RequestMatchers.java │ │ │ │ └── package-info.java │ │ │ └── package-info.java │ │ └── resources/ │ │ ├── META-INF/ │ │ │ ├── services/ │ │ │ │ └── io.micrometer.context.ThreadLocalAccessor │ │ │ └── spring/ │ │ │ └── aot.factories │ │ └── org/ │ │ └── springframework/ │ │ └── security/ │ │ ├── default-ui.css │ │ ├── user-credentials-schema-postgres.sql │ │ ├── user-credentials-schema.sql │ │ └── user-entities-schema.sql │ └── test/ │ ├── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── security/ │ │ ├── MockFilterConfig.java │ │ ├── MockSecurityContextHolderStrategy.java │ │ ├── test/ │ │ │ └── web/ │ │ │ ├── CodecTestUtils.java │ │ │ └── reactive/ │ │ │ └── server/ │ │ │ ├── WebTestClientBuilder.java │ │ │ └── WebTestHandler.java │ │ └── web/ │ │ ├── DefaultRedirectStrategyTests.java │ │ ├── FilterChainProxyTests.java │ │ ├── FilterInvocationTests.java │ │ ├── FormPostRedirectStrategyTests.java │ │ ├── ObservationFilterChainDecoratorTests.java │ │ ├── PortMapperImplTests.java │ │ ├── RequestMatcherRedirectFilterTests.java │ │ ├── ServerWebExchangeThreadLocalAccessorTests.java │ │ ├── access/ │ │ │ ├── AuthorizationManagerWebInvocationPrivilegeEvaluatorTests.java │ │ │ ├── DelegatingAccessDeniedHandlerTests.java │ │ │ ├── DelegatingMissingAuthorityAccessDeniedHandlerTests.java │ │ │ ├── ExceptionTranslationFilterTests.java │ │ │ ├── HttpStatusAccessDeniedHandlerTests.java │ │ │ ├── NoOpAccessDeniedHandlerTests.java │ │ │ ├── RequestMatcherDelegatingAccessDeniedHandlerTests.java │ │ │ ├── RequestMatcherDelegatingWebInvocationPrivilegeEvaluatorTests.java │ │ │ ├── TestWebInvocationPrivilegeEvaluators.java │ │ │ ├── expression/ │ │ │ │ ├── AbstractVariableEvaluationContextPostProcessorTests.java │ │ │ │ ├── DefaultHttpSecurityExpressionHandlerTests.java │ │ │ │ ├── DelegatingEvaluationContextTests.java │ │ │ │ ├── WebExpressionAuthorizationManagerTests.java │ │ │ │ └── WebSecurityExpressionRootTests.java │ │ │ └── intercept/ │ │ │ ├── AuthorizationFilterTests.java │ │ │ ├── RequestKeyTests.java │ │ │ └── RequestMatcherDelegatingAuthorizationManagerTests.java │ │ ├── aot/ │ │ │ └── hint/ │ │ │ └── WebMvcSecurityRuntimeHintsTests.java │ │ ├── authentication/ │ │ │ ├── AbstractAuthenticationProcessingFilterTests.java │ │ │ ├── AbstractAuthenticationTargetUrlRequestHandlerTests.java │ │ │ ├── AnonymousAuthenticationFilterTests.java │ │ │ ├── AuthenticationEntryPointFailureHandlerTests.java │ │ │ ├── AuthenticationFilterTests.java │ │ │ ├── DefaultEqualsGrantedAuthority.java │ │ │ ├── DefaultLoginPageGeneratingFilterTests.java │ │ │ ├── DelegatingAuthenticationConverterTests.java │ │ │ ├── DelegatingAuthenticationEntryPointContextTests.java │ │ │ ├── DelegatingAuthenticationEntryPointTests.java │ │ │ ├── DelegatingAuthenticationFailureHandlerTests.java │ │ │ ├── ExceptionMappingAuthenticationFailureHandlerTests.java │ │ │ ├── ForwardAuthenticaionSuccessHandlerTests.java │ │ │ ├── ForwardAuthenticationFailureHandlerTests.java │ │ │ ├── HttpMessageConverterAuthenticationSuccessHandlerTests.java │ │ │ ├── HttpStatusEntryPointTests.java │ │ │ ├── LoginUrlAuthenticationEntryPointTests.java │ │ │ ├── NoOpAuthenticationEntryPointTests.java │ │ │ ├── RequestMatcherDelegatingAuthenticationManagerResolverTests.java │ │ │ ├── SavedRequestAwareAuthenticationSuccessHandlerTests.java │ │ │ ├── SimpleUrlAuthenticationFailureHandlerTests.java │ │ │ ├── SimpleUrlAuthenticationSuccessHandlerTests.java │ │ │ ├── UsernamePasswordAuthenticationFilterTests.java │ │ │ ├── logout/ │ │ │ │ ├── CompositeLogoutHandlerTests.java │ │ │ │ ├── CookieClearingLogoutHandlerTests.java │ │ │ │ ├── DelegatingLogoutSuccessHandlerTests.java │ │ │ │ ├── ForwardLogoutSuccessHandlerTests.java │ │ │ │ ├── HeaderWriterLogoutHandlerTests.java │ │ │ │ ├── HttpStatusReturningLogoutSuccessHandlerTests.java │ │ │ │ ├── LogoutHandlerTests.java │ │ │ │ ├── LogoutSuccessEventPublishingLogoutHandlerTests.java │ │ │ │ ├── SecurityContextLogoutHandlerTests.java │ │ │ │ └── SimpleUrlLogoutSuccessHandlerTests.java │ │ │ ├── ott/ │ │ │ │ ├── DefaultGenerateOneTimeTokenRequestResolverTests.java │ │ │ │ ├── GenerateOneTimeTokenFilterTests.java │ │ │ │ ├── OneTimeTokenAuthenticationConverterTests.java │ │ │ │ ├── OneTimeTokenAuthenticationFilterTests.java │ │ │ │ └── RedirectOneTimeTokenGenerationSuccessHandlerTests.java │ │ │ ├── password/ │ │ │ │ ├── HaveIBeenPwnedRestApiPasswordCheckerTests.java │ │ │ │ └── HaveIBeenPwnedRestApiReactivePasswordCheckerTests.java │ │ │ ├── preauth/ │ │ │ │ ├── AbstractPreAuthenticatedProcessingFilterTests.java │ │ │ │ ├── Http403ForbiddenEntryPointTests.java │ │ │ │ ├── PreAuthenticatedAuthenticationProviderTests.java │ │ │ │ ├── PreAuthenticatedAuthenticationTokenTests.java │ │ │ │ ├── PreAuthenticatedGrantedAuthoritiesUserDetailsServiceTests.java │ │ │ │ ├── PreAuthenticatedGrantedAuthoritiesWebAuthenticationDetailsTests.java │ │ │ │ ├── RequestAttributeAuthenticationFilterTests.java │ │ │ │ ├── header/ │ │ │ │ │ └── RequestHeaderAuthenticationFilterTests.java │ │ │ │ ├── j2ee/ │ │ │ │ │ ├── J2eeBasedPreAuthenticatedWebAuthenticationDetailsSourceTests.java │ │ │ │ │ ├── J2eePreAuthenticatedProcessingFilterTests.java │ │ │ │ │ └── WebXmlJ2eeDefinedRolesRetrieverTests.java │ │ │ │ ├── websphere/ │ │ │ │ │ └── WebSpherePreAuthenticatedProcessingFilterTests.java │ │ │ │ └── x509/ │ │ │ │ ├── SubjectDnX509PrincipalExtractorTests.java │ │ │ │ ├── SubjectX500PrincipalExtractorTests.java │ │ │ │ └── X509TestUtils.java │ │ │ ├── rememberme/ │ │ │ │ ├── AbstractRememberMeServicesTests.java │ │ │ │ ├── JdbcTokenRepositoryImplTests.java │ │ │ │ ├── NullRememberMeServicesTests.java │ │ │ │ ├── PersistentTokenBasedRememberMeServicesTests.java │ │ │ │ ├── RememberMeAuthenticationFilterTests.java │ │ │ │ └── TokenBasedRememberMeServicesTests.java │ │ │ ├── session/ │ │ │ │ ├── ChangeSessionIdAuthenticationStrategyTests.java │ │ │ │ ├── CompositeSessionAuthenticationStrategyTests.java │ │ │ │ ├── ConcurrentSessionControlAuthenticationStrategyTests.java │ │ │ │ ├── RegisterSessionAuthenticationStrategyTests.java │ │ │ │ └── SessionLimitTests.java │ │ │ ├── switchuser/ │ │ │ │ ├── SwitchUserFilterTests.java │ │ │ │ └── SwitchUserGrantedAuthorityTests.java │ │ │ ├── ui/ │ │ │ │ ├── DefaultLogoutPageGeneratingFilterTests.java │ │ │ │ ├── DefaultOneTimeTokenSubmitPageGeneratingFilterTests.java │ │ │ │ ├── DefaultResourcesFilterTests.java │ │ │ │ └── HtmlTemplatesTests.java │ │ │ └── www/ │ │ │ ├── BasicAuthenticationConverterTests.java │ │ │ ├── BasicAuthenticationEntryPointTests.java │ │ │ ├── BasicAuthenticationFilterTests.java │ │ │ ├── DigestAuthUtilsTests.java │ │ │ ├── DigestAuthenticationEntryPointTests.java │ │ │ └── DigestAuthenticationFilterTests.java │ │ ├── bind/ │ │ │ └── support/ │ │ │ └── AuthenticationPrincipalArgumentResolverTests.java │ │ ├── concurrent/ │ │ │ └── ConcurrentSessionFilterTests.java │ │ ├── context/ │ │ │ ├── AbstractSecurityWebApplicationInitializerTests.java │ │ │ ├── DelegatingSecurityContextRepositoryTests.java │ │ │ ├── HttpSessionSecurityContextRepositoryTests.java │ │ │ ├── RequestAttributeSecurityContextRepositoryTests.java │ │ │ ├── SaveContextOnUpdateOrErrorResponseWrapperTests.java │ │ │ ├── SecurityContextHolderFilterTests.java │ │ │ ├── SecurityContextPersistenceFilterTests.java │ │ │ ├── SecurityContextRepositoryTests.java │ │ │ └── request/ │ │ │ └── async/ │ │ │ ├── SecurityContextCallableProcessingInterceptorTests.java │ │ │ └── WebAsyncManagerIntegrationFilterTests.java │ │ ├── csrf/ │ │ │ ├── CookieCsrfTokenRepositoryTests.java │ │ │ ├── CsrfAuthenticationStrategyTests.java │ │ │ ├── CsrfFilterTests.java │ │ │ ├── CsrfLogoutHandlerTests.java │ │ │ ├── CsrfTokenAssert.java │ │ │ ├── CsrfTokenRequestAttributeHandlerTests.java │ │ │ ├── DefaultCsrfTokenTests.java │ │ │ ├── HttpSessionCsrfTokenRepositoryTests.java │ │ │ ├── MissingCsrfTokenExceptionTests.java │ │ │ ├── TestDeferredCsrfToken.java │ │ │ └── XorCsrfTokenRequestAttributeHandlerTests.java │ │ ├── debug/ │ │ │ └── DebugFilterTests.java │ │ ├── firewall/ │ │ │ ├── CompositeRequestRejectedHandlerTests.java │ │ │ ├── DefaultHttpFirewallTests.java │ │ │ ├── DefaultRequestRejectedHandlerTests.java │ │ │ ├── FirewalledResponseTests.java │ │ │ ├── HttpStatusRequestRejectedHandlerTests.java │ │ │ ├── RequestWrapperTests.java │ │ │ └── StrictHttpFirewallTests.java │ │ ├── header/ │ │ │ ├── HeaderWriterFilterTests.java │ │ │ └── writers/ │ │ │ ├── CacheControlHeadersWriterTests.java │ │ │ ├── ClearSiteDataHeaderWriterTests.java │ │ │ ├── CompositeHeaderWriterTests.java │ │ │ ├── ContentSecurityPolicyHeaderWriterTests.java │ │ │ ├── CrossOriginEmbedderPolicyHeaderWriterTests.java │ │ │ ├── CrossOriginOpenerPolicyHeaderWriterTests.java │ │ │ ├── CrossOriginResourcePolicyHeaderWriterTests.java │ │ │ ├── DelegatingRequestMatcherHeaderWriterTests.java │ │ │ ├── FeaturePolicyHeaderWriterTests.java │ │ │ ├── HpkpHeaderWriterTests.java │ │ │ ├── HstsHeaderWriterTests.java │ │ │ ├── PermissionsPolicyHeaderWriterTests.java │ │ │ ├── ReferrerPolicyHeaderWriterTests.java │ │ │ ├── StaticHeaderWriterTests.java │ │ │ ├── XContentTypeOptionsHeaderWriterTests.java │ │ │ ├── XXssProtectionHeaderWriterTests.java │ │ │ └── frameoptions/ │ │ │ ├── AbstractRequestParameterAllowFromStrategyTests.java │ │ │ ├── FrameOptionsHeaderWriterTests.java │ │ │ ├── RegExpAllowFromStrategyTests.java │ │ │ ├── StaticAllowFromStrategyTests.java │ │ │ ├── WhiteListedAllowFromStrategyTests.java │ │ │ └── XFrameOptionsHeaderWriterTests.java │ │ ├── jaasapi/ │ │ │ └── JaasApiIntegrationFilterTests.java │ │ ├── jackson/ │ │ │ ├── AbstractMixinTests.java │ │ │ ├── CookieMixinTests.java │ │ │ ├── DefaultCsrfTokenMixinTests.java │ │ │ ├── DefaultSavedRequestMixinTests.java │ │ │ ├── PreAuthenticatedAuthenticationTokenMixinTests.java │ │ │ ├── SavedCookieMixinTests.java │ │ │ ├── SwitchUserGrantedAuthorityMixInTests.java │ │ │ └── WebAuthenticationDetailsMixinTests.java │ │ ├── jackson2/ │ │ │ ├── AbstractMixinTests.java │ │ │ ├── CookieMixinTests.java │ │ │ ├── DefaultCsrfTokenMixinTests.java │ │ │ ├── DefaultSavedRequestMixinTests.java │ │ │ ├── PreAuthenticatedAuthenticationTokenMixinTests.java │ │ │ ├── SavedCookieMixinTests.java │ │ │ ├── SwitchUserGrantedAuthorityMixInTests.java │ │ │ └── WebAuthenticationDetailsMixinTests.java │ │ ├── method/ │ │ │ ├── ResolvableMethod.java │ │ │ └── annotation/ │ │ │ ├── AuthenticationPrincipalArgumentResolverTests.java │ │ │ ├── CsrfTokenArgumentResolverTests.java │ │ │ └── CurrentSecurityContextArgumentResolverTests.java │ │ ├── reactive/ │ │ │ └── result/ │ │ │ ├── method/ │ │ │ │ └── annotation/ │ │ │ │ ├── AuthenticationPrincipalArgumentResolverTests.java │ │ │ │ └── CurrentSecurityContextArgumentResolverTests.java │ │ │ └── view/ │ │ │ └── CsrfRequestDataValueProcessorTests.java │ │ ├── savedrequest/ │ │ │ ├── CookieRequestCacheTests.java │ │ │ ├── DefaultSavedRequestTests.java │ │ │ ├── HttpSessionRequestCacheTests.java │ │ │ ├── RequestCacheAwareFilterTests.java │ │ │ ├── SavedCookieTests.java │ │ │ ├── SavedRequestAwareWrapperTests.java │ │ │ └── SimpleSavedRequestTests.java │ │ ├── server/ │ │ │ ├── DefaultServerRedirectStrategyTests.java │ │ │ ├── DelegatingServerAuthenticationEntryPointTests.java │ │ │ ├── ExchangeMatcherRedirectWebFilterTests.java │ │ │ ├── FormPostServerRedirectStrategyTests.java │ │ │ ├── ObservationWebFilterChainDecoratorTests.java │ │ │ ├── WebFilterChainProxyTests.java │ │ │ ├── WebFilterExchangeTests.java │ │ │ ├── authentication/ │ │ │ │ ├── AnonymousAuthenticationWebFilterTests.java │ │ │ │ ├── AuthenticationConverterServerWebExchangeMatcherTests.java │ │ │ │ ├── AuthenticationWebFilterTests.java │ │ │ │ ├── DelegatingServerAuthenticationConverterTests.java │ │ │ │ ├── DelegatingServerAuthenticationSuccessHandlerTests.java │ │ │ │ ├── HttpBasicServerAuthenticationEntryPointTests.java │ │ │ │ ├── HttpStatusServerEntryPointTests.java │ │ │ │ ├── ReactivePreAuthenticatedAuthenticationManagerTests.java │ │ │ │ ├── RedirectServerAuthenticationEntryPointTests.java │ │ │ │ ├── RedirectServerAuthenticationFailureHandlerTests.java │ │ │ │ ├── RedirectServerAuthenticationSuccessHandlerTests.java │ │ │ │ ├── ServerAuthenticationEntryPointFailureHandlerTests.java │ │ │ │ ├── ServerFormLoginAuthenticationConverterTests.java │ │ │ │ ├── ServerHttpBasicAuthenticationConverterTests.java │ │ │ │ ├── ServerWebExchangeDelegatingReactiveAuthenticationManagerResolverTests.java │ │ │ │ ├── ServerX509AuthenticationConverterTests.java │ │ │ │ ├── SwitchUserWebFilterTests.java │ │ │ │ ├── logout/ │ │ │ │ │ ├── DelegatingServerLogoutHandlerTests.java │ │ │ │ │ ├── HeaderWriterServerLogoutHandlerTests.java │ │ │ │ │ ├── HttpStatusReturningServerLogoutSuccessHandlerTests.java │ │ │ │ │ ├── LogoutWebFilterTests.java │ │ │ │ │ └── WebSessionServerLogoutHandlerTests.java │ │ │ │ ├── ott/ │ │ │ │ │ ├── DefaultServerGenerateOneTimeTokenRequestResolverTests.java │ │ │ │ │ ├── GenerateOneTimeTokenWebFilterTests.java │ │ │ │ │ ├── ServerOneTimeTokenAuthenticationConverterTests.java │ │ │ │ │ └── ServerRedirectOneTimeTokenGenerationSuccessHandlerTests.java │ │ │ │ └── session/ │ │ │ │ ├── ConcurrentSessionControlServerAuthenticationSuccessHandlerTests.java │ │ │ │ ├── InMemoryReactiveSessionRegistryTests.java │ │ │ │ ├── InvalidateLeastUsedServerMaximumSessionsExceededHandlerTests.java │ │ │ │ ├── PreventLoginServerMaximumSessionsExceededHandlerTests.java │ │ │ │ └── RegisterSessionServerAuthenticationSuccessHandlerTests.java │ │ │ ├── authorization/ │ │ │ │ ├── AuthorizationWebFilterTests.java │ │ │ │ ├── DelegatingReactiveAuthorizationManagerTests.java │ │ │ │ ├── ExceptionTranslationWebFilterTests.java │ │ │ │ ├── HttpStatusServerAccessDeniedHandlerTests.java │ │ │ │ ├── IpAddressReactiveAuthorizationManagerTests.java │ │ │ │ └── ServerWebExchangeDelegatingServerAccessDeniedHandlerTests.java │ │ │ ├── context/ │ │ │ │ ├── NoOpServerSecurityContextRepositoryTests.java │ │ │ │ ├── ReactorContextWebFilterTests.java │ │ │ │ ├── SecurityContextServerWebExchangeWebFilterTests.java │ │ │ │ └── WebSessionServerSecurityContextRepositoryTests.java │ │ │ ├── csrf/ │ │ │ │ ├── CookieServerCsrfTokenRepositoryTests.java │ │ │ │ ├── CsrfServerLogoutHandlerTests.java │ │ │ │ ├── CsrfWebFilterTests.java │ │ │ │ ├── ServerCsrfTokenRequestAttributeHandlerTests.java │ │ │ │ ├── WebSessionServerCsrfTokenRepositoryTests.java │ │ │ │ └── XorServerCsrfTokenRequestAttributeHandlerTests.java │ │ │ ├── firewall/ │ │ │ │ └── StrictServerWebExchangeFirewallTests.java │ │ │ ├── header/ │ │ │ │ ├── CacheControlServerHttpHeadersWriterTests.java │ │ │ │ ├── ClearSiteDataServerHttpHeadersWriterTests.java │ │ │ │ ├── CompositeServerHttpHeadersWriterTests.java │ │ │ │ ├── ContentSecurityPolicyServerHttpHeadersWriterTests.java │ │ │ │ ├── ContentTypeOptionsServerHttpHeadersWriterTests.java │ │ │ │ ├── CrossOriginEmbedderPolicyServerHttpHeadersWriterTests.java │ │ │ │ ├── CrossOriginOpenerPolicyServerHttpHeadersWriterTests.java │ │ │ │ ├── CrossOriginResourcePolicyServerHttpHeadersWriterTests.java │ │ │ │ ├── FeaturePolicyServerHttpHeadersWriterTests.java │ │ │ │ ├── HttpHeaderWriterWebFilterTests.java │ │ │ │ ├── PermissionsPolicyServerHttpHeadersWriterTests.java │ │ │ │ ├── ReferrerPolicyServerHttpHeadersWriterTests.java │ │ │ │ ├── ServerWebExchangeDelegatingServerHttpHeadersWriterTests.java │ │ │ │ ├── StaticServerHttpHeadersWriterTests.java │ │ │ │ ├── StrictTransportSecurityServerHttpHeadersWriterTests.java │ │ │ │ ├── XContentTypeOptionsServerHttpHeadersWriterTests.java │ │ │ │ ├── XFrameOptionsServerHttpHeadersWriterTests.java │ │ │ │ └── XXssProtectionServerHttpHeadersWriterTests.java │ │ │ ├── jackson/ │ │ │ │ └── DefaultCsrfServerTokenMixinTests.java │ │ │ ├── jackson2/ │ │ │ │ └── DefaultCsrfServerTokenMixinTests.java │ │ │ ├── savedrequest/ │ │ │ │ ├── CookieServerRequestCacheTests.java │ │ │ │ ├── ServerRequestCacheWebFilterTests.java │ │ │ │ └── WebSessionServerRequestCacheTests.java │ │ │ ├── transport/ │ │ │ │ └── HttpsRedirectWebFilterTests.java │ │ │ ├── ui/ │ │ │ │ ├── DefaultResourcesWebFilterTests.java │ │ │ │ ├── LoginPageGeneratingWebFilterTests.java │ │ │ │ ├── LogoutPageGeneratingWebFilterTests.java │ │ │ │ └── OneTimeTokenSubmitPageGeneratingWebFilterTests.java │ │ │ └── util/ │ │ │ └── matcher/ │ │ │ ├── AndServerWebExchangeMatcherTests.java │ │ │ ├── IpAddressServerWebExchangeMatcherTests.java │ │ │ ├── MediaTypeServerWebExchangeMatcherTests.java │ │ │ ├── NegatedServerWebExchangeMatcherTests.java │ │ │ ├── OrServerWebExchangeMatcherTests.java │ │ │ ├── PathMatcherServerWebExchangeMatcherTests.java │ │ │ ├── PathPatternParserServerWebExchangeMatcherTests.java │ │ │ └── ServerWebExchangeMatchersTests.java │ │ ├── servlet/ │ │ │ ├── MockServletContext.java │ │ │ ├── TestMockHttpServletMappings.java │ │ │ ├── TestMockHttpServletRequests.java │ │ │ ├── support/ │ │ │ │ └── csrf/ │ │ │ │ └── CsrfRequestDataValueProcessorTests.java │ │ │ └── util/ │ │ │ └── matcher/ │ │ │ └── PathPatternRequestMatcherTests.java │ │ ├── servletapi/ │ │ │ ├── SecurityContextHolderAwareRequestFilterTests.java │ │ │ └── SecurityContextHolderAwareRequestWrapperTests.java │ │ ├── session/ │ │ │ ├── DefaultSessionAuthenticationStrategyTests.java │ │ │ ├── DisableEncodeUrlFilterTests.java │ │ │ ├── ForceEagerSessionCreationFilterTests.java │ │ │ ├── HttpSessionDestroyedEventTests.java │ │ │ ├── HttpSessionEventPublisherTests.java │ │ │ ├── MockApplicationListener.java │ │ │ ├── SessionInformationExpiredEventTests.java │ │ │ └── SessionManagementFilterTests.java │ │ ├── transport/ │ │ │ └── HttpsRedirectFilterTests.java │ │ └── util/ │ │ ├── OnCommittedResponseWrapperTests.java │ │ ├── TextEscapeUtilsTests.java │ │ ├── ThrowableAnalyzerTests.java │ │ ├── UrlUtilsTests.java │ │ └── matcher/ │ │ ├── AndRequestMatcherTests.java │ │ ├── DispatcherTypeRequestMatcherTests.java │ │ ├── ELRequestMatcherTests.java │ │ ├── InetAddressMatcherTests.java │ │ ├── InetAddressMatchersTests.java │ │ ├── IpAddressMatcherTests.java │ │ ├── IpInetAddressMatcherTests.java │ │ ├── MediaTypeRequestMatcherRequestHCNSTests.java │ │ ├── MediaTypeRequestMatcherTests.java │ │ ├── NegatedRequestMatcherTests.java │ │ ├── OrRequestMatcherTests.java │ │ ├── ParameterRequestMatcherTests.java │ │ ├── RegexRequestMatcherTests.java │ │ ├── RequestHeaderRequestMatcherTests.java │ │ ├── RequestMatcherEntryTests.java │ │ └── RequestMatchersTests.java │ └── resources/ │ ├── logback-test.xml │ ├── org/ │ │ └── springframework/ │ │ └── security/ │ │ ├── test.css │ │ └── web/ │ │ └── authentication/ │ │ └── DelegatingAuthenticationEntryPointTest-context.xml │ └── webxml/ │ ├── NoRoles.web.xml │ └── Role1-4.web.xml └── webauthn/ ├── spring-security-webauthn.gradle └── src/ ├── main/ │ ├── java/ │ │ └── org/ │ │ └── springframework/ │ │ └── security/ │ │ └── web/ │ │ └── webauthn/ │ │ ├── aot/ │ │ │ ├── PublicKeyCredentialUserEntityRuntimeHints.java │ │ │ ├── UserCredentialRuntimeHints.java │ │ │ └── package-info.java │ │ ├── api/ │ │ │ ├── AttestationConveyancePreference.java │ │ │ ├── AuthenticationExtensionsClientInput.java │ │ │ ├── AuthenticationExtensionsClientInputs.java │ │ │ ├── AuthenticationExtensionsClientOutput.java │ │ │ ├── AuthenticationExtensionsClientOutputs.java │ │ │ ├── AuthenticatorAssertionResponse.java │ │ │ ├── AuthenticatorAttachment.java │ │ │ ├── AuthenticatorAttestationResponse.java │ │ │ ├── AuthenticatorResponse.java │ │ │ ├── AuthenticatorSelectionCriteria.java │ │ │ ├── AuthenticatorTransport.java │ │ │ ├── Bytes.java │ │ │ ├── COSEAlgorithmIdentifier.java │ │ │ ├── CredProtectAuthenticationExtensionsClientInput.java │ │ │ ├── CredentialPropertiesOutput.java │ │ │ ├── CredentialRecord.java │ │ │ ├── ImmutableAuthenticationExtensionsClientInput.java │ │ │ ├── ImmutableAuthenticationExtensionsClientInputs.java │ │ │ ├── ImmutableAuthenticationExtensionsClientOutputs.java │ │ │ ├── ImmutableCredentialRecord.java │ │ │ ├── ImmutablePublicKeyCose.java │ │ │ ├── ImmutablePublicKeyCredentialUserEntity.java │ │ │ ├── PublicKeyCose.java │ │ │ ├── PublicKeyCredential.java │ │ │ ├── PublicKeyCredentialCreationOptions.java │ │ │ ├── PublicKeyCredentialDescriptor.java │ │ │ ├── PublicKeyCredentialParameters.java │ │ │ ├── PublicKeyCredentialRequestOptions.java │ │ │ ├── PublicKeyCredentialRpEntity.java │ │ │ ├── PublicKeyCredentialType.java │ │ │ ├── PublicKeyCredentialUserEntity.java │ │ │ ├── ResidentKeyRequirement.java │ │ │ ├── UserVerificationRequirement.java │ │ │ └── package-info.java │ │ ├── authentication/ │ │ │ ├── HttpSessionPublicKeyCredentialRequestOptionsRepository.java │ │ │ ├── PublicKeyCredentialRequestOptionsFilter.java │ │ │ ├── PublicKeyCredentialRequestOptionsRepository.java │ │ │ ├── WebAuthnAuthentication.java │ │ │ ├── WebAuthnAuthenticationFilter.java │ │ │ ├── WebAuthnAuthenticationProvider.java │ │ │ ├── WebAuthnAuthenticationRequestToken.java │ │ │ └── package-info.java │ │ ├── jackson/ │ │ │ ├── AttestationConveyancePreferenceJackson2Mixin.java │ │ │ ├── AttestationConveyancePreferenceJackson2Serializer.java │ │ │ ├── AttestationConveyancePreferenceMixin.java │ │ │ ├── AttestationConveyancePreferenceSerializer.java │ │ │ ├── AuthenticationExtensionsClientInputJackson2Mixin.java │ │ │ ├── AuthenticationExtensionsClientInputJackson2Serializer.java │ │ │ ├── AuthenticationExtensionsClientInputMixin.java │ │ │ ├── AuthenticationExtensionsClientInputSerializer.java │ │ │ ├── AuthenticationExtensionsClientInputsJackson2Mixin.java │ │ │ ├── AuthenticationExtensionsClientInputsJackson2Serializer.java │ │ │ ├── AuthenticationExtensionsClientInputsMixin.java │ │ │ ├── AuthenticationExtensionsClientInputsSerializer.java │ │ │ ├── AuthenticationExtensionsClientOutputsDeserializer.java │ │ │ ├── AuthenticationExtensionsClientOutputsJackson2Deserializer.java │ │ │ ├── AuthenticationExtensionsClientOutputsJackson2Mixin.java │ │ │ ├── AuthenticationExtensionsClientOutputsMixin.java │ │ │ ├── AuthenticatorAssertionResponseJackson2Mixin.java │ │ │ ├── AuthenticatorAssertionResponseMixin.java │ │ │ ├── AuthenticatorAttachmentDeserializer.java │ │ │ ├── AuthenticatorAttachmentJackson2Deserializer.java │ │ │ ├── AuthenticatorAttachmentJackson2Mixin.java │ │ │ ├── AuthenticatorAttachmentJackson2Serializer.java │ │ │ ├── AuthenticatorAttachmentMixin.java │ │ │ ├── AuthenticatorAttachmentSerializer.java │ │ │ ├── AuthenticatorAttestationResponseJackson2Mixin.java │ │ │ ├── AuthenticatorAttestationResponseMixin.java │ │ │ ├── AuthenticatorSelectionCriteriaMixin.java │ │ │ ├── AuthenticatorTransportDeserializer.java │ │ │ ├── AuthenticatorTransportJackson2Deserializer.java │ │ │ ├── AuthenticatorTransportJackson2Mixin.java │ │ │ ├── AuthenticatorTransportJackson2Serializer.java │ │ │ ├── AuthenticatorTransportMixin.java │ │ │ ├── AuthenticatorTransportSerializer.java │ │ │ ├── BytesJackson2Mixin.java │ │ │ ├── BytesJackson2Serializer.java │ │ │ ├── BytesMixin.java │ │ │ ├── BytesSerializer.java │ │ │ ├── COSEAlgorithmIdentifierDeserializer.java │ │ │ ├── COSEAlgorithmIdentifierJackson2Deserializer.java │ │ │ ├── COSEAlgorithmIdentifierJackson2Mixin.java │ │ │ ├── COSEAlgorithmIdentifierJackson2Serializer.java │ │ │ ├── COSEAlgorithmIdentifierMixin.java │ │ │ ├── COSEAlgorithmIdentifierSerializer.java │ │ │ ├── CredProtectAuthenticationExtensionsClientInputJackson2Mixin.java │ │ │ ├── CredProtectAuthenticationExtensionsClientInputJackson2Serializer.java │ │ │ ├── CredProtectAuthenticationExtensionsClientInputMixin.java │ │ │ ├── CredProtectAuthenticationExtensionsClientInputSerializer.java │ │ │ ├── CredentialPropertiesOutputJackson2Mixin.java │ │ │ ├── CredentialPropertiesOutputMixin.java │ │ │ ├── DurationJackson2Serializer.java │ │ │ ├── DurationSerializer.java │ │ │ ├── ImmutablePublicKeyCredentialUserEntityMixin.java │ │ │ ├── PublicKeyCredentialCreationOptionsJackson2Mixin.java │ │ │ ├── PublicKeyCredentialCreationOptionsMixin.java │ │ │ ├── PublicKeyCredentialJackson2Mixin.java │ │ │ ├── PublicKeyCredentialMixin.java │ │ │ ├── PublicKeyCredentialRequestOptionsJackson2Mixin.java │ │ │ ├── PublicKeyCredentialRequestOptionsMixin.java │ │ │ ├── PublicKeyCredentialTypeDeserializer.java │ │ │ ├── PublicKeyCredentialTypeJackson2Deserializer.java │ │ │ ├── PublicKeyCredentialTypeJackson2Mixin.java │ │ │ ├── PublicKeyCredentialTypeJackson2Serializer.java │ │ │ ├── PublicKeyCredentialTypeMixin.java │ │ │ ├── PublicKeyCredentialTypeSerializer.java │ │ │ ├── RelyingPartyPublicKeyJackson2Mixin.java │ │ │ ├── RelyingPartyPublicKeyMixin.java │ │ │ ├── ResidentKeyRequirementJackson2Mixin.java │ │ │ ├── ResidentKeyRequirementJackson2Serializer.java │ │ │ ├── ResidentKeyRequirementMixin.java │ │ │ ├── ResidentKeyRequirementSerializer.java │ │ │ ├── UserVerificationRequirementJackson2Mixin.java │ │ │ ├── UserVerificationRequirementJackson2Serializer.java │ │ │ ├── UserVerificationRequirementMixin.java │ │ │ ├── UserVerificationRequirementSerializer.java │ │ │ ├── WebAuthnAuthenticationMixin.java │ │ │ ├── WebauthnJackson2Module.java │ │ │ ├── WebauthnJacksonModule.java │ │ │ └── package-info.java │ │ ├── management/ │ │ │ ├── ImmutablePublicKeyCredentialCreationOptionsRequest.java │ │ │ ├── ImmutablePublicKeyCredentialRequestOptionsRequest.java │ │ │ ├── ImmutableRelyingPartyRegistrationRequest.java │ │ │ ├── JdbcPublicKeyCredentialUserEntityRepository.java │ │ │ ├── JdbcUserCredentialRepository.java │ │ │ ├── MapPublicKeyCredentialUserEntityRepository.java │ │ │ ├── MapUserCredentialRepository.java │ │ │ ├── PublicKeyCredentialCreationOptionsRequest.java │ │ │ ├── PublicKeyCredentialRequestOptionsRequest.java │ │ │ ├── PublicKeyCredentialUserEntityRepository.java │ │ │ ├── RelyingPartyAuthenticationRequest.java │ │ │ ├── RelyingPartyPublicKey.java │ │ │ ├── RelyingPartyRegistrationRequest.java │ │ │ ├── UserCredentialRepository.java │ │ │ ├── WebAuthnRelyingPartyOperations.java │ │ │ ├── Webauthn4JRelyingPartyOperations.java │ │ │ └── package-info.java │ │ └── registration/ │ │ ├── DefaultWebAuthnRegistrationPageGeneratingFilter.java │ │ ├── HtmlTemplates.java │ │ ├── HttpSessionPublicKeyCredentialCreationOptionsRepository.java │ │ ├── PublicKeyCredentialCreationOptionsFilter.java │ │ ├── PublicKeyCredentialCreationOptionsRepository.java │ │ ├── WebAuthnRegistrationFilter.java │ │ └── package-info.java │ └── resources/ │ └── META-INF/ │ └── spring/ │ └── aot.factories └── test/ └── java/ └── org/ └── springframework/ └── security/ └── web/ └── webauthn/ ├── aot/ │ ├── PublicKeyCredentialUserEntityRuntimeHintsTests.java │ └── UserCredentialRuntimeHintsTests.java ├── api/ │ ├── COSEAlgorithmIdentifierTests.java │ ├── TestAuthenticationAssertionResponses.java │ ├── TestAuthenticatorAttestationResponses.java │ ├── TestBytes.java │ ├── TestCredentialRecords.java │ ├── TestPublicKeyCredentialCreationOptions.java │ ├── TestPublicKeyCredentialRequestOptions.java │ ├── TestPublicKeyCredentialUserEntities.java │ └── TestPublicKeyCredentials.java ├── authentication/ │ ├── HttpSessionPublicKeyCredentialRequestOptionsRepositoryTests.java │ ├── PublicKeyCredentialRequestOptionsFilterTests.java │ ├── WebAuthnAuthenticationFilterTests.java │ ├── WebAuthnAuthenticationProviderTests.java │ └── WebAuthnAuthenticationTests.java ├── jackson/ │ ├── CredProtectAuthenticationExtensionsClientInputJackson2Tests.java │ ├── CredProtectAuthenticationExtensionsClientInputJacksonTests.java │ ├── Jackson2Tests.java │ ├── JacksonTests.java │ ├── PublicKeyCredentialJson.java │ └── WebAuthnAuthenticationMixinTests.java ├── management/ │ ├── JdbcPublicKeyCredentialUserEntityRepositoryTests.java │ ├── JdbcUserCredentialRepositoryTests.java │ ├── MapPublicKeyCredentialUserEntityRepositoryTests.java │ ├── MapUserCredentialRepositoryTests.java │ ├── TestPublicKeyCredentialRpEntities.java │ └── Webauthn4jRelyingPartyOperationsTests.java └── registration/ ├── DefaultWebAuthnRegistrationPageGeneratingFilterTests.java ├── HttpSessionPublicKeyCredentialCreationOptionsRepositoryTests.java ├── PublicKeyCredentialCreationOptionsFilterTests.java ├── WebAuthnRegistrationFilterTests.java └── WebAuthnRegistrationRequestJacksonTests.java ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ # EditorConfig for Spring Security # see https://github.com/spring-projects/spring-security/blob/master/CONTRIBUTING.adoc#mind-the-whitespace root = true [*] end_of_line = lf trim_trailing_whitespace = true insert_final_newline = true max_line_length = 120 [*.{java,xml}] indent_style = tab indent_size = 4 charset = utf-8 continuation_indent_size = 8 ij_smart_tabs = false ij_java_align_multiline_parameters = false [*.gradle] indent_style = tab ================================================ FILE: .gitattributes ================================================ # Normalize line endings to auto. * text auto # Ensure that line endings for DOS batch files are not modified. *.bat -text # Ensure the following are treated as binary. *.cer binary *.graffle binary *.jar binary *.jpeg binary *.jpg binary *.keystore binary *.odg binary *.otg binary *.png binary *.hsx binary *.serialized binary ================================================ FILE: .github/ISSUE_TEMPLATE/bug.md ================================================ --- name: Bug about: Create a bug report to help us improve title: '' labels: 'status: waiting-for-triage, type: bug' assignees: '' --- **Describe the bug** A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior. **Expected behavior** A clear and concise description of what you expected to happen. **Sample** A link to a GitHub repository with a [minimal, reproducible sample](https://stackoverflow.com/help/minimal-reproducible-example). Reports that include a sample will take priority over reports that do not. At times, we may require a sample, so it is good to try and include a sample up front. ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ blank_issues_enabled: false contact_links: - name: Community Support url: https://stackoverflow.com/questions/tagged/spring-security about: Please ask and answer questions on StackOverflow with the tag `spring-security`. ================================================ FILE: .github/ISSUE_TEMPLATE/enhancement.md ================================================ --- name: Enhancement about: Suggest an enhancement for this project title: '' labels: 'status: waiting-for-triage, type: enhancement' assignees: '' --- **Expected Behavior** **Current Behavior** **Context** ================================================ FILE: .github/ISSUE_TEMPLATE.md ================================================ ### Summary ### Actual Behavior ### Expected Behavior ### Configuration ### Version ### Sample ================================================ FILE: .github/PULL_REQUEST_TEMPLATE.md ================================================ ================================================ FILE: .github/dco.yml ================================================ require: members: false ================================================ FILE: .github/dependabot.yml ================================================ version: 2 registries: shibboleth: type: maven-repository url: https://build.shibboleth.net/maven/releases updates: # 6.5.x - package-ecosystem: gradle target-branch: 6.5.x directory: / schedule: interval: daily time: '03:00' timezone: Etc/UTC labels: - 'type: dependency-upgrade' registries: - shibboleth ignore: - dependency-name: com.nimbusds:nimbus-jose-jwt - dependency-name: org.python:jython - dependency-name: org.apache.directory.server:* - dependency-name: org.apache.directory.shared:* - dependency-name: org.junit:junit-bom update-types: - version-update:semver-major - dependency-name: org.mockito:mockito-bom update-types: - version-update:semver-major - dependency-name: '*' update-types: - version-update:semver-major - version-update:semver-minor - package-ecosystem: npm target-branch: 6.5.x directory: /docs schedule: interval: weekly labels: - 'type: task' - 'type: dependency-upgrade' - 'in: build' - package-ecosystem: github-actions target-branch: 6.5.x directory: / schedule: interval: weekly labels: - 'type: task' - 'type: dependency-upgrade' - 'in: build' # 7.0.x - package-ecosystem: gradle target-branch: 7.0.x directory: / schedule: interval: daily time: '03:00' timezone: Etc/UTC labels: - 'type: dependency-upgrade' registries: - shibboleth ignore: - dependency-name: com.nimbusds:nimbus-jose-jwt - dependency-name: io.spring.nullability:* - dependency-name: org.python:jython - dependency-name: org.apache.directory.server:* - dependency-name: org.apache.directory.shared:* - dependency-name: org.junit:junit-bom update-types: - version-update:semver-major - dependency-name: org.mockito:mockito-bom update-types: - version-update:semver-major - dependency-name: com.gradle.enterprise update-types: - version-update:semver-major - version-update:semver-minor - dependency-name: '*' update-types: - version-update:semver-major - version-update:semver-minor - package-ecosystem: npm target-branch: 7.0.x directory: /docs schedule: interval: weekly labels: - 'type: task' - 'type: dependency-upgrade' - 'in: build' - package-ecosystem: github-actions target-branch: 7.0.x directory: / schedule: interval: weekly labels: - 'type: task' - 'type: dependency-upgrade' - 'in: build' # main - package-ecosystem: gradle target-branch: main directory: / schedule: interval: daily time: '03:00' timezone: Etc/UTC labels: - 'type: dependency-upgrade' registries: - shibboleth ignore: - dependency-name: com.nimbusds:nimbus-jose-jwt - dependency-name: org.python:jython - dependency-name: org.apache.directory.server:* - dependency-name: org.apache.directory.shared:* - dependency-name: org.junit:junit-bom update-types: - version-update:semver-major - dependency-name: org.mockito:mockito-bom update-types: - version-update:semver-major - dependency-name: com.gradle.enterprise update-types: - version-update:semver-major - version-update:semver-minor - dependency-name: '*' update-types: - version-update:semver-major - package-ecosystem: npm target-branch: main directory: /docs schedule: interval: weekly labels: - 'type: task' - 'type: dependency-upgrade' - 'in: build' - package-ecosystem: github-actions target-branch: main directory: / schedule: interval: weekly labels: - 'type: task' - 'type: dependency-upgrade' - 'in: build' # docs-build - package-ecosystem: gradle target-branch: docs-build directory: / schedule: interval: daily time: '03:00' timezone: Etc/UTC labels: - 'type: dependency-upgrade' registries: - shibboleth ignore: - dependency-name: com.nimbusds:nimbus-jose-jwt - dependency-name: org.python:jython - dependency-name: org.apache.directory.server:* - dependency-name: org.apache.directory.shared:* - dependency-name: org.junit:junit-bom update-types: - version-update:semver-major - dependency-name: org.mockito:mockito-bom update-types: - version-update:semver-major - dependency-name: com.gradle.enterprise update-types: - version-update:semver-major - version-update:semver-minor - dependency-name: '*' update-types: - version-update:semver-major - package-ecosystem: npm target-branch: docs-build directory: / schedule: interval: weekly labels: - 'type: task' - 'type: dependency-upgrade' - 'in: build' - package-ecosystem: github-actions target-branch: docs-build directory: / schedule: interval: weekly labels: - 'type: task' - 'type: dependency-upgrade' - 'in: build' ================================================ FILE: .github/workflows/auto-merge-dependabot.yml ================================================ name: Merge Dependabot PR on: pull_request: branches: - main - '*.x' run-name: Merge Dependabot PR ${{ github.ref_name }} jobs: merge-dependabot-pr: permissions: write-all uses: spring-io/spring-github-workflows/.github/workflows/spring-merge-dependabot-pr.yml@v7 with: mergeArguments: --auto --rebase ================================================ FILE: .github/workflows/check-snapshots.yml ================================================ name: CI on: schedule: - cron: '0 10 * * *' # Once per day at 10am UTC workflow_dispatch: # Manual trigger env: DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }} permissions: contents: read jobs: snapshot-test: name: Test Against Snapshots uses: spring-io/spring-security-release-tools/.github/workflows/test.yml@b92832ecbc7cbe969201e6beafbde0ee400cf095 # v1.0.15 strategy: matrix: include: - java-version: 25 toolchain: 25 with: java-version: ${{ matrix.java-version }} test-args: --refresh-dependencies -PforceMavenRepositories=snapshot,https://oss.sonatype.org/content/repositories/snapshots -PisOverrideVersionCatalog -PtestToolchain=${{ matrix.toolchain }} -PspringFrameworkVersion=7.+ -PreactorVersion=2025.+ -PspringDataVersion=2025.+ --stacktrace secrets: inherit send-notification: name: Send Notification needs: [ snapshot-test ] if: ${{ !success() }} runs-on: ubuntu-latest steps: - name: Send Notification uses: spring-io/spring-security-release-tools/.github/actions/send-notification@b92832ecbc7cbe969201e6beafbde0ee400cf095 # v1.0.15 with: webhook-url: ${{ secrets.SPRING_SECURITY_CI_GCHAT_WEBHOOK_URL }} ================================================ FILE: .github/workflows/clean_build_artifacts.yml ================================================ name: Clean build artifacts on: schedule: - cron: '0 10 * * *' # Once per day at 10am UTC permissions: contents: read jobs: main: runs-on: ubuntu-latest if: ${{ github.repository == 'spring-projects/spring-security' }} permissions: contents: none steps: - name: Delete artifacts in cron job env: GH_ACTIONS_REPO_TOKEN: ${{ secrets.GH_ACTIONS_REPO_TOKEN }} run: | echo "Running clean build artifacts logic" output=$(curl -X GET -H "Authorization: token $GH_ACTIONS_REPO_TOKEN" https://api.github.com/repos/spring-projects/spring-security/actions/artifacts | grep '"id"' | cut -d : -f2 | sed 's/,*$//g') echo Output is $output for id in $output; do curl -X DELETE -H "Authorization: token $GH_ACTIONS_REPO_TOKEN" https://api.github.com/repos/spring-projects/spring-security/actions/artifacts/$id; done; ================================================ FILE: .github/workflows/codeql.yml ================================================ name: "CodeQL Advanced" on: push: pull_request: workflow_dispatch: schedule: # https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#schedule - cron: '0 5 * * *' permissions: read-all jobs: codeql-analysis-call: permissions: actions: read contents: read security-events: write uses: spring-io/github-actions/.github/workflows/codeql-analysis.yml@1 ================================================ FILE: .github/workflows/continuous-integration-workflow.yml ================================================ name: CI on: push: branches-ignore: - "dependabot/**" schedule: - cron: '0 10 * * *' # Once per day at 10am UTC workflow_dispatch: # Manual trigger env: DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }} permissions: contents: read jobs: build: name: Build uses: spring-io/spring-security-release-tools/.github/workflows/build.yml@b92832ecbc7cbe969201e6beafbde0ee400cf095 # v1.0.15 strategy: matrix: os: [ ubuntu-latest, windows-latest ] jdk: [ 25 ] with: runs-on: ${{ matrix.os }} java-version: ${{ matrix.jdk }} distribution: temurin secrets: inherit deploy-artifacts: name: Deploy Artifacts needs: [ build] uses: spring-io/spring-security-release-tools/.github/workflows/deploy-artifacts.yml@b92832ecbc7cbe969201e6beafbde0ee400cf095 # v1.0.15 with: should-deploy-artifacts: ${{ needs.build.outputs.should-deploy-artifacts }} default-publish-milestones-central: true java-version: 25 secrets: inherit deploy-schema: name: Deploy Schema needs: [ build ] uses: spring-io/spring-security-release-tools/.github/workflows/deploy-schema.yml@b92832ecbc7cbe969201e6beafbde0ee400cf095 # v1.0.15 with: should-deploy-schema: ${{ needs.build.outputs.should-deploy-artifacts }} java-version: 25 secrets: inherit perform-release: name: Perform Release needs: [ deploy-artifacts, deploy-schema ] uses: spring-io/spring-security-release-tools/.github/workflows/perform-release.yml@b92832ecbc7cbe969201e6beafbde0ee400cf095 # v1.0.15 with: should-perform-release: ${{ needs.deploy-artifacts.outputs.artifacts-deployed }} project-version: ${{ needs.deploy-artifacts.outputs.project-version }} milestone-repo-url: https://repo1.maven.org/maven2 release-repo-url: https://repo1.maven.org/maven2 artifact-path: org/springframework/security/spring-security-core slack-announcing-id: spring-security-announcing java-version: 25 secrets: inherit send-notification: name: Send Notification needs: [ perform-release ] if: ${{ !success() }} runs-on: ubuntu-latest steps: - name: Send Notification uses: spring-io/spring-security-release-tools/.github/actions/send-notification@b92832ecbc7cbe969201e6beafbde0ee400cf095 # v1.0.15 with: webhook-url: ${{ secrets.SPRING_SECURITY_CI_GCHAT_WEBHOOK_URL }} ================================================ FILE: .github/workflows/defer-issues.yml ================================================ name: Defer Issues on: workflow_dispatch: permissions: contents: read jobs: defer-issues: name: Defer Issues runs-on: ubuntu-latest if: github.repository_owner == 'spring-projects' permissions: issues: write steps: - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Compute Version id: compute-version uses: spring-io/spring-release-actions/compute-version@0.0.3 - name: Get Today's Release Version id: todays-release uses: spring-io/spring-release-actions/get-todays-release-version@0.0.3 with: snapshot-version: ${{ steps.compute-version.outputs.version }} milestone-repository: ${{ github.repository }} milestone-token: ${{ secrets.GITHUB_TOKEN }} - name: Compute Next Version id: next-version uses: spring-io/spring-release-actions/compute-next-version@0.0.3 with: version: ${{ steps.todays-release.outputs.release-version }} - name: Schedule Next Milestone uses: spring-io/spring-release-actions/schedule-milestone@0.0.3 with: version: ${{ steps.next-version.outputs.version }} version-date: ${{ steps.next-version.outputs.version-date }} repository: ${{ github.repository }} token: ${{ secrets.GITHUB_TOKEN }} - name: Move Open Issues to Next Milestone env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} CURRENT_MILESTONE: ${{ steps.todays-release.outputs.release-version }} NEXT_MILESTONE: ${{ steps.next-version.outputs.version }} run: | current_milestone_number=$(gh api repos/${{ github.repository }}/milestones \ --jq ".[] | select(.title == \"$CURRENT_MILESTONE\") | .number") if [ -z "$current_milestone_number" ]; then echo "No milestone found for $CURRENT_MILESTONE" exit 0 fi next_milestone_number=$(gh api repos/${{ github.repository }}/milestones \ --jq ".[] | select(.title == \"$NEXT_MILESTONE\") | .number") if [ -z "$next_milestone_number" ]; then echo "No milestone found for $NEXT_MILESTONE" exit 1 fi echo "Moving open issues from milestone '$CURRENT_MILESTONE' (#$current_milestone_number) to '$NEXT_MILESTONE' (#$next_milestone_number)" page=1 while true; do issues=$(gh api "repos/${{ github.repository }}/issues?milestone=$current_milestone_number&state=open&per_page=100&page=$page" \ --jq '.[].number') if [ -z "$issues" ]; then break fi for issue in $issues; do echo "Moving issue/PR #$issue to milestone $NEXT_MILESTONE" gh api repos/${{ github.repository }}/issues/$issue \ --method PATCH \ --field milestone=$next_milestone_number \ --silent done page=$((page + 1)) done echo "Done." ================================================ FILE: .github/workflows/deploy-docs.yml ================================================ name: Deploy Docs on: push: branches-ignore: - "gh-pages" - "dependabot/**" tags: '**' repository_dispatch: types: request-build-reference # legacy #schedule: #- cron: '0 10 * * *' # Once per day at 10am UTC workflow_dispatch: permissions: read-all jobs: build: runs-on: ubuntu-latest if: github.repository_owner == 'spring-projects' steps: - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: ref: docs-build fetch-depth: 1 - name: Dispatch (partial build) if: github.ref_type == 'branch' env: GH_TOKEN: ${{ secrets.GH_ACTIONS_REPO_TOKEN }} run: gh workflow run deploy-docs.yml -r $(git rev-parse --abbrev-ref HEAD) -f build-refname=${{ github.ref_name }} - name: Dispatch (full build) if: github.ref_type == 'tag' env: GH_TOKEN: ${{ secrets.GH_ACTIONS_REPO_TOKEN }} run: gh workflow run deploy-docs.yml -r $(git rev-parse --abbrev-ref HEAD) ================================================ FILE: .github/workflows/finalize-release.yml ================================================ name: Finalize Release on: workflow_dispatch: # Manual trigger inputs: version: description: The Spring Security release to finalize (e.g. 7.0.0-RC2) required: true env: DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }} permissions: contents: read jobs: perform-release: name: Perform Release uses: spring-io/spring-security-release-tools/.github/workflows/perform-release.yml@b92832ecbc7cbe969201e6beafbde0ee400cf095 # v1.0.15 with: should-perform-release: true project-version: ${{ inputs.version }} milestone-repo-url: https://repo1.maven.org/maven2 release-repo-url: https://repo1.maven.org/maven2 artifact-path: org/springframework/security/spring-security-core slack-announcing-id: spring-security-announcing secrets: inherit ================================================ FILE: .github/workflows/gradle-wrapper-upgrade-execution.yml ================================================ name: Execute Gradle Wrapper Upgrade on: schedule: - cron: '0 2 * * *' # 2am UTC workflow_dispatch: permissions: pull-requests: write jobs: upgrade_wrapper: name: Execution if: ${{ github.repository == 'spring-projects/spring-security' }} runs-on: ubuntu-latest steps: - name: Set up Git configuration env: TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | git config --global url."https://unused-username:${TOKEN}@github.com/".insteadOf "https://github.com/" git config --global user.name 'github-actions[bot]' git config --global user.email 'github-actions[bot]@users.noreply.github.com' - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Set up JDK 25 uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 with: java-version: '25' distribution: 'temurin' - name: Set up Gradle uses: gradle/setup-gradle@f29f5a9d7b09a7c6b29859002d29d24e1674c884 # v5.0.1 - name: Upgrade Wrappers run: ./gradlew clean upgradeGradleWrapperAll --continue -Porg.gradle.java.installations.auto-download=false env: WRAPPER_UPGRADE_GIT_TOKEN: ${{ secrets.GITHUB_TOKEN }} ================================================ FILE: .github/workflows/milestone-spring-releasetrain.yml ================================================ name: Check Milestone on: milestone: types: [created, opened, edited] env: DUE_ON: ${{ github.event.milestone.due_on }} TITLE: ${{ github.event.milestone.title }} permissions: contents: read jobs: spring-releasetrain-checks: name: Check DueOn is on a Release Date runs-on: ubuntu-latest if: ${{ github.repository == 'spring-projects/spring-security' }} permissions: contents: none steps: - name: Print Milestone Being Checked run: echo "Validating DueOn '$DUE_ON' for milestone '$TITLE'" - name: Validate DueOn if: env.DUE_ON != '' run: | export TOOL_VERSION=0.1.1 wget "https://repo.maven.apache.org/maven2/io/spring/releasetrain/spring-release-train-tools/$TOOL_VERSION/spring-release-train-tools-$TOOL_VERSION.jar" java -cp "spring-release-train-tools-$TOOL_VERSION.jar" io.spring.releasetrain.CheckMilestoneDueOnMain --dueOn "$DUE_ON" --expectedDayOfWeek MONDAY --expectedMondayCount 3 send-notification: name: Send Notification needs: [ spring-releasetrain-checks ] if: ${{ failure() || cancelled() }} runs-on: ubuntu-latest steps: - name: Send Notification uses: spring-io/spring-security-release-tools/.github/actions/send-notification@b92832ecbc7cbe969201e6beafbde0ee400cf095 # v1.0.15 with: webhook-url: ${{ secrets.SPRING_SECURITY_CI_GCHAT_WEBHOOK_URL }} ================================================ FILE: .github/workflows/pr-build-workflow.yml ================================================ name: PR Build on: pull_request permissions: contents: read jobs: build: name: Build runs-on: ubuntu-latest if: ${{ github.repository == 'spring-projects/spring-security' }} steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Set up gradle uses: spring-io/spring-gradle-build-action@efc55f07f4dfa22f2afd97f9ea1be4212eeed737 # v2.0.5 with: java-version: '25' distribution: 'temurin' - name: Build with Gradle run: ./gradlew clean build -PskipCheckExpectedBranchVersion --continue --scan generate-docs: name: Generate Docs runs-on: ubuntu-latest if: ${{ github.repository == 'spring-projects/spring-security' }} steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Set up gradle uses: spring-io/spring-gradle-build-action@efc55f07f4dfa22f2afd97f9ea1be4212eeed737 # v2.0.5 with: java-version: '25' distribution: 'temurin' - name: Run Antora run: ./gradlew -PbuildSrc.skipTests=true :spring-security-docs:antora - name: Upload Docs id: upload uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: name: docs path: docs/build/site overwrite: true send-notification: name: Send Notification needs: [ build, generate-docs ] if: ${{ failure() && github.event.pull_request.user.login == 'dependabot[bot]' && github.repository == 'spring-projects/spring-security' }} runs-on: ubuntu-latest steps: - name: Send Notification uses: spring-io/spring-security-release-tools/.github/actions/send-notification@b92832ecbc7cbe969201e6beafbde0ee400cf095 # v1.0.15 with: webhook-url: ${{ secrets.SPRING_SECURITY_CI_GCHAT_WEBHOOK_URL }} ================================================ FILE: .github/workflows/release-scheduler.yml ================================================ name: Release Scheduler on: schedule: - cron: '15 15 * * MON' # Every Monday at 3:15pm UTC workflow_dispatch: permissions: read-all jobs: dispatch_scheduled_releases: name: Dispatch scheduled releases if: github.repository_owner == 'spring-projects' strategy: matrix: # List of active maintenance branches. branch: [ main, 7.0.x, 6.5.x, 6.4.x, 6.3.x ] runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 1 - name: Dispatch env: GH_TOKEN: ${{ secrets.GH_ACTIONS_REPO_TOKEN }} run: gh workflow run update-scheduled-release-version.yml -r ${{ matrix.branch }} ================================================ FILE: .github/workflows/update-antora-ui-spring.yml ================================================ name: Update Antora UI Spring on: schedule: - cron: '0 10 * * *' # Once per day at 10am UTC workflow_dispatch: permissions: pull-requests: write issues: write contents: write jobs: update-antora-ui-spring: name: Update on Supported Branches if: ${{ github.repository == 'spring-projects/spring-security' }} runs-on: ubuntu-latest strategy: matrix: branch: [ '6.5.x', '7.0.x', 'main' ] steps: - uses: spring-io/spring-doc-actions/update-antora-spring-ui@415e2b11a766ba64799fffb5c97a4f7e17f677cf name: Update with: docs-branch: ${{ matrix.branch }} token: ${{ secrets.GITHUB_TOKEN }} antora-file-path: 'docs/antora-playbook.yml' update-antora-ui-spring-docs-build: name: Update on docs-build if: ${{ github.repository == 'spring-projects/spring-security' }} runs-on: ubuntu-latest steps: - uses: spring-io/spring-doc-actions/update-antora-spring-ui@415e2b11a766ba64799fffb5c97a4f7e17f677cf name: Update with: docs-branch: 'docs-build' token: ${{ secrets.GITHUB_TOKEN }} ================================================ FILE: .github/workflows/update-scheduled-release-version.yml ================================================ name: Update Scheduled Release Version on: workflow_dispatch: # Manual trigger only. Triggered by release-scheduler.yml on main. permissions: contents: read jobs: update-scheduled-release-version: name: Update Scheduled Release Version uses: spring-io/spring-security-release-tools/.github/workflows/update-scheduled-release-version.yml@b92832ecbc7cbe969201e6beafbde0ee400cf095 # v1.0.15 secrets: inherit send-notification: name: Send Notification needs: [ update-scheduled-release-version ] if: ${{ failure() || cancelled() }} runs-on: ubuntu-latest steps: - name: Send Notification uses: spring-io/spring-security-release-tools/.github/actions/send-notification@b92832ecbc7cbe969201e6beafbde0ee400cf095 # v1.0.15 with: webhook-url: ${{ secrets.SPRING_SECURITY_CI_GCHAT_WEBHOOK_URL }} ================================================ FILE: .gitignore ================================================ classes/ target/ */src/*/java/META-INF */src/META-INF/ */src/*/java/META-INF/ .classpath .springBeans .project .DS_Store .settings/ .idea/* out/ bin/ intellij/ build/ *.log *.log.* *.iml *.ipr *.iws .gradle/ atlassian-ide-plugin.xml !etc/eclipse/.checkstyle .checkstyle s101plugin.state .attach_pid* .~lock.*# .kotlin/ !.idea/checkstyle-idea.xml !.idea/externalDependencies.xml node_modules ================================================ FILE: .idea/checkstyle-idea.xml ================================================ ================================================ FILE: .idea/externalDependencies.xml ================================================ ================================================ FILE: .sdkmanrc ================================================ # Use sdkman to run "sdk env" to initialize with correct JDK version # Enable auto-env through the sdkman_auto_env config # See https://sdkman.io/usage#config # A summary is to add the following to ~/.sdkman/etc/config # sdkman_auto_env=true java=25-librca ================================================ FILE: .vscode/settings.json ================================================ { "java.gradle.buildServer.enabled": "off" } ================================================ FILE: CONTRIBUTING.adoc ================================================ = Contributing to Spring Security First off, thank you for taking the time to contribute! :+1: :tada: == Table of Contents * <> * <> * <> * <> * <> * <> * <> * <> * <> [[code-of-conduct]] == Code of Conduct This project is governed by the https://github.com/spring-projects/.github/blob/main/CODE_OF_CONDUCT.md[Spring code of conduct]. By participating you are expected to uphold this code. Please report unacceptable behavior to spring-code-of-conduct@pivotal.io. [[how-to-contribute]] == How to Contribute [[ask-questions]] === Ask Questions If you have a question, check Stack Overflow using https://stackoverflow.com/questions/tagged/spring-security+or+spring-ldap+or+spring-authorization-server+or+spring-session?tab=Newest[this list of tags]. Find an existing discussion, or start a new one if necessary. If you believe there is an issue, search through https://github.com/spring-projects/spring-security/issues[existing issues] trying a few different ways to find discussions, past or current, that are related to the issue. Reading those discussions helps you to learn about the issue, and helps us to make a decision. [[find-an-issue]] === Find an Existing Issue There are many issues in Spring Security with the labels https://github.com/spring-projects/spring-security/issues?q=is%3Aissue+is%3Aopen+label%3A%22status%3A+ideal-for-contribution%22[`ideal-for-contribution`] or https://github.com/spring-projects/spring-security/issues?q=is%3Aissue+is%3Aopen+label%3A%22status%3A+first-timers-only%22[`first-timers-only`] that are a great way to contribute to a discussion or <>. You can volunteer by commenting on these tickets, and we will assign them to you. [[create-an-issue]] === Create an Issue Reporting an issue or making a feature request is a great way to contribute. Your feedback and the conversations that result from it provide a continuous flow of ideas. However, before creating a ticket, please take the time to <> first. If you create an issue after a discussion on Stack Overflow, please provide a description in the issue instead of simply referring to Stack Overflow. The issue tracker is an important place of record for design discussions and should be self-sufficient. Once you're ready, create an issue on https://github.com/spring-projects/spring-security/issues[GitHub]. Many issues are caused by subtle behavior, typos, and unintended configuration. Creating a https://stackoverflow.com/help/minimal-reproducible-example[Minimal Reproducible Example] (starting with https://start.spring.io for example) of the problem helps the team quickly triage your issue and get to the core of the problem. We love contributors, and we may ask you to <>. [[issue-lifecycle]] === Issue Lifecycle When an issue is first created, it is flagged `waiting-for-triage` waiting for a team member to triage it. Once the issue has been reviewed, the team may ask for further information if needed, and based on the findings, the issue is either assigned a target branch (or no branch if a feature) or is closed with a specific status. The target branch is https://spring.io/projects/spring-security#support[the earliest supported branch] where <>. When a fix is ready, the issue is closed and may still be re-opened until the fix is released. After that the issue will typically no longer be reopened. In rare cases if the issue was not at all fixed, the issue may be re-opened. In most cases however any follow-up reports will need to be created as new issues with a fresh description. [[build-from-source]] === Build from Source See https://github.com/spring-projects/spring-security/tree/main#building-from-source[Build from Source] for instructions on how to check out, build, and import the Spring Security source code into your IDE. [[code-style]] === Source Code Style The wiki pages https://github.com/spring-projects/spring-framework/wiki/Code-Style[Code Style] and https://github.com/spring-projects/spring-framework/wiki/IntelliJ-IDEA-Editor-Settings[IntelliJ IDEA Editor Settings] define the source file coding standards we use along with some IDEA editor settings we customize. Additionally, since Streams are https://github.com/spring-projects/spring-security/issues/7154[much slower] than `for` loops, please use them judiciously. The team may ask you to change to a `for` loop if the given code is along a hot path. To format the code as well as check the style, run `./gradlew format && ./gradlew check`. [[submit-a-pull-request]] === Submit a Pull Request We are excited for your pull request! :heart: Please do your best to follow these steps. Don't worry if you don't get them all correct the first time, we will help you. 1. [[sign-cla]] All commits must include a __Signed-off-by__ trailer at the end of each commit message to indicate that the contributor agrees to the Developer Certificate of Origin. For additional details, please refer to the blog post https://spring.io/blog/2025/01/06/hello-dco-goodbye-cla-simplifying-contributions-to-spring[Hello DCO, Goodbye CLA: Simplifying Contributions to Spring]. 2. [[create-an-issue-list]] Must you https://github.com/spring-projects/spring-security/issues/new/choose[create an issue] first? No, but it is recommended for features and larger bug fixes. It's easier to discuss with the team first to determine the right fix or enhancement. For typos and straightforward bug fixes, starting with a pull request is encouraged. Please include a description for context and motivation. Note that the team may close your pull request if it's not a fit for the project. 3. [[choose-a-branch]] Always check out the branch indicated in the milestone and submit pull requests against it (for example, for milestone `5.8.3` use the `5.8.x` branch). If there is no milestone, choose `main`. Once merged, the fix will be forwarded-ported to applicable branches including `main`. 4. [[create-a-local-branch]] Create a local branch If this is for an issue, consider a branch name with the issue number, like `gh-22276`. 5. [[write-tests]] Add documentation and JUnit Tests for your changes. 6. [[update-copyright]] In all files you edited, if the copyright header is of the form 2002-20xx, update the final copyright year to the current year. 7. [[add-since]] If on `main`, add `@since` JavaDoc attributes to new public APIs that your PR adds 8. [[change-rnc]] If you are updating the XSD, please instead update the RNC file and then run `./gradlew :spring-security-config:rncToXsd`. 9. [[format-code]] For each commit, build the code using `./gradlew format && ./gradlew check`. This command ensures the code meets most of <>; a notable exception is import order. 10. [[commit-atomically]] Choose the granularity of your commits consciously and squash commits that represent multiple edits or corrections of the same logical change. See https://git-scm.com/book/en/Git-Tools-Rewriting-History[Rewriting History section of Pro Git] for an overview of streamlining the commit history. 11. [[format-commit-messages]] Format commit messages using 55 characters for the subject line, 72 characters per line for the description, followed by the issue fixed, for example, `Closes gh-22276`. See the https://git-scm.com/book/en/Distributed-Git-Contributing-to-a-Project#Commit-Guidelines[Commit Guidelines section of Pro Git] for best practices around commit messages, and use `git log` to see some examples. Favor imperative tense over present tense (use "Fix" instead of "Fixes"); avoid past tense (use "Fix" instead of "Fixed"). + [indent=0] ---- Address NullPointerException Closes gh-22276 ---- [[reference-issue]] 1. If there is a prior issue, reference the GitHub issue number in the description of the pull request. + [indent=0] ---- Closes gh-22276 ---- If accepted, your contribution may be heavily modified as needed prior to merging. You will likely retain author attribution for your Git commits granted that the bulk of your changes remain intact. You may also be asked to rework the submission. If asked to make corrections, simply push the changes against the same branch, and your pull request will be updated. In other words, you do not need to create a new pull request when asked to make changes. When it is time to merge, you'll be asked to squash your commits. ==== Participate in Reviews Helping to review pull requests is another great way to contribute. Your feedback can help to shape the implementation of new features. When reviewing pull requests, however, please refrain from approving or rejecting a PR unless you are a core committer for Spring Security. ================================================ FILE: LICENSE.txt ================================================ Apache License Version 2.0, January 2004 https://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright {yyyy} {name of copyright owner} 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. ================================================ FILE: README.adoc ================================================ image::https://badges.gitter.im/Join%20Chat.svg[Gitter,link=https://gitter.im/spring-projects/spring-security?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge] image:https://github.com/spring-projects/spring-security/actions/workflows/continuous-integration-workflow.yml/badge.svg?branch=main["Build Status", link="https://github.com/spring-projects/spring-security/actions/workflows/continuous-integration-workflow.yml"] image:https://img.shields.io/badge/Revved%20up%20by-Develocity-06A0CE?logo=Gradle&labelColor=02303A["Revved up by Develocity", link="https://ge.spring.io/scans?search.rootProjectNames=spring-security"] = Spring Security Spring Security provides security services for the https://docs.spring.io[Spring IO Platform]. Spring Security 6.0 requires Spring 6.0 as a minimum and also requires Java 17. For a detailed list of features and access to the latest release, please visit https://spring.io/projects[Spring projects]. == Code of Conduct Please see our https://github.com/spring-projects/.github/blob/main/CODE_OF_CONDUCT.md[code of conduct] == Downloading Artifacts See https://docs.spring.io/spring-security/reference/getting-spring-security.html[Getting Spring Security] for how to obtain Spring Security. == Documentation Be sure to read the https://docs.spring.io/spring-security/reference/[Spring Security Reference]. Extensive JavaDoc for the Spring Security code is also available in the https://docs.spring.io/spring-security/site/docs/current/api/[Spring Security API Documentation]. You may also want to check out https://docs.spring.io/spring-security/reference/whats-new.html[what's new in the latest release]. == Quick Start See https://docs.spring.io/spring-security/reference/servlet/getting-started.html[Hello Spring Security] to get started with a "Hello, World" application. == Building from Source Spring Security uses a https://gradle.org[Gradle]-based build system. In the instructions below, https://vimeo.com/34436402[`./gradlew`] is invoked from the root of the source tree and serves as a cross-platform, self-contained bootstrap mechanism for the build. === Prerequisites https://docs.github.com/en/get-started/quickstart/set-up-git[Git] and the https://www.oracle.com/java/technologies/downloads/#java17[JDK17 build]. Be sure that your `JAVA_HOME` environment variable points to the `jdk-17` folder extracted from the JDK download. === Check out sources [indent=0] ---- git clone git@github.com:spring-projects/spring-security.git ---- === Install all `spring-*.jar` into your local Maven repository. [indent=0] ---- ./gradlew publishToMavenLocal ---- === Compile and test; build all JARs, distribution zips, and docs [indent=0] ---- ./gradlew build ---- The reference docs are not currently included in the distribution zip. You can build the reference docs for this branch by running the following command: ---- ./gradlew :spring-security-docs:antora ---- That command publishes the docs site to the `_docs/build/site_` directory. The https://github.com/spring-projects/spring-security/tree/docs-build[playbook branch] describes how to build the reference docs in detail. Discover more commands with `./gradlew tasks`. === IDE setup (IntelliJ) No special steps are needed to open Spring Security in IntelliJ. === IDE setup (Eclipse and VS Code) To work in Eclipse or VS Code, first generate Eclipse metadata so you can import the project into Eclipse or VS Code: [indent=0] ---- ./gradlew cleanEclipse eclipse ---- If you have not built the project yet, run `./gradlew publishToMavenLocal` first so dependencies are resolved. *VS Code:* Open the repository root as a folder. The repository includes `.vscode/settings.json` which disables automatic Gradle import so that the generated Eclipse metadata (`.classpath`, `.project`) is used. Do not use the Gradle for Java extension to import the project. *Eclipse:* File → Import → General → Existing Projects into Workspace, then select the repository root. The build uses a custom Eclipse plugin to work around Gradle dependency cycles that confuse IDE metadata generation. You may see Eclipse warnings about `xml-apis` from some test dependencies; those are excluded in the build and can be ignored. == Getting Support Check out the https://stackoverflow.com/questions/tagged/spring-security[Spring Security tags on Stack Overflow]. https://spring.io/support[Commercial support] is available too. == Contributing https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request[Pull requests] are welcome; see the https://github.com/spring-projects/spring-security/blob/main/CONTRIBUTING.adoc[contributor guidelines] for details. == License Spring Security is Open Source software released under the https://www.apache.org/licenses/LICENSE-2.0.html[Apache 2.0 license]. ================================================ FILE: RELEASE.adoc ================================================ = Release Process The release process for Spring Security is entirely automated via the https://github.com/spring-io/spring-security-release-tools/blob/main/release-plugin/README.adoc[Spring Security Release Plugin] and https://github.com/spring-io/spring-security-release-tools/tree/main/.github/workflows[reusable workflows]. The following table outlines the steps that are taken by the automation. WARNING: The `5.8.x` branch does not have all of the improvements from the `6.x.x` branches. See "Status (5.8.x)" for which steps are still manual. In case of a failure, you can follow the links below to read about each step, which includes instructions for performing the step manually if applicable. See <> for troubleshooting tips. [cols="1,1,1"] |=== | Step | Status (5.8.x) | Status (6.0.x+) | <> | :white_check_mark: automated | :white_check_mark: automated | <> | :white_check_mark: automated | :white_check_mark: automated | <> | :white_check_mark: automated | :white_check_mark: automated | <> | :white_check_mark: automated | :white_check_mark: automated | <> | :white_check_mark: automated | :white_check_mark: automated | <> | :white_check_mark: automated | :white_check_mark: automated | <> | :white_check_mark: automated | :white_check_mark: automated | <> | :x: manual | :white_check_mark: automated | <> | :x: manual | :white_check_mark: automated | <> | :white_check_mark: automated | :white_check_mark: automated | <> | :white_check_mark: automated | :white_check_mark: automated | <> | :white_check_mark: automated | :white_check_mark: automated | <> | :x: manual | :x: manual |=== [#update-dependencies] == Update dependencies Dependency versions are managed in the file xref:./gradle/libs.versions.toml[libs.versions.toml] and are automatically updated by xref:./.github/dependabot.yml[dependabot]. [#check-all-issues-are-closed] == Check all issues are closed The first step of a release is to check if there are any open issues remaining in a milestone. NOTE: A scheduled release will not proceed if there are any open issues. TIP: If you need to prevent a release from occurring automatically, the easiest way to block a release is to add an unresolved issue to the milestone. The https://github.com/spring-io/spring-security-release-tools/blob/main/release-plugin/README.adoc#checkMilestoneHasNoOpenIssues[`checkMilestoneHasOpenIssues`] command will check if there are any open issues for the release. Before running the command manually, replace the following values: * `` - Replace with the title of the milestone you are releasing now (i.e. 5.5.0-RC1) * `` - Replace with a https://github.com/settings/tokens[GitHub personal access token] that has a scope of `public_repo`. This is optional since you are unlikely to reach the rate limit for such a simple check. [source,bash] ---- ./gradlew checkMilestoneHasOpenIssues -PnextVersion= -PgitHubAccessToken= ---- Alternatively, you can manually check using the https://github.com/spring-projects/spring-security/milestones[milestones] page. [#update-release-version] == Update release version If all issues for the release are <>, the version number is automatically updated using the milestone title. When performing this step manually, update the version number in `gradle.properties` for the release (for example `5.5.0`) and commit the change using the message "Release x.y.z". [#tag-release] == Tag release The release will automatically be tagged using the milestone title. It is not required to tag manually. However, you can perform this step manually by running the following command: [source,bash] ---- git tag 5.5.0 ---- [#push-release-commit] == Push release commit During a scheduled release, the release commit will automatically be pushed to trigger a build. If performing this step manually, you can push the commit and tag and GitHub actions will build and deploy the artifacts with the following command: [source,bash] ---- git push --atomic origin main 5.5.0 ---- The build will automatically wait for artifacts to be released to Maven Central. You can get notified manually when uploading is complete by running the following: [source,bash] ---- ./scripts/release/wait-for-done.sh 5.5.0 ---- [#build-locally] == Build All checks will automatically be performed by the build prior to uploading the artifacts to Maven Central. If something goes wrong, you can run the build locally using: [source,bash] ---- ./gradlew check ---- [#update-release-notes-on-github] == Update release notes on GitHub Once the release has been uploaded to Maven Central, release notes will automatically be generated and a GitHub release will be created. To do this manually, you can use the https://github.com/spring-io/spring-security-release-tools/blob/main/release-plugin/README.adoc#generateChangelog[`generateChangelog`] command to generate the release notes by replacing: * `` - Replace with the milestone you are releasing now (i.e. 5.5.0) [source,bash] ---- ./gradlew generateChangelog -PnextVersion= ---- Then copy the release notes to your clipboard (your mileage may vary with the following command): [source,bash] ---- cat build/changelog/release-notes.md | xclip -selection clipboard ---- Finally, create the https://github.com/spring-projects/spring-security/releases[release on GitHub], associate it with the tag, and paste the generated notes. Alternatively, you can run the https://github.com/spring-io/spring-security-release-tools/blob/main/release-plugin/README.adoc#createGitHubRelease[`createGitHubRelease`] command to perform these steps automatically, replacing: * `` - Replace with the milestone you are releasing now (i.e. 5.5.0) * `` - The name of the branch to be tagged (if the release commit has not already been tagged) * `` - Replace with a https://github.com/settings/tokens[GitHub personal access token] that has a scope of `write:org` [source,bash] ---- ./gradlew createGitHubRelease -PnextVersion= -Pbranch= -PcreateRelease=true -PgitHubAccessToken= ---- [#update-version-on-project-page] == Update version on project page The build will automatically update the project versions on https://spring.io/projects/spring-security#learn. To do this manually, you can use the https://github.com/spring-io/spring-security-release-tools/blob/main/release-plugin/README.adoc#createSaganRelease[`createSaganRelease`] and https://github.com/spring-io/spring-security-release-tools/blob/main/release-plugin/README.adoc#deleteSaganRelease[`deleteSaganRelease`] commands using the following parameters: * `` - Replace with the milestone you are releasing now (i.e. 5.5.0) * `` - Replace with the previous release which will be removed from the listed versions (i.e. 5.5.0-RC1) * `` - Replace with a https://github.com/settings/tokens[GitHub personal access token] that has a scope of `read:org` as https://spring.io/restdocs/index.html#authentication[documented for spring.io api] [source,bash] ---- ./gradlew createSaganRelease deleteSaganRelease -PnextVersion= -PpreviousVersion= -PgitHubAccessToken= ---- Alternatively, you can log into Contentful and update the versions manually on the Spring Security project page. [#close-create-milestone] == Close / Create milestone The release milestone will be automatically closed once the release is complete. To proceed manually, perform the following steps: 1. Visit https://github.com/spring-projects/spring-security/milestones[GitHub Milestones] and create a new milestone for the next release version 2. Move any open issues from the existing milestone you just released to the new milestone 3. Close the milestone for the release NOTE: Remember that scheduled releases <> if there are still open issues in the milestone. [#announce-release-on-slack] == Announce release on Slack The release will automatically be announced on Slack. If proceeding manually, announce the release on Slack in the channel https://pivotal.slack.com/messages/spring-release[#spring-release], including the keyword `+spring-security-announcing+` in the message. Something like: .... spring-security-announcing `5.5.0` is available now .... [#update-to-next-development-version] == Update to next development version After the release is complete and artifacts have been uploaded to Maven Central, the build will automatically update to the next development version, commit and push. If proceeding manually, update the version in `gradle.properties` to the next `+SNAPSHOT+` version with the commit message "Next development version" and then push. [#announce-release-on-other-channels] == Announce release on other channels * Create a blog post on Contentful * Tweet from https://twitter.com/springsecurity[@SpringSecurity] [[frequently-asked-questions]] == Frequently Asked Questions *When should I update dependencies manually?* Dependencies should be updated at the latest the end of the week prior to the release. This is usually the Friday following the 2nd Monday of the month (counting from the first week with a Monday). When in doubt, check the https://github.com/spring-projects/spring-security/milestones[milestones] page for release due dates. *When do scheduled releases occur?* Automated releases are scheduled to occur at *3:15 PM UTC* on the *3rd Monday of the month* (counting from the first week with a Monday). [NOTE] The scheduled release process currently runs every Monday but only releases when a release is due. See the performed checks below for more information. The automated release process occurs on the following branches: * `main` * `6.2.x` * `6.1.x` * `6.0.x` (commercial only) * `5.8.x` For each of the above branches, the automated process performs the following checks before proceeding with the release: 1. _Check if the milestone is due today._ This check compares the current (SNAPSHOT) version of the branch with available milestones and chooses the first match (sorted alphabetically). If the due date on the matched milestone is *not* today, the process stops. 2. _Check if all issues are closed._ This check uses the milestone from the previous step and looks for open issues. If any open issues are found, the process stops. [IMPORTANT] You should ensure all issues are closed or moved to another milestone prior to a scheduled release. If the above checks pass, the version number is updated (in `gradle.properties`) and a commit is pushed to trigger the CI process. *How do I trigger a release manually?* You can trigger a release manually in two ways: 1. Trigger a release for a particular branch via https://github.com/spring-projects/spring-security/actions/workflows/update-scheduled-release-version.yml[`update-scheduled-release-version.yml`] on the desired branch. The above checks are performed for that branch, and the release will proceed if all checks pass. _This is the recommended way to trigger a release that did not pass the above checks during a regularly scheduled release._ 2. Trigger releases for all branches via https://github.com/spring-projects/spring-security/actions/workflows/release-scheduler.yml[`release-scheduler.yml`] on the `main` branch. The above checks are performed for each branch, and only releases that pass all checks will proceed. *When should additional manual steps be performed?* All other automated steps listed above occur during the normal CI process. Additional manual steps can be performed at any time once the builds pass and releases are finished. *What if something goes wrong?* If the normal CI process fails, you can retry by re-running the failed jobs with the "Re-run failed jobs" option in GitHub Actions. If changes are required, you should revert the "Release x.y.z" commit, delete the tag, and proceed manually. ================================================ FILE: access/spring-security-access.gradle ================================================ plugins { id 'compile-warnings-error' id 'javadoc-warnings-error' } apply plugin: 'io.spring.convention.spring-module' dependencies { management platform(project(":spring-security-dependencies")) api project(':spring-security-crypto') api project(':spring-security-core') api 'org.springframework:spring-aop' api 'org.springframework:spring-beans' api 'org.springframework:spring-context' api 'org.springframework:spring-core' api 'org.springframework:spring-expression' api 'io.micrometer:micrometer-observation' optional project(':spring-security-acl') optional project(':spring-security-messaging') optional project(':spring-security-web') optional 'org.springframework:spring-websocket' optional 'com.fasterxml.jackson.core:jackson-databind' optional 'io.micrometer:context-propagation' optional 'io.projectreactor:reactor-core' optional 'jakarta.annotation:jakarta.annotation-api' optional 'org.aspectj:aspectjrt' optional 'org.springframework:spring-jdbc' optional 'org.springframework:spring-tx' optional 'org.jetbrains.kotlinx:kotlinx-coroutines-reactor' provided 'jakarta.servlet:jakarta.servlet-api' testImplementation project(path : ':spring-security-web', configuration : 'tests') testImplementation 'commons-collections:commons-collections' testImplementation 'io.projectreactor:reactor-test' testImplementation "org.assertj:assertj-core" testImplementation "org.junit.jupiter:junit-jupiter-api" testImplementation "org.junit.jupiter:junit-jupiter-params" testImplementation "org.junit.jupiter:junit-jupiter-engine" testImplementation "org.mockito:mockito-core" testImplementation "org.mockito:mockito-junit-jupiter" testImplementation "org.springframework:spring-core-test" testImplementation "org.springframework:spring-test" testImplementation 'org.skyscreamer:jsonassert' testImplementation 'org.springframework:spring-test' testImplementation 'org.jetbrains.kotlin:kotlin-reflect' testImplementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8' testImplementation 'io.mockk:mockk' testRuntimeOnly 'org.hsqldb:hsqldb' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' } ================================================ FILE: access/src/main/java/org/springframework/security/access/AccessDecisionManager.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.access; import java.util.Collection; import org.springframework.security.authentication.InsufficientAuthenticationException; import org.springframework.security.authorization.AuthorizationManager; import org.springframework.security.core.Authentication; /** * Makes a final access control (authorization) decision. * * @author Ben Alex * @deprecated Use {@link AuthorizationManager} instead */ @Deprecated public interface AccessDecisionManager { /** * Resolves an access control decision for the passed parameters. * @param authentication the caller invoking the method (not null) * @param object the secured object being called * @param configAttributes the configuration attributes associated with the secured * object being invoked * @throws AccessDeniedException if access is denied as the authentication does not * hold a required authority or ACL privilege * @throws InsufficientAuthenticationException if access is denied as the * authentication does not provide a sufficient level of trust */ void decide(Authentication authentication, Object object, Collection configAttributes) throws AccessDeniedException, InsufficientAuthenticationException; /** * Indicates whether this AccessDecisionManager is able to process * authorization requests presented with the passed ConfigAttribute. *

* This allows the AbstractSecurityInterceptor to check every * configuration attribute can be consumed by the configured * AccessDecisionManager and/or RunAsManager and/or * AfterInvocationManager. *

* @param attribute a configuration attribute that has been configured against the * AbstractSecurityInterceptor * @return true if this AccessDecisionManager can support the passed * configuration attribute */ boolean supports(ConfigAttribute attribute); /** * Indicates whether the AccessDecisionManager implementation is able to * provide access control decisions for the indicated secured object type. * @param clazz the class that is being queried * @return true if the implementation can process the indicated class */ boolean supports(Class clazz); } ================================================ FILE: access/src/main/java/org/springframework/security/access/AccessDecisionVoter.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.access; import java.util.Collection; import org.springframework.security.authorization.AuthorizationManager; import org.springframework.security.core.Authentication; /** * Indicates a class is responsible for voting on authorization decisions. *

* The coordination of voting (ie polling {@code AccessDecisionVoter}s, tallying their * responses, and making the final authorization decision) is performed by an * {@link org.springframework.security.access.AccessDecisionManager}. * * @author Ben Alex * @deprecated Use {@link AuthorizationManager} instead */ @Deprecated public interface AccessDecisionVoter { int ACCESS_GRANTED = 1; int ACCESS_ABSTAIN = 0; int ACCESS_DENIED = -1; /** * Indicates whether this {@code AccessDecisionVoter} is able to vote on the passed * {@code ConfigAttribute}. *

* This allows the {@code AbstractSecurityInterceptor} to check every configuration * attribute can be consumed by the configured {@code AccessDecisionManager} and/or * {@code RunAsManager} and/or {@code AfterInvocationManager}. * @param attribute a configuration attribute that has been configured against the * {@code AbstractSecurityInterceptor} * @return true if this {@code AccessDecisionVoter} can support the passed * configuration attribute */ boolean supports(ConfigAttribute attribute); /** * Indicates whether the {@code AccessDecisionVoter} implementation is able to provide * access control votes for the indicated secured object type. * @param clazz the class that is being queried * @return true if the implementation can process the indicated class */ boolean supports(Class clazz); /** * Indicates whether or not access is granted. *

* The decision must be affirmative ({@code ACCESS_GRANTED}), negative ( * {@code ACCESS_DENIED}) or the {@code AccessDecisionVoter} can abstain ( * {@code ACCESS_ABSTAIN}) from voting. Under no circumstances should implementing * classes return any other value. If a weighting of results is desired, this should * be handled in a custom * {@link org.springframework.security.access.AccessDecisionManager} instead. *

* Unless an {@code AccessDecisionVoter} is specifically intended to vote on an access * control decision due to a passed method invocation or configuration attribute * parameter, it must return {@code ACCESS_ABSTAIN}. This prevents the coordinating * {@code AccessDecisionManager} from counting votes from those * {@code AccessDecisionVoter}s without a legitimate interest in the access control * decision. *

* Whilst the secured object (such as a {@code MethodInvocation}) is passed as a * parameter to maximise flexibility in making access control decisions, implementing * classes should not modify it or cause the represented invocation to take place (for * example, by calling {@code MethodInvocation.proceed()}). * @param authentication the caller making the invocation * @param object the secured object being invoked * @param attributes the configuration attributes associated with the secured object * @return either {@link #ACCESS_GRANTED}, {@link #ACCESS_ABSTAIN} or * {@link #ACCESS_DENIED} */ int vote(Authentication authentication, S object, Collection attributes); } ================================================ FILE: access/src/main/java/org/springframework/security/access/AfterInvocationProvider.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.access; import java.util.Collection; import org.springframework.security.access.intercept.AfterInvocationProviderManager; import org.springframework.security.authorization.AuthorizationManager; import org.springframework.security.core.Authentication; /** * Indicates a class is responsible for participating in an * {@link AfterInvocationProviderManager} decision. * * @author Ben Alex * @see org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor * @see org.springframework.security.authorization.method.AuthorizationManagerAfterMethodInterceptor * @deprecated Use delegation with {@link AuthorizationManager} */ @Deprecated public interface AfterInvocationProvider { Object decide(Authentication authentication, Object object, Collection attributes, Object returnedObject) throws AccessDeniedException; /** * Indicates whether this AfterInvocationProvider is able to participate * in a decision involving the passed ConfigAttribute. *

* This allows the AbstractSecurityInterceptor to check every * configuration attribute can be consumed by the configured * AccessDecisionManager and/or RunAsManager and/or * AccessDecisionManager. *

* @param attribute a configuration attribute that has been configured against the * AbstractSecurityInterceptor * @return true if this AfterInvocationProvider can support the passed * configuration attribute */ boolean supports(ConfigAttribute attribute); /** * Indicates whether the AfterInvocationProvider is able to provide * "after invocation" processing for the indicated secured object type. * @param clazz the class of secure object that is being queried * @return true if the implementation can process the indicated class */ boolean supports(Class clazz); } ================================================ FILE: access/src/main/java/org/springframework/security/access/ConfigAttribute.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 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; /** * Stores a security system related configuration attribute. * *

* When an * {@link org.springframework.security.access.intercept.AbstractSecurityInterceptor} is * set up, a list of configuration attributes is defined for secure object patterns. These * configuration attributes have special meaning to a {@link RunAsManager}, * {@link AccessDecisionManager} or AccessDecisionManager delegate. * *

* Stored at runtime with other ConfigAttributes for the same secure object * target. * * @author Ben Alex * @deprecated In modern Spring Security APIs, each API manages its own configuration * context. As such there is no direct replacement for this interface. In the case of * method security, please see {@link SecurityAnnotationScanner} and * {@link AuthorizationManager}. In the case of channel security, please see * {@code HttpsRedirectFilter}. In the case of web security, please see * {@link AuthorizationManager}. */ @Deprecated @NullUnmarked public interface ConfigAttribute extends Serializable { /** * If the ConfigAttribute can be represented as a String and * that String is sufficient in precision to be relied upon as a * configuration parameter by a {@link RunAsManager}, {@link AccessDecisionManager} or * AccessDecisionManager delegate, this method should return such a * String. *

* If the ConfigAttribute cannot be expressed with sufficient precision * as a String, null should be returned. Returning * null will require any relying classes to specifically support the * ConfigAttribute implementation, so returning null should * be avoided unless actually required. * @return a representation of the configuration attribute (or null if * the configuration attribute cannot be expressed as a String with * sufficient precision). */ String getAttribute(); } ================================================ FILE: access/src/main/java/org/springframework/security/access/SecurityConfig.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.access; import java.io.Serial; import java.util.ArrayList; import java.util.List; import org.springframework.security.authorization.AuthorizationManager; import org.springframework.security.core.annotation.SecurityAnnotationScanner; import org.springframework.util.Assert; import org.springframework.util.StringUtils; /** * Stores a {@link ConfigAttribute} as a String. * * @author Ben Alex * @deprecated In modern Spring Security APIs, each API manages its own configuration * context. As such there is no direct replacement for this interface. In the case of * method security, please see {@link SecurityAnnotationScanner} and * {@link AuthorizationManager}. In the case of channel security, please see * {@code HttpsRedirectFilter}. In the case of web security, please see * {@link AuthorizationManager}. */ @Deprecated public class SecurityConfig implements ConfigAttribute { @Serial private static final long serialVersionUID = -7138084564199804304L; private final String attrib; public SecurityConfig(String config) { Assert.hasText(config, "You must provide a configuration attribute"); this.attrib = config; } @Override public boolean equals(Object obj) { if (obj instanceof ConfigAttribute attr) { return this.attrib.equals(attr.getAttribute()); } return false; } @Override public String getAttribute() { return this.attrib; } @Override public int hashCode() { return this.attrib.hashCode(); } @Override public String toString() { return this.attrib; } public static List createListFromCommaDelimitedString(String access) { return createList(StringUtils.commaDelimitedListToStringArray(access)); } public static List createList(String... attributeNames) { Assert.notNull(attributeNames, "You must supply an array of attribute names"); List attributes = new ArrayList<>(attributeNames.length); for (String attribute : attributeNames) { attributes.add(new SecurityConfig(attribute.trim())); } return attributes; } } ================================================ FILE: access/src/main/java/org/springframework/security/access/SecurityMetadataSource.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.access; import java.util.Collection; import org.springframework.aop.framework.AopInfrastructureBean; import org.springframework.security.access.intercept.AbstractSecurityInterceptor; import org.springframework.security.authorization.AuthorizationManager; import org.springframework.security.core.annotation.SecurityAnnotationScanner; /** * Implemented by classes that store and can identify the {@link ConfigAttribute}s that * applies to a given secure object invocation. * * @author Ben Alex * @deprecated In modern Spring Security APIs, each API manages its own configuration * context. As such there is no direct replacement for this interface. In the case of * method security, please see {@link SecurityAnnotationScanner} and * {@link AuthorizationManager}. In the case of channel security, please see * {@code HttpsRedirectFilter}. In the case of web security, please see * {@link AuthorizationManager}. */ @Deprecated public interface SecurityMetadataSource extends AopInfrastructureBean { /** * Accesses the {@code ConfigAttribute}s that apply to a given secure object. * @param object the object being secured * @return the attributes that apply to the passed in secured object. Should return an * empty collection if there are no applicable attributes. * @throws IllegalArgumentException if the passed object is not of a type supported by * the SecurityMetadataSource implementation */ Collection getAttributes(Object object) throws IllegalArgumentException; /** * If available, returns all of the {@code ConfigAttribute}s defined by the * implementing class. *

* This is used by the {@link AbstractSecurityInterceptor} to perform startup time * validation of each {@code ConfigAttribute} configured against it. * @return the {@code ConfigAttribute}s or {@code null} if unsupported */ Collection getAllConfigAttributes(); /** * Indicates whether the {@code SecurityMetadataSource} implementation is able to * provide {@code ConfigAttribute}s for the indicated secure object type. * @param clazz the class that is being queried * @return true if the implementation can process the indicated class */ boolean supports(Class clazz); } ================================================ FILE: access/src/main/java/org/springframework/security/access/annotation/AnnotationMetadataExtractor.java ================================================ /* * 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 org.springframework.security.access.annotation; import java.lang.annotation.Annotation; import java.util.Collection; import org.springframework.security.access.ConfigAttribute; /** * Strategy to process a custom security annotation to extract the relevant * {@code ConfigAttribute}s for securing a method. *

* Used by {@code SecuredAnnotationSecurityMetadataSource}. * * @author Luke Taylor * @deprecated Used only by now-deprecated classes. Consider * {@link org.springframework.security.authorization.method.SecuredAuthorizationManager} * for `@Secured` methods. */ @Deprecated public interface AnnotationMetadataExtractor { Collection extractAttributes(A securityAnnotation); } ================================================ FILE: access/src/main/java/org/springframework/security/access/annotation/Jsr250MethodSecurityMetadataSource.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.access.annotation; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collection; import java.util.List; import jakarta.annotation.security.DenyAll; import jakarta.annotation.security.PermitAll; import jakarta.annotation.security.RolesAllowed; import org.jspecify.annotations.NullUnmarked; import org.jspecify.annotations.Nullable; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.access.method.AbstractFallbackMethodSecurityMetadataSource; import org.springframework.util.StringUtils; /** * Sources method security metadata from major JSR 250 security annotations. * * @author Ben Alex * @since 2.0 * @deprecated Use * {@link org.springframework.security.authorization.method.Jsr250AuthorizationManager} * instead */ @NullUnmarked @Deprecated public class Jsr250MethodSecurityMetadataSource extends AbstractFallbackMethodSecurityMetadataSource { private String defaultRolePrefix = "ROLE_"; /** *

* Sets the default prefix to be added to {@link RolesAllowed}. For example, if * {@code @RolesAllowed("ADMIN")} or {@code @RolesAllowed("ADMIN")} is used, then the * role ROLE_ADMIN will be used when the defaultRolePrefix is "ROLE_" (default). *

* *

* If null or empty, then no default role prefix is used. *

* @param defaultRolePrefix the default prefix to add to roles. Default "ROLE_". */ public void setDefaultRolePrefix(String defaultRolePrefix) { this.defaultRolePrefix = defaultRolePrefix; } @Override protected Collection findAttributes(Class clazz) { return processAnnotations(clazz.getAnnotations()); } @Override protected Collection findAttributes(Method method, Class targetClass) { return processAnnotations(AnnotationUtils.getAnnotations(method)); } @Override public @Nullable Collection getAllConfigAttributes() { return null; } private @Nullable List processAnnotations(Annotation @Nullable [] annotations) { if (annotations == null || annotations.length == 0) { return null; } List attributes = new ArrayList<>(); for (Annotation annotation : annotations) { if (annotation instanceof DenyAll) { attributes.add(Jsr250SecurityConfig.DENY_ALL_ATTRIBUTE); return attributes; } if (annotation instanceof PermitAll) { attributes.add(Jsr250SecurityConfig.PERMIT_ALL_ATTRIBUTE); return attributes; } if (annotation instanceof RolesAllowed ra) { for (String allowed : ra.value()) { String defaultedAllowed = getRoleWithDefaultPrefix(allowed); attributes.add(new Jsr250SecurityConfig(defaultedAllowed)); } return attributes; } } return null; } private String getRoleWithDefaultPrefix(String role) { if (role == null) { return role; } if (!StringUtils.hasLength(this.defaultRolePrefix)) { return role; } if (role.startsWith(this.defaultRolePrefix)) { return role; } return this.defaultRolePrefix + role; } } ================================================ FILE: access/src/main/java/org/springframework/security/access/annotation/Jsr250SecurityConfig.java ================================================ /* * 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 org.springframework.security.access.annotation; import jakarta.annotation.security.DenyAll; import jakarta.annotation.security.PermitAll; import org.springframework.security.access.SecurityConfig; import org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor; /** * Security config applicable as a JSR 250 annotation attribute. * * @author Ryan Heaton * @since 2.0 * @deprecated Use {@link AuthorizationManagerBeforeMethodInterceptor#jsr250()} instead */ @Deprecated @SuppressWarnings("serial") public class Jsr250SecurityConfig extends SecurityConfig { public static final Jsr250SecurityConfig PERMIT_ALL_ATTRIBUTE = new Jsr250SecurityConfig(PermitAll.class.getName()); public static final Jsr250SecurityConfig DENY_ALL_ATTRIBUTE = new Jsr250SecurityConfig(DenyAll.class.getName()); public Jsr250SecurityConfig(String role) { super(role); } } ================================================ FILE: access/src/main/java/org/springframework/security/access/annotation/Jsr250Voter.java ================================================ /* * 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 org.springframework.security.access.annotation; import java.util.Collection; import org.springframework.security.access.AccessDecisionVoter; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; /** * Voter on JSR-250 configuration attributes. * * @author Ryan Heaton * @since 2.0 * @deprecated Use * {@link org.springframework.security.authorization.method.Jsr250AuthorizationManager} * instead */ @Deprecated public class Jsr250Voter implements AccessDecisionVoter { /** * The specified config attribute is supported if its an instance of a * {@link Jsr250SecurityConfig}. * @param configAttribute The config attribute. * @return whether the config attribute is supported. */ @Override public boolean supports(ConfigAttribute configAttribute) { return configAttribute instanceof Jsr250SecurityConfig; } /** * All classes are supported. * @param clazz the class. * @return true */ @Override public boolean supports(Class clazz) { return true; } /** * Votes according to JSR 250. *

* If no JSR-250 attributes are found, it will abstain, otherwise it will grant or * deny access based on the attributes that are found. * @param authentication The authentication object. * @param object The access object. * @param definition The configuration definition. * @return The vote. */ @Override public int vote(Authentication authentication, Object object, Collection definition) { boolean jsr250AttributeFound = false; for (ConfigAttribute attribute : definition) { if (Jsr250SecurityConfig.PERMIT_ALL_ATTRIBUTE.equals(attribute)) { return ACCESS_GRANTED; } if (Jsr250SecurityConfig.DENY_ALL_ATTRIBUTE.equals(attribute)) { return ACCESS_DENIED; } if (supports(attribute)) { jsr250AttributeFound = true; // Attempt to find a matching granted authority for (GrantedAuthority authority : authentication.getAuthorities()) { if (attribute.getAttribute().equals(authority.getAuthority())) { return ACCESS_GRANTED; } } } } return jsr250AttributeFound ? ACCESS_DENIED : ACCESS_ABSTAIN; } } ================================================ FILE: access/src/main/java/org/springframework/security/access/annotation/SecuredAnnotationSecurityMetadataSource.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.access.annotation; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collection; import java.util.List; import org.jspecify.annotations.NullUnmarked; import org.jspecify.annotations.Nullable; import org.springframework.core.GenericTypeResolver; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.access.SecurityConfig; import org.springframework.security.access.method.AbstractFallbackMethodSecurityMetadataSource; import org.springframework.util.Assert; /** * Sources method security metadata from Spring Security's {@link Secured} annotation. *

* Can also be used with custom security annotations by injecting an * {@link AnnotationMetadataExtractor}. The annotation type will then be obtained from the * generic parameter type supplied to this interface * * @author Ben Alex * @author Luke Taylor * @deprecated Use * {@link org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor#secured} */ @NullUnmarked @Deprecated @SuppressWarnings({ "unchecked" }) public class SecuredAnnotationSecurityMetadataSource extends AbstractFallbackMethodSecurityMetadataSource { private AnnotationMetadataExtractor annotationExtractor; private @Nullable Class annotationType; public SecuredAnnotationSecurityMetadataSource() { this(new SecuredAnnotationMetadataExtractor()); } public SecuredAnnotationSecurityMetadataSource(AnnotationMetadataExtractor annotationMetadataExtractor) { Assert.notNull(annotationMetadataExtractor, "annotationMetadataExtractor cannot be null"); this.annotationExtractor = annotationMetadataExtractor; this.annotationType = (Class) GenericTypeResolver .resolveTypeArgument(this.annotationExtractor.getClass(), AnnotationMetadataExtractor.class); Assert.notNull(this.annotationType, () -> this.annotationExtractor.getClass().getName() + " must supply a generic parameter for AnnotationMetadataExtractor"); } @Override protected Collection findAttributes(Class clazz) { return processAnnotation(AnnotationUtils.findAnnotation(clazz, this.annotationType)); } @Override protected Collection findAttributes(Method method, Class targetClass) { return processAnnotation(AnnotationUtils.findAnnotation(method, this.annotationType)); } @Override public @Nullable Collection getAllConfigAttributes() { return null; } private @Nullable Collection processAnnotation(@Nullable Annotation annotation) { return (annotation != null) ? this.annotationExtractor.extractAttributes(annotation) : null; } static class SecuredAnnotationMetadataExtractor implements AnnotationMetadataExtractor { @Override public Collection extractAttributes(Secured secured) { String[] attributeTokens = secured.value(); List attributes = new ArrayList<>(attributeTokens.length); for (String token : attributeTokens) { attributes.add(new SecurityConfig(token)); } return attributes; } } } ================================================ FILE: access/src/main/java/org/springframework/security/access/event/AbstractAuthorizationEvent.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.access.event; import org.springframework.context.ApplicationEvent; /** * Abstract superclass for all security interception related events. * * @author Ben Alex * @deprecated Authorization events have moved. Consider * {@link org.springframework.security.authorization.event.AuthorizationGrantedEvent} and * {@link org.springframework.security.authorization.event.AuthorizationDeniedEvent} */ @Deprecated public abstract class AbstractAuthorizationEvent extends ApplicationEvent { /** * Construct the event, passing in the secure object being intercepted. * @param secureObject the secure object */ public AbstractAuthorizationEvent(Object secureObject) { super(secureObject); } } ================================================ FILE: access/src/main/java/org/springframework/security/access/event/AuthenticationCredentialsNotFoundEvent.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.access.event; import java.util.Collection; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException; import org.springframework.util.Assert; /** * Indicates a secure object invocation failed because the Authentication * could not be obtained from the SecurityContextHolder. * * @author Ben Alex * @deprecated Authentication is now separated from authorization. Consider * {@link org.springframework.security.authentication.event.AbstractAuthenticationFailureEvent} * instead. */ @Deprecated @SuppressWarnings("serial") public class AuthenticationCredentialsNotFoundEvent extends AbstractAuthorizationEvent { private final AuthenticationCredentialsNotFoundException credentialsNotFoundException; private final Collection configAttribs; /** * Construct the event. * @param secureObject the secure object * @param attributes that apply to the secure object * @param credentialsNotFoundException exception returned to the caller (contains * reason) * */ public AuthenticationCredentialsNotFoundEvent(Object secureObject, Collection attributes, AuthenticationCredentialsNotFoundException credentialsNotFoundException) { super(secureObject); Assert.isTrue(attributes != null && credentialsNotFoundException != null, "All parameters are required and cannot be null"); this.configAttribs = attributes; this.credentialsNotFoundException = credentialsNotFoundException; } public Collection getConfigAttributes() { return this.configAttribs; } public AuthenticationCredentialsNotFoundException getCredentialsNotFoundException() { return this.credentialsNotFoundException; } } ================================================ FILE: access/src/main/java/org/springframework/security/access/event/AuthorizationFailureEvent.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.access.event; import java.util.Collection; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.core.Authentication; import org.springframework.util.Assert; /** * Indicates a secure object invocation failed because the principal could not be * authorized for the request. * *

* This event might be thrown as a result of either an * {@link org.springframework.security.access.AccessDecisionManager AccessDecisionManager} * or an {@link org.springframework.security.access.intercept.AfterInvocationManager * AfterInvocationManager}. * * @author Ben Alex * @deprecated Use * {@link org.springframework.security.authorization.event.AuthorizationDeniedEvent} * instead */ @Deprecated @SuppressWarnings("serial") public class AuthorizationFailureEvent extends AbstractAuthorizationEvent { private final AccessDeniedException accessDeniedException; private final Authentication authentication; private final Collection configAttributes; /** * Construct the event. * @param secureObject the secure object * @param attributes that apply to the secure object * @param authentication that was found in the SecurityContextHolder * @param accessDeniedException that was returned by the * AccessDecisionManager * @throws IllegalArgumentException if any null arguments are presented. */ public AuthorizationFailureEvent(Object secureObject, Collection attributes, Authentication authentication, AccessDeniedException accessDeniedException) { super(secureObject); Assert.isTrue(attributes != null && authentication != null && accessDeniedException != null, "All parameters are required and cannot be null"); this.configAttributes = attributes; this.authentication = authentication; this.accessDeniedException = accessDeniedException; } public AccessDeniedException getAccessDeniedException() { return this.accessDeniedException; } public Authentication getAuthentication() { return this.authentication; } public Collection getConfigAttributes() { return this.configAttributes; } } ================================================ FILE: access/src/main/java/org/springframework/security/access/event/AuthorizedEvent.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.access.event; import java.util.Collection; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.core.Authentication; import org.springframework.util.Assert; /** * Event indicating a secure object was invoked successfully. *

* Published just before the secure object attempts to proceed. *

* * @author Ben Alex * @deprecated Use * {@link org.springframework.security.authorization.event.AuthorizationGrantedEvent} * instead */ @Deprecated @SuppressWarnings("serial") public class AuthorizedEvent extends AbstractAuthorizationEvent { private final Authentication authentication; private final Collection configAttributes; /** * Construct the event. * @param secureObject the secure object * @param attributes that apply to the secure object * @param authentication that successfully called the secure object * */ public AuthorizedEvent(Object secureObject, Collection attributes, Authentication authentication) { super(secureObject); Assert.isTrue(attributes != null && authentication != null, "All parameters are required and cannot be null"); this.configAttributes = attributes; this.authentication = authentication; } public Authentication getAuthentication() { return this.authentication; } public Collection getConfigAttributes() { return this.configAttributes; } } ================================================ FILE: access/src/main/java/org/springframework/security/access/event/LoggerListener.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.access.event; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.context.ApplicationListener; import org.springframework.core.log.LogMessage; /** * Outputs interceptor-related application events to Commons Logging. *

* All failures are logged at the warning level, with success events logged at the * information level, and public invocation events logged at the debug level. *

* * @author Ben Alex * @deprecated Logging is now embedded in Spring Security components. If you need further * logging, please consider using your own {@link ApplicationListener} */ @Deprecated public class LoggerListener implements ApplicationListener { private static final Log logger = LogFactory.getLog(LoggerListener.class); @Override public void onApplicationEvent(AbstractAuthorizationEvent event) { if (event instanceof AuthenticationCredentialsNotFoundEvent) { onAuthenticationCredentialsNotFoundEvent((AuthenticationCredentialsNotFoundEvent) event); } if (event instanceof AuthorizationFailureEvent) { onAuthorizationFailureEvent((AuthorizationFailureEvent) event); } if (event instanceof AuthorizedEvent) { onAuthorizedEvent((AuthorizedEvent) event); } if (event instanceof PublicInvocationEvent) { onPublicInvocationEvent((PublicInvocationEvent) event); } } private void onAuthenticationCredentialsNotFoundEvent(AuthenticationCredentialsNotFoundEvent authEvent) { logger.warn(LogMessage.format( "Security interception failed due to: %s; secure object: %s; configuration attributes: %s", authEvent.getCredentialsNotFoundException(), authEvent.getSource(), authEvent.getConfigAttributes())); } private void onPublicInvocationEvent(PublicInvocationEvent event) { logger.info(LogMessage.format("Security interception not required for public secure object: %s", event.getSource())); } private void onAuthorizedEvent(AuthorizedEvent authEvent) { logger.info(LogMessage.format( "Security authorized for authenticated principal: %s; secure object: %s; configuration attributes: %s", authEvent.getAuthentication(), authEvent.getSource(), authEvent.getConfigAttributes())); } private void onAuthorizationFailureEvent(AuthorizationFailureEvent authEvent) { logger.warn(LogMessage.format( "Security authorization failed due to: %s; authenticated principal: %s; secure object: %s; configuration attributes: %s", authEvent.getAccessDeniedException(), authEvent.getAuthentication(), authEvent.getSource(), authEvent.getConfigAttributes())); } } ================================================ FILE: access/src/main/java/org/springframework/security/access/event/PublicInvocationEvent.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.access.event; import org.springframework.security.authorization.event.AuthorizationGrantedEvent; /** * Event that is generated whenever a public secure object is invoked. *

* A public secure object is a secure object that has no ConfigAttributes * defined. A public secure object will not cause the SecurityContextHolder * to be inspected or authenticated, and no authorization will take place. *

*

* Published just before the secure object attempts to proceed. *

* * @author Ben Alex * @deprecated Only used by now-deprecated classes. Consider * {@link AuthorizationGrantedEvent#getSource()} to deduce public invocations. */ @Deprecated @SuppressWarnings("serial") public class PublicInvocationEvent extends AbstractAuthorizationEvent { /** * Construct the event, passing in the public secure object. * @param secureObject the public secure object */ public PublicInvocationEvent(Object secureObject) { super(secureObject); } } ================================================ FILE: access/src/main/java/org/springframework/security/access/event/package-info.java ================================================ /* * Copyright 2004-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * Authorization event and listener classes. */ @NullMarked package org.springframework.security.access.event; import org.jspecify.annotations.NullMarked; ================================================ FILE: access/src/main/java/org/springframework/security/access/expression/method/AbstractExpressionBasedMethodConfigAttribute.java ================================================ /* * 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 org.springframework.security.access.expression.method; import org.jspecify.annotations.NullUnmarked; import org.jspecify.annotations.Nullable; import org.springframework.expression.Expression; import org.springframework.expression.ParseException; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.security.access.ConfigAttribute; import org.springframework.util.Assert; /** * Contains both filtering and authorization expression meta-data for Spring-EL based * access control. *

* Base class for pre or post-invocation phases of a method invocation. *

* Either filter or authorization expressions may be null, but not both. * * @author Luke Taylor * @since 3.0 * @deprecated Use {@link org.springframework.security.authorization.AuthorizationManager} * interceptors instead */ @NullUnmarked @Deprecated abstract class AbstractExpressionBasedMethodConfigAttribute implements ConfigAttribute { private final @Nullable Expression filterExpression; private final @Nullable Expression authorizeExpression; /** * Parses the supplied expressions as Spring-EL. */ AbstractExpressionBasedMethodConfigAttribute(String filterExpression, String authorizeExpression) throws ParseException { Assert.isTrue(filterExpression != null || authorizeExpression != null, "Filter and authorization Expressions cannot both be null"); SpelExpressionParser parser = new SpelExpressionParser(); this.filterExpression = (filterExpression != null) ? parser.parseExpression(filterExpression) : null; this.authorizeExpression = (authorizeExpression != null) ? parser.parseExpression(authorizeExpression) : null; } AbstractExpressionBasedMethodConfigAttribute(Expression filterExpression, Expression authorizeExpression) throws ParseException { Assert.isTrue(filterExpression != null || authorizeExpression != null, "Filter and authorization Expressions cannot both be null"); this.filterExpression = (filterExpression != null) ? filterExpression : null; this.authorizeExpression = (authorizeExpression != null) ? authorizeExpression : null; } Expression getFilterExpression() { return this.filterExpression; } Expression getAuthorizeExpression() { return this.authorizeExpression; } @Override public @Nullable String getAttribute() { return null; } } ================================================ FILE: access/src/main/java/org/springframework/security/access/expression/method/ExpressionBasedAnnotationAttributeFactory.java ================================================ /* * 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 org.springframework.security.access.expression.method; import org.jspecify.annotations.NullUnmarked; import org.jspecify.annotations.Nullable; import org.springframework.expression.Expression; import org.springframework.expression.ExpressionParser; import org.springframework.expression.ParseException; import org.springframework.security.access.prepost.PostInvocationAttribute; import org.springframework.security.access.prepost.PreInvocationAttribute; import org.springframework.security.access.prepost.PrePostInvocationAttributeFactory; import org.springframework.util.Assert; /** * {@link PrePostInvocationAttributeFactory} which interprets the annotation value as an * expression to be evaluated at runtime. * * @author Luke Taylor * @author Rob Winch * @since 3.0 * @deprecated Use {@link org.springframework.security.authorization.AuthorizationManager} * interceptors instead */ @NullUnmarked @Deprecated public class ExpressionBasedAnnotationAttributeFactory implements PrePostInvocationAttributeFactory { private final Object parserLock = new Object(); private @Nullable ExpressionParser parser; private MethodSecurityExpressionHandler handler; public ExpressionBasedAnnotationAttributeFactory(MethodSecurityExpressionHandler handler) { Assert.notNull(handler, "handler cannot be null"); this.handler = handler; } @Override public PreInvocationAttribute createPreInvocationAttribute(String preFilterAttribute, String filterObject, String preAuthorizeAttribute) { try { // TODO: Optimization of permitAll ExpressionParser parser = getParser(); Expression preAuthorizeExpression = (preAuthorizeAttribute != null) ? parser.parseExpression(preAuthorizeAttribute) : parser.parseExpression("permitAll"); Expression preFilterExpression = (preFilterAttribute != null) ? parser.parseExpression(preFilterAttribute) : null; return new PreInvocationExpressionAttribute(preFilterExpression, filterObject, preAuthorizeExpression); } catch (ParseException ex) { throw new IllegalArgumentException("Failed to parse expression '" + ex.getExpressionString() + "'", ex); } } @Override public @Nullable PostInvocationAttribute createPostInvocationAttribute(String postFilterAttribute, String postAuthorizeAttribute) { try { ExpressionParser parser = getParser(); Expression postAuthorizeExpression = (postAuthorizeAttribute != null) ? parser.parseExpression(postAuthorizeAttribute) : null; Expression postFilterExpression = (postFilterAttribute != null) ? parser.parseExpression(postFilterAttribute) : null; if (postFilterExpression != null || postAuthorizeExpression != null) { return new PostInvocationExpressionAttribute(postFilterExpression, postAuthorizeExpression); } } catch (ParseException ex) { throw new IllegalArgumentException("Failed to parse expression '" + ex.getExpressionString() + "'", ex); } return null; } /** * Delay the lookup of the {@link ExpressionParser} to prevent SEC-2136 * @return */ private ExpressionParser getParser() { if (this.parser != null) { return this.parser; } synchronized (this.parserLock) { this.parser = this.handler.getExpressionParser(); this.handler = null; } return this.parser; } } ================================================ FILE: access/src/main/java/org/springframework/security/access/expression/method/ExpressionBasedPostInvocationAdvice.java ================================================ /* * 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 org.springframework.security.access.expression.method; import org.aopalliance.intercept.MethodInvocation; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.core.log.LogMessage; import org.springframework.expression.EvaluationContext; import org.springframework.expression.Expression; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.expression.ExpressionUtils; import org.springframework.security.access.prepost.PostInvocationAttribute; import org.springframework.security.access.prepost.PostInvocationAuthorizationAdvice; import org.springframework.security.core.Authentication; /** * @author Luke Taylor * @since 3.0 * @deprecated Use * {@link org.springframework.security.authorization.method.AuthorizationManagerAfterMethodInterceptor} * instead */ @Deprecated public class ExpressionBasedPostInvocationAdvice implements PostInvocationAuthorizationAdvice { protected final Log logger = LogFactory.getLog(getClass()); private final MethodSecurityExpressionHandler expressionHandler; public ExpressionBasedPostInvocationAdvice(MethodSecurityExpressionHandler expressionHandler) { this.expressionHandler = expressionHandler; } @Override public Object after(Authentication authentication, MethodInvocation mi, PostInvocationAttribute postAttr, Object returnedObject) throws AccessDeniedException { PostInvocationExpressionAttribute pia = (PostInvocationExpressionAttribute) postAttr; EvaluationContext ctx = this.expressionHandler.createEvaluationContext(authentication, mi); Expression postFilter = pia.getFilterExpression(); Expression postAuthorize = pia.getAuthorizeExpression(); if (postFilter != null) { this.logger.debug(LogMessage.format("Applying PostFilter expression %s", postFilter)); if (returnedObject != null) { returnedObject = this.expressionHandler.filter(returnedObject, postFilter, ctx); } else { this.logger.debug("Return object is null, filtering will be skipped"); } } this.expressionHandler.setReturnObject(returnedObject, ctx); if (postAuthorize != null && !ExpressionUtils.evaluateAsBoolean(postAuthorize, ctx)) { this.logger.debug("PostAuthorize expression rejected access"); throw new AccessDeniedException("Access is denied"); } return returnedObject; } } ================================================ FILE: access/src/main/java/org/springframework/security/access/expression/method/ExpressionBasedPreInvocationAdvice.java ================================================ /* * 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 org.springframework.security.access.expression.method; import java.util.Collection; import org.aopalliance.intercept.MethodInvocation; import org.jspecify.annotations.NullUnmarked; import org.springframework.expression.EvaluationContext; import org.springframework.expression.Expression; import org.springframework.security.access.expression.ExpressionUtils; import org.springframework.security.access.prepost.PreInvocationAttribute; import org.springframework.security.access.prepost.PreInvocationAuthorizationAdvice; import org.springframework.security.core.Authentication; import org.springframework.util.Assert; /** * Method pre-invocation handling based on expressions. * * @author Luke Taylor * @since 3.0 * @deprecated Use * {@link org.springframework.security.authorization.method.AuthorizationManagerAfterMethodInterceptor} * instead */ @NullUnmarked @Deprecated public class ExpressionBasedPreInvocationAdvice implements PreInvocationAuthorizationAdvice { private MethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler(); @Override public boolean before(Authentication authentication, MethodInvocation mi, PreInvocationAttribute attr) { PreInvocationExpressionAttribute preAttr = (PreInvocationExpressionAttribute) attr; EvaluationContext ctx = this.expressionHandler.createEvaluationContext(authentication, mi); Expression preFilter = preAttr.getFilterExpression(); Expression preAuthorize = preAttr.getAuthorizeExpression(); if (preFilter != null) { Object filterTarget = findFilterTarget(preAttr.getFilterTarget(), ctx, mi); this.expressionHandler.filter(filterTarget, preFilter, ctx); } return (preAuthorize != null) ? ExpressionUtils.evaluateAsBoolean(preAuthorize, ctx) : true; } private Object findFilterTarget(String filterTargetName, EvaluationContext ctx, MethodInvocation invocation) { Object filterTarget = null; if (filterTargetName.length() > 0) { filterTarget = ctx.lookupVariable(filterTargetName); Assert.notNull(filterTarget, () -> "Filter target was null, or no argument with name " + filterTargetName + " found in method"); } else if (invocation.getArguments().length == 1) { Object arg = invocation.getArguments()[0]; if (arg.getClass().isArray() || arg instanceof Collection) { filterTarget = arg; } Assert.notNull(filterTarget, () -> "A PreFilter expression was set but the method argument type" + arg.getClass() + " is not filterable"); } else if (invocation.getArguments().length > 1) { throw new IllegalArgumentException( "Unable to determine the method argument for filtering. Specify the filter target."); } Assert.isTrue(!filterTarget.getClass().isArray(), "Pre-filtering on array types is not supported. Using a Collection will solve this problem"); return filterTarget; } public void setExpressionHandler(MethodSecurityExpressionHandler expressionHandler) { this.expressionHandler = expressionHandler; } } ================================================ FILE: access/src/main/java/org/springframework/security/access/expression/method/PostInvocationExpressionAttribute.java ================================================ /* * 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 org.springframework.security.access.expression.method; import org.jspecify.annotations.Nullable; import org.springframework.expression.Expression; import org.springframework.expression.ParseException; import org.springframework.security.access.prepost.PostInvocationAttribute; /** * @author Luke Taylor * @since 3.0 * @deprecated Use * {@link org.springframework.security.authorization.method.AuthorizationManagerAfterMethodInterceptor} * instead */ @Deprecated @SuppressWarnings("serial") class PostInvocationExpressionAttribute extends AbstractExpressionBasedMethodConfigAttribute implements PostInvocationAttribute { PostInvocationExpressionAttribute(String filterExpression, String authorizeExpression) throws ParseException { super(filterExpression, authorizeExpression); } PostInvocationExpressionAttribute(@Nullable Expression filterExpression, @Nullable Expression authorizeExpression) throws ParseException { super(filterExpression, authorizeExpression); } @Override public String toString() { StringBuilder sb = new StringBuilder(); Expression authorize = getAuthorizeExpression(); Expression filter = getFilterExpression(); sb.append("[authorize: '").append((authorize != null) ? authorize.getExpressionString() : "null"); sb.append("', filter: '").append((filter != null) ? filter.getExpressionString() : "null").append("']"); return sb.toString(); } } ================================================ FILE: access/src/main/java/org/springframework/security/access/expression/method/PreInvocationExpressionAttribute.java ================================================ /* * 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 org.springframework.security.access.expression.method; import org.jspecify.annotations.Nullable; import org.springframework.expression.Expression; import org.springframework.expression.ParseException; import org.springframework.security.access.prepost.PreInvocationAttribute; /** * @author Luke Taylor * @since 3.0 * @deprecated Use * {@link org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor} * instead */ @Deprecated @SuppressWarnings("serial") class PreInvocationExpressionAttribute extends AbstractExpressionBasedMethodConfigAttribute implements PreInvocationAttribute { private final String filterTarget; PreInvocationExpressionAttribute(String filterExpression, String filterTarget, String authorizeExpression) throws ParseException { super(filterExpression, authorizeExpression); this.filterTarget = filterTarget; } PreInvocationExpressionAttribute(@Nullable Expression filterExpression, String filterTarget, Expression authorizeExpression) throws ParseException { super(filterExpression, authorizeExpression); this.filterTarget = filterTarget; } /** * The parameter name of the target argument (must be a Collection) to which filtering * will be applied. * @return the method parameter name */ String getFilterTarget() { return this.filterTarget; } @Override public String toString() { StringBuilder sb = new StringBuilder(); Expression authorize = getAuthorizeExpression(); Expression filter = getFilterExpression(); sb.append("[authorize: '").append((authorize != null) ? authorize.getExpressionString() : "null"); sb.append("', filter: '").append((filter != null) ? filter.getExpressionString() : "null"); sb.append("', filterTarget: '").append(this.filterTarget).append("']"); return sb.toString(); } } ================================================ FILE: access/src/main/java/org/springframework/security/access/intercept/AbstractSecurityInterceptor.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.access.intercept; import java.util.Collection; import java.util.HashSet; import java.util.Set; 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.beans.factory.InitializingBean; import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisherAware; import org.springframework.context.MessageSource; import org.springframework.context.MessageSourceAware; import org.springframework.context.support.MessageSourceAccessor; import org.springframework.core.log.LogMessage; import org.springframework.security.access.AccessDecisionManager; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.access.SecurityMetadataSource; import org.springframework.security.access.event.AuthenticationCredentialsNotFoundEvent; import org.springframework.security.access.event.AuthorizationFailureEvent; import org.springframework.security.access.event.AuthorizedEvent; import org.springframework.security.access.event.PublicInvocationEvent; import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationServiceException; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.SpringSecurityMessageSource; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; /** * Abstract class that implements security interception for secure objects. *

* The AbstractSecurityInterceptor will ensure the proper startup * configuration of the security interceptor. It will also implement the proper handling * of secure object invocations, namely: *

    *
  1. Obtain the {@link Authentication} object from the * {@link SecurityContextHolder}.
  2. *
  3. Determine if the request relates to a secured or public invocation by looking up * the secure object request against the {@link SecurityMetadataSource}.
  4. *
  5. For an invocation that is secured (there is a list of ConfigAttributes * for the secure object invocation): *
      *
    1. If either the * {@link org.springframework.security.core.Authentication#isAuthenticated()} returns * false, or the {@link #alwaysReauthenticate} is true, * authenticate the request against the configured {@link AuthenticationManager}. When * authenticated, replace the Authentication object on the * SecurityContextHolder with the returned value.
    2. *
    3. Authorize the request against the configured {@link AccessDecisionManager}.
    4. *
    5. Perform any run-as replacement via the configured {@link RunAsManager}.
    6. *
    7. Pass control back to the concrete subclass, which will actually proceed with * executing the object. A {@link InterceptorStatusToken} is returned so that after the * subclass has finished proceeding with execution of the object, its finally clause can * ensure the AbstractSecurityInterceptor is re-called and tidies up * correctly using {@link #finallyInvocation(InterceptorStatusToken)}.
    8. *
    9. The concrete subclass will re-call the AbstractSecurityInterceptor via * the {@link #afterInvocation(InterceptorStatusToken, Object)} method.
    10. *
    11. If the RunAsManager replaced the Authentication object, * return the SecurityContextHolder to the object that existed after the call * to AuthenticationManager.
    12. *
    13. If an AfterInvocationManager is defined, invoke the invocation manager * and allow it to replace the object due to be returned to the caller.
    14. *
    *
  6. *
  7. For an invocation that is public (there are no ConfigAttributes for * the secure object invocation): *
      *
    1. As described above, the concrete subclass will be returned an * InterceptorStatusToken which is subsequently re-presented to the * AbstractSecurityInterceptor after the secure object has been executed. The * AbstractSecurityInterceptor will take no further action when its * {@link #afterInvocation(InterceptorStatusToken, Object)} is called.
    2. *
    *
  8. *
  9. Control again returns to the concrete subclass, along with the Object * that should be returned to the caller. The subclass will then return that result or * exception to the original caller.
  10. *
* * @author Ben Alex * @author Rob Winch * @deprecated Use * {@link org.springframework.security.web.access.intercept.AuthorizationFilter} instead * for filter security, * {@link org.springframework.security.messaging.access.intercept.AuthorizationChannelInterceptor} * for messaging security, or * {@link org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor} * and * {@link org.springframework.security.authorization.method.AuthorizationManagerAfterMethodInterceptor} * for method security. */ @NullUnmarked @Deprecated public abstract class AbstractSecurityInterceptor implements InitializingBean, ApplicationEventPublisherAware, MessageSourceAware { protected final Log logger = LogFactory.getLog(getClass()); protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor(); private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder .getContextHolderStrategy(); private @Nullable ApplicationEventPublisher eventPublisher; private @Nullable AccessDecisionManager accessDecisionManager; private @Nullable AfterInvocationManager afterInvocationManager; private AuthenticationManager authenticationManager = new NoOpAuthenticationManager(); private RunAsManager runAsManager = new NullRunAsManager(); private boolean alwaysReauthenticate = false; private boolean rejectPublicInvocations = false; private boolean validateConfigAttributes = true; private boolean publishAuthorizationSuccess = false; @Override public void afterPropertiesSet() { Assert.notNull(getSecureObjectClass(), "Subclass must provide a non-null response to getSecureObjectClass()"); Assert.notNull(this.messages, "A message source must be set"); Assert.notNull(this.authenticationManager, "An AuthenticationManager is required"); Assert.notNull(this.accessDecisionManager, "An AccessDecisionManager is required"); Assert.notNull(this.runAsManager, "A RunAsManager is required"); Assert.notNull(this.obtainSecurityMetadataSource(), "An SecurityMetadataSource is required"); Assert.isTrue(this.obtainSecurityMetadataSource().supports(getSecureObjectClass()), () -> "SecurityMetadataSource does not support secure object class: " + getSecureObjectClass()); Assert.isTrue(this.runAsManager.supports(getSecureObjectClass()), () -> "RunAsManager does not support secure object class: " + getSecureObjectClass()); Assert.isTrue(this.accessDecisionManager.supports(getSecureObjectClass()), () -> "AccessDecisionManager does not support secure object class: " + getSecureObjectClass()); if (this.afterInvocationManager != null) { Assert.isTrue(this.afterInvocationManager.supports(getSecureObjectClass()), () -> "AfterInvocationManager does not support secure object class: " + getSecureObjectClass()); } if (this.validateConfigAttributes) { Collection attributeDefs = this.obtainSecurityMetadataSource().getAllConfigAttributes(); if (attributeDefs == null) { this.logger.warn("Could not validate configuration attributes as the " + "SecurityMetadataSource did not return any attributes from getAllConfigAttributes()"); return; } validateAttributeDefs(attributeDefs); } } private void validateAttributeDefs(Collection attributeDefs) { Set unsupportedAttrs = new HashSet<>(); for (ConfigAttribute attr : attributeDefs) { if (!this.runAsManager.supports(attr) && !this.accessDecisionManager.supports(attr) && ((this.afterInvocationManager == null) || !this.afterInvocationManager.supports(attr))) { unsupportedAttrs.add(attr); } } if (unsupportedAttrs.size() != 0) { this.logger .trace("Did not validate configuration attributes since validateConfigurationAttributes is false"); throw new IllegalArgumentException("Unsupported configuration attributes: " + unsupportedAttrs); } else { this.logger.trace("Validated configuration attributes"); } } protected @Nullable InterceptorStatusToken beforeInvocation(Object object) { Assert.notNull(object, "Object was null"); if (!getSecureObjectClass().isAssignableFrom(object.getClass())) { throw new IllegalArgumentException("Security invocation attempted for object " + object.getClass().getName() + " but AbstractSecurityInterceptor only configured to support secure objects of type: " + getSecureObjectClass()); } Collection attributes = this.obtainSecurityMetadataSource().getAttributes(object); if (CollectionUtils.isEmpty(attributes)) { Assert.isTrue(!this.rejectPublicInvocations, () -> "Secure object invocation " + object + " was denied as public invocations are not allowed via this interceptor. " + "This indicates a configuration error because the " + "rejectPublicInvocations property is set to 'true'"); if (this.logger.isDebugEnabled()) { this.logger.debug(LogMessage.format("Authorized public object %s", object)); } publishEvent(new PublicInvocationEvent(object)); return null; // no further work post-invocation } if (this.securityContextHolderStrategy.getContext().getAuthentication() == null) { credentialsNotFound(this.messages.getMessage("AbstractSecurityInterceptor.authenticationNotFound", "An Authentication object was not found in the SecurityContext"), object, attributes); } Authentication authenticated = authenticateIfRequired(); if (this.logger.isTraceEnabled()) { this.logger.trace(LogMessage.format("Authorizing %s with attributes %s", object, attributes)); } // Attempt authorization attemptAuthorization(object, attributes, authenticated); if (this.logger.isDebugEnabled()) { this.logger.debug(LogMessage.format("Authorized %s with attributes %s", object, attributes)); } if (this.publishAuthorizationSuccess) { publishEvent(new AuthorizedEvent(object, attributes, authenticated)); } // Attempt to run as a different user Authentication runAs = this.runAsManager.buildRunAs(authenticated, object, attributes); if (runAs != null) { SecurityContext origCtx = this.securityContextHolderStrategy.getContext(); SecurityContext newCtx = this.securityContextHolderStrategy.createEmptyContext(); newCtx.setAuthentication(runAs); this.securityContextHolderStrategy.setContext(newCtx); if (this.logger.isDebugEnabled()) { this.logger.debug(LogMessage.format("Switched to RunAs authentication %s", runAs)); } // need to revert to token.Authenticated post-invocation return new InterceptorStatusToken(origCtx, true, attributes, object); } this.logger.trace("Did not switch RunAs authentication since RunAsManager returned null"); // no further work post-invocation return new InterceptorStatusToken(this.securityContextHolderStrategy.getContext(), false, attributes, object); } private void attemptAuthorization(Object object, Collection attributes, Authentication authenticated) { try { this.accessDecisionManager.decide(authenticated, object, attributes); } catch (AccessDeniedException ex) { if (this.logger.isTraceEnabled()) { this.logger.trace(LogMessage.format("Failed to authorize %s with attributes %s using %s", object, attributes, this.accessDecisionManager)); } else if (this.logger.isDebugEnabled()) { this.logger.debug(LogMessage.format("Failed to authorize %s with attributes %s", object, attributes)); } publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated, ex)); throw ex; } } /** * Cleans up the work of the AbstractSecurityInterceptor after the secure * object invocation has been completed. This method should be invoked after the * secure object invocation and before afterInvocation regardless of the secure object * invocation returning successfully (i.e. it should be done in a finally block). * @param token as returned by the {@link #beforeInvocation(Object)} method */ protected void finallyInvocation(InterceptorStatusToken token) { if (token != null && token.isContextHolderRefreshRequired()) { this.securityContextHolderStrategy.setContext(token.getSecurityContext()); if (this.logger.isDebugEnabled()) { this.logger.debug(LogMessage .of(() -> "Reverted to original authentication " + token.getSecurityContext().getAuthentication())); } } } /** * Completes the work of the AbstractSecurityInterceptor after the secure * object invocation has been completed. * @param token as returned by the {@link #beforeInvocation(Object)} method * @param returnedObject any object returned from the secure object invocation (may be * null) * @return the object the secure object invocation should ultimately return to its * caller (may be null) */ protected Object afterInvocation(InterceptorStatusToken token, @Nullable Object returnedObject) { if (token == null) { // public object return returnedObject; } finallyInvocation(token); // continue to clean in this method for passivity if (this.afterInvocationManager != null) { // Attempt after invocation handling try { returnedObject = this.afterInvocationManager.decide(token.getSecurityContext().getAuthentication(), token.getSecureObject(), token.getAttributes(), returnedObject); } catch (AccessDeniedException ex) { publishEvent(new AuthorizationFailureEvent(token.getSecureObject(), token.getAttributes(), token.getSecurityContext().getAuthentication(), ex)); throw ex; } } return returnedObject; } /** * Checks the current authentication token and passes it to the AuthenticationManager * if {@link org.springframework.security.core.Authentication#isAuthenticated()} * returns false or the property alwaysReauthenticate has been set to true. * @return an authenticated Authentication object. */ private Authentication authenticateIfRequired() { Authentication authentication = this.securityContextHolderStrategy.getContext().getAuthentication(); if (authentication.isAuthenticated() && !this.alwaysReauthenticate) { if (this.logger.isTraceEnabled()) { this.logger.trace(LogMessage.format("Did not re-authenticate %s before authorizing", authentication)); } return authentication; } authentication = this.authenticationManager.authenticate(authentication); // Don't authenticated.setAuthentication(true) because each provider does that if (this.logger.isDebugEnabled()) { this.logger.debug(LogMessage.format("Re-authenticated %s before authorizing", authentication)); } SecurityContext context = this.securityContextHolderStrategy.createEmptyContext(); context.setAuthentication(authentication); this.securityContextHolderStrategy.setContext(context); return authentication; } /** * Helper method which generates an exception containing the passed reason, and * publishes an event to the application context. *

* Always throws an exception. * @param reason to be provided in the exception detail * @param secureObject that was being called * @param configAttribs that were defined for the secureObject */ private void credentialsNotFound(String reason, Object secureObject, Collection configAttribs) { AuthenticationCredentialsNotFoundException exception = new AuthenticationCredentialsNotFoundException(reason); AuthenticationCredentialsNotFoundEvent event = new AuthenticationCredentialsNotFoundEvent(secureObject, configAttribs, exception); publishEvent(event); throw exception; } public AccessDecisionManager getAccessDecisionManager() { return this.accessDecisionManager; } public AfterInvocationManager getAfterInvocationManager() { return this.afterInvocationManager; } public AuthenticationManager getAuthenticationManager() { return this.authenticationManager; } public RunAsManager getRunAsManager() { return this.runAsManager; } /** * Indicates the type of secure objects the subclass will be presenting to the * abstract parent for processing. This is used to ensure collaborators wired to the * {@code AbstractSecurityInterceptor} all support the indicated secure object class. * @return the type of secure object the subclass provides services for */ public abstract Class getSecureObjectClass(); public boolean isAlwaysReauthenticate() { return this.alwaysReauthenticate; } public boolean isRejectPublicInvocations() { return this.rejectPublicInvocations; } public boolean isValidateConfigAttributes() { return this.validateConfigAttributes; } public abstract SecurityMetadataSource obtainSecurityMetadataSource(); /** * Sets the {@link SecurityContextHolderStrategy} to use. The default action is to use * the {@link SecurityContextHolderStrategy} stored in {@link SecurityContextHolder}. * * @since 5.8 */ public void setSecurityContextHolderStrategy(SecurityContextHolderStrategy securityContextHolderStrategy) { Assert.notNull(securityContextHolderStrategy, "securityContextHolderStrategy cannot be null"); this.securityContextHolderStrategy = securityContextHolderStrategy; } public void setAccessDecisionManager(AccessDecisionManager accessDecisionManager) { this.accessDecisionManager = accessDecisionManager; } public void setAfterInvocationManager(AfterInvocationManager afterInvocationManager) { this.afterInvocationManager = afterInvocationManager; } /** * Indicates whether the AbstractSecurityInterceptor should ignore the * {@link Authentication#isAuthenticated()} property. Defaults to false, * meaning by default the Authentication.isAuthenticated() property is * trusted and re-authentication will not occur if the principal has already been * authenticated. * @param alwaysReauthenticate true to force * AbstractSecurityInterceptor to disregard the value of * Authentication.isAuthenticated() and always re-authenticate the * request (defaults to false). */ public void setAlwaysReauthenticate(boolean alwaysReauthenticate) { this.alwaysReauthenticate = alwaysReauthenticate; } @Override public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { this.eventPublisher = applicationEventPublisher; } public void setAuthenticationManager(AuthenticationManager newManager) { this.authenticationManager = newManager; } @Override public void setMessageSource(MessageSource messageSource) { this.messages = new MessageSourceAccessor(messageSource); } /** * Only {@code AuthorizationFailureEvent} will be published. If you set this property * to {@code true}, {@code AuthorizedEvent}s will also be published. * @param publishAuthorizationSuccess default value is {@code false} */ public void setPublishAuthorizationSuccess(boolean publishAuthorizationSuccess) { this.publishAuthorizationSuccess = publishAuthorizationSuccess; } /** * By rejecting public invocations (and setting this property to true), * essentially you are ensuring that every secure object invocation advised by * AbstractSecurityInterceptor has a configuration attribute defined. * This is useful to ensure a "fail safe" mode where undeclared secure objects will be * rejected and configuration omissions detected early. An * IllegalArgumentException will be thrown by the * AbstractSecurityInterceptor if you set this property to true and * an attempt is made to invoke a secure object that has no configuration attributes. * @param rejectPublicInvocations set to true to reject invocations of * secure objects that have no configuration attributes (by default it is * false which treats undeclared secure objects as "public" or * unauthorized). */ public void setRejectPublicInvocations(boolean rejectPublicInvocations) { this.rejectPublicInvocations = rejectPublicInvocations; } public void setRunAsManager(RunAsManager runAsManager) { this.runAsManager = runAsManager; } public void setValidateConfigAttributes(boolean validateConfigAttributes) { this.validateConfigAttributes = validateConfigAttributes; } private void publishEvent(ApplicationEvent event) { if (this.eventPublisher != null) { this.eventPublisher.publishEvent(event); } } private static class NoOpAuthenticationManager implements AuthenticationManager { @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { throw new AuthenticationServiceException("Cannot authenticate " + authentication); } } } ================================================ FILE: access/src/main/java/org/springframework/security/access/intercept/AfterInvocationManager.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.access.intercept; import java.util.Collection; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.authorization.AuthorizationManager; import org.springframework.security.core.Authentication; /** * Reviews the Object returned from a secure object invocation, being able to * modify the Object or throw an {@link AccessDeniedException}. *

* Typically used to ensure the principal is permitted to access the domain object * instance returned by a service layer bean. Can also be used to mutate the domain object * instance so the principal is only able to access authorised bean properties or * Collection elements. *

* Special consideration should be given to using an AfterInvocationManager * on bean methods that modify a database. Typically an * AfterInvocationManager is used with read-only methods, such as * public DomainObject getById(id). If used with methods that modify a * database, a transaction manager should be used to ensure any * AccessDeniedException will cause a rollback of the changes made by the * transaction. *

* * @author Ben Alex * @see org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor * @see org.springframework.security.authorization.method.AuthorizationManagerAfterMethodInterceptor * @deprecated Use delegation with {@link AuthorizationManager} */ @Deprecated public interface AfterInvocationManager { /** * Given the details of a secure object invocation including its returned * Object, make an access control decision or optionally modify the * returned Object. * @param authentication the caller that invoked the method * @param object the secured object that was called * @param attributes the configuration attributes associated with the secured object * that was invoked * @param returnedObject the Object that was returned from the secure * object invocation * @return the Object that will ultimately be returned to the caller (if * an implementation does not wish to modify the object to be returned to the caller, * the implementation should simply return the same object it was passed by the * returnedObject method argument) * @throws AccessDeniedException if access is denied */ Object decide(Authentication authentication, Object object, Collection attributes, Object returnedObject) throws AccessDeniedException; /** * Indicates whether this AfterInvocationManager is able to process * "after invocation" requests presented with the passed ConfigAttribute. *

* This allows the AbstractSecurityInterceptor to check every * configuration attribute can be consumed by the configured * AccessDecisionManager and/or RunAsManager and/or * AfterInvocationManager. *

* @param attribute a configuration attribute that has been configured against the * AbstractSecurityInterceptor * @return true if this AfterInvocationManager can support the passed * configuration attribute */ boolean supports(ConfigAttribute attribute); /** * Indicates whether the AfterInvocationManager implementation is able to * provide access control decisions for the indicated secured object type. * @param clazz the class that is being queried * @return true if the implementation can process the indicated class */ boolean supports(Class clazz); } ================================================ FILE: access/src/main/java/org/springframework/security/access/intercept/AfterInvocationProviderManager.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.access.intercept; import java.util.ArrayList; import java.util.Collection; import java.util.List; 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.beans.factory.InitializingBean; import org.springframework.core.log.LogMessage; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.AfterInvocationProvider; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.authorization.AuthorizationManager; import org.springframework.security.core.Authentication; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; /** * Provider-based implementation of {@link AfterInvocationManager}. *

* Handles configuration of a bean context defined list of {@link AfterInvocationProvider} * s. *

* Every AfterInvocationProvider will be polled when the * {@link #decide(Authentication, Object, Collection, Object)} method is called. The * Object returned from each provider will be presented to the successive * provider for processing. This means each provider must ensure they return the * Object, even if they are not interested in the "after invocation" decision * (perhaps as the secure object invocation did not include a configuration attribute a * given provider is configured to respond to). * * @author Ben Alex * @see org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor * @see org.springframework.security.authorization.method.AuthorizationManagerAfterMethodInterceptor * @deprecated Use delegation with {@link AuthorizationManager} */ @NullUnmarked @Deprecated public class AfterInvocationProviderManager implements AfterInvocationManager, InitializingBean { protected static final Log logger = LogFactory.getLog(AfterInvocationProviderManager.class); @SuppressWarnings("NullAway.Init") private @Nullable List providers; @Override public void afterPropertiesSet() { checkIfValidList(this.providers); } @Override public Object decide(Authentication authentication, Object object, Collection config, Object returnedObject) throws AccessDeniedException { Object result = returnedObject; for (AfterInvocationProvider provider : this.providers) { result = provider.decide(authentication, object, config, result); } return result; } public List getProviders() { return this.providers; } public void setProviders(List newList) { checkIfValidList(newList); this.providers = new ArrayList<>(newList.size()); for (Object currentObject : newList) { Assert.isInstanceOf(AfterInvocationProvider.class, currentObject, () -> "AfterInvocationProvider " + currentObject.getClass().getName() + " must implement AfterInvocationProvider"); this.providers.add((AfterInvocationProvider) currentObject); } } private void checkIfValidList(List listToCheck) { Assert.isTrue(!CollectionUtils.isEmpty(listToCheck), "A list of AfterInvocationProviders is required"); } @Override public boolean supports(ConfigAttribute attribute) { for (AfterInvocationProvider provider : this.providers) { logger.debug(LogMessage.format("Evaluating %s against %s", attribute, provider)); if (provider.supports(attribute)) { return true; } } return false; } /** * Iterates through all AfterInvocationProviders and ensures each can * support the presented class. *

* If one or more providers cannot support the presented class, false is * returned. * @param clazz the secure object class being queries * @return if the AfterInvocationProviderManager can support the secure * object class, which requires every one of its AfterInvocationProviders * to support the secure object class */ @Override public boolean supports(Class clazz) { for (AfterInvocationProvider provider : this.providers) { if (!provider.supports(clazz)) { return false; } } return true; } } ================================================ FILE: access/src/main/java/org/springframework/security/access/intercept/InterceptorStatusToken.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.access.intercept; import java.util.Collection; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.authorization.AuthorizationManager; import org.springframework.security.core.context.SecurityContext; /** * A return object received by {@link AbstractSecurityInterceptor} subclasses. *

* This class reflects the status of the security interception, so that the final call to * {@link org.springframework.security.access.intercept.AbstractSecurityInterceptor#afterInvocation(InterceptorStatusToken, Object)} * can tidy up correctly. * * @author Ben Alex * @see org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor * @see org.springframework.security.authorization.method.AuthorizationManagerAfterMethodInterceptor * @deprecated Use delegation with {@link AuthorizationManager} */ @Deprecated public class InterceptorStatusToken { private SecurityContext securityContext; private Collection attr; private Object secureObject; private boolean contextHolderRefreshRequired; public InterceptorStatusToken(SecurityContext securityContext, boolean contextHolderRefreshRequired, Collection attributes, Object secureObject) { this.securityContext = securityContext; this.contextHolderRefreshRequired = contextHolderRefreshRequired; this.attr = attributes; this.secureObject = secureObject; } public Collection getAttributes() { return this.attr; } public SecurityContext getSecurityContext() { return this.securityContext; } public Object getSecureObject() { return this.secureObject; } public boolean isContextHolderRefreshRequired() { return this.contextHolderRefreshRequired; } } ================================================ FILE: access/src/main/java/org/springframework/security/access/intercept/MethodInvocationPrivilegeEvaluator.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.access.intercept; import java.util.Collection; import org.aopalliance.intercept.MethodInvocation; 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.beans.factory.InitializingBean; import org.springframework.core.log.LogMessage; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.core.Authentication; import org.springframework.util.Assert; /** * Allows users to determine whether they have "before invocation" privileges for a given * method invocation. *

* Of course, if an * {@link org.springframework.security.access.intercept.AfterInvocationManager} is used to * authorize the result of a method invocation, this class cannot assist * determine whether or not the AfterInvocationManager will enable access. * Instead this class aims to allow applications to determine whether or not the current * principal would be allowed to at least attempt to invoke the method, irrespective of * the "after" invocation handling. *

* * @author Ben Alex * @deprecated Use {@link org.springframework.security.authorization.AuthorizationManager} * instead */ @NullUnmarked @Deprecated public class MethodInvocationPrivilegeEvaluator implements InitializingBean { protected static final Log logger = LogFactory.getLog(MethodInvocationPrivilegeEvaluator.class); @SuppressWarnings("NullAway.Init") private @Nullable AbstractSecurityInterceptor securityInterceptor; @Override public void afterPropertiesSet() { Assert.notNull(this.securityInterceptor, "SecurityInterceptor required"); } public boolean isAllowed(MethodInvocation invocation, Authentication authentication) { Assert.notNull(invocation, "MethodInvocation required"); Assert.notNull(invocation.getMethod(), "MethodInvocation must provide a non-null getMethod()"); Collection attrs = this.securityInterceptor.obtainSecurityMetadataSource() .getAttributes(invocation); if (attrs == null) { return !this.securityInterceptor.isRejectPublicInvocations(); } if (authentication == null || authentication.getAuthorities().isEmpty()) { return false; } try { this.securityInterceptor.getAccessDecisionManager().decide(authentication, invocation, attrs); return true; } catch (AccessDeniedException unauthorized) { logger.debug(LogMessage.format("%s denied for %s", invocation, authentication), unauthorized); return false; } } public void setSecurityInterceptor(AbstractSecurityInterceptor securityInterceptor) { Assert.notNull(securityInterceptor, "AbstractSecurityInterceptor cannot be null"); Assert.isTrue(MethodInvocation.class.equals(securityInterceptor.getSecureObjectClass()), "AbstractSecurityInterceptor does not support MethodInvocations"); Assert.notNull(securityInterceptor.getAccessDecisionManager(), "AbstractSecurityInterceptor must provide a non-null AccessDecisionManager"); this.securityInterceptor = securityInterceptor; } } ================================================ FILE: access/src/main/java/org/springframework/security/access/intercept/NullRunAsManager.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.access.intercept; import java.util.Collection; import org.jspecify.annotations.NullUnmarked; import org.jspecify.annotations.Nullable; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.core.Authentication; /** * Implementation of a {@link RunAsManager} that does nothing. *

* This class should be used if you do not require run-as authentication replacement * functionality. * * @author Ben Alex * @deprecated please see {@link RunAsManager} deprecation notice */ @NullUnmarked @Deprecated final class NullRunAsManager implements RunAsManager { @Override public @Nullable Authentication buildRunAs(Authentication authentication, Object object, Collection config) { return null; } @Override public boolean supports(ConfigAttribute attribute) { return false; } @Override public boolean supports(Class clazz) { return true; } } ================================================ FILE: access/src/main/java/org/springframework/security/access/intercept/RunAsImplAuthenticationProvider.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.access.intercept; import org.jspecify.annotations.NullUnmarked; import org.jspecify.annotations.Nullable; import org.springframework.beans.factory.InitializingBean; import org.springframework.context.MessageSource; import org.springframework.context.MessageSourceAware; import org.springframework.context.support.MessageSourceAccessor; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.SpringSecurityMessageSource; import org.springframework.util.Assert; /** * An {@link AuthenticationProvider} implementation that can authenticate a * {@link RunAsUserToken}. *

* Configured in the bean context with a key that should match the key used by adapters to * generate the RunAsUserToken. It treats as valid any * RunAsUserToken instance presenting a hash code that matches the * RunAsImplAuthenticationProvider-configured key. *

*

* If the key does not match, a BadCredentialsException is thrown. *

* * @deprecated Authentication is now separated from authorization in Spring Security. This * class is only used by now-deprecated components. There is not yet an equivalent * replacement in Spring Security. */ @NullUnmarked @Deprecated public class RunAsImplAuthenticationProvider implements InitializingBean, AuthenticationProvider, MessageSourceAware { protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor(); @SuppressWarnings("NullAway.Init") private @Nullable String key; @Override public void afterPropertiesSet() { Assert.notNull(this.key, "A Key is required and should match that configured for the RunAsManagerImpl"); } @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { RunAsUserToken token = (RunAsUserToken) authentication; if (token.getKeyHash() != this.key.hashCode()) { throw new BadCredentialsException(this.messages.getMessage("RunAsImplAuthenticationProvider.incorrectKey", "The presented RunAsUserToken does not contain the expected key")); } return authentication; } public String getKey() { return this.key; } public void setKey(String key) { this.key = key; } @Override public void setMessageSource(MessageSource messageSource) { this.messages = new MessageSourceAccessor(messageSource); } @Override public boolean supports(Class authentication) { return RunAsUserToken.class.isAssignableFrom(authentication); } } ================================================ FILE: access/src/main/java/org/springframework/security/access/intercept/RunAsManager.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.access.intercept; import java.util.Collection; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.core.Authentication; /** * Creates a new temporary {@link Authentication} object for the current secure object * invocation only. * *

* This interface permits implementations to replace the Authentication * object that applies to the current secure object invocation only. The * {@link org.springframework.security.access.intercept.AbstractSecurityInterceptor} will * replace the Authentication object held in the * {@link org.springframework.security.core.context.SecurityContext SecurityContext} for * the duration of the secure object callback only, returning it to the original * Authentication object when the callback ends. *

* *

* This is provided so that systems with two layers of objects can be established. One * layer is public facing and has normal secure methods with the granted authorities * expected to be held by external callers. The other layer is private, and is only * expected to be called by objects within the public facing layer. The objects in this * private layer still need security (otherwise they would be public methods) and they * also need security in such a manner that prevents them being called directly by * external callers. The objects in the private layer would be configured to require * granted authorities never granted to external callers. The RunAsManager * interface provides a mechanism to elevate security in this manner. *

* *

* It is expected implementations will provide a corresponding concrete * Authentication and AuthenticationProvider so that the * replacement Authentication object can be authenticated. Some form of * security will need to be implemented to ensure the AuthenticationProvider * only accepts Authentication objects created by an authorized concrete * implementation of RunAsManager. *

* * @author Ben Alex * @deprecated Authentication is now separated from authorization in Spring Security. This * class is only used by now-deprecated components. There is not yet an equivalent * replacement in Spring Security. */ @Deprecated public interface RunAsManager { /** * Returns a replacement Authentication object for the current secure * object invocation, or null if replacement not required. * @param authentication the caller invoking the secure object * @param object the secured object being called * @param attributes the configuration attributes associated with the secure object * being invoked * @return a replacement object to be used for duration of the secure object * invocation, or null if the Authentication should be left * as is */ Authentication buildRunAs(Authentication authentication, Object object, Collection attributes); /** * Indicates whether this RunAsManager is able to process the passed * ConfigAttribute. *

* This allows the AbstractSecurityInterceptor to check every * configuration attribute can be consumed by the configured * AccessDecisionManager and/or RunAsManager and/or * AfterInvocationManager. *

* @param attribute a configuration attribute that has been configured against the * AbstractSecurityInterceptor * @return true if this RunAsManager can support the passed * configuration attribute */ boolean supports(ConfigAttribute attribute); /** * Indicates whether the RunAsManager implementation is able to provide * run-as replacement for the indicated secure object type. * @param clazz the class that is being queried * @return true if the implementation can process the indicated class */ boolean supports(Class clazz); } ================================================ FILE: access/src/main/java/org/springframework/security/access/intercept/RunAsManagerImpl.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.access.intercept; import java.util.ArrayList; import java.util.Collection; import java.util.List; import org.jspecify.annotations.NullUnmarked; import org.jspecify.annotations.Nullable; import org.springframework.beans.factory.InitializingBean; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.util.Assert; /** * Basic concrete implementation of a {@link RunAsManager}. *

* Is activated if any {@link ConfigAttribute#getAttribute()} is prefixed with * RUN_AS_. If found, it generates a new {@link RunAsUserToken} containing * the same principal, credentials and granted authorities as the original * {@link Authentication} object, along with {@link SimpleGrantedAuthority}s for each * RUN_AS_ indicated. The created SimpleGrantedAuthoritys will * be prefixed with a special prefix indicating that it is a role (default prefix value is * ROLE_), and then the remainder of the RUN_AS_ keyword. For * example, RUN_AS_FOO will result in the creation of a granted authority of * ROLE_RUN_AS_FOO. *

* The role prefix may be overridden from the default, to match that used elsewhere, for * example when using an existing role database with another prefix. An empty role prefix * may also be specified. Note however that there are potential issues with using an empty * role prefix since different categories of {@link ConfigAttribute} can not be properly * discerned based on the prefix, with possible consequences when performing voting and * other actions. However, this option may be of some use when using pre-existing role * names without a prefix, and no ability exists to prefix them with a role prefix on * reading them in, such as provided for example in * {@link org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl}. * * @author Ben Alex * @author colin sampaleanu * @deprecated Authentication is now separated from authorization in Spring Security. This * class is only used by now-deprecated components. There is not yet an equivalent * replacement in Spring Security. */ @NullUnmarked @Deprecated public class RunAsManagerImpl implements RunAsManager, InitializingBean { @SuppressWarnings("NullAway.Init") private @Nullable String key; private String rolePrefix = "ROLE_"; @Override public void afterPropertiesSet() { Assert.notNull(this.key, "A Key is required and should match that configured for the RunAsImplAuthenticationProvider"); } @Override public @Nullable Authentication buildRunAs(Authentication authentication, Object object, Collection attributes) { List newAuthorities = new ArrayList<>(); for (ConfigAttribute attribute : attributes) { if (this.supports(attribute)) { GrantedAuthority extraAuthority = new SimpleGrantedAuthority( getRolePrefix() + attribute.getAttribute()); newAuthorities.add(extraAuthority); } } if (newAuthorities.isEmpty()) { return null; } // Add existing authorities newAuthorities.addAll(authentication.getAuthorities()); return new RunAsUserToken(this.key, authentication.getPrincipal(), authentication.getCredentials(), newAuthorities, authentication.getClass()); } public String getKey() { return this.key; } public String getRolePrefix() { return this.rolePrefix; } public void setKey(String key) { this.key = key; } /** * Allows the default role prefix of ROLE_ to be overridden. May be set * to an empty value, although this is usually not desirable. * @param rolePrefix the new prefix */ public void setRolePrefix(String rolePrefix) { this.rolePrefix = rolePrefix; } @Override public boolean supports(ConfigAttribute attribute) { return attribute.getAttribute() != null && attribute.getAttribute().startsWith("RUN_AS_"); } /** * This implementation supports any type of class, because it does not query the * presented secure object. * @param clazz the secure object * @return always true */ @Override public boolean supports(Class clazz) { return true; } } ================================================ FILE: access/src/main/java/org/springframework/security/access/intercept/RunAsUserToken.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.access.intercept; import java.util.Collection; import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; /** * An immutable {@link org.springframework.security.core.Authentication} implementation * that supports {@link RunAsManagerImpl}. * * @author Ben Alex * @deprecated Authentication is now separated from authorization in Spring Security. This * class is only used by now-deprecated components. There is not yet an equivalent * replacement in Spring Security. */ @Deprecated public class RunAsUserToken extends AbstractAuthenticationToken { private static final long serialVersionUID = 620L; private final Class originalAuthentication; private final Object credentials; private final Object principal; private final int keyHash; public RunAsUserToken(String key, Object principal, Object credentials, Collection authorities, Class originalAuthentication) { super(authorities); this.keyHash = key.hashCode(); this.principal = principal; this.credentials = credentials; this.originalAuthentication = originalAuthentication; setAuthenticated(true); } @Override public Object getCredentials() { return this.credentials; } public int getKeyHash() { return this.keyHash; } public Class getOriginalAuthentication() { return this.originalAuthentication; } @Override public Object getPrincipal() { return this.principal; } @Override public String toString() { StringBuilder sb = new StringBuilder(super.toString()); String className = (this.originalAuthentication != null) ? this.originalAuthentication.getName() : null; sb.append("; Original Class: ").append(className); return sb.toString(); } } ================================================ FILE: access/src/main/java/org/springframework/security/access/intercept/aopalliance/MethodSecurityInterceptor.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.access.intercept.aopalliance; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; import org.jspecify.annotations.NullUnmarked; import org.jspecify.annotations.Nullable; import org.springframework.security.access.SecurityMetadataSource; import org.springframework.security.access.intercept.AbstractSecurityInterceptor; import org.springframework.security.access.intercept.InterceptorStatusToken; import org.springframework.security.access.method.MethodSecurityMetadataSource; /** * Provides security interception of AOP Alliance based method invocations. *

* The SecurityMetadataSource required by this security interceptor is of * type {@link MethodSecurityMetadataSource}. This is shared with the AspectJ based * security interceptor (AspectJSecurityInterceptor), since both work with * Java Methods. *

* Refer to {@link AbstractSecurityInterceptor} for details on the workflow. * * @author Ben Alex * @author Rob Winch * @deprecated Please use * {@link org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor} * and * {@link org.springframework.security.authorization.method.AuthorizationManagerAfterMethodInterceptor} * instead */ @NullUnmarked @Deprecated public class MethodSecurityInterceptor extends AbstractSecurityInterceptor implements MethodInterceptor { private @Nullable MethodSecurityMetadataSource securityMetadataSource; @Override public Class getSecureObjectClass() { return MethodInvocation.class; } /** * This method should be used to enforce security on a MethodInvocation. * @param mi The method being invoked which requires a security decision * @return The returned value from the method invocation (possibly modified by the * {@code AfterInvocationManager}). * @throws Throwable if any error occurs */ @Override public Object invoke(MethodInvocation mi) throws Throwable { InterceptorStatusToken token = super.beforeInvocation(mi); Object result; try { result = mi.proceed(); } finally { super.finallyInvocation(token); } return super.afterInvocation(token, result); } public MethodSecurityMetadataSource getSecurityMetadataSource() { return this.securityMetadataSource; } @Override public SecurityMetadataSource obtainSecurityMetadataSource() { return this.securityMetadataSource; } public void setSecurityMetadataSource(MethodSecurityMetadataSource newSource) { this.securityMetadataSource = newSource; } } ================================================ FILE: access/src/main/java/org/springframework/security/access/intercept/aopalliance/MethodSecurityMetadataSourceAdvisor.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.access.intercept.aopalliance; import java.io.IOException; import java.io.ObjectInputStream; import java.io.Serializable; import java.lang.reflect.Method; import org.aopalliance.aop.Advice; import org.aopalliance.intercept.MethodInterceptor; import org.jspecify.annotations.NullUnmarked; import org.jspecify.annotations.Nullable; import org.springframework.aop.Pointcut; import org.springframework.aop.support.AbstractPointcutAdvisor; import org.springframework.aop.support.StaticMethodMatcherPointcut; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.security.access.method.MethodSecurityMetadataSource; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; /** * Advisor driven by a {@link MethodSecurityMetadataSource}, used to exclude a * {@link MethodInterceptor} from public (non-secure) methods. *

* Because the AOP framework caches advice calculations, this is normally faster than just * letting the MethodInterceptor run and find out itself that it has no work * to do. *

* This class also allows the use of Spring's {@code DefaultAdvisorAutoProxyCreator}, * which makes configuration easier than setup a ProxyFactoryBean for each * object requiring security. Note that autoproxying is not supported for BeanFactory * implementations, as post-processing is automatic only for application contexts. *

* Based on Spring's TransactionAttributeSourceAdvisor. * * @author Ben Alex * @author Luke Taylor * @deprecated Use * org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity * or publish interceptors directly */ @NullUnmarked @Deprecated @SuppressWarnings("serial") public class MethodSecurityMetadataSourceAdvisor extends AbstractPointcutAdvisor implements BeanFactoryAware { private transient MethodSecurityMetadataSource attributeSource; private transient @Nullable MethodInterceptor interceptor; private final Pointcut pointcut = new MethodSecurityMetadataSourcePointcut(); private @Nullable BeanFactory beanFactory; private final String adviceBeanName; private final String metadataSourceBeanName; private transient volatile Object adviceMonitor = new Object(); /** * Alternative constructor for situations where we want the advisor decoupled from the * advice. Instead the advice bean name should be set. This prevents eager * instantiation of the interceptor (and hence the AuthenticationManager). See * SEC-773, for example. The metadataSourceBeanName is used rather than a direct * reference to support serialization via a bean factory lookup. * @param adviceBeanName name of the MethodSecurityInterceptor bean * @param attributeSource the SecurityMetadataSource (should be the same as the one * used on the interceptor) * @param attributeSourceBeanName the bean name of the attributeSource (required for * serialization) */ public MethodSecurityMetadataSourceAdvisor(String adviceBeanName, MethodSecurityMetadataSource attributeSource, String attributeSourceBeanName) { Assert.notNull(adviceBeanName, "The adviceBeanName cannot be null"); Assert.notNull(attributeSource, "The attributeSource cannot be null"); Assert.notNull(attributeSourceBeanName, "The attributeSourceBeanName cannot be null"); this.adviceBeanName = adviceBeanName; this.attributeSource = attributeSource; this.metadataSourceBeanName = attributeSourceBeanName; } @Override public Pointcut getPointcut() { return this.pointcut; } @Override public Advice getAdvice() { synchronized (this.adviceMonitor) { if (this.interceptor == null) { Assert.notNull(this.adviceBeanName, "'adviceBeanName' must be set for use with bean factory lookup."); Assert.state(this.beanFactory != null, "BeanFactory must be set to resolve 'adviceBeanName'"); this.interceptor = this.beanFactory.getBean(this.adviceBeanName, MethodInterceptor.class); } return this.interceptor; } } @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { this.beanFactory = beanFactory; } private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { ois.defaultReadObject(); this.adviceMonitor = new Object(); this.attributeSource = this.beanFactory.getBean(this.metadataSourceBeanName, MethodSecurityMetadataSource.class); } class MethodSecurityMetadataSourcePointcut extends StaticMethodMatcherPointcut implements Serializable { @Override public boolean matches(Method m, Class targetClass) { MethodSecurityMetadataSource source = MethodSecurityMetadataSourceAdvisor.this.attributeSource; return !CollectionUtils.isEmpty(source.getAttributes(m, targetClass)); } } } ================================================ FILE: access/src/main/java/org/springframework/security/access/intercept/aopalliance/package-info.java ================================================ /* * 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. */ /** * Enforces security for AOP Alliance MethodInvocations, such as via Spring * AOP. */ @NullMarked package org.springframework.security.access.intercept.aopalliance; import org.jspecify.annotations.NullMarked; ================================================ FILE: access/src/main/java/org/springframework/security/access/intercept/aspectj/AspectJCallback.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.access.intercept.aspectj; /** * Called by the {@link AspectJMethodSecurityInterceptor} when it wishes for the AspectJ * processing to continue. Typically implemented in the around() advice as a * simple return proceed(); statement. * * @author Ben Alex * @deprecated This class will be removed from the public API. Please either use * `spring-security-aspects`, Spring Security's method security support or create your own * class that uses Spring AOP annotations. */ @Deprecated public interface AspectJCallback { Object proceedWithObject(); } ================================================ FILE: access/src/main/java/org/springframework/security/access/intercept/aspectj/AspectJMethodSecurityInterceptor.java ================================================ /* * 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 org.springframework.security.access.intercept.aspectj; import org.aspectj.lang.JoinPoint; import org.springframework.security.access.intercept.InterceptorStatusToken; import org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor; /** * AspectJ {@code JoinPoint} security interceptor which wraps the {@code JoinPoint} in a * {@code MethodInvocation} adapter to make it compatible with security infrastructure * classes which only support {@code MethodInvocation}s. *

* One of the {@code invoke} methods should be called from the {@code around()} advice in * your aspect. Alternatively you can use one of the pre-defined aspects from the aspects * module. * * @author Luke Taylor * @author Rob Winch * @since 3.0.3 * @deprecated This class will be removed from the public API. Please either use * `spring-security-aspects`, Spring Security's method security support or create your own * class that uses Spring AOP annotations. */ @Deprecated public final class AspectJMethodSecurityInterceptor extends MethodSecurityInterceptor { /** * Method that is suitable for user with @Aspect notation. * @param jp The AspectJ joint point being invoked which requires a security decision * @return The returned value from the method invocation * @throws Throwable if the invocation throws one */ public Object invoke(JoinPoint jp) throws Throwable { return super.invoke(new MethodInvocationAdapter(jp)); } /** * Method that is suitable for user with traditional AspectJ-code aspects. * @param jp The AspectJ joint point being invoked which requires a security decision * @param advisorProceed the advice-defined anonymous class that implements * {@code AspectJCallback} containing a simple {@code return proceed();} statement * @return The returned value from the method invocation */ public Object invoke(JoinPoint jp, AspectJCallback advisorProceed) { InterceptorStatusToken token = super.beforeInvocation(new MethodInvocationAdapter(jp)); Object result; try { result = advisorProceed.proceedWithObject(); } finally { super.finallyInvocation(token); } return super.afterInvocation(token, result); } } ================================================ FILE: access/src/main/java/org/springframework/security/access/intercept/aspectj/MethodInvocationAdapter.java ================================================ /* * 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 org.springframework.security.access.intercept.aspectj; import java.lang.reflect.AccessibleObject; import java.lang.reflect.Method; import org.aopalliance.intercept.MethodInvocation; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.reflect.CodeSignature; import org.jspecify.annotations.NullUnmarked; import org.springframework.util.Assert; /** * Decorates a JoinPoint to allow it to be used with method-security infrastructure * classes which support {@code MethodInvocation} instances. * * @author Luke Taylor * @since 3.0.3 * @deprecated This class will be removed from the public API. See * `JoinPointMethodInvocation` in `spring-security-aspects` for its replacement */ @NullUnmarked @Deprecated public final class MethodInvocationAdapter implements MethodInvocation { private final ProceedingJoinPoint jp; private final Method method; private final Object target; MethodInvocationAdapter(JoinPoint jp) { this.jp = (ProceedingJoinPoint) jp; if (jp.getTarget() != null) { this.target = jp.getTarget(); } else { // SEC-1295: target may be null if an ITD is in use this.target = jp.getSignature().getDeclaringType(); } String targetMethodName = jp.getStaticPart().getSignature().getName(); Class[] types = ((CodeSignature) jp.getStaticPart().getSignature()).getParameterTypes(); Class declaringType = jp.getStaticPart().getSignature().getDeclaringType(); this.method = findMethod(targetMethodName, declaringType, types); Assert.notNull(this.method, () -> "Could not obtain target method from JoinPoint: '" + jp + "'"); } private Method findMethod(String name, Class declaringType, Class[] params) { Method method = null; try { method = declaringType.getMethod(name, params); } catch (NoSuchMethodException ignored) { } if (method == null) { try { method = declaringType.getDeclaredMethod(name, params); } catch (NoSuchMethodException ignored) { } } return method; } @Override public Method getMethod() { return this.method; } @Override public Object[] getArguments() { return this.jp.getArgs(); } @Override public AccessibleObject getStaticPart() { return this.method; } @Override public Object getThis() { return this.target; } @Override public Object proceed() throws Throwable { return this.jp.proceed(); } } ================================================ FILE: access/src/main/java/org/springframework/security/access/intercept/aspectj/package-info.java ================================================ /* * 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. */ /** * Enforces security for AspectJ JointPoints, delegating secure object * callbacks to the calling aspect. */ @NullMarked package org.springframework.security.access.intercept.aspectj; import org.jspecify.annotations.NullMarked; ================================================ FILE: access/src/main/java/org/springframework/security/access/intercept/package-info.java ================================================ /* * 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. */ /** * Abstract level security interception classes which are responsible for enforcing the * configured security constraints for a secure object. *

* A secure object is a term frequently used throughout the security system. It * does not refer to a business object that is being secured, but instead refers to * some infrastructure object that can have security facilities provided for it by Spring * Security. For example, one secure object would be MethodInvocation, whilst * another would be HTTP {@code org.springframework.security.web.FilterInvocation}. Note * these are infrastructure objects and their design allows them to represent a large * variety of actual resources that might need to be secured, such as business objects or * HTTP request URLs. *

* Each secure object typically has its own interceptor package. Each package usually * includes a concrete security interceptor (which subclasses * {@link org.springframework.security.access.intercept.AbstractSecurityInterceptor}) and * an appropriate {@link org.springframework.security.access.SecurityMetadataSource} for * the type of resources the secure object represents. */ @NullMarked package org.springframework.security.access.intercept; import org.jspecify.annotations.NullMarked; ================================================ FILE: access/src/main/java/org/springframework/security/access/method/AbstractFallbackMethodSecurityMetadataSource.java ================================================ /* * 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 org.springframework.security.access.method; import java.lang.reflect.Method; import java.util.Collection; import java.util.Collections; import org.jspecify.annotations.Nullable; import org.springframework.aop.support.AopUtils; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.authorization.AuthorizationManager; /** * Abstract implementation of {@link MethodSecurityMetadataSource} that supports both * Spring AOP and AspectJ and performs attribute resolution from: 1. specific target * method; 2. target class; 3. declaring method; 4. declaring class/interface. Use with * {@link DelegatingMethodSecurityMetadataSource} for caching support. *

* This class mimics the behaviour of Spring's * AbstractFallbackTransactionAttributeSource class. *

* Note that this class cannot extract security metadata where that metadata is expressed * by way of a target method/class (i.e. #1 and #2 above) AND the target method/class is * encapsulated in another proxy object. Spring Security does not walk a proxy chain to * locate the concrete/final target object. Consider making Spring Security your final * advisor (so it advises the final target, as opposed to another proxy), move the * metadata to declared methods or interfaces the proxy implements, or provide your own * replacement MethodSecurityMetadataSource. * * @author Ben Alex * @author Luke taylor * @since 2.0 * @deprecated Use the {@code use-authorization-manager} attribute for * {@code } and {@code } instead or use * annotation-based or {@link AuthorizationManager}-based authorization */ @Deprecated public abstract class AbstractFallbackMethodSecurityMetadataSource extends AbstractMethodSecurityMetadataSource { @Override public Collection getAttributes(Method method, @Nullable Class targetClass) { // The method may be on an interface, but we need attributes from the target // class. // If the target class is null, the method will be unchanged. Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass); // First try is the method in the target class. Collection attr = findAttributes(specificMethod, targetClass); if (attr != null) { return attr; } // Second try is the config attribute on the target class. attr = findAttributes(specificMethod.getDeclaringClass()); if (attr != null) { return attr; } if (specificMethod != method || targetClass == null) { // Fallback is to look at the original method. attr = findAttributes(method, method.getDeclaringClass()); if (attr != null) { return attr; } // Last fallback is the class of the original method. return findAttributes(method.getDeclaringClass()); } return Collections.emptyList(); } /** * Obtains the security metadata applicable to the specified method invocation. * *

* Note that the {@link Method#getDeclaringClass()} may not equal the * targetClass. Both parameters are provided to assist subclasses which * may wish to provide advanced capabilities related to method metadata being * "registered" against a method even if the target class does not declare the method * (i.e. the subclass may only inherit the method). * @param method the method for the current invocation (never null) * @param targetClass the target class for the invocation (may be null) * @return the security metadata (or null if no metadata applies) */ protected abstract Collection findAttributes(Method method, @Nullable Class targetClass); /** * Obtains the security metadata registered against the specified class. * *

* Subclasses should only return metadata expressed at a class level. Subclasses * should NOT aggregate metadata for each method registered against a class, as the * abstract superclass will separate invoke {@link #findAttributes(Method, Class)} for * individual methods as appropriate. * @param clazz the target class for the invocation (never null) * @return the security metadata (or null if no metadata applies) */ protected abstract Collection findAttributes(Class clazz); } ================================================ FILE: access/src/main/java/org/springframework/security/access/method/AbstractMethodSecurityMetadataSource.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.access.method; import java.util.Collection; import org.aopalliance.intercept.MethodInvocation; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.jspecify.annotations.NullUnmarked; import org.springframework.aop.framework.AopProxyUtils; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.authorization.AuthorizationManager; /** * Abstract implementation of MethodSecurityMetadataSource which resolves the * secured object type to a MethodInvocation. * * @author Ben Alex * @author Luke Taylor * @deprecated Use the {@code use-authorization-manager} attribute for * {@code } and {@code } instead or use * annotation-based or {@link AuthorizationManager}-based authorization */ @NullUnmarked @Deprecated public abstract class AbstractMethodSecurityMetadataSource implements MethodSecurityMetadataSource { protected final Log logger = LogFactory.getLog(getClass()); @Override public final Collection getAttributes(Object object) { if (object instanceof MethodInvocation mi) { Object target = mi.getThis(); Class targetClass = null; if (target != null) { targetClass = (target instanceof Class) ? (Class) target : AopProxyUtils.ultimateTargetClass(target); } Collection attrs = getAttributes(mi.getMethod(), targetClass); if (attrs != null && !attrs.isEmpty()) { return attrs; } if (target != null && !(target instanceof Class)) { attrs = getAttributes(mi.getMethod(), target.getClass()); } return attrs; } throw new IllegalArgumentException("Object must be a non-null MethodInvocation"); } @Override public final boolean supports(Class clazz) { return (MethodInvocation.class.isAssignableFrom(clazz)); } } ================================================ FILE: access/src/main/java/org/springframework/security/access/method/DelegatingMethodSecurityMetadataSource.java ================================================ /* * 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 org.springframework.security.access.method; import java.lang.reflect.Method; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.jspecify.annotations.Nullable; import org.springframework.core.log.LogMessage; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.authorization.AuthorizationManager; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; /** * Automatically tries a series of method definition sources, relying on the first source * of metadata that provides a non-null/non-empty response. Provides automatic caching of * the retrieved metadata. * * @author Ben Alex * @author Luke Taylor * @deprecated Use the {@code use-authorization-manager} attribute for * {@code } and {@code } instead or use * annotation-based or {@link AuthorizationManager}-based authorization */ @Deprecated public final class DelegatingMethodSecurityMetadataSource extends AbstractMethodSecurityMetadataSource { private static final List NULL_CONFIG_ATTRIBUTE = Collections.emptyList(); private final List methodSecurityMetadataSources; private final Map> attributeCache = new HashMap<>(); public DelegatingMethodSecurityMetadataSource(List methodSecurityMetadataSources) { Assert.notNull(methodSecurityMetadataSources, "MethodSecurityMetadataSources cannot be null"); this.methodSecurityMetadataSources = methodSecurityMetadataSources; } @Override public Collection getAttributes(Method method, @Nullable Class targetClass) { DefaultCacheKey cacheKey = new DefaultCacheKey(method, targetClass); synchronized (this.attributeCache) { Collection cached = this.attributeCache.get(cacheKey); // Check for canonical value indicating there is no config attribute, if (cached != null) { return cached; } // No cached value, so query the sources to find a result Collection attributes = null; for (MethodSecurityMetadataSource s : this.methodSecurityMetadataSources) { attributes = s.getAttributes(method, targetClass); if (attributes != null && !attributes.isEmpty()) { break; } } // Put it in the cache. if (attributes == null || attributes.isEmpty()) { this.attributeCache.put(cacheKey, NULL_CONFIG_ATTRIBUTE); return NULL_CONFIG_ATTRIBUTE; } this.logger.debug(LogMessage.format("Caching method [%s] with attributes %s", cacheKey, attributes)); this.attributeCache.put(cacheKey, attributes); return attributes; } } @Override public Collection getAllConfigAttributes() { Set set = new HashSet<>(); for (MethodSecurityMetadataSource s : this.methodSecurityMetadataSources) { Collection attrs = s.getAllConfigAttributes(); if (attrs != null) { set.addAll(attrs); } } return set; } public List getMethodSecurityMetadataSources() { return this.methodSecurityMetadataSources; } private static class DefaultCacheKey { private final Method method; private final @Nullable Class targetClass; DefaultCacheKey(Method method, @Nullable Class targetClass) { this.method = method; this.targetClass = targetClass; } @Override public boolean equals(Object other) { DefaultCacheKey otherKey = (DefaultCacheKey) other; return (this.method.equals(otherKey.method) && ObjectUtils.nullSafeEquals(this.targetClass, otherKey.targetClass)); } @Override public int hashCode() { return this.method.hashCode() * 21 + ((this.targetClass != null) ? this.targetClass.hashCode() : 0); } @Override public String toString() { String targetClassName = (this.targetClass != null) ? this.targetClass.getName() : "-"; return "CacheKey[" + targetClassName + "; " + this.method + "]"; } } } ================================================ FILE: access/src/main/java/org/springframework/security/access/method/MapBasedMethodSecurityMetadataSource.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.access.method; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.jspecify.annotations.NullUnmarked; import org.jspecify.annotations.Nullable; import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.core.log.LogMessage; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.authorization.AuthorizationManager; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; /** * Stores a list of ConfigAttributes for a method or class signature. * *

* This class is the preferred implementation of {@link MethodSecurityMetadataSource} for * XML-based definition of method security metadata. To assist in XML-based definition, * wildcard support is provided. *

* * @author Ben Alex * @since 2.0 * @deprecated Use the {@code use-authorization-manager} attribute for * {@code } and {@code } instead or use * annotation-based or {@link AuthorizationManager}-based authorization */ @NullUnmarked @Deprecated public class MapBasedMethodSecurityMetadataSource extends AbstractFallbackMethodSecurityMetadataSource implements BeanClassLoaderAware { @SuppressWarnings("NullAway") private @Nullable ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader(); /** * Map from RegisteredMethod to ConfigAttribute list */ protected final Map> methodMap = new HashMap<>(); /** * Map from RegisteredMethod to name pattern used for registration */ private final Map nameMap = new HashMap<>(); public MapBasedMethodSecurityMetadataSource() { } /** * Creates the MapBasedMethodSecurityMetadataSource from a * @param methodMap map of method names to ConfigAttributes. */ public MapBasedMethodSecurityMetadataSource(Map> methodMap) { for (Map.Entry> entry : methodMap.entrySet()) { addSecureMethod(entry.getKey(), entry.getValue()); } } /** * Implementation does not support class-level attributes. */ @Override protected @Nullable Collection findAttributes(Class clazz) { return null; } /** * Will walk the method inheritance tree to find the most specific declaration * applicable. */ @Override protected @Nullable Collection findAttributes(Method method, Class targetClass) { if (targetClass == null) { return null; } return findAttributesSpecifiedAgainst(method, targetClass); } private @Nullable List findAttributesSpecifiedAgainst(Method method, Class clazz) { RegisteredMethod registeredMethod = new RegisteredMethod(method, clazz); if (this.methodMap.containsKey(registeredMethod)) { return this.methodMap.get(registeredMethod); } // Search superclass if (clazz.getSuperclass() != null) { return findAttributesSpecifiedAgainst(method, clazz.getSuperclass()); } return null; } /** * Add configuration attributes for a secure method. Method names can end or start * with * for matching multiple methods. * @param name type and method name, separated by a dot * @param attr the security attributes associated with the method */ private void addSecureMethod(String name, List attr) { int lastDotIndex = name.lastIndexOf("."); Assert.isTrue(lastDotIndex != -1, () -> "'" + name + "' is not a valid method name: format is FQN.methodName"); String methodName = name.substring(lastDotIndex + 1); Assert.hasText(methodName, () -> "Method not found for '" + name + "'"); String typeName = name.substring(0, lastDotIndex); Class type = ClassUtils.resolveClassName(typeName, this.beanClassLoader); addSecureMethod(type, methodName, attr); } /** * Add configuration attributes for a secure method. Mapped method names can end or * start with * for matching multiple methods. * @param javaType target interface or class the security configuration attribute * applies to * @param mappedName mapped method name, which the javaType has declared or inherited * @param attr required authorities associated with the method */ public void addSecureMethod(Class javaType, String mappedName, List attr) { String name = javaType.getName() + '.' + mappedName; this.logger.debug(LogMessage.format("Request to add secure method [%s] with attributes [%s]", name, attr)); Method[] methods = javaType.getMethods(); List matchingMethods = new ArrayList<>(); for (Method method : methods) { if (method.getName().equals(mappedName) || isMatch(method.getName(), mappedName)) { matchingMethods.add(method); } } Assert.notEmpty(matchingMethods, () -> "Couldn't find method '" + mappedName + "' on '" + javaType + "'"); registerAllMatchingMethods(javaType, attr, name, matchingMethods); } private void registerAllMatchingMethods(Class javaType, List attr, String name, List matchingMethods) { for (Method method : matchingMethods) { RegisteredMethod registeredMethod = new RegisteredMethod(method, javaType); String regMethodName = this.nameMap.get(registeredMethod); if ((regMethodName == null) || (!regMethodName.equals(name) && (regMethodName.length() <= name.length()))) { // no already registered method name, or more specific // method name specification (now) -> (re-)register method if (regMethodName != null) { this.logger.debug(LogMessage.format( "Replacing attributes for secure method [%s]: current name [%s] is more specific than [%s]", method, name, regMethodName)); } this.nameMap.put(registeredMethod, name); addSecureMethod(registeredMethod, attr); } else { this.logger.debug(LogMessage.format( "Keeping attributes for secure method [%s]: current name [%s] is not more specific than [%s]", method, name, regMethodName)); } } } /** * Adds configuration attributes for a specific method, for example where the method * has been matched using a pointcut expression. If a match already exists in the map * for the method, then the existing match will be retained, so that if this method is * called for a more general pointcut it will not override a more specific one which * has already been added. *

* This method should only be called during initialization of the {@code BeanFactory}. */ public void addSecureMethod(Class javaType, Method method, List attr) { RegisteredMethod key = new RegisteredMethod(method, javaType); if (this.methodMap.containsKey(key)) { this.logger.debug(LogMessage.format("Method [%s] is already registered with attributes [%s]", method, this.methodMap.get(key))); return; } this.methodMap.put(key, attr); } /** * Add configuration attributes for a secure method. * @param method the method to be secured * @param attr required authorities associated with the method */ private void addSecureMethod(RegisteredMethod method, List attr) { Assert.notNull(method, "RegisteredMethod required"); Assert.notNull(attr, "Configuration attribute required"); this.logger.info(LogMessage.format("Adding secure method [%s] with attributes [%s]", method, attr)); this.methodMap.put(method, attr); } /** * Obtains the configuration attributes explicitly defined against this bean. * @return the attributes explicitly defined against this bean */ @Override public Collection getAllConfigAttributes() { Set allAttributes = new HashSet<>(); this.methodMap.values().forEach(allAttributes::addAll); return allAttributes; } /** * Return if the given method name matches the mapped name. The default implementation * checks for "xxx" and "xxx" matches. * @param methodName the method name of the class * @param mappedName the name in the descriptor * @return if the names match */ private boolean isMatch(String methodName, String mappedName) { return (mappedName.endsWith("*") && methodName.startsWith(mappedName.substring(0, mappedName.length() - 1))) || (mappedName.startsWith("*") && methodName.endsWith(mappedName.substring(1, mappedName.length()))); } @Override public void setBeanClassLoader(ClassLoader beanClassLoader) { Assert.notNull(beanClassLoader, "Bean class loader required"); this.beanClassLoader = beanClassLoader; } /** * @return map size (for unit tests and diagnostics) */ public int getMethodMapSize() { return this.methodMap.size(); } /** * Stores both the Java Method as well as the Class we obtained the Method from. This * is necessary because Method only provides us access to the declaring class. It * doesn't provide a way for us to introspect which Class the Method was registered * against. If a given Class inherits and redeclares a method (i.e. calls super();) * the registered Class and declaring Class are the same. If a given class merely * inherits but does not redeclare a method, the registered Class will be the Class * we're invoking against and the Method will provide details of the declared class. */ private static class RegisteredMethod { private final Method method; private final Class registeredJavaType; RegisteredMethod(Method method, Class registeredJavaType) { Assert.notNull(method, "Method required"); Assert.notNull(registeredJavaType, "Registered Java Type required"); this.method = method; this.registeredJavaType = registeredJavaType; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj instanceof RegisteredMethod rhs) { return this.method.equals(rhs.method) && this.registeredJavaType.equals(rhs.registeredJavaType); } return false; } @Override public int hashCode() { return this.method.hashCode() * this.registeredJavaType.hashCode(); } @Override public String toString() { return "RegisteredMethod[" + this.registeredJavaType.getName() + "; " + this.method + "]"; } } } ================================================ FILE: access/src/main/java/org/springframework/security/access/method/MethodSecurityMetadataSource.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.access.method; import java.lang.reflect.Method; import java.util.Collection; import org.jspecify.annotations.Nullable; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.access.SecurityMetadataSource; import org.springframework.security.authorization.AuthorizationManager; /** * Interface for SecurityMetadataSource implementations that are designed to * perform lookups keyed on Methods. * * @author Ben Alex * @see org.springframework.security.authorization.method.PreAuthorizeAuthorizationManager * @see org.springframework.security.authorization.method.PostAuthorizeAuthorizationManager * @deprecated Use the {@code use-authorization-manager} attribute for * {@code } and {@code } instead or use * annotation-based or {@link AuthorizationManager}-based authorization */ @Deprecated public interface MethodSecurityMetadataSource extends SecurityMetadataSource { Collection getAttributes(Method method, @Nullable Class targetClass); } ================================================ FILE: access/src/main/java/org/springframework/security/access/method/P.java ================================================ /* * 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 org.springframework.security.access.method; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.springframework.security.core.parameters.AnnotationParameterNameDiscoverer; /** * An annotation that can be used along with {@link AnnotationParameterNameDiscoverer} to * specify parameter names. This is useful for interfaces prior to JDK 8 which cannot * contain the parameter names. * * @see AnnotationParameterNameDiscoverer * @author Rob Winch * @since 3.2 * @deprecated use @{code org.springframework.security.core.parameters.P} */ @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) @Documented @Deprecated public @interface P { /** * The parameter name * @return */ String value(); } ================================================ FILE: access/src/main/java/org/springframework/security/access/method/package-info.java ================================================ /* * 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. */ /** * Provides {@code SecurityMetadataSource} implementations for securing Java method * invocations via different AOP libraries. */ @NullMarked package org.springframework.security.access.method; import org.jspecify.annotations.NullMarked; ================================================ FILE: access/src/main/java/org/springframework/security/access/prepost/PostInvocationAdviceProvider.java ================================================ /* * 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 org.springframework.security.access.prepost; import java.util.Collection; import org.aopalliance.intercept.MethodInvocation; 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.security.access.AccessDeniedException; import org.springframework.security.access.AfterInvocationProvider; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.core.Authentication; /** * AfterInvocationProvider which delegates to a * {@link PostInvocationAuthorizationAdvice} instance passing it the * PostInvocationAttribute created from @PostAuthorize and @PostFilter * annotations. * * @author Luke Taylor * @author Alexander Furer * @since 3.0 * @deprecated Use * {@link org.springframework.security.authorization.method.AuthorizationManagerAfterMethodInterceptor} * instead */ @NullUnmarked @Deprecated public class PostInvocationAdviceProvider implements AfterInvocationProvider { protected final Log logger = LogFactory.getLog(getClass()); private final PostInvocationAuthorizationAdvice postAdvice; public PostInvocationAdviceProvider(PostInvocationAuthorizationAdvice postAdvice) { this.postAdvice = postAdvice; } @Override public Object decide(Authentication authentication, Object object, Collection config, Object returnedObject) throws AccessDeniedException { PostInvocationAttribute postInvocationAttribute = findPostInvocationAttribute(config); if (postInvocationAttribute == null) { return returnedObject; } return this.postAdvice.after(authentication, (MethodInvocation) object, postInvocationAttribute, returnedObject); } private @Nullable PostInvocationAttribute findPostInvocationAttribute(Collection config) { for (ConfigAttribute attribute : config) { if (attribute instanceof PostInvocationAttribute) { return (PostInvocationAttribute) attribute; } } return null; } @Override public boolean supports(ConfigAttribute attribute) { return attribute instanceof PostInvocationAttribute; } @Override public boolean supports(Class clazz) { return MethodInvocation.class.isAssignableFrom(clazz); } } ================================================ FILE: access/src/main/java/org/springframework/security/access/prepost/PostInvocationAttribute.java ================================================ /* * 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 org.springframework.security.access.prepost; import org.springframework.security.access.ConfigAttribute; /** * Marker interface for attributes which are created from combined @PostFilter * and @PostAuthorize annotations. *

* Consumed by a {@link PostInvocationAuthorizationAdvice}. * * @author Luke Taylor * @since 3.0 * @deprecated Use * {@link org.springframework.security.authorization.method.AuthorizationManagerAfterMethodInterceptor} * instead */ @Deprecated public interface PostInvocationAttribute extends ConfigAttribute { } ================================================ FILE: access/src/main/java/org/springframework/security/access/prepost/PostInvocationAuthorizationAdvice.java ================================================ /* * 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 org.springframework.security.access.prepost; import org.aopalliance.intercept.MethodInvocation; import org.springframework.aop.framework.AopInfrastructureBean; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.core.Authentication; /** * Performs filtering and authorization logic after a method is invoked. * * @author Luke Taylor * @since 3.0 * @deprecated Use * {@link org.springframework.security.authorization.method.AuthorizationManagerAfterMethodInterceptor} * instead */ @Deprecated public interface PostInvocationAuthorizationAdvice extends AopInfrastructureBean { Object after(Authentication authentication, MethodInvocation mi, PostInvocationAttribute pia, Object returnedObject) throws AccessDeniedException; } ================================================ FILE: access/src/main/java/org/springframework/security/access/prepost/PreInvocationAttribute.java ================================================ /* * 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 org.springframework.security.access.prepost; import org.springframework.security.access.ConfigAttribute; /** * Marker interface for attributes which are created from combined @PreFilter * and @PreAuthorize annotations. *

* Consumed by a {@link PreInvocationAuthorizationAdvice}. * * @author Luke Taylor * @since 3.0 * @deprecated Use * {@link org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor} * instead */ @Deprecated public interface PreInvocationAttribute extends ConfigAttribute { } ================================================ FILE: access/src/main/java/org/springframework/security/access/prepost/PreInvocationAuthorizationAdvice.java ================================================ /* * 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 org.springframework.security.access.prepost; import org.aopalliance.intercept.MethodInvocation; import org.springframework.aop.framework.AopInfrastructureBean; import org.springframework.security.core.Authentication; /** * Performs argument filtering and authorization logic before a method is invoked. * * @author Luke Taylor * @since 3.0 * @deprecated Use * {@link org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor} * instead */ @Deprecated public interface PreInvocationAuthorizationAdvice extends AopInfrastructureBean { /** * The "before" advice which should be executed to perform any filtering necessary and * to decide whether the method call is authorised. * @param authentication the information on the principal on whose account the * decision should be made * @param mi the method invocation being attempted * @param preInvocationAttribute the attribute built from the @PreFilter * and @PostFilter annotations. * @return true if authorised, false otherwise */ boolean before(Authentication authentication, MethodInvocation mi, PreInvocationAttribute preInvocationAttribute); } ================================================ FILE: access/src/main/java/org/springframework/security/access/prepost/PreInvocationAuthorizationAdviceVoter.java ================================================ /* * 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 org.springframework.security.access.prepost; import java.util.Collection; import org.aopalliance.intercept.MethodInvocation; 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.security.access.AccessDecisionVoter; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.core.Authentication; /** * Voter which performs the actions using a PreInvocationAuthorizationAdvice * implementation generated from @PreFilter and @PreAuthorize annotations. *

* In practice, if these annotations are being used, they will normally contain all the * necessary access control logic, so a voter-based system is not really necessary and a * single AccessDecisionManager which contained the same logic would suffice. * However, this class fits in readily with the traditional voter-based * AccessDecisionManager implementations used by Spring Security. * * @author Luke Taylor * @since 3.0 * @deprecated Use * {@link org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor} * instead */ @NullUnmarked @Deprecated public class PreInvocationAuthorizationAdviceVoter implements AccessDecisionVoter { protected final Log logger = LogFactory.getLog(getClass()); private final PreInvocationAuthorizationAdvice preAdvice; public PreInvocationAuthorizationAdviceVoter(PreInvocationAuthorizationAdvice pre) { this.preAdvice = pre; } @Override public boolean supports(ConfigAttribute attribute) { return attribute instanceof PreInvocationAttribute; } @Override public boolean supports(Class clazz) { return MethodInvocation.class.isAssignableFrom(clazz); } @Override public int vote(Authentication authentication, MethodInvocation method, Collection attributes) { // Find prefilter and preauth (or combined) attributes // if both null, abstain else call advice with them PreInvocationAttribute preAttr = findPreInvocationAttribute(attributes); if (preAttr == null) { // No expression based metadata, so abstain return ACCESS_ABSTAIN; } return this.preAdvice.before(authentication, method, preAttr) ? ACCESS_GRANTED : ACCESS_DENIED; } private @Nullable PreInvocationAttribute findPreInvocationAttribute(Collection config) { for (ConfigAttribute attribute : config) { if (attribute instanceof PreInvocationAttribute) { return (PreInvocationAttribute) attribute; } } return null; } } ================================================ FILE: access/src/main/java/org/springframework/security/access/prepost/PrePostAdviceReactiveMethodInterceptor.java ================================================ /* * 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 org.springframework.security.access.prepost; import java.lang.reflect.Method; import java.util.Collection; import kotlinx.coroutines.reactive.ReactiveFlowKt; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; import org.jspecify.annotations.NullUnmarked; import org.jspecify.annotations.Nullable; import org.reactivestreams.Publisher; import reactor.core.Exceptions; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import org.springframework.core.KotlinDetector; import org.springframework.core.MethodParameter; import org.springframework.core.ReactiveAdapter; import org.springframework.core.ReactiveAdapterRegistry; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.access.method.MethodSecurityMetadataSource; import org.springframework.security.authentication.AnonymousAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.context.ReactiveSecurityContextHolder; import org.springframework.security.core.context.SecurityContext; import org.springframework.util.Assert; /** * A {@link MethodInterceptor} that supports {@link PreAuthorize} and * {@link PostAuthorize} for methods that return {@link Mono} or {@link Flux} and Kotlin * coroutine functions. * * @author Rob Winch * @author Eleftheria Stein * @since 5.0 * @deprecated Use * {@link org.springframework.security.authorization.method.AuthorizationManagerBeforeReactiveMethodInterceptor} * or * {@link org.springframework.security.authorization.method.AuthorizationManagerAfterReactiveMethodInterceptor} */ @NullUnmarked @Deprecated public class PrePostAdviceReactiveMethodInterceptor implements MethodInterceptor { private Authentication anonymous = new AnonymousAuthenticationToken("key", "anonymous", AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS")); private final MethodSecurityMetadataSource attributeSource; private final PreInvocationAuthorizationAdvice preInvocationAdvice; private final PostInvocationAuthorizationAdvice postAdvice; private static final String COROUTINES_FLOW_CLASS_NAME = "kotlinx.coroutines.flow.Flow"; private static final int RETURN_TYPE_METHOD_PARAMETER_INDEX = -1; /** * Creates a new instance * @param attributeSource the {@link MethodSecurityMetadataSource} to use * @param preInvocationAdvice the {@link PreInvocationAuthorizationAdvice} to use * @param postInvocationAdvice the {@link PostInvocationAuthorizationAdvice} to use */ public PrePostAdviceReactiveMethodInterceptor(MethodSecurityMetadataSource attributeSource, PreInvocationAuthorizationAdvice preInvocationAdvice, PostInvocationAuthorizationAdvice postInvocationAdvice) { Assert.notNull(attributeSource, "attributeSource cannot be null"); Assert.notNull(preInvocationAdvice, "preInvocationAdvice cannot be null"); Assert.notNull(postInvocationAdvice, "postInvocationAdvice cannot be null"); this.attributeSource = attributeSource; this.preInvocationAdvice = preInvocationAdvice; this.postAdvice = postInvocationAdvice; } @Override public Object invoke(final MethodInvocation invocation) { Method method = invocation.getMethod(); Class returnType = method.getReturnType(); boolean isSuspendingFunction = KotlinDetector.isSuspendingFunction(method); boolean hasFlowReturnType = COROUTINES_FLOW_CLASS_NAME .equals(new MethodParameter(method, RETURN_TYPE_METHOD_PARAMETER_INDEX).getParameterType().getName()); boolean hasReactiveReturnType = Publisher.class.isAssignableFrom(returnType) || isSuspendingFunction || hasFlowReturnType; Assert.state(hasReactiveReturnType, () -> "The returnType " + returnType + " on " + method + " must return an instance of org.reactivestreams.Publisher " + "(i.e. Mono / Flux) or the function must be a Kotlin coroutine " + "function in order to support Reactor Context"); Class targetClass = invocation.getThis().getClass(); Collection attributes = this.attributeSource.getAttributes(method, targetClass); PreInvocationAttribute preAttr = findPreInvocationAttribute(attributes); // @formatter:off Mono toInvoke = ReactiveSecurityContextHolder.getContext() .map(SecurityContext::getAuthentication) .defaultIfEmpty(this.anonymous) .filter((auth) -> this.preInvocationAdvice.before(auth, invocation, preAttr)) .switchIfEmpty(Mono.defer(() -> Mono.error(new AccessDeniedException("Denied")))); // @formatter:on PostInvocationAttribute attr = findPostInvocationAttribute(attributes); if (Mono.class.isAssignableFrom(returnType)) { return toInvoke.flatMap((auth) -> PrePostAdviceReactiveMethodInterceptor.>proceed(invocation) .map((r) -> (attr != null) ? this.postAdvice.after(auth, invocation, attr, r) : r)); } if (Flux.class.isAssignableFrom(returnType)) { return toInvoke.flatMapMany((auth) -> PrePostAdviceReactiveMethodInterceptor.>proceed(invocation) .map((r) -> (attr != null) ? this.postAdvice.after(auth, invocation, attr, r) : r)); } if (hasFlowReturnType) { if (isSuspendingFunction) { return toInvoke .flatMapMany((auth) -> Flux.from(PrePostAdviceReactiveMethodInterceptor.proceed(invocation)) .map((r) -> (attr != null) ? this.postAdvice.after(auth, invocation, attr, r) : r)); } else { ReactiveAdapter adapter = ReactiveAdapterRegistry.getSharedInstance().getAdapter(returnType); Assert.state(adapter != null, () -> "The returnType " + returnType + " on " + method + " must have a org.springframework.core.ReactiveAdapter registered"); Flux response = toInvoke.flatMapMany((auth) -> Flux .from(adapter.toPublisher(PrePostAdviceReactiveMethodInterceptor.flowProceed(invocation))) .map((r) -> (attr != null) ? this.postAdvice.after(auth, invocation, attr, r) : r)); return KotlinDelegate.asFlow(response); } } return toInvoke.flatMap((auth) -> Mono.from(PrePostAdviceReactiveMethodInterceptor.proceed(invocation)) .map((r) -> (attr != null) ? this.postAdvice.after(auth, invocation, attr, r) : r)); } @SuppressWarnings("unchecked") private static > @Nullable T proceed(final MethodInvocation invocation) { try { return (T) invocation.proceed(); } catch (Throwable throwable) { throw Exceptions.propagate(throwable); } } private static @Nullable Object flowProceed(final MethodInvocation invocation) { try { return invocation.proceed(); } catch (Throwable throwable) { throw Exceptions.propagate(throwable); } } private static @Nullable PostInvocationAttribute findPostInvocationAttribute(Collection config) { for (ConfigAttribute attribute : config) { if (attribute instanceof PostInvocationAttribute) { return (PostInvocationAttribute) attribute; } } return null; } private static @Nullable PreInvocationAttribute findPreInvocationAttribute(Collection config) { for (ConfigAttribute attribute : config) { if (attribute instanceof PreInvocationAttribute) { return (PreInvocationAttribute) attribute; } } return null; } /** * Inner class to avoid a hard dependency on Kotlin at runtime. */ private static class KotlinDelegate { private static Object asFlow(Publisher publisher) { return ReactiveFlowKt.asFlow(publisher); } } } ================================================ FILE: access/src/main/java/org/springframework/security/access/prepost/PrePostAnnotationSecurityMetadataSource.java ================================================ /* * 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 org.springframework.security.access.prepost; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import org.jspecify.annotations.NullUnmarked; import org.jspecify.annotations.Nullable; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.log.LogMessage; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.access.method.AbstractMethodSecurityMetadataSource; import org.springframework.util.ClassUtils; /** * MethodSecurityMetadataSource which extracts metadata from the @PreFilter * and @PreAuthorize annotations placed on a method. This class is merely responsible for * locating the relevant annotations (if any). It delegates the actual * ConfigAttribute creation to its {@link PrePostInvocationAttributeFactory}, * thus decoupling itself from the mechanism which will enforce the annotations' * behaviour. *

* Annotations may be specified on classes or methods, and method-specific annotations * will take precedence. If you use any annotation and do not specify a pre-authorization * condition, then the method will be allowed as if a @PreAuthorize("permitAll") were * present. *

* Since we are handling multiple annotations here, it's possible that we may have to * combine annotations defined in multiple locations for a single method - they may be * defined on the method itself, or at interface or class level. * * @author Luke Taylor * @since 3.0 * @see PreInvocationAuthorizationAdviceVoter * @deprecated Use * {@link org.springframework.security.authorization.method.PreAuthorizeAuthorizationManager} * and * {@link org.springframework.security.authorization.method.PostAuthorizeAuthorizationManager} * instead */ @NullUnmarked @Deprecated public class PrePostAnnotationSecurityMetadataSource extends AbstractMethodSecurityMetadataSource { private final PrePostInvocationAttributeFactory attributeFactory; public PrePostAnnotationSecurityMetadataSource(PrePostInvocationAttributeFactory attributeFactory) { this.attributeFactory = attributeFactory; } @Override public Collection getAttributes(Method method, Class targetClass) { if (method.getDeclaringClass() == Object.class) { return Collections.emptyList(); } PreFilter preFilter = findAnnotation(method, targetClass, PreFilter.class); PreAuthorize preAuthorize = findAnnotation(method, targetClass, PreAuthorize.class); PostFilter postFilter = findAnnotation(method, targetClass, PostFilter.class); // TODO: Can we check for void methods and throw an exception here? PostAuthorize postAuthorize = findAnnotation(method, targetClass, PostAuthorize.class); if (preFilter == null && preAuthorize == null && postFilter == null && postAuthorize == null) { // There is no meta-data so return return Collections.emptyList(); } String preFilterAttribute = (preFilter != null) ? preFilter.value() : null; String filterObject = (preFilter != null) ? preFilter.filterTarget() : null; String preAuthorizeAttribute = (preAuthorize != null) ? preAuthorize.value() : null; String postFilterAttribute = (postFilter != null) ? postFilter.value() : null; String postAuthorizeAttribute = (postAuthorize != null) ? postAuthorize.value() : null; ArrayList attrs = new ArrayList<>(2); PreInvocationAttribute pre = this.attributeFactory.createPreInvocationAttribute(preFilterAttribute, filterObject, preAuthorizeAttribute); if (pre != null) { attrs.add(pre); } PostInvocationAttribute post = this.attributeFactory.createPostInvocationAttribute(postFilterAttribute, postAuthorizeAttribute); if (post != null) { attrs.add(post); } attrs.trimToSize(); return attrs; } @Override public @Nullable Collection getAllConfigAttributes() { return null; } /** * See * {@link org.springframework.security.access.method.AbstractFallbackMethodSecurityMetadataSource#getAttributes(Method, Class)} * for the logic of this method. The ordering here is slightly different in that we * consider method-specific annotations on an interface before class-level ones. */ private @Nullable A findAnnotation(Method method, Class targetClass, Class annotationClass) { // The method may be on an interface, but we need attributes from the target // class. // If the target class is null, the method will be unchanged. Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass); A annotation = AnnotationUtils.findAnnotation(specificMethod, annotationClass); if (annotation != null) { this.logger.debug(LogMessage.format("%s found on specific method: %s", annotation, specificMethod)); return annotation; } // Check the original (e.g. interface) method if (specificMethod != method) { annotation = AnnotationUtils.findAnnotation(method, annotationClass); if (annotation != null) { this.logger.debug(LogMessage.format("%s found on: %s", annotation, method)); return annotation; } } // Check the class-level (note declaringClass, not targetClass, which may not // actually implement the method) annotation = AnnotationUtils.findAnnotation(specificMethod.getDeclaringClass(), annotationClass); if (annotation != null) { this.logger .debug(LogMessage.format("%s found on: %s", annotation, specificMethod.getDeclaringClass().getName())); return annotation; } return null; } } ================================================ FILE: access/src/main/java/org/springframework/security/access/prepost/PrePostInvocationAttributeFactory.java ================================================ /* * 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 org.springframework.security.access.prepost; import org.jspecify.annotations.Nullable; import org.springframework.aop.framework.AopInfrastructureBean; import org.springframework.security.authorization.AuthorizationManager; /** * @author Luke Taylor * @since 3.0 * @see org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor * @see org.springframework.security.authorization.method.AuthorizationManagerAfterMethodInterceptor * @deprecated Use delegation with {@link AuthorizationManager} */ @Deprecated public interface PrePostInvocationAttributeFactory extends AopInfrastructureBean { PreInvocationAttribute createPreInvocationAttribute(@Nullable String preFilterAttribute, @Nullable String filterObject, @Nullable String preAuthorizeAttribute); PostInvocationAttribute createPostInvocationAttribute(@Nullable String postFilterAttribute, @Nullable String postAuthorizeAttribute); } ================================================ FILE: access/src/main/java/org/springframework/security/access/vote/AbstractAccessDecisionManager.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.access.vote; import java.util.List; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.InitializingBean; import org.springframework.context.MessageSource; import org.springframework.context.MessageSourceAware; import org.springframework.context.support.MessageSourceAccessor; import org.springframework.security.access.AccessDecisionManager; import org.springframework.security.access.AccessDecisionVoter; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.authorization.AuthorizationManager; import org.springframework.security.core.SpringSecurityMessageSource; import org.springframework.util.Assert; /** * Abstract implementation of {@link AccessDecisionManager}. * *

* Handles configuration of a bean context defined list of {@link AccessDecisionVoter}s * and the access control behaviour if all voters abstain from voting (defaults to deny * access). * * @deprecated Use {@link AuthorizationManager} instead */ @Deprecated public abstract class AbstractAccessDecisionManager implements AccessDecisionManager, InitializingBean, MessageSourceAware { protected final Log logger = LogFactory.getLog(getClass()); private List> decisionVoters; protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor(); private boolean allowIfAllAbstainDecisions = false; protected AbstractAccessDecisionManager(List> decisionVoters) { Assert.notEmpty(decisionVoters, "A list of AccessDecisionVoters is required"); this.decisionVoters = decisionVoters; } @Override public void afterPropertiesSet() { Assert.notEmpty(this.decisionVoters, "A list of AccessDecisionVoters is required"); Assert.notNull(this.messages, "A message source must be set"); } protected final void checkAllowIfAllAbstainDecisions() { if (!this.isAllowIfAllAbstainDecisions()) { throw new AccessDeniedException( this.messages.getMessage("AbstractAccessDecisionManager.accessDenied", "Access is denied")); } } public List> getDecisionVoters() { return this.decisionVoters; } public boolean isAllowIfAllAbstainDecisions() { return this.allowIfAllAbstainDecisions; } public void setAllowIfAllAbstainDecisions(boolean allowIfAllAbstainDecisions) { this.allowIfAllAbstainDecisions = allowIfAllAbstainDecisions; } @Override public void setMessageSource(MessageSource messageSource) { this.messages = new MessageSourceAccessor(messageSource); } @Override public boolean supports(ConfigAttribute attribute) { for (AccessDecisionVoter voter : this.decisionVoters) { if (voter.supports(attribute)) { return true; } } return false; } /** * Iterates through all AccessDecisionVoters and ensures each can support * the presented class. *

* If one or more voters cannot support the presented class, false is * returned. * @param clazz the type of secured object being presented * @return true if this type is supported */ @Override public boolean supports(Class clazz) { for (AccessDecisionVoter voter : this.decisionVoters) { if (!voter.supports(clazz)) { return false; } } return true; } @Override public String toString() { return this.getClass().getSimpleName() + " [DecisionVoters=" + this.decisionVoters + ", AllowIfAllAbstainDecisions=" + this.allowIfAllAbstainDecisions + "]"; } } ================================================ FILE: access/src/main/java/org/springframework/security/access/vote/AbstractAclVoter.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.access.vote; import org.aopalliance.intercept.MethodInvocation; import org.jspecify.annotations.NullUnmarked; import org.jspecify.annotations.Nullable; import org.springframework.security.access.AccessDecisionVoter; import org.springframework.security.access.AuthorizationServiceException; import org.springframework.util.Assert; /** * Provides helper methods for writing domain object ACL voters. Not bound to any * particular ACL system. * * @author Ben Alex * @deprecated Now used by only-deprecated classes. Generally speaking, in-memory ACL is * no longer advised, so no replacement is planned at this point. */ @NullUnmarked @Deprecated public abstract class AbstractAclVoter implements AccessDecisionVoter { @SuppressWarnings("NullAway.Init") private @Nullable Class processDomainObjectClass; protected Object getDomainObjectInstance(MethodInvocation invocation) { Object[] args = invocation.getArguments(); Class[] params = invocation.getMethod().getParameterTypes(); for (int i = 0; i < params.length; i++) { if (this.processDomainObjectClass.isAssignableFrom(params[i])) { return args[i]; } } throw new AuthorizationServiceException("MethodInvocation: " + invocation + " did not provide any argument of type: " + this.processDomainObjectClass); } public Class getProcessDomainObjectClass() { return this.processDomainObjectClass; } public void setProcessDomainObjectClass(Class processDomainObjectClass) { Assert.notNull(processDomainObjectClass, "processDomainObjectClass cannot be set to null"); this.processDomainObjectClass = processDomainObjectClass; } /** * This implementation supports only MethodSecurityInterceptor, because * it queries the presented MethodInvocation. * @param clazz the secure object * @return true if the secure object is MethodInvocation, * false otherwise */ @Override public boolean supports(Class clazz) { return (MethodInvocation.class.isAssignableFrom(clazz)); } } ================================================ FILE: access/src/main/java/org/springframework/security/access/vote/AffirmativeBased.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.access.vote; import java.util.Collection; import java.util.List; import org.springframework.security.access.AccessDecisionVoter; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.authorization.AuthorizationManager; import org.springframework.security.core.Authentication; /** * Simple concrete implementation of * {@link org.springframework.security.access.AccessDecisionManager} that grants access if * any AccessDecisionVoter returns an affirmative response. * * @deprecated Use {@link AuthorizationManager} instead */ @Deprecated public class AffirmativeBased extends AbstractAccessDecisionManager { public AffirmativeBased(List> decisionVoters) { super(decisionVoters); } /** * This concrete implementation simply polls all configured * {@link AccessDecisionVoter}s and grants access if any * AccessDecisionVoter voted affirmatively. Denies access only if there * was a deny vote AND no affirmative votes. *

* If every AccessDecisionVoter abstained from voting, the decision will * be based on the {@link #isAllowIfAllAbstainDecisions()} property (defaults to * false). *

* @param authentication the caller invoking the method * @param object the secured object * @param configAttributes the configuration attributes associated with the method * being invoked * @throws AccessDeniedException if access is denied */ @Override @SuppressWarnings({ "rawtypes", "unchecked" }) public void decide(Authentication authentication, Object object, Collection configAttributes) throws AccessDeniedException { int deny = 0; for (AccessDecisionVoter voter : getDecisionVoters()) { int result = voter.vote(authentication, object, configAttributes); switch (result) { case AccessDecisionVoter.ACCESS_GRANTED: return; case AccessDecisionVoter.ACCESS_DENIED: deny++; break; default: break; } } if (deny > 0) { throw new AccessDeniedException( this.messages.getMessage("AbstractAccessDecisionManager.accessDenied", "Access is denied")); } // To get this far, every AccessDecisionVoter abstained checkAllowIfAllAbstainDecisions(); } } ================================================ FILE: access/src/main/java/org/springframework/security/access/vote/AuthenticatedVoter.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.access.vote; import java.util.Collection; import org.springframework.security.access.AccessDecisionVoter; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.authentication.AuthenticationTrustResolver; import org.springframework.security.authentication.AuthenticationTrustResolverImpl; import org.springframework.security.core.Authentication; import org.springframework.util.Assert; /** * Votes if a {@link ConfigAttribute#getAttribute()} of * IS_AUTHENTICATED_FULLY or IS_AUTHENTICATED_REMEMBERED or * IS_AUTHENTICATED_ANONYMOUSLY is present. This list is in order of most * strict checking to least strict checking. *

* The current Authentication will be inspected to determine if the principal * has a particular level of authentication. The "FULLY" authenticated option means the * user is authenticated fully (i.e. * {@link org.springframework.security.authentication.AuthenticationTrustResolver#isAnonymous(Authentication)} * is false and * {@link org.springframework.security.authentication.AuthenticationTrustResolver#isRememberMe(Authentication)} * is false). The "REMEMBERED" will grant access if the principal was either authenticated * via remember-me OR is fully authenticated. The "ANONYMOUSLY" will grant access if the * principal was authenticated via remember-me, OR anonymously, OR via full * authentication. *

* All comparisons and prefixes are case sensitive. * * @author Ben Alex * @deprecated Use * {@link org.springframework.security.authorization.AuthorityAuthorizationManager} * instead */ @Deprecated public class AuthenticatedVoter implements AccessDecisionVoter { public static final String IS_AUTHENTICATED_FULLY = "IS_AUTHENTICATED_FULLY"; public static final String IS_AUTHENTICATED_REMEMBERED = "IS_AUTHENTICATED_REMEMBERED"; public static final String IS_AUTHENTICATED_ANONYMOUSLY = "IS_AUTHENTICATED_ANONYMOUSLY"; private AuthenticationTrustResolver authenticationTrustResolver = new AuthenticationTrustResolverImpl(); private boolean isFullyAuthenticated(Authentication authentication) { return this.authenticationTrustResolver.isFullyAuthenticated(authentication); } public void setAuthenticationTrustResolver(AuthenticationTrustResolver authenticationTrustResolver) { Assert.notNull(authenticationTrustResolver, "AuthenticationTrustResolver cannot be set to null"); this.authenticationTrustResolver = authenticationTrustResolver; } @Override public boolean supports(ConfigAttribute attribute) { return (attribute.getAttribute() != null) && (IS_AUTHENTICATED_FULLY.equals(attribute.getAttribute()) || IS_AUTHENTICATED_REMEMBERED.equals(attribute.getAttribute()) || IS_AUTHENTICATED_ANONYMOUSLY.equals(attribute.getAttribute())); } /** * This implementation supports any type of class, because it does not query the * presented secure object. * @param clazz the secure object type * @return always {@code true} */ @Override public boolean supports(Class clazz) { return true; } @Override public int vote(Authentication authentication, Object object, Collection attributes) { int result = ACCESS_ABSTAIN; for (ConfigAttribute attribute : attributes) { if (this.supports(attribute)) { result = ACCESS_DENIED; if (IS_AUTHENTICATED_FULLY.equals(attribute.getAttribute())) { if (isFullyAuthenticated(authentication)) { return ACCESS_GRANTED; } } if (IS_AUTHENTICATED_REMEMBERED.equals(attribute.getAttribute())) { if (this.authenticationTrustResolver.isRememberMe(authentication) || isFullyAuthenticated(authentication)) { return ACCESS_GRANTED; } } if (IS_AUTHENTICATED_ANONYMOUSLY.equals(attribute.getAttribute())) { if (this.authenticationTrustResolver.isAnonymous(authentication) || isFullyAuthenticated(authentication) || this.authenticationTrustResolver.isRememberMe(authentication)) { return ACCESS_GRANTED; } } } } return result; } } ================================================ FILE: access/src/main/java/org/springframework/security/access/vote/ConsensusBased.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.access.vote; import java.util.Collection; import java.util.List; import org.springframework.security.access.AccessDecisionVoter; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.authorization.AuthorizationManager; import org.springframework.security.core.Authentication; /** * Simple concrete implementation of * {@link org.springframework.security.access.AccessDecisionManager} that uses a * consensus-based approach. *

* "Consensus" here means majority-rule (ignoring abstains) rather than unanimous * agreement (ignoring abstains). If you require unanimity, please see * {@link UnanimousBased}. * * @deprecated Use {@link AuthorizationManager} instead */ @Deprecated public class ConsensusBased extends AbstractAccessDecisionManager { private boolean allowIfEqualGrantedDeniedDecisions = true; public ConsensusBased(List> decisionVoters) { super(decisionVoters); } /** * This concrete implementation simply polls all configured * {@link AccessDecisionVoter}s and upon completion determines the consensus of * granted against denied responses. *

* If there were an equal number of grant and deny votes, the decision will be based * on the {@link #isAllowIfEqualGrantedDeniedDecisions()} property (defaults to true). *

* If every AccessDecisionVoter abstained from voting, the decision will * be based on the {@link #isAllowIfAllAbstainDecisions()} property (defaults to * false). * @param authentication the caller invoking the method * @param object the secured object * @param configAttributes the configuration attributes associated with the method * being invoked * @throws AccessDeniedException if access is denied */ @Override @SuppressWarnings({ "rawtypes", "unchecked" }) public void decide(Authentication authentication, Object object, Collection configAttributes) throws AccessDeniedException { int grant = 0; int deny = 0; for (AccessDecisionVoter voter : getDecisionVoters()) { int result = voter.vote(authentication, object, configAttributes); switch (result) { case AccessDecisionVoter.ACCESS_GRANTED -> grant++; case AccessDecisionVoter.ACCESS_DENIED -> deny++; default -> { } } } if (grant > deny) { return; } if (deny > grant) { throw new AccessDeniedException( this.messages.getMessage("AbstractAccessDecisionManager.accessDenied", "Access is denied")); } if ((grant == deny) && (grant != 0)) { if (this.allowIfEqualGrantedDeniedDecisions) { return; } throw new AccessDeniedException( this.messages.getMessage("AbstractAccessDecisionManager.accessDenied", "Access is denied")); } // To get this far, every AccessDecisionVoter abstained checkAllowIfAllAbstainDecisions(); } public boolean isAllowIfEqualGrantedDeniedDecisions() { return this.allowIfEqualGrantedDeniedDecisions; } public void setAllowIfEqualGrantedDeniedDecisions(boolean allowIfEqualGrantedDeniedDecisions) { this.allowIfEqualGrantedDeniedDecisions = allowIfEqualGrantedDeniedDecisions; } } ================================================ FILE: access/src/main/java/org/springframework/security/access/vote/RoleHierarchyVoter.java ================================================ /* * 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 org.springframework.security.access.vote; import java.util.Collection; import org.jspecify.annotations.NullUnmarked; import org.jspecify.annotations.Nullable; import org.springframework.security.access.hierarchicalroles.RoleHierarchy; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.util.Assert; /** * Extended RoleVoter which uses a {@link RoleHierarchy} definition to determine the roles * allocated to the current user before voting. * * @author Luke Taylor * @since 2.0.4 * @deprecated Use * {@link org.springframework.security.authorization.AuthorityAuthorizationManager#setRoleHierarchy} * instead */ @NullUnmarked @Deprecated public class RoleHierarchyVoter extends RoleVoter { @SuppressWarnings("NullAway") private @Nullable RoleHierarchy roleHierarchy = null; public RoleHierarchyVoter(RoleHierarchy roleHierarchy) { Assert.notNull(roleHierarchy, "RoleHierarchy must not be null"); this.roleHierarchy = roleHierarchy; } /** * Calls the RoleHierarchy to obtain the complete set of user authorities. */ @Override Collection extractAuthorities(Authentication authentication) { return this.roleHierarchy.getReachableGrantedAuthorities(authentication.getAuthorities()); } } ================================================ FILE: access/src/main/java/org/springframework/security/access/vote/RoleVoter.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 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; import org.springframework.security.core.GrantedAuthority; /** * Votes if any {@link ConfigAttribute#getAttribute()} starts with a prefix indicating * that it is a role. The default prefix string is ROLE_, but this may be * overridden to any value. It may also be set to empty, which means that essentially any * attribute will be voted on. As described further below, the effect of an empty prefix * may not be quite desirable. *

* Abstains from voting if no configuration attribute commences with the role prefix. * Votes to grant access if there is an exact matching * {@link org.springframework.security.core.GrantedAuthority} to a * ConfigAttribute starting with the role prefix. Votes to deny access if * there is no exact matching GrantedAuthority to a * ConfigAttribute starting with the role prefix. *

* An empty role prefix means that the voter will vote for every ConfigAttribute. When * there are different categories of ConfigAttributes used, this will not be optimal since * the voter will be voting for attributes which do not represent roles. However, this * option may be of some use when using pre-existing role names without a prefix, and no * ability exists to prefix them with a role prefix on reading them in, such as provided * for example in {@link org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl}. *

* All comparisons and prefixes are case sensitive. * * @author Ben Alex * @author colin sampaleanu * @deprecated Use * {@link org.springframework.security.authorization.AuthorityAuthorizationManager} * instead */ @Deprecated @NullUnmarked public class RoleVoter implements AccessDecisionVoter { private String rolePrefix = "ROLE_"; public String getRolePrefix() { return this.rolePrefix; } /** * Allows the default role prefix of ROLE_ to be overridden. May be set * to an empty value, although this is usually not desirable. * @param rolePrefix the new prefix */ public void setRolePrefix(String rolePrefix) { this.rolePrefix = rolePrefix; } @Override public boolean supports(ConfigAttribute attribute) { return (attribute.getAttribute() != null) && attribute.getAttribute().startsWith(getRolePrefix()); } /** * This implementation supports any type of class, because it does not query the * presented secure object. * @param clazz the secure object * @return always true */ @Override public boolean supports(Class clazz) { return true; } @Override public int vote(Authentication authentication, Object object, Collection attributes) { if (authentication == null) { return ACCESS_DENIED; } int result = ACCESS_ABSTAIN; Collection authorities = extractAuthorities(authentication); for (ConfigAttribute attribute : attributes) { if (this.supports(attribute)) { result = ACCESS_DENIED; // Attempt to find a matching granted authority for (GrantedAuthority authority : authorities) { if (attribute.getAttribute().equals(authority.getAuthority())) { return ACCESS_GRANTED; } } } } return result; } Collection extractAuthorities(Authentication authentication) { return authentication.getAuthorities(); } } ================================================ FILE: access/src/main/java/org/springframework/security/access/vote/UnanimousBased.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.access.vote; import java.util.ArrayList; import java.util.Collection; import java.util.List; import org.springframework.security.access.AccessDecisionVoter; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.authorization.AuthorizationManager; import org.springframework.security.core.Authentication; /** * Simple concrete implementation of * {@link org.springframework.security.access.AccessDecisionManager} that requires all * voters to abstain or grant access. * * @deprecated Use {@link AuthorizationManager} instead */ @Deprecated public class UnanimousBased extends AbstractAccessDecisionManager { public UnanimousBased(List> decisionVoters) { super(decisionVoters); } /** * This concrete implementation polls all configured {@link AccessDecisionVoter}s for * each {@link ConfigAttribute} and grants access if only grant (or abstain) * votes were received. *

* Other voting implementations usually pass the entire list of * ConfigAttributes to the AccessDecisionVoter. This * implementation differs in that each AccessDecisionVoter knows only * about a single ConfigAttribute at a time. *

* If every AccessDecisionVoter abstained from voting, the decision will * be based on the {@link #isAllowIfAllAbstainDecisions()} property (defaults to * false). * @param authentication the caller invoking the method * @param object the secured object * @param attributes the configuration attributes associated with the method being * invoked * @throws AccessDeniedException if access is denied */ @Override @SuppressWarnings({ "rawtypes", "unchecked" }) public void decide(Authentication authentication, Object object, Collection attributes) throws AccessDeniedException { int grant = 0; List singleAttributeList = new ArrayList<>(1); singleAttributeList.add(null); for (ConfigAttribute attribute : attributes) { singleAttributeList.set(0, attribute); for (AccessDecisionVoter voter : getDecisionVoters()) { int result = voter.vote(authentication, object, singleAttributeList); switch (result) { case AccessDecisionVoter.ACCESS_GRANTED: grant++; break; case AccessDecisionVoter.ACCESS_DENIED: throw new AccessDeniedException(this.messages .getMessage("AbstractAccessDecisionManager.accessDenied", "Access is denied")); default: break; } } } // To get this far, there were no deny votes if (grant > 0) { return; } // To get this far, every AccessDecisionVoter abstained checkAllowIfAllAbstainDecisions(); } } ================================================ FILE: access/src/main/java/org/springframework/security/access/vote/package-info.java ================================================ /* * 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. */ /** * Implements a vote-based approach to authorization decisions. */ @NullMarked package org.springframework.security.access.vote; import org.jspecify.annotations.NullMarked; ================================================ FILE: access/src/main/java/org/springframework/security/acls/AclEntryVoter.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.acls; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Arrays; import java.util.Collection; import java.util.List; import org.aopalliance.intercept.MethodInvocation; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.security.access.AuthorizationServiceException; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.access.vote.AbstractAclVoter; import org.springframework.security.acls.domain.ObjectIdentityRetrievalStrategyImpl; import org.springframework.security.acls.domain.SidRetrievalStrategyImpl; import org.springframework.security.acls.model.Acl; import org.springframework.security.acls.model.AclService; import org.springframework.security.acls.model.NotFoundException; import org.springframework.security.acls.model.ObjectIdentity; import org.springframework.security.acls.model.ObjectIdentityRetrievalStrategy; import org.springframework.security.acls.model.Permission; import org.springframework.security.acls.model.Sid; import org.springframework.security.acls.model.SidRetrievalStrategy; import org.springframework.security.core.Authentication; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; /** *

* Given a domain object instance passed as a method argument, ensures the principal has * appropriate permission as indicated by the {@link AclService}. *

* The AclService is used to retrieve the access control list (ACL) permissions * associated with a domain object instance for the current Authentication * object. *

* The voter will vote if any {@link ConfigAttribute#getAttribute()} matches the * {@link #processConfigAttribute}. The provider will then locate the first method * argument of type {@link #processDomainObjectClass}. Assuming that method argument is * non-null, the provider will then lookup the ACLs from the AclManager and * ensure the principal is {@link Acl#isGranted(List, List, boolean)} when presenting the * {@link #requirePermission} array to that method. *

* If the method argument is null, the voter will abstain from voting. If the * method argument could not be found, an {@link AuthorizationServiceException} will be * thrown. *

* In practical terms users will typically setup a number of AclEntryVoters. Each * will have a different {@link #setProcessDomainObjectClass processDomainObjectClass}, * {@link #processConfigAttribute} and {@link #requirePermission} combination. For * example, a small application might employ the following instances of * AclEntryVoter: *

    *
  • Process domain object class BankAccount, configuration attribute * VOTE_ACL_BANK_ACCOUNT_READ, require permission * BasePermission.READ
  • *
  • Process domain object class BankAccount, configuration attribute * VOTE_ACL_BANK_ACCOUNT_WRITE, require permission list * BasePermission.WRITE and BasePermission.CREATE (allowing the * principal to have either of these two permissions)
  • *
  • Process domain object class Customer, configuration attribute * VOTE_ACL_CUSTOMER_READ, require permission * BasePermission.READ
  • *
  • Process domain object class Customer, configuration attribute * VOTE_ACL_CUSTOMER_WRITE, require permission list * BasePermission.WRITE and BasePermission.CREATE
  • *
* Alternatively, you could have used a common superclass or interface for the * {@link #processDomainObjectClass} if both BankAccount and * Customer had common parents. * *

* If the principal does not have sufficient permissions, the voter will vote to deny * access. * *

* All comparisons and prefixes are case sensitive. * * @author Ben Alex * @deprecated please use {@link AclPermissionEvaluator} instead. Spring Method Security * annotations may also prove useful, for example * {@code @PreAuthorize("hasPermission(#id, ObjectsReturnType.class, read)")} */ @Deprecated public class AclEntryVoter extends AbstractAclVoter { private static final Log logger = LogFactory.getLog(AclEntryVoter.class); private final AclService aclService; private final String processConfigAttribute; private final List requirePermission; private ObjectIdentityRetrievalStrategy objectIdentityRetrievalStrategy = new ObjectIdentityRetrievalStrategyImpl(); private SidRetrievalStrategy sidRetrievalStrategy = new SidRetrievalStrategyImpl(); private String internalMethod; public AclEntryVoter(AclService aclService, String processConfigAttribute, Permission[] requirePermission) { Assert.notNull(processConfigAttribute, "A processConfigAttribute is mandatory"); Assert.notNull(aclService, "An AclService is mandatory"); Assert.isTrue(!ObjectUtils.isEmpty(requirePermission), "One or more requirePermission entries is mandatory"); this.aclService = aclService; this.processConfigAttribute = processConfigAttribute; this.requirePermission = Arrays.asList(requirePermission); } /** * Optionally specifies a method of the domain object that will be used to obtain a * contained domain object. That contained domain object will be used for the ACL * evaluation. This is useful if a domain object contains a parent that an ACL * evaluation should be targeted for, instead of the child domain object (which * perhaps is being created and as such does not yet have any ACL permissions) * @return null to use the domain object, or the name of a method (that * requires no arguments) that should be invoked to obtain an Object * which will be the domain object used for ACL evaluation */ protected String getInternalMethod() { return this.internalMethod; } public void setInternalMethod(String internalMethod) { this.internalMethod = internalMethod; } protected String getProcessConfigAttribute() { return this.processConfigAttribute; } public void setObjectIdentityRetrievalStrategy(ObjectIdentityRetrievalStrategy objectIdentityRetrievalStrategy) { Assert.notNull(objectIdentityRetrievalStrategy, "ObjectIdentityRetrievalStrategy required"); this.objectIdentityRetrievalStrategy = objectIdentityRetrievalStrategy; } public void setSidRetrievalStrategy(SidRetrievalStrategy sidRetrievalStrategy) { Assert.notNull(sidRetrievalStrategy, "SidRetrievalStrategy required"); this.sidRetrievalStrategy = sidRetrievalStrategy; } @Override public boolean supports(ConfigAttribute attribute) { return (attribute.getAttribute() != null) && attribute.getAttribute().equals(getProcessConfigAttribute()); } @Override public int vote(Authentication authentication, MethodInvocation object, Collection attributes) { for (ConfigAttribute attr : attributes) { if (!supports(attr)) { continue; } // Need to make an access decision on this invocation // Attempt to locate the domain object instance to process Object domainObject = getDomainObjectInstance(object); // If domain object is null, vote to abstain if (domainObject == null) { logger.debug("Voting to abstain - domainObject is null"); return ACCESS_ABSTAIN; } // Evaluate if we are required to use an inner domain object if (StringUtils.hasText(this.internalMethod)) { domainObject = invokeInternalMethod(domainObject); } // Obtain the OID applicable to the domain object ObjectIdentity objectIdentity = this.objectIdentityRetrievalStrategy.getObjectIdentity(domainObject); // Obtain the SIDs applicable to the principal List sids = this.sidRetrievalStrategy.getSids(authentication); Acl acl; try { // Lookup only ACLs for SIDs we're interested in acl = this.aclService.readAclById(objectIdentity, sids); } catch (NotFoundException ex) { logger.debug("Voting to deny access - no ACLs apply for this principal"); return ACCESS_DENIED; } try { if (acl.isGranted(this.requirePermission, sids, false)) { logger.debug("Voting to grant access"); return ACCESS_GRANTED; } logger.debug("Voting to deny access - ACLs returned, but insufficient permissions for this principal"); return ACCESS_DENIED; } catch (NotFoundException ex) { logger.debug("Voting to deny access - no ACLs apply for this principal"); return ACCESS_DENIED; } } // No configuration attribute matched, so abstain return ACCESS_ABSTAIN; } private Object invokeInternalMethod(Object domainObject) { try { Class domainObjectType = domainObject.getClass(); Method method = domainObjectType.getMethod(this.internalMethod, new Class[0]); return method.invoke(domainObject); } catch (NoSuchMethodException ex) { throw new AuthorizationServiceException("Object of class '" + domainObject.getClass() + "' does not provide the requested internalMethod: " + this.internalMethod); } catch (IllegalAccessException ex) { logger.debug("IllegalAccessException", ex); throw new AuthorizationServiceException( "Problem invoking internalMethod: " + this.internalMethod + " for object: " + domainObject); } catch (InvocationTargetException ex) { logger.debug("InvocationTargetException", ex); throw new AuthorizationServiceException( "Problem invoking internalMethod: " + this.internalMethod + " for object: " + domainObject); } } } ================================================ FILE: access/src/main/java/org/springframework/security/acls/afterinvocation/AbstractAclProvider.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.acls.afterinvocation; import java.util.List; import org.springframework.security.access.AfterInvocationProvider; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.acls.AclPermissionEvaluator; import org.springframework.security.acls.domain.ObjectIdentityRetrievalStrategyImpl; import org.springframework.security.acls.domain.SidRetrievalStrategyImpl; import org.springframework.security.acls.model.Acl; import org.springframework.security.acls.model.AclService; import org.springframework.security.acls.model.NotFoundException; import org.springframework.security.acls.model.ObjectIdentity; import org.springframework.security.acls.model.ObjectIdentityRetrievalStrategy; import org.springframework.security.acls.model.Permission; import org.springframework.security.acls.model.Sid; import org.springframework.security.acls.model.SidRetrievalStrategy; import org.springframework.security.core.Authentication; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; /** * Abstract {@link AfterInvocationProvider} which provides commonly-used ACL-related * services. * * @author Ben Alex * @deprecated please use {@link AclPermissionEvaluator} instead. Spring Method Security * annotations may also prove useful, for example * {@code @PostAuthorize("hasPermission(filterObject, read)")} */ @Deprecated public abstract class AbstractAclProvider implements AfterInvocationProvider { protected final AclService aclService; protected String processConfigAttribute; protected Class processDomainObjectClass = Object.class; protected ObjectIdentityRetrievalStrategy objectIdentityRetrievalStrategy = new ObjectIdentityRetrievalStrategyImpl(); protected SidRetrievalStrategy sidRetrievalStrategy = new SidRetrievalStrategyImpl(); protected final List requirePermission; public AbstractAclProvider(AclService aclService, String processConfigAttribute, List requirePermission) { Assert.hasText(processConfigAttribute, "A processConfigAttribute is mandatory"); Assert.notNull(aclService, "An AclService is mandatory"); Assert.isTrue(!ObjectUtils.isEmpty(requirePermission), "One or more requirePermission entries is mandatory"); this.aclService = aclService; this.processConfigAttribute = processConfigAttribute; this.requirePermission = requirePermission; } protected Class getProcessDomainObjectClass() { return this.processDomainObjectClass; } protected boolean hasPermission(Authentication authentication, Object domainObject) { // Obtain the OID applicable to the domain object ObjectIdentity objectIdentity = this.objectIdentityRetrievalStrategy.getObjectIdentity(domainObject); // Obtain the SIDs applicable to the principal List sids = this.sidRetrievalStrategy.getSids(authentication); try { // Lookup only ACLs for SIDs we're interested in Acl acl = this.aclService.readAclById(objectIdentity, sids); return acl.isGranted(this.requirePermission, sids, false); } catch (NotFoundException ex) { return false; } } public void setObjectIdentityRetrievalStrategy(ObjectIdentityRetrievalStrategy objectIdentityRetrievalStrategy) { Assert.notNull(objectIdentityRetrievalStrategy, "ObjectIdentityRetrievalStrategy required"); this.objectIdentityRetrievalStrategy = objectIdentityRetrievalStrategy; } protected void setProcessConfigAttribute(String processConfigAttribute) { Assert.hasText(processConfigAttribute, "A processConfigAttribute is mandatory"); this.processConfigAttribute = processConfigAttribute; } public void setProcessDomainObjectClass(Class processDomainObjectClass) { Assert.notNull(processDomainObjectClass, "processDomainObjectClass cannot be set to null"); this.processDomainObjectClass = processDomainObjectClass; } public void setSidRetrievalStrategy(SidRetrievalStrategy sidRetrievalStrategy) { Assert.notNull(sidRetrievalStrategy, "SidRetrievalStrategy required"); this.sidRetrievalStrategy = sidRetrievalStrategy; } @Override public boolean supports(ConfigAttribute attribute) { return this.processConfigAttribute.equals(attribute.getAttribute()); } /** * This implementation supports any type of class, because it does not query the * presented secure object. * @param clazz the secure object * @return always true */ @Override public boolean supports(Class clazz) { return true; } } ================================================ FILE: access/src/main/java/org/springframework/security/acls/afterinvocation/AclEntryAfterInvocationCollectionFilteringProvider.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.acls.afterinvocation; import java.util.Collection; import java.util.List; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.core.log.LogMessage; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.AuthorizationServiceException; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.acls.AclPermissionEvaluator; import org.springframework.security.acls.model.AclService; import org.springframework.security.acls.model.Permission; import org.springframework.security.core.Authentication; /** *

* Given a Collection of domain object instances returned from a secure * object invocation, remove any Collection elements the principal does not * have appropriate permission to access as defined by the {@link AclService}. *

* The AclService is used to retrieve the access control list (ACL) * permissions associated with each Collection domain object instance element * for the current Authentication object. *

* This after invocation provider will fire if any {@link ConfigAttribute#getAttribute()} * matches the {@link #processConfigAttribute}. The provider will then lookup the ACLs * from the AclService and ensure the principal is * {@link org.springframework.security.acls.model.Acl#isGranted(List, List, boolean) * Acl.isGranted()} when presenting the {@link #requirePermission} array to that method. *

* If the principal does not have permission, that element will not be included in the * returned Collection. *

* Often users will setup a BasicAclEntryAfterInvocationProvider with a * {@link #processConfigAttribute} of AFTER_ACL_COLLECTION_READ and a * {@link #requirePermission} of BasePermission.READ. These are also the * defaults. *

* If the provided returnObject is null, a null * Collection will be returned. If the provided returnObject is * not a Collection, an {@link AuthorizationServiceException} will be thrown. *

* All comparisons and prefixes are case sensitive. * * @author Ben Alex * @author Paulo Neves * @deprecated please use {@link AclPermissionEvaluator} instead. Spring Method Security * annotations may also prove useful, for example * {@code @PostFilter("hasPermission(filterObject, read)")} */ @Deprecated public class AclEntryAfterInvocationCollectionFilteringProvider extends AbstractAclProvider { protected static final Log logger = LogFactory.getLog(AclEntryAfterInvocationCollectionFilteringProvider.class); public AclEntryAfterInvocationCollectionFilteringProvider(AclService aclService, List requirePermission) { super(aclService, "AFTER_ACL_COLLECTION_READ", requirePermission); } @Override @SuppressWarnings("unchecked") public Object decide(Authentication authentication, Object object, Collection config, Object returnedObject) throws AccessDeniedException { if (returnedObject == null) { logger.debug("Return object is null, skipping"); return null; } for (ConfigAttribute attr : config) { if (!this.supports(attr)) { continue; } // Need to process the Collection for this invocation Filterer filterer = getFilterer(returnedObject); // Locate unauthorised Collection elements for (Object domainObject : filterer) { // Ignore nulls or entries which aren't instances of the configured domain // object class if (domainObject == null || !getProcessDomainObjectClass().isAssignableFrom(domainObject.getClass())) { continue; } if (!hasPermission(authentication, domainObject)) { filterer.remove(domainObject); logger.debug(LogMessage.of(() -> "Principal is NOT authorised for element: " + domainObject)); } } return filterer.getFilteredObject(); } return returnedObject; } @SuppressWarnings({ "unchecked", "rawtypes" }) private Filterer getFilterer(Object returnedObject) { if (returnedObject instanceof Collection) { return new CollectionFilterer((Collection) returnedObject); } if (returnedObject.getClass().isArray()) { return new ArrayFilterer((Object[]) returnedObject); } throw new AuthorizationServiceException("A Collection or an array (or null) was required as the " + "returnedObject, but the returnedObject was: " + returnedObject); } } ================================================ FILE: access/src/main/java/org/springframework/security/acls/afterinvocation/AclEntryAfterInvocationProvider.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.acls.afterinvocation; import java.util.Collection; import java.util.List; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.context.MessageSource; import org.springframework.context.MessageSourceAware; import org.springframework.context.support.MessageSourceAccessor; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.acls.AclPermissionEvaluator; import org.springframework.security.acls.model.AclService; import org.springframework.security.acls.model.Permission; import org.springframework.security.core.Authentication; import org.springframework.security.core.SpringSecurityMessageSource; /** * Given a domain object instance returned from a secure object invocation, ensures the * principal has appropriate permission as defined by the {@link AclService}. *

* The AclService is used to retrieve the access control list (ACL) * permissions associated with a domain object instance for the current * Authentication object. *

* This after invocation provider will fire if any {@link ConfigAttribute#getAttribute()} * matches the {@link #processConfigAttribute}. The provider will then lookup the ACLs * from the AclService and ensure the principal is * {@link org.springframework.security.acls.model.Acl#isGranted(List, List, boolean) * Acl.isGranted(List, List, boolean)} when presenting the {@link #requirePermission} * array to that method. *

* Often users will set up an AclEntryAfterInvocationProvider with a * {@link #processConfigAttribute} of AFTER_ACL_READ and a * {@link #requirePermission} of BasePermission.READ. These are also the * defaults. *

* If the principal does not have sufficient permissions, an * AccessDeniedException will be thrown. *

* If the provided returnedObject is null, permission will always be * granted and null will be returned. *

* All comparisons and prefixes are case sensitive. * * @deprecated please use {@link AclPermissionEvaluator} instead. Spring Method Security * annotations may also prove useful, for example * {@code @PostAuthorize("hasPermission(filterObject, read)")} */ @Deprecated public class AclEntryAfterInvocationProvider extends AbstractAclProvider implements MessageSourceAware { protected static final Log logger = LogFactory.getLog(AclEntryAfterInvocationProvider.class); protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor(); public AclEntryAfterInvocationProvider(AclService aclService, List requirePermission) { this(aclService, "AFTER_ACL_READ", requirePermission); } public AclEntryAfterInvocationProvider(AclService aclService, String processConfigAttribute, List requirePermission) { super(aclService, processConfigAttribute, requirePermission); } @Override public Object decide(Authentication authentication, Object object, Collection config, Object returnedObject) throws AccessDeniedException { if (returnedObject == null) { // AclManager interface contract prohibits nulls // As they have permission to null/nothing, grant access logger.debug("Return object is null, skipping"); return null; } if (!getProcessDomainObjectClass().isAssignableFrom(returnedObject.getClass())) { logger.debug("Return object is not applicable for this provider, skipping"); return returnedObject; } for (ConfigAttribute attr : config) { if (!this.supports(attr)) { continue; } // Need to make an access decision on this invocation if (hasPermission(authentication, returnedObject)) { return returnedObject; } logger.debug("Denying access"); throw new AccessDeniedException(this.messages.getMessage("AclEntryAfterInvocationProvider.noPermission", new Object[] { authentication.getName(), returnedObject }, "Authentication {0} has NO permissions to the domain object {1}")); } return returnedObject; } @Override public void setMessageSource(MessageSource messageSource) { this.messages = new MessageSourceAccessor(messageSource); } } ================================================ FILE: access/src/main/java/org/springframework/security/acls/afterinvocation/ArrayFilterer.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.acls.afterinvocation; import java.lang.reflect.Array; import java.util.HashSet; import java.util.Iterator; import java.util.NoSuchElementException; import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.core.log.LogMessage; /** * A filter used to filter arrays. * * @author Ben Alex * @author Paulo Neves * @deprecated please see {@code PostFilter} */ @Deprecated class ArrayFilterer implements Filterer { protected static final Log logger = LogFactory.getLog(ArrayFilterer.class); private final Set removeList; private final T[] list; ArrayFilterer(T[] list) { this.list = list; // Collect the removed objects to a HashSet so that // it is fast to lookup them when a filtered array // is constructed. this.removeList = new HashSet<>(); } @Override @SuppressWarnings("unchecked") public T[] getFilteredObject() { // Recreate an array of same type and filter the removed objects. int originalSize = this.list.length; int sizeOfResultingList = originalSize - this.removeList.size(); T[] filtered = (T[]) Array.newInstance(this.list.getClass().getComponentType(), sizeOfResultingList); for (int i = 0, j = 0; i < this.list.length; i++) { T object = this.list[i]; if (!this.removeList.contains(object)) { filtered[j] = object; j++; } } logger.debug(LogMessage.of(() -> "Original array contained " + originalSize + " elements; now contains " + sizeOfResultingList + " elements")); return filtered; } @Override public Iterator iterator() { return new ArrayFiltererIterator(); } @Override public void remove(T object) { this.removeList.add(object); } /** * Iterator for {@link ArrayFilterer} elements. */ private class ArrayFiltererIterator implements Iterator { private int index = 0; @Override public boolean hasNext() { return this.index < ArrayFilterer.this.list.length; } @Override public T next() { if (hasNext()) { return ArrayFilterer.this.list[this.index++]; } throw new NoSuchElementException(); } } } ================================================ FILE: access/src/main/java/org/springframework/security/acls/afterinvocation/CollectionFilterer.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.acls.afterinvocation; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.core.log.LogMessage; /** * A filter used to filter Collections. * * @author Ben Alex * @author Paulo Neves * @deprecated please see {@code PostFilter} */ @Deprecated class CollectionFilterer implements Filterer { protected static final Log logger = LogFactory.getLog(CollectionFilterer.class); private final Collection collection; private final Set removeList; CollectionFilterer(Collection collection) { this.collection = collection; // We create a Set of objects to be removed from the Collection, // as ConcurrentModificationException prevents removal during // iteration, and making a new Collection to be returned is // problematic as the original Collection implementation passed // to the method may not necessarily be re-constructable (as // the Collection(collection) constructor is not guaranteed and // manually adding may lose sort order or other capabilities) this.removeList = new HashSet<>(); } @Override public Object getFilteredObject() { // Now the Iterator has ended, remove Objects from Collection Iterator removeIter = this.removeList.iterator(); int originalSize = this.collection.size(); while (removeIter.hasNext()) { this.collection.remove(removeIter.next()); } logger.debug(LogMessage.of(() -> "Original collection contained " + originalSize + " elements; now contains " + this.collection.size() + " elements")); return this.collection; } @Override public Iterator iterator() { return this.collection.iterator(); } @Override public void remove(T object) { this.removeList.add(object); } } ================================================ FILE: access/src/main/java/org/springframework/security/acls/afterinvocation/Filterer.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.acls.afterinvocation; import java.util.Iterator; /** * Filterer strategy interface. * * @author Ben Alex * @author Paulo Neves * @deprecated please use {@code PreFilter} and {@code @PostFilter} instead */ @Deprecated interface Filterer extends Iterable { /** * Gets the filtered collection or array. * @return the filtered collection or array */ Object getFilteredObject(); /** * Returns an iterator over the filtered collection or array. * @return an Iterator */ @Override Iterator iterator(); /** * Removes the given object from the resulting list. * @param object the object to be removed */ void remove(T object); } ================================================ FILE: access/src/main/java/org/springframework/security/acls/afterinvocation/package-info.java ================================================ /* * 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. */ /** * After-invocation providers for collection and array filtering. Consider using a * {@code PostFilter} annotation in preference. */ package org.springframework.security.acls.afterinvocation; ================================================ FILE: access/src/main/java/org/springframework/security/messaging/access/expression/EvaluationContextPostProcessor.java ================================================ /* * 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 org.springframework.security.messaging.access.expression; import org.springframework.expression.EvaluationContext; /** * Allows post processing the {@link EvaluationContext} * *

* This API is intentionally kept package scope as it may evolve over time. *

* * @author Daniel Bustamante Ospina * @since 5.2 * @deprecated Since {@link MessageExpressionVoter} is deprecated, there is no more need * for this class */ @Deprecated interface EvaluationContextPostProcessor { /** * Allows post processing of the {@link EvaluationContext}. Implementations may return * a new instance of {@link EvaluationContext} or modify the {@link EvaluationContext} * that was passed in. * @param context the original {@link EvaluationContext} * @param invocation the security invocation object (i.e. Message) * @return the updated context. */ EvaluationContext postProcess(EvaluationContext context, I invocation); } ================================================ FILE: access/src/main/java/org/springframework/security/messaging/access/expression/ExpressionBasedMessageSecurityMetadataSourceFactory.java ================================================ /* * 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 org.springframework.security.messaging.access.expression; import java.util.Arrays; import java.util.Collection; import java.util.LinkedHashMap; import java.util.Map; import org.springframework.expression.Expression; import org.springframework.messaging.Message; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.access.expression.SecurityExpressionHandler; import org.springframework.security.messaging.access.intercept.DefaultMessageSecurityMetadataSource; import org.springframework.security.messaging.access.intercept.MessageSecurityMetadataSource; import org.springframework.security.messaging.util.matcher.MessageMatcher; /** * A class used to create a {@link MessageSecurityMetadataSource} that uses * {@link MessageMatcher} mapped to Spring Expressions. * * @author Rob Winch * @since 4.0 * @deprecated Use * {@link org.springframework.security.messaging.access.intercept.MessageMatcherDelegatingAuthorizationManager} * instead */ @Deprecated public final class ExpressionBasedMessageSecurityMetadataSourceFactory { private ExpressionBasedMessageSecurityMetadataSourceFactory() { } /** * Create a {@link MessageSecurityMetadataSource} that uses {@link MessageMatcher} * mapped to Spring Expressions. Each entry is considered in order and only the first * match is used. * * For example: * *
	 *     LinkedHashMap<MessageMatcher<?>,String> matcherToExpression = new LinkedHashMap<MessageMatcher<Object>,String>();
	 *     matcherToExpression.put(new SimDestinationMessageMatcher("/public/**"), "permitAll");
	 *     matcherToExpression.put(new SimDestinationMessageMatcher("/admin/**"), "hasRole('ROLE_ADMIN')");
	 *     matcherToExpression.put(new SimDestinationMessageMatcher("/topics/{name}/**"), "@someBean.customLogic(authentication, #name)");
	 *     matcherToExpression.put(new SimDestinationMessageMatcher("/**"), "authenticated");
	 *
	 *     MessageSecurityMetadataSource metadataSource = createExpressionMessageMetadataSource(matcherToExpression);
	 * 
* *

* If our destination is "/public/hello", it would match on "/public/**" and on "/**". * However, only "/public/**" would be used since it is the first entry. That means * that a destination of "/public/hello" will be mapped to "permitAll". * *

* For a complete listing of expressions see {@link MessageSecurityExpressionRoot} * @param matcherToExpression an ordered mapping of {@link MessageMatcher} to Strings * that are turned into an Expression using * {@link DefaultMessageSecurityExpressionHandler#getExpressionParser()} * @return the {@link MessageSecurityMetadataSource} to use. Cannot be null. */ public static MessageSecurityMetadataSource createExpressionMessageMetadataSource( LinkedHashMap, String> matcherToExpression) { return createExpressionMessageMetadataSource(matcherToExpression, new DefaultMessageSecurityExpressionHandler<>()); } /** * Create a {@link MessageSecurityMetadataSource} that uses {@link MessageMatcher} * mapped to Spring Expressions. Each entry is considered in order and only the first * match is used. * * For example: * *

	 *     LinkedHashMap<MessageMatcher<?>,String> matcherToExpression = new LinkedHashMap<MessageMatcher<Object>,String>();
	 *     matcherToExpression.put(new SimDestinationMessageMatcher("/public/**"), "permitAll");
	 *     matcherToExpression.put(new SimDestinationMessageMatcher("/admin/**"), "hasRole('ROLE_ADMIN')");
	 *     matcherToExpression.put(new SimDestinationMessageMatcher("/topics/{name}/**"), "@someBean.customLogic(authentication, #name)");
	 *     matcherToExpression.put(new SimDestinationMessageMatcher("/**"), "authenticated");
	 *
	 *     MessageSecurityMetadataSource metadataSource = createExpressionMessageMetadataSource(matcherToExpression);
	 * 
* *

* If our destination is "/public/hello", it would match on "/public/**" and on "/**". * However, only "/public/**" would be used since it is the first entry. That means * that a destination of "/public/hello" will be mapped to "permitAll". *

* *

* For a complete listing of expressions see {@link MessageSecurityExpressionRoot} *

* @param matcherToExpression an ordered mapping of {@link MessageMatcher} to Strings * that are turned into an Expression using * {@link DefaultMessageSecurityExpressionHandler#getExpressionParser()} * @param handler the {@link SecurityExpressionHandler} to use * @return the {@link MessageSecurityMetadataSource} to use. Cannot be null. */ public static MessageSecurityMetadataSource createExpressionMessageMetadataSource( LinkedHashMap, String> matcherToExpression, SecurityExpressionHandler> handler) { LinkedHashMap, Collection> matcherToAttrs = new LinkedHashMap<>(); for (Map.Entry, String> entry : matcherToExpression.entrySet()) { MessageMatcher matcher = entry.getKey(); String rawExpression = entry.getValue(); Expression expression = handler.getExpressionParser().parseExpression(rawExpression); ConfigAttribute attribute = new MessageExpressionConfigAttribute(expression, matcher); matcherToAttrs.put(matcher, Arrays.asList(attribute)); } return new DefaultMessageSecurityMetadataSource(matcherToAttrs); } } ================================================ FILE: access/src/main/java/org/springframework/security/messaging/access/expression/MessageExpressionConfigAttribute.java ================================================ /* * 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 org.springframework.security.messaging.access.expression; import java.util.Map; import org.jspecify.annotations.Nullable; import org.springframework.expression.EvaluationContext; import org.springframework.expression.Expression; import org.springframework.messaging.Message; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.messaging.util.matcher.MessageMatcher; import org.springframework.util.Assert; /** * Simple expression configuration attribute for use in {@link Message} authorizations. * * @author Rob Winch * @author Daniel Bustamante Ospina * @since 4.0 * @deprecated Use * {@link org.springframework.security.messaging.access.intercept.MessageMatcherDelegatingAuthorizationManager} * instead */ @Deprecated @SuppressWarnings("serial") class MessageExpressionConfigAttribute implements ConfigAttribute, EvaluationContextPostProcessor> { private final Expression authorizeExpression; private final MessageMatcher matcher; /** * Creates a new instance * @param authorizeExpression the {@link Expression} to use. Cannot be null * @param matcher the {@link MessageMatcher} used to match the messages. */ @SuppressWarnings("unchecked") MessageExpressionConfigAttribute(Expression authorizeExpression, MessageMatcher matcher) { Assert.notNull(authorizeExpression, "authorizeExpression cannot be null"); Assert.notNull(matcher, "matcher cannot be null"); this.authorizeExpression = authorizeExpression; this.matcher = (MessageMatcher) matcher; } Expression getAuthorizeExpression() { return this.authorizeExpression; } @Override public @Nullable String getAttribute() { return null; } @Override public String toString() { return this.authorizeExpression.getExpressionString(); } @Override public EvaluationContext postProcess(EvaluationContext ctx, Message message) { Map variables = this.matcher.matcher(message).getVariables(); for (Map.Entry entry : variables.entrySet()) { ctx.setVariable(entry.getKey(), entry.getValue()); } return ctx; } } ================================================ FILE: access/src/main/java/org/springframework/security/messaging/access/expression/MessageExpressionVoter.java ================================================ /* * 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 org.springframework.security.messaging.access.expression; import java.util.Collection; import org.jspecify.annotations.Nullable; import org.springframework.expression.EvaluationContext; import org.springframework.messaging.Message; import org.springframework.security.access.AccessDecisionVoter; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.access.expression.ExpressionUtils; import org.springframework.security.access.expression.SecurityExpressionHandler; import org.springframework.security.core.Authentication; import org.springframework.util.Assert; /** * Voter which handles {@link Message} authorisation decisions. If a * {@link MessageExpressionConfigAttribute} is found, then its expression is evaluated. If * true, {@code ACCESS_GRANTED} is returned. If false, {@code ACCESS_DENIED} is returned. * If no {@code MessageExpressionConfigAttribute} is found, then {@code ACCESS_ABSTAIN} is * returned. * * @author Rob Winch * @author Daniel Bustamante Ospina * @since 4.0 * @deprecated Use * {@link org.springframework.security.messaging.access.intercept.MessageMatcherDelegatingAuthorizationManager} * instead */ @Deprecated public class MessageExpressionVoter implements AccessDecisionVoter> { private SecurityExpressionHandler> expressionHandler = new DefaultMessageSecurityExpressionHandler<>(); @Override public int vote(Authentication authentication, Message message, Collection attributes) { Assert.notNull(authentication, "authentication must not be null"); Assert.notNull(message, "message must not be null"); Assert.notNull(attributes, "attributes must not be null"); MessageExpressionConfigAttribute attr = findConfigAttribute(attributes); if (attr == null) { return ACCESS_ABSTAIN; } EvaluationContext ctx = this.expressionHandler.createEvaluationContext(authentication, message); ctx = attr.postProcess(ctx, message); return ExpressionUtils.evaluateAsBoolean(attr.getAuthorizeExpression(), ctx) ? ACCESS_GRANTED : ACCESS_DENIED; } private @Nullable MessageExpressionConfigAttribute findConfigAttribute(Collection attributes) { for (ConfigAttribute attribute : attributes) { if (attribute instanceof MessageExpressionConfigAttribute) { return (MessageExpressionConfigAttribute) attribute; } } return null; } @Override public boolean supports(ConfigAttribute attribute) { return attribute instanceof MessageExpressionConfigAttribute; } @Override public boolean supports(Class clazz) { return Message.class.isAssignableFrom(clazz); } public void setExpressionHandler(SecurityExpressionHandler> expressionHandler) { Assert.notNull(expressionHandler, "expressionHandler cannot be null"); this.expressionHandler = expressionHandler; } } ================================================ FILE: access/src/main/java/org/springframework/security/messaging/access/intercept/ChannelSecurityInterceptor.java ================================================ /* * 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 org.springframework.security.messaging.access.intercept; import org.jspecify.annotations.Nullable; import org.springframework.messaging.Message; import org.springframework.messaging.MessageChannel; import org.springframework.messaging.support.ChannelInterceptor; import org.springframework.security.access.SecurityMetadataSource; import org.springframework.security.access.intercept.AbstractSecurityInterceptor; import org.springframework.security.access.intercept.InterceptorStatusToken; import org.springframework.security.messaging.access.expression.ExpressionBasedMessageSecurityMetadataSourceFactory; import org.springframework.util.Assert; /** * Performs security handling of Message resources via a ChannelInterceptor * implementation. *

* The SecurityMetadataSource required by this security interceptor is of * type {@link MessageSecurityMetadataSource}. *

* Refer to {@link AbstractSecurityInterceptor} for details on the workflow. * * @author Rob Winch * @since 4.0 * @deprecated Use {@code AuthorizationChannelInterceptor} instead */ @Deprecated public final class ChannelSecurityInterceptor extends AbstractSecurityInterceptor implements ChannelInterceptor { private static final ThreadLocal tokenHolder = new ThreadLocal<>(); private final MessageSecurityMetadataSource metadataSource; /** * Creates a new instance * @param metadataSource the MessageSecurityMetadataSource to use. Cannot be null. * * @see DefaultMessageSecurityMetadataSource * @see ExpressionBasedMessageSecurityMetadataSourceFactory */ public ChannelSecurityInterceptor(MessageSecurityMetadataSource metadataSource) { Assert.notNull(metadataSource, "metadataSource cannot be null"); this.metadataSource = metadataSource; } @Override public Class getSecureObjectClass() { return Message.class; } @Override public SecurityMetadataSource obtainSecurityMetadataSource() { return this.metadataSource; } @Override public Message preSend(Message message, MessageChannel channel) { InterceptorStatusToken token = beforeInvocation(message); if (token != null) { tokenHolder.set(token); } return message; } @Override public void postSend(Message message, MessageChannel channel, boolean sent) { InterceptorStatusToken token = clearToken(); afterInvocation(token, null); } @Override public void afterSendCompletion(Message message, MessageChannel channel, boolean sent, @Nullable Exception ex) { InterceptorStatusToken token = clearToken(); finallyInvocation(token); } @Override public boolean preReceive(MessageChannel channel) { return true; } @Override public Message postReceive(Message message, MessageChannel channel) { return message; } @Override public void afterReceiveCompletion(@Nullable Message message, MessageChannel channel, @Nullable Exception ex) { } private InterceptorStatusToken clearToken() { InterceptorStatusToken token = tokenHolder.get(); tokenHolder.remove(); return token; } } ================================================ FILE: access/src/main/java/org/springframework/security/messaging/access/intercept/DefaultMessageSecurityMetadataSource.java ================================================ /* * 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 org.springframework.security.messaging.access.intercept; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; import org.springframework.messaging.Message; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.messaging.access.expression.ExpressionBasedMessageSecurityMetadataSourceFactory; import org.springframework.security.messaging.util.matcher.MessageMatcher; /** * A default implementation of {@link MessageSecurityMetadataSource} that looks up the * {@link ConfigAttribute} instances using a {@link MessageMatcher}. * *

* Each entry is considered in order. The first entry that matches, the corresponding * {@code Collection} is returned. *

* * @author Rob Winch * @since 4.0 * @see ChannelSecurityInterceptor * @see ExpressionBasedMessageSecurityMetadataSourceFactory * @deprecated Use {@link MessageMatcherDelegatingAuthorizationManager} instead */ @Deprecated public final class DefaultMessageSecurityMetadataSource implements MessageSecurityMetadataSource { private final Map, Collection> messageMap; public DefaultMessageSecurityMetadataSource( LinkedHashMap, Collection> messageMap) { this.messageMap = messageMap; } @Override @SuppressWarnings({ "rawtypes", "unchecked" }) public Collection getAttributes(Object object) throws IllegalArgumentException { final Message message = (Message) object; for (Map.Entry, Collection> entry : this.messageMap.entrySet()) { if (entry.getKey().matches(message)) { return entry.getValue(); } } return Collections.emptyList(); } @Override public Collection getAllConfigAttributes() { Set allAttributes = new HashSet<>(); for (Collection entry : this.messageMap.values()) { allAttributes.addAll(entry); } return allAttributes; } @Override public boolean supports(Class clazz) { return Message.class.isAssignableFrom(clazz); } } ================================================ FILE: access/src/main/java/org/springframework/security/messaging/access/intercept/MessageSecurityMetadataSource.java ================================================ /* * 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 org.springframework.security.messaging.access.intercept; import org.springframework.messaging.Message; import org.springframework.security.access.SecurityMetadataSource; /** * A {@link SecurityMetadataSource} that is used for securing {@link Message} * * @author Rob Winch * @since 4.0 * @see ChannelSecurityInterceptor * @see DefaultMessageSecurityMetadataSource * @deprecated Use {@link MessageMatcherDelegatingAuthorizationManager} instead */ @Deprecated public interface MessageSecurityMetadataSource extends SecurityMetadataSource { } ================================================ FILE: access/src/main/java/org/springframework/security/web/access/DefaultWebInvocationPrivilegeEvaluator.java ================================================ /* * 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 org.springframework.security.web.access; 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; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.access.intercept.AbstractSecurityInterceptor; import org.springframework.security.core.Authentication; import org.springframework.security.web.FilterInvocation; import org.springframework.util.Assert; import org.springframework.web.context.ServletContextAware; /** * Allows users to determine whether they have privileges for a given web URI. * * @author Ben Alex * @author Luke Taylor * @since 3.0 * @deprecated Use {@link AuthorizationManagerWebInvocationPrivilegeEvaluator} instead */ @Deprecated public class DefaultWebInvocationPrivilegeEvaluator implements WebInvocationPrivilegeEvaluator, ServletContextAware { protected static final Log logger = LogFactory.getLog(DefaultWebInvocationPrivilegeEvaluator.class); private final AbstractSecurityInterceptor securityInterceptor; private @Nullable ServletContext servletContext; public DefaultWebInvocationPrivilegeEvaluator(AbstractSecurityInterceptor securityInterceptor) { Assert.notNull(securityInterceptor, "SecurityInterceptor cannot be null"); Assert.isTrue(FilterInvocation.class.equals(securityInterceptor.getSecureObjectClass()), "AbstractSecurityInterceptor does not support FilterInvocations"); Assert.notNull(securityInterceptor.getAccessDecisionManager(), "AbstractSecurityInterceptor must provide a non-null AccessDecisionManager"); this.securityInterceptor = securityInterceptor; } /** * Determines whether the user represented by the supplied Authentication * object is allowed to invoke the supplied URI. * @param uri the URI excluding the context path (a default context path setting will * be used) */ @Override public boolean isAllowed(String uri, @Nullable Authentication authentication) { return isAllowed(null, uri, null, authentication); } /** * Determines whether the user represented by the supplied Authentication * object is allowed to invoke the supplied URI, with the given . *

* Note the default implementation of FilterInvocationSecurityMetadataSource * disregards the contextPath when evaluating which secure object * metadata applies to a given request URI, so generally the contextPath * is unimportant unless you are using a custom * FilterInvocationSecurityMetadataSource. * @param uri the URI excluding the context path * @param contextPath the context path (may be null, in which case a default value * will be used). * @param method the HTTP method (or null, for any method) * @param authentication the Authentication instance whose authorities should * be used in evaluation whether access should be granted. * @return true if access is allowed, false if denied */ @Override public boolean isAllowed(@Nullable String contextPath, String uri, @Nullable String method, @Nullable Authentication authentication) { Assert.notNull(uri, "uri parameter is required"); FilterInvocation filterInvocation = new FilterInvocation(contextPath, uri, method, this.servletContext); Collection attributes = this.securityInterceptor.obtainSecurityMetadataSource() .getAttributes(filterInvocation); if (attributes == null) { return (!this.securityInterceptor.isRejectPublicInvocations()); } if (authentication == null) { return false; } try { this.securityInterceptor.getAccessDecisionManager().decide(authentication, filterInvocation, attributes); return true; } catch (AccessDeniedException ex) { logger.debug(LogMessage.format("%s denied for %s", filterInvocation, authentication), ex); return false; } } @Override public void setServletContext(ServletContext servletContext) { this.servletContext = servletContext; } } ================================================ FILE: access/src/main/java/org/springframework/security/web/access/channel/AbstractRetryEntryPoint.java ================================================ /* * 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 org.springframework.security.web.access.channel; import java.io.IOException; 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; import org.springframework.security.web.PortMapper; import org.springframework.security.web.PortMapperImpl; import org.springframework.security.web.RedirectStrategy; import org.springframework.util.Assert; /** * @author Luke Taylor * @deprecated please use * {@link org.springframework.security.web.transport.HttpsRedirectFilter} and its * associated {@link PortMapper} */ @Deprecated public abstract class AbstractRetryEntryPoint implements ChannelEntryPoint { protected final Log logger = LogFactory.getLog(getClass()); private PortMapper portMapper = new PortMapperImpl(); /** * The scheme ("http://" or "https://") */ private final String scheme; /** * The standard port for the scheme (80 for http, 443 for https) */ private final int standardPort; private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy(); public AbstractRetryEntryPoint(String scheme, int standardPort) { this.scheme = scheme; this.standardPort = standardPort; } @Override public void commence(HttpServletRequest request, HttpServletResponse response) throws IOException { String queryString = request.getQueryString(); String redirectUrl = request.getRequestURI() + ((queryString != null) ? ("?" + queryString) : ""); Integer currentPort = this.portMapper.getServerPort(request); Integer redirectPort = getMappedPort(currentPort); if (redirectPort != null) { boolean includePort = redirectPort != this.standardPort; String port = (includePort) ? (":" + redirectPort) : ""; redirectUrl = this.scheme + request.getServerName() + port + redirectUrl; } this.logger.debug(LogMessage.format("Redirecting to: %s", redirectUrl)); this.redirectStrategy.sendRedirect(request, response, redirectUrl); } protected abstract @Nullable Integer getMappedPort(Integer mapFromPort); protected final PortMapper getPortMapper() { return this.portMapper; } public void setPortMapper(PortMapper portMapper) { Assert.notNull(portMapper, "portMapper cannot be null"); this.portMapper = portMapper; } /** * Sets the strategy to be used for redirecting to the required channel URL. A * {@code DefaultRedirectStrategy} instance will be used if not set. * @param redirectStrategy the strategy instance to which the URL will be passed. */ public void setRedirectStrategy(RedirectStrategy redirectStrategy) { Assert.notNull(redirectStrategy, "redirectStrategy cannot be null"); this.redirectStrategy = redirectStrategy; } protected final RedirectStrategy getRedirectStrategy() { return this.redirectStrategy; } } ================================================ FILE: access/src/main/java/org/springframework/security/web/access/channel/ChannelDecisionManager.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.web.access.channel; import java.io.IOException; import java.util.Collection; import jakarta.servlet.ServletException; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.web.FilterInvocation; import org.springframework.security.web.util.matcher.RequestMatcher; /** * Decides whether a web channel provides sufficient security. * * @author Ben Alex * @deprecated no replacement is planned, though consider using a custom * {@link RequestMatcher} for any sophisticated decision-making */ @Deprecated public interface ChannelDecisionManager { /** * Decided whether the presented {@link FilterInvocation} provides the appropriate * level of channel security based on the requested list of ConfigAttributes. * */ void decide(FilterInvocation invocation, Collection config) throws IOException, ServletException; /** * Indicates whether this ChannelDecisionManager is able to process the * passed ConfigAttribute. *

* This allows the ChannelProcessingFilter to check every configuration * attribute can be consumed by the configured ChannelDecisionManager. *

* @param attribute a configuration attribute that has been configured against the * ChannelProcessingFilter * @return true if this ChannelDecisionManager can support the passed * configuration attribute */ boolean supports(ConfigAttribute attribute); } ================================================ FILE: access/src/main/java/org/springframework/security/web/access/channel/ChannelDecisionManagerImpl.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.web.access.channel; import java.io.IOException; import java.util.ArrayList; 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; import org.springframework.security.web.FilterInvocation; import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.util.Assert; /** * Implementation of {@link ChannelDecisionManager}. *

* Iterates through each configured {@link ChannelProcessor}. If a * ChannelProcessor has any issue with the security of the request, it should * cause a redirect, exception or whatever other action is appropriate for the * ChannelProcessor implementation. *

* Once any response is committed (ie a redirect is written to the response object), the * ChannelDecisionManagerImpl will not iterate through any further * ChannelProcessors. *

* The attribute "ANY_CHANNEL" if applied to a particular URL, the iteration through the * channel processors will be skipped (see SEC-494, SEC-335). * * @author Ben Alex * @deprecated no replacement is planned, though consider using a custom * {@link RequestMatcher} for any sophisticated decision-making */ @Deprecated @NullUnmarked public class ChannelDecisionManagerImpl implements ChannelDecisionManager, InitializingBean { public static final String ANY_CHANNEL = "ANY_CHANNEL"; private List channelProcessors; @Override public void afterPropertiesSet() { Assert.notEmpty(this.channelProcessors, "A list of ChannelProcessors is required"); } @Override public void decide(FilterInvocation invocation, Collection config) throws IOException, ServletException { for (ConfigAttribute attribute : config) { if (ANY_CHANNEL.equals(attribute.getAttribute())) { return; } } for (ChannelProcessor processor : this.channelProcessors) { processor.decide(invocation, config); if (invocation.getResponse().isCommitted()) { break; } } } protected @Nullable List getChannelProcessors() { return this.channelProcessors; } @SuppressWarnings("cast") public void setChannelProcessors(List channelProcessors) { Assert.notEmpty(channelProcessors, "A list of ChannelProcessors is required"); this.channelProcessors = new ArrayList<>(channelProcessors.size()); for (Object currentObject : channelProcessors) { Assert.isInstanceOf(ChannelProcessor.class, currentObject, () -> "ChannelProcessor " + currentObject.getClass().getName() + " must implement ChannelProcessor"); this.channelProcessors.add((ChannelProcessor) currentObject); } } @Override public boolean supports(ConfigAttribute attribute) { if (ANY_CHANNEL.equals(attribute.getAttribute())) { return true; } for (ChannelProcessor processor : this.channelProcessors) { if (processor.supports(attribute)) { return true; } } return false; } } ================================================ FILE: access/src/main/java/org/springframework/security/web/access/channel/ChannelEntryPoint.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.web.access.channel; import java.io.IOException; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.security.web.PortMapper; /** * May be used by a {@link ChannelProcessor} to launch a web channel. * *

* ChannelProcessors can elect to launch a new web channel directly, or they * can delegate to another class. The ChannelEntryPoint is a pluggable * interface to assist ChannelProcessors in performing this delegation. * * @author Ben Alex * @deprecated please use * {@link org.springframework.security.web.transport.HttpsRedirectFilter} and its * associated {@link PortMapper} */ @Deprecated public interface ChannelEntryPoint { /** * Commences a secure channel. *

* Implementations should modify the headers on the ServletResponse as * necessary to commence the user agent using the implementation's supported channel * type. * @param request that a ChannelProcessor has rejected * @param response so that the user agent can begin using a new channel * */ void commence(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException; } ================================================ FILE: access/src/main/java/org/springframework/security/web/access/channel/ChannelProcessingFilter.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.web.access.channel; import java.io.IOException; import java.util.Collection; import java.util.HashSet; import java.util.Set; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; 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; import org.springframework.security.web.FilterInvocation; import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource; import org.springframework.util.Assert; import org.springframework.web.filter.GenericFilterBean; /** * Ensures a web request is delivered over the required channel. *

* Internally uses a {@link FilterInvocation} to represent the request, allowing a * {@code FilterInvocationSecurityMetadataSource} to be used to lookup the attributes * which apply. *

* Delegates the actual channel security decisions and necessary actions to the configured * {@link ChannelDecisionManager}. If a response is committed by the * {@code ChannelDecisionManager}, the filter chain will not proceed. *

* The most common usage is to ensure that a request takes place over HTTPS, where the * {@link ChannelDecisionManagerImpl} is configured with a {@link SecureChannelProcessor} * and an {@link InsecureChannelProcessor}. A typical configuration would be * *

 *
 * <bean id="channelProcessingFilter" class="org.springframework.security.web.access.channel.ChannelProcessingFilter">
 *   <property name="channelDecisionManager" ref="channelDecisionManager"/>
 *   <property name="securityMetadataSource">
 *     <security:filter-security-metadata-source request-matcher="regex">
 *       <security:intercept-url pattern="\A/secure/.*\Z" access="REQUIRES_SECURE_CHANNEL"/>
 *       <security:intercept-url pattern="\A/login.jsp.*\Z" access="REQUIRES_SECURE_CHANNEL"/>
 *       <security:intercept-url pattern="\A/.*\Z" access="ANY_CHANNEL"/>
 *     </security:filter-security-metadata-source>
 *   </property>
 * </bean>
 *
 * <bean id="channelDecisionManager" class="org.springframework.security.web.access.channel.ChannelDecisionManagerImpl">
 *   <property name="channelProcessors">
 *     <list>
 *     <ref bean="secureChannelProcessor"/>
 *     <ref bean="insecureChannelProcessor"/>
 *     </list>
 *   </property>
 * </bean>
 *
 * <bean id="secureChannelProcessor"
 *   class="org.springframework.security.web.access.channel.SecureChannelProcessor"/>
 * <bean id="insecureChannelProcessor"
 *   class="org.springframework.security.web.access.channel.InsecureChannelProcessor"/>
 *
 * 
* * which would force the login form and any access to the {@code /secure} path to be made * over HTTPS. * * @author Ben Alex * @deprecated see {@link org.springframework.security.web.transport.HttpsRedirectFilter} */ @Deprecated public class ChannelProcessingFilter extends GenericFilterBean { @SuppressWarnings("NullAway.Init") private ChannelDecisionManager channelDecisionManager; @SuppressWarnings("NullAway.Init") private FilterInvocationSecurityMetadataSource securityMetadataSource; @Override public void afterPropertiesSet() { Assert.notNull(this.securityMetadataSource, "securityMetadataSource must be specified"); Assert.notNull(this.channelDecisionManager, "channelDecisionManager must be specified"); Collection attributes = this.securityMetadataSource.getAllConfigAttributes(); if (attributes == null) { this.logger.warn("Could not validate configuration attributes as the " + "FilterInvocationSecurityMetadataSource did not return any attributes"); return; } Set unsupportedAttributes = getUnsupportedAttributes(attributes); Assert.isTrue(unsupportedAttributes.isEmpty(), () -> "Unsupported configuration attributes: " + unsupportedAttributes); this.logger.info("Validated configuration attributes"); } private Set getUnsupportedAttributes(Collection attrDefs) { Set unsupportedAttributes = new HashSet<>(); for (ConfigAttribute attr : attrDefs) { if (!this.channelDecisionManager.supports(attr)) { unsupportedAttributes.add(attr); } } return unsupportedAttributes; } @Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; FilterInvocation filterInvocation = new FilterInvocation(request, response, chain); Collection attributes = this.securityMetadataSource.getAttributes(filterInvocation); if (attributes != null) { this.logger.debug(LogMessage.format("Request: %s; ConfigAttributes: %s", filterInvocation, attributes)); this.channelDecisionManager.decide(filterInvocation, attributes); @Nullable HttpServletResponse channelResponse = filterInvocation.getResponse(); Assert.notNull(channelResponse, "HttpServletResponse is required"); if (channelResponse.isCommitted()) { return; } } chain.doFilter(request, response); } protected @Nullable ChannelDecisionManager getChannelDecisionManager() { return this.channelDecisionManager; } protected FilterInvocationSecurityMetadataSource getSecurityMetadataSource() { return this.securityMetadataSource; } public void setChannelDecisionManager(ChannelDecisionManager channelDecisionManager) { this.channelDecisionManager = channelDecisionManager; } public void setSecurityMetadataSource( FilterInvocationSecurityMetadataSource filterInvocationSecurityMetadataSource) { this.securityMetadataSource = filterInvocationSecurityMetadataSource; } } ================================================ FILE: access/src/main/java/org/springframework/security/web/access/channel/ChannelProcessor.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.web.access.channel; import java.io.IOException; import java.util.Collection; import jakarta.servlet.ServletException; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.web.FilterInvocation; import org.springframework.security.web.util.matcher.RequestMatcher; /** * Decides whether a web channel meets a specific security condition. *

* ChannelProcessor implementations are iterated by the * {@link ChannelDecisionManagerImpl}. *

* If an implementation has an issue with the channel security, they should take action * themselves. The callers of the implementation do not take any action. * * @author Ben Alex * @deprecated no replacement is planned, though consider using a custom * {@link RequestMatcher} for any sophisticated decision-making */ @Deprecated public interface ChannelProcessor { /** * Decided whether the presented {@link FilterInvocation} provides the appropriate * level of channel security based on the requested list of ConfigAttributes. */ void decide(FilterInvocation invocation, Collection config) throws IOException, ServletException; /** * Indicates whether this ChannelProcessor is able to process the passed * ConfigAttribute. *

* This allows the ChannelProcessingFilter to check every configuration * attribute can be consumed by the configured ChannelDecisionManager. * @param attribute a configuration attribute that has been configured against the * ChannelProcessingFilter. * @return true if this ChannelProcessor can support the passed * configuration attribute */ boolean supports(ConfigAttribute attribute); } ================================================ FILE: access/src/main/java/org/springframework/security/web/access/channel/InsecureChannelProcessor.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.web.access.channel; 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; import org.springframework.security.web.FilterInvocation; import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.util.Assert; /** * Ensures channel security is inactive by review of * HttpServletRequest.isSecure() responses. *

* The class responds to one case-sensitive keyword, {@link #getInsecureKeyword}. If this * keyword is detected, HttpServletRequest.isSecure() is used to determine * the channel security offered. If channel security is present, the configured * ChannelEntryPoint is called. By default the entry point is * {@link RetryWithHttpEntryPoint}. *

* The default insecureKeyword is REQUIRES_INSECURE_CHANNEL. * * @author Ben Alex * @deprecated no replacement is planned, though consider using a custom * {@link RequestMatcher} for any sophisticated decision-making */ @Deprecated public class InsecureChannelProcessor implements InitializingBean, ChannelProcessor { private ChannelEntryPoint entryPoint = new RetryWithHttpEntryPoint(); private String insecureKeyword = "REQUIRES_INSECURE_CHANNEL"; @Override public void afterPropertiesSet() { Assert.hasLength(this.insecureKeyword, "insecureKeyword required"); Assert.notNull(this.entryPoint, "entryPoint required"); } @Override public void decide(FilterInvocation invocation, Collection config) throws IOException, ServletException { Assert.isTrue(invocation != null && config != null, "Nulls cannot be provided"); for (ConfigAttribute attribute : config) { if (supports(attribute)) { if (invocation.getHttpRequest().isSecure()) { @Nullable HttpServletResponse response = invocation.getResponse(); Assert.notNull(response, "HttpServletResponse required"); this.entryPoint.commence(invocation.getRequest(), response); } } } } public ChannelEntryPoint getEntryPoint() { return this.entryPoint; } public String getInsecureKeyword() { return this.insecureKeyword; } public void setEntryPoint(ChannelEntryPoint entryPoint) { this.entryPoint = entryPoint; } public void setInsecureKeyword(String secureKeyword) { this.insecureKeyword = secureKeyword; } @Override public boolean supports(ConfigAttribute attribute) { return (attribute != null) && (attribute.getAttribute() != null) && attribute.getAttribute().equals(getInsecureKeyword()); } } ================================================ FILE: access/src/main/java/org/springframework/security/web/access/channel/RetryWithHttpEntryPoint.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.web.access.channel; import org.jspecify.annotations.Nullable; import org.springframework.security.web.PortMapper; /** * Commences an insecure channel by retrying the original request using HTTP. *

* This entry point should suffice in most circumstances. However, it is not intended to * properly handle HTTP POSTs or other usage where a standard redirect would cause an * issue. * * @author Ben Alex * @deprecated please use * {@link org.springframework.security.web.transport.HttpsRedirectFilter} and its * associated {@link PortMapper} */ @Deprecated(since = "6.5") public class RetryWithHttpEntryPoint extends AbstractRetryEntryPoint { public RetryWithHttpEntryPoint() { super("http://", 80); } @Override protected @Nullable Integer getMappedPort(Integer mapFromPort) { return getPortMapper().lookupHttpPort(mapFromPort); } } ================================================ FILE: access/src/main/java/org/springframework/security/web/access/channel/RetryWithHttpsEntryPoint.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.web.access.channel; import org.jspecify.annotations.Nullable; import org.springframework.security.web.PortMapper; /** * Commences a secure channel by retrying the original request using HTTPS. *

* This entry point should suffice in most circumstances. However, it is not intended to * properly handle HTTP POSTs or other usage where a standard redirect would cause an * issue. *

* * @author Ben Alex * @deprecated please use * {@link org.springframework.security.web.transport.HttpsRedirectFilter} and its * associated {@link PortMapper} */ @Deprecated(since = "6.5") public class RetryWithHttpsEntryPoint extends AbstractRetryEntryPoint { public RetryWithHttpsEntryPoint() { super("https://", 443); } @Override protected @Nullable Integer getMappedPort(Integer mapFromPort) { return getPortMapper().lookupHttpsPort(mapFromPort); } } ================================================ FILE: access/src/main/java/org/springframework/security/web/access/channel/SecureChannelProcessor.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.web.access.channel; 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; import org.springframework.security.web.FilterInvocation; import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.util.Assert; /** * Ensures channel security is active by review of * HttpServletRequest.isSecure() responses. *

* The class responds to one case-sensitive keyword, {@link #getSecureKeyword}. If this * keyword is detected, HttpServletRequest.isSecure() is used to determine * the channel security offered. If channel security is not present, the configured * ChannelEntryPoint is called. By default the entry point is * {@link RetryWithHttpsEntryPoint}. *

* The default secureKeyword is REQUIRES_SECURE_CHANNEL. * * @author Ben Alex * @deprecated no replacement is planned, though consider using a custom * {@link RequestMatcher} for any sophisticated decision-making */ @Deprecated public class SecureChannelProcessor implements InitializingBean, ChannelProcessor { private ChannelEntryPoint entryPoint = new RetryWithHttpsEntryPoint(); private String secureKeyword = "REQUIRES_SECURE_CHANNEL"; @Override public void afterPropertiesSet() { Assert.hasLength(this.secureKeyword, "secureKeyword required"); Assert.notNull(this.entryPoint, "entryPoint required"); } @Override public void decide(FilterInvocation invocation, Collection config) throws IOException, ServletException { Assert.isTrue((invocation != null) && (config != null), "Nulls cannot be provided"); for (ConfigAttribute attribute : config) { if (supports(attribute)) { if (!invocation.getHttpRequest().isSecure()) { HttpServletResponse response = invocation.getResponse(); Assert.notNull(response, "HttpServletResponse is required"); this.entryPoint.commence(invocation.getRequest(), response); } } } } public ChannelEntryPoint getEntryPoint() { return this.entryPoint; } public String getSecureKeyword() { return this.secureKeyword; } public void setEntryPoint(ChannelEntryPoint entryPoint) { this.entryPoint = entryPoint; } public void setSecureKeyword(String secureKeyword) { this.secureKeyword = secureKeyword; } @Override public boolean supports(ConfigAttribute attribute) { return (attribute != null) && (attribute.getAttribute() != null) && attribute.getAttribute().equals(getSecureKeyword()); } } ================================================ FILE: access/src/main/java/org/springframework/security/web/access/channel/package-info.java ================================================ /* * 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 that ensure web requests are received over required transport channels. *

* 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; ================================================ FILE: access/src/main/java/org/springframework/security/web/access/expression/DefaultWebSecurityExpressionHandler.java ================================================ /* * 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 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; import org.springframework.security.authentication.AuthenticationTrustResolver; import org.springframework.security.authentication.AuthenticationTrustResolverImpl; import org.springframework.security.authorization.AuthorizationManagerFactory; import org.springframework.security.core.Authentication; import org.springframework.security.web.FilterInvocation; /** * @author Luke Taylor * @author Eddú Meléndez * @author Steve Riesenberg * @since 3.0 */ public class DefaultWebSecurityExpressionHandler extends AbstractSecurityExpressionHandler implements SecurityExpressionHandler { private static final String DEFAULT_ROLE_PREFIX = "ROLE_"; private String defaultRolePrefix = DEFAULT_ROLE_PREFIX; @Override @SuppressWarnings("deprecation") protected SecurityExpressionOperations createSecurityExpressionRoot(@Nullable Authentication authentication, FilterInvocation fi) { FilterInvocationExpressionRoot root = new FilterInvocationExpressionRoot(() -> authentication, fi); root.setAuthorizationManagerFactory(getAuthorizationManagerFactory()); root.setPermissionEvaluator(getPermissionEvaluator()); if (!DEFAULT_ROLE_PREFIX.equals(this.defaultRolePrefix)) { // Ensure SecurityExpressionRoot can strip the custom role prefix root.setDefaultRolePrefix(this.defaultRolePrefix); } return root; } /** * Sets the {@link AuthenticationTrustResolver} to be used. The default is * {@link AuthenticationTrustResolverImpl}. * @param trustResolver the {@link AuthenticationTrustResolver} to use. Cannot be * null. * @deprecated Use * {@link #setAuthorizationManagerFactory(AuthorizationManagerFactory)} instead */ @Deprecated(since = "7.0") public void setTrustResolver(AuthenticationTrustResolver trustResolver) { getDefaultAuthorizationManagerFactory().setTrustResolver(trustResolver); } /** *

* Sets the default prefix to be added to * {@link org.springframework.security.access.expression.SecurityExpressionRoot#hasAnyRole(String...)} * or * {@link org.springframework.security.access.expression.SecurityExpressionRoot#hasRole(String)}. * For example, if hasRole("ADMIN") or hasRole("ROLE_ADMIN") is passed in, then the * role ROLE_ADMIN will be used when the defaultRolePrefix is "ROLE_" (default). *

* *

* If null or empty, then no default role prefix is used. *

* @param defaultRolePrefix the default prefix to add to roles. Default "ROLE_". * @deprecated Use * {@link #setAuthorizationManagerFactory(AuthorizationManagerFactory)} instead */ @Deprecated(since = "7.0") public void setDefaultRolePrefix(@Nullable String defaultRolePrefix) { if (defaultRolePrefix == null) { defaultRolePrefix = ""; } getDefaultAuthorizationManagerFactory().setRolePrefix(defaultRolePrefix); this.defaultRolePrefix = defaultRolePrefix; } } ================================================ FILE: access/src/main/java/org/springframework/security/web/access/expression/ExpressionBasedFilterInvocationSecurityMetadataSource.java ================================================ /* * 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 org.springframework.security.web.access.expression; import java.util.ArrayList; import java.util.Collection; import java.util.LinkedHashMap; import java.util.Map; import java.util.function.BiConsumer; import jakarta.servlet.http.HttpServletRequest; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.core.log.LogMessage; import org.springframework.expression.ExpressionParser; import org.springframework.expression.ParseException; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.access.expression.SecurityExpressionHandler; import org.springframework.security.authorization.AuthorizationManager; import org.springframework.security.core.annotation.SecurityAnnotationScanner; import org.springframework.security.web.FilterInvocation; import org.springframework.security.web.access.intercept.DefaultFilterInvocationSecurityMetadataSource; import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.util.Assert; /** * Expression-based {@code FilterInvocationSecurityMetadataSource}. * * @author Luke Taylor * @author Eddú Meléndez * @since 3.0 * @deprecated In modern Spring Security APIs, each API manages its own configuration * context. As such there is no direct replacement for this interface. In the case of * method security, please see {@link SecurityAnnotationScanner} and * {@link AuthorizationManager}. In the case of channel security, please see * {@code HttpsRedirectFilter}. In the case of web security, please see * {@link AuthorizationManager}. */ @Deprecated public final class ExpressionBasedFilterInvocationSecurityMetadataSource extends DefaultFilterInvocationSecurityMetadataSource { private static final Log logger = LogFactory.getLog(ExpressionBasedFilterInvocationSecurityMetadataSource.class); public ExpressionBasedFilterInvocationSecurityMetadataSource( LinkedHashMap> requestMap, SecurityExpressionHandler expressionHandler) { super(processMap(requestMap, expressionHandler.getExpressionParser())); Assert.notNull(expressionHandler, "A non-null SecurityExpressionHandler is required"); } private static LinkedHashMap> processMap( LinkedHashMap> requestMap, ExpressionParser parser) { Assert.notNull(parser, "SecurityExpressionHandler returned a null parser object"); LinkedHashMap> processed = new LinkedHashMap<>(requestMap); requestMap.forEach((request, value) -> process(parser, request, value, processed::put)); return processed; } private static void process(ExpressionParser parser, RequestMatcher request, Collection value, BiConsumer> consumer) { String expression = getExpression(request, value); if (logger.isDebugEnabled()) { logger.debug(LogMessage.format("Adding web access control expression [%s] for %s", expression, request)); } AbstractVariableEvaluationContextPostProcessor postProcessor = createPostProcessor(request); ArrayList processed = new ArrayList<>(1); try { processed.add(new WebExpressionConfigAttribute(parser.parseExpression(expression), postProcessor)); } catch (ParseException ex) { throw new IllegalArgumentException("Failed to parse expression '" + expression + "'"); } consumer.accept(request, processed); } private static String getExpression(RequestMatcher request, Collection value) { Assert.isTrue(value.size() == 1, () -> "Expected a single expression attribute for " + request); return value.toArray(new ConfigAttribute[1])[0].getAttribute(); } private static AbstractVariableEvaluationContextPostProcessor createPostProcessor(RequestMatcher request) { return new RequestVariablesExtractorEvaluationContextPostProcessor(request); } static class RequestVariablesExtractorEvaluationContextPostProcessor extends AbstractVariableEvaluationContextPostProcessor { private final RequestMatcher matcher; RequestVariablesExtractorEvaluationContextPostProcessor(RequestMatcher matcher) { this.matcher = matcher; } @Override Map extractVariables(HttpServletRequest request) { return this.matcher.matcher(request).getVariables(); } } } ================================================ FILE: access/src/main/java/org/springframework/security/web/access/expression/WebExpressionConfigAttribute.java ================================================ /* * 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 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; import org.springframework.security.authorization.AuthorizationManager; import org.springframework.security.web.FilterInvocation; /** * Simple expression configuration attribute for use in web request authorizations. * * @author Luke Taylor * @since 3.0 * @deprecated In modern Spring Security APIs, each API manages its own configuration * context. As such there is no direct replacement for this interface. Please see * {@link AuthorizationManager}. */ @Deprecated @NullUnmarked class WebExpressionConfigAttribute implements ConfigAttribute, EvaluationContextPostProcessor { private final Expression authorizeExpression; private final EvaluationContextPostProcessor postProcessor; WebExpressionConfigAttribute(Expression authorizeExpression, EvaluationContextPostProcessor postProcessor) { this.authorizeExpression = authorizeExpression; this.postProcessor = postProcessor; } Expression getAuthorizeExpression() { return this.authorizeExpression; } @Override public EvaluationContext postProcess(EvaluationContext context, FilterInvocation fi) { return (this.postProcessor != null) ? this.postProcessor.postProcess(context, fi) : context; } @Override public String getAttribute() { return null; } @Override public String toString() { return this.authorizeExpression.getExpressionString(); } } ================================================ FILE: access/src/main/java/org/springframework/security/web/access/expression/WebExpressionVoter.java ================================================ /* * 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 org.springframework.security.web.access.expression; 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; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.access.expression.ExpressionUtils; import org.springframework.security.access.expression.SecurityExpressionHandler; import org.springframework.security.core.Authentication; import org.springframework.security.web.FilterInvocation; import org.springframework.util.Assert; /** * Voter which handles web authorisation decisions. * * @author Luke Taylor * @since 3.0 * @deprecated Use {@link WebExpressionAuthorizationManager} instead */ @Deprecated public class WebExpressionVoter implements AccessDecisionVoter { private final Log logger = LogFactory.getLog(getClass()); private SecurityExpressionHandler expressionHandler = new DefaultWebSecurityExpressionHandler(); @Override public int vote(Authentication authentication, FilterInvocation filterInvocation, Collection attributes) { Assert.notNull(authentication, "authentication must not be null"); Assert.notNull(filterInvocation, "filterInvocation must not be null"); Assert.notNull(attributes, "attributes must not be null"); WebExpressionConfigAttribute webExpressionConfigAttribute = findConfigAttribute(attributes); if (webExpressionConfigAttribute == null) { this.logger .trace("Abstained since did not find a config attribute of instance WebExpressionConfigAttribute"); return ACCESS_ABSTAIN; } EvaluationContext ctx = webExpressionConfigAttribute.postProcess( this.expressionHandler.createEvaluationContext(authentication, filterInvocation), filterInvocation); boolean granted = ExpressionUtils.evaluateAsBoolean(webExpressionConfigAttribute.getAuthorizeExpression(), ctx); if (granted) { return ACCESS_GRANTED; } this.logger.trace("Voted to deny authorization"); return ACCESS_DENIED; } private @Nullable WebExpressionConfigAttribute findConfigAttribute(Collection attributes) { for (ConfigAttribute attribute : attributes) { if (attribute instanceof WebExpressionConfigAttribute) { return (WebExpressionConfigAttribute) attribute; } } return null; } @Override public boolean supports(ConfigAttribute attribute) { return attribute instanceof WebExpressionConfigAttribute; } @Override public boolean supports(Class clazz) { return FilterInvocation.class.isAssignableFrom(clazz); } public void setExpressionHandler(SecurityExpressionHandler expressionHandler) { this.expressionHandler = expressionHandler; } } ================================================ FILE: access/src/main/java/org/springframework/security/web/access/intercept/DefaultFilterInvocationSecurityMetadataSource.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 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; import java.util.Set; import jakarta.servlet.http.HttpServletRequest; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.core.log.LogMessage; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.authorization.AuthorizationManager; import org.springframework.security.core.annotation.SecurityAnnotationScanner; import org.springframework.security.web.FilterInvocation; import org.springframework.security.web.util.matcher.RequestMatcher; /** * Default implementation of FilterInvocationDefinitionSource. *

* Stores an ordered map of {@link RequestMatcher}s to ConfigAttribute * collections and provides matching of {@code FilterInvocation}s against the items stored * in the map. *

* The order of the {@link RequestMatcher}s in the map is very important. The first * one which matches the request will be used. Later matchers in the map will not be * invoked if a match has already been found. Accordingly, the most specific matchers * should be registered first, with the most general matches registered last. *

* The most common method creating an instance is using the Spring Security namespace. For * example, the {@code pattern} and {@code access} attributes of the * {@code } elements defined as children of the {@code } element are * combined to build the instance used by the {@code FilterSecurityInterceptor}. * * @author Ben Alex * @author Luke Taylor * @deprecated In modern Spring Security APIs, each API manages its own configuration * context. As such there is no direct replacement for this interface. In the case of * method security, please see {@link SecurityAnnotationScanner} and * {@link AuthorizationManager}. In the case of channel security, please see * {@code HttpsRedirectFilter}. In the case of web security, please see * {@link AuthorizationManager}. */ @Deprecated public class DefaultFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource { protected final Log logger = LogFactory.getLog(getClass()); private final Map> requestMap; /** * Sets the internal request map from the supplied map. The key elements should be of * type {@link RequestMatcher}, which. The path stored in the key will depend on the * type of the supplied UrlMatcher. * @param requestMap order-preserving map of request definitions to attribute lists */ public DefaultFilterInvocationSecurityMetadataSource( LinkedHashMap> requestMap) { this.requestMap = requestMap; } @Override public Collection getAllConfigAttributes() { Set allAttributes = new HashSet<>(); this.requestMap.values().forEach(allAttributes::addAll); return allAttributes; } @Override public Collection getAttributes(Object object) { final HttpServletRequest request = getHttpServletRequest(object); int count = 0; for (Map.Entry> entry : this.requestMap.entrySet()) { if (entry.getKey().matches(request)) { return entry.getValue(); } else { if (this.logger.isTraceEnabled()) { this.logger.trace(LogMessage.format("Did not match request to %s - %s (%d/%d)", entry.getKey(), entry.getValue(), ++count, this.requestMap.size())); } } } return Collections.emptyList(); } @Override public boolean supports(Class clazz) { return FilterInvocation.class.isAssignableFrom(clazz); } private HttpServletRequest getHttpServletRequest(Object object) { if (object instanceof FilterInvocation invocation) { return invocation.getHttpRequest(); } if (object instanceof HttpServletRequest request) { return request; } throw new IllegalArgumentException("object must be of type FilterInvocation or HttpServletRequest"); } } ================================================ FILE: access/src/main/java/org/springframework/security/web/access/intercept/FilterInvocationSecurityMetadataSource.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.web.access.intercept; import org.springframework.security.access.SecurityMetadataSource; import org.springframework.security.authorization.AuthorizationManager; import org.springframework.security.core.annotation.SecurityAnnotationScanner; import org.springframework.security.web.FilterInvocation; /** * Marker interface for SecurityMetadataSource implementations that are * designed to perform lookups keyed on {@link FilterInvocation}s. * * @author Ben Alex * @deprecated In modern Spring Security APIs, each API manages its own configuration * context. As such there is no direct replacement for this interface. In the case of * method security, please see {@link SecurityAnnotationScanner} and * {@link AuthorizationManager}. In the case of channel security, please see * {@code HttpsRedirectFilter}. In the case of web security, please see * {@link AuthorizationManager}. */ @Deprecated public interface FilterInvocationSecurityMetadataSource extends SecurityMetadataSource { } ================================================ FILE: access/src/main/java/org/springframework/security/web/access/intercept/FilterSecurityInterceptor.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.web.access.intercept; import java.io.IOException; import jakarta.servlet.Filter; import jakarta.servlet.FilterChain; 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; import org.springframework.security.access.intercept.InterceptorStatusToken; import org.springframework.security.web.FilterInvocation; /** * Performs security handling of HTTP resources via a filter implementation. *

* The SecurityMetadataSource required by this security interceptor is of * type {@link FilterInvocationSecurityMetadataSource}. *

* Refer to {@link AbstractSecurityInterceptor} for details on the workflow. *

* * @author Ben Alex * @author Rob Winch * @deprecated Use {@link AuthorizationFilter} instead */ @Deprecated public class FilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter { private static final String FILTER_APPLIED = "__spring_security_filterSecurityInterceptor_filterApplied"; private @Nullable FilterInvocationSecurityMetadataSource securityMetadataSource; private boolean observeOncePerRequest = false; /** * Not used (we rely on IoC container lifecycle services instead) * @param arg0 ignored * */ @Override public void init(FilterConfig arg0) { } /** * Not used (we rely on IoC container lifecycle services instead) */ @Override public void destroy() { } /** * Method that is actually called by the filter chain. Simply delegates to the * {@link #invoke(FilterInvocation)} method. * @param request the servlet request * @param response the servlet response * @param chain the filter chain * @throws IOException if the filter chain fails * @throws ServletException if the filter chain fails */ @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { invoke(new FilterInvocation(request, response, chain)); } public @Nullable FilterInvocationSecurityMetadataSource getSecurityMetadataSource() { return this.securityMetadataSource; } @Override public @Nullable SecurityMetadataSource obtainSecurityMetadataSource() { return this.securityMetadataSource; } public void setSecurityMetadataSource(FilterInvocationSecurityMetadataSource newSource) { this.securityMetadataSource = newSource; } @Override public Class getSecureObjectClass() { return FilterInvocation.class; } public void invoke(FilterInvocation filterInvocation) throws IOException, ServletException { if (isApplied(filterInvocation) && this.observeOncePerRequest) { // filter already applied to this request and user wants us to observe // once-per-request handling, so don't re-do security checking filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse()); return; } // first time this request being called, so perform security checking if (filterInvocation.getRequest() != null && this.observeOncePerRequest) { filterInvocation.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE); } InterceptorStatusToken token = super.beforeInvocation(filterInvocation); try { filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse()); } finally { super.finallyInvocation(token); } super.afterInvocation(token, null); } private boolean isApplied(FilterInvocation filterInvocation) { return (filterInvocation.getRequest() != null) && (filterInvocation.getRequest().getAttribute(FILTER_APPLIED) != null); } /** * Indicates whether once-per-request handling will be observed. By default this is * true, meaning the FilterSecurityInterceptor will only * execute once-per-request. Sometimes users may wish it to execute more than once per * request, such as when JSP forwards are being used and filter security is desired on * each included fragment of the HTTP request. * @return true (the default) if once-per-request is honoured, otherwise * false if FilterSecurityInterceptor will enforce * authorizations for each and every fragment of the HTTP request. */ public boolean isObserveOncePerRequest() { return this.observeOncePerRequest; } public void setObserveOncePerRequest(boolean observeOncePerRequest) { this.observeOncePerRequest = observeOncePerRequest; } } ================================================ FILE: access/src/test/java/org/springframework/security/access/AuthenticationCredentialsNotFoundEventTests.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.access; import org.junit.jupiter.api.Test; import org.springframework.security.access.event.AuthenticationCredentialsNotFoundEvent; import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException; import org.springframework.security.util.SimpleMethodInvocation; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; /** * Tests {@link AuthenticationCredentialsNotFoundEvent}. * * @author Ben Alex */ @SuppressWarnings("deprecation") public class AuthenticationCredentialsNotFoundEventTests { @Test public void testRejectsNulls() { assertThatIllegalArgumentException().isThrownBy(() -> new AuthenticationCredentialsNotFoundEvent(null, SecurityConfig.createList("TEST"), new AuthenticationCredentialsNotFoundException("test"))); } @Test public void testRejectsNulls2() { assertThatIllegalArgumentException() .isThrownBy(() -> new AuthenticationCredentialsNotFoundEvent(new SimpleMethodInvocation(), null, new AuthenticationCredentialsNotFoundException("test"))); } @Test public void testRejectsNulls3() { assertThatIllegalArgumentException() .isThrownBy(() -> new AuthenticationCredentialsNotFoundEvent(new SimpleMethodInvocation(), SecurityConfig.createList("TEST"), null)); } } ================================================ FILE: access/src/test/java/org/springframework/security/access/AuthorizationFailureEventTests.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.access; import java.util.List; import org.junit.jupiter.api.Test; import org.springframework.security.access.event.AuthorizationFailureEvent; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.util.SimpleMethodInvocation; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; /** * Tests {@link AuthorizationFailureEvent}. * * @author Ben Alex */ @SuppressWarnings("deprecation") public class AuthorizationFailureEventTests { private final UsernamePasswordAuthenticationToken foo = UsernamePasswordAuthenticationToken.unauthenticated("foo", "bar"); private List attributes = SecurityConfig.createList("TEST"); private AccessDeniedException exception = new AuthorizationServiceException("error", new Throwable()); @Test public void rejectsNullSecureObject() { assertThatIllegalArgumentException() .isThrownBy(() -> new AuthorizationFailureEvent(null, this.attributes, this.foo, this.exception)); } @Test public void rejectsNullAttributesList() { assertThatIllegalArgumentException().isThrownBy( () -> new AuthorizationFailureEvent(new SimpleMethodInvocation(), null, this.foo, this.exception)); } @Test public void rejectsNullAuthentication() { assertThatIllegalArgumentException() .isThrownBy(() -> new AuthorizationFailureEvent(new SimpleMethodInvocation(), this.attributes, null, this.exception)); } @Test public void rejectsNullException() { assertThatIllegalArgumentException().isThrownBy( () -> new AuthorizationFailureEvent(new SimpleMethodInvocation(), this.attributes, this.foo, null)); } @Test public void gettersReturnCtorSuppliedData() { AuthorizationFailureEvent event = new AuthorizationFailureEvent(new Object(), this.attributes, this.foo, this.exception); assertThat(event.getConfigAttributes()).isSameAs(this.attributes); assertThat(event.getAccessDeniedException()).isSameAs(this.exception); assertThat(event.getAuthentication()).isSameAs(this.foo); } } ================================================ FILE: access/src/test/java/org/springframework/security/access/AuthorizedEventTests.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.access; import org.junit.jupiter.api.Test; import org.springframework.security.access.event.AuthorizedEvent; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.util.SimpleMethodInvocation; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; /** * Tests {@link AuthorizedEvent}. * * @author Ben Alex */ @SuppressWarnings("deprecation") public class AuthorizedEventTests { @Test public void testRejectsNulls() { assertThatIllegalArgumentException().isThrownBy(() -> new AuthorizedEvent(null, SecurityConfig.createList("TEST"), UsernamePasswordAuthenticationToken.unauthenticated("foo", "bar"))); } @Test public void testRejectsNulls2() { assertThatIllegalArgumentException().isThrownBy(() -> new AuthorizedEvent(new SimpleMethodInvocation(), null, UsernamePasswordAuthenticationToken.unauthenticated("foo", "bar"))); } @Test public void testRejectsNulls3() { assertThatIllegalArgumentException().isThrownBy( () -> new AuthorizedEvent(new SimpleMethodInvocation(), SecurityConfig.createList("TEST"), null)); } } ================================================ FILE: access/src/test/java/org/springframework/security/access/ITargetObject.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.access; /** * Represents the interface of a secured object. * * @author Ben Alex */ public interface ITargetObject { Integer computeHashCode(String input); int countLength(String input); String makeLowerCase(String input); String makeUpperCase(String input); String publicMakeLowerCase(String input); } ================================================ FILE: access/src/test/java/org/springframework/security/access/OtherTargetObject.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.access; /** * Simply extends {@link TargetObject} so we have a different object to put configuration * attributes against. *

* There is no different behaviour. We have to define each method so that * Class.getMethod(methodName, args) returns a Method * referencing this class rather than the parent class. *

*

* We need to implement ITargetObject again because the * MethodDefinitionAttributes only locates attributes on interfaces * explicitly defined by the intercepted class (not the interfaces defined by its parent * class or classes). *

* * @author Ben Alex */ public class OtherTargetObject extends TargetObject implements ITargetObject { @Override public String makeLowerCase(String input) { return super.makeLowerCase(input); } @Override public String makeUpperCase(String input) { return super.makeUpperCase(input); } @Override public String publicMakeLowerCase(String input) { return super.publicMakeLowerCase(input); } } ================================================ FILE: access/src/test/java/org/springframework/security/access/SecurityConfigTests.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.access; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; /** * Tests {@link SecurityConfig}. * * @author Ben Alex */ @SuppressWarnings("deprecation") public class SecurityConfigTests { @Test public void testHashCode() { SecurityConfig config = new SecurityConfig("TEST"); assertThat(config.hashCode()).isEqualTo("TEST".hashCode()); } @Test public void testCannotConstructWithNullAttribute() { assertThatIllegalArgumentException().isThrownBy(() -> new SecurityConfig(null)); // SEC-727 } @Test public void testCannotConstructWithEmptyAttribute() { assertThatIllegalArgumentException().isThrownBy(() -> new SecurityConfig("")); // SEC-727 } @Test public void testNoArgConstructorDoesntExist() throws Exception { assertThatExceptionOfType(NoSuchMethodException.class) .isThrownBy(() -> SecurityConfig.class.getDeclaredConstructor((Class[]) null)); } @Test public void testObjectEquals() { SecurityConfig security1 = new SecurityConfig("TEST"); SecurityConfig security2 = new SecurityConfig("TEST"); assertThat(security2).isEqualTo(security1); // SEC-311: Must observe symmetry requirement of Object.equals(Object) contract String securityString1 = "TEST"; assertThat(securityString1).isNotSameAs(security1); String securityString2 = "NOT_EQUAL"; assertThat(!security1.equals(securityString2)).isTrue(); SecurityConfig security3 = new SecurityConfig("NOT_EQUAL"); assertThat(!security1.equals(security3)).isTrue(); MockConfigAttribute mock1 = new MockConfigAttribute("TEST"); assertThat(security1).isEqualTo(mock1); MockConfigAttribute mock2 = new MockConfigAttribute("NOT_EQUAL"); assertThat(security1).isNotEqualTo(mock2); Integer int1 = 987; assertThat(security1).isNotEqualTo(int1); } @Test public void testToString() { SecurityConfig config = new SecurityConfig("TEST"); assertThat(config.toString()).isEqualTo("TEST"); } private class MockConfigAttribute implements ConfigAttribute { private String attribute; MockConfigAttribute(String configuration) { this.attribute = configuration; } @Override public String getAttribute() { return this.attribute; } } } ================================================ FILE: access/src/test/java/org/springframework/security/access/TargetObject.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.access; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; /** * Represents a secured object. * * @author Ben Alex */ public class TargetObject implements ITargetObject { @Override public Integer computeHashCode(String input) { return input.hashCode(); } @Override public int countLength(String input) { return input.length(); } /** * Returns the lowercase string, followed by security environment information. * @param input the message to make lowercase * @return the lowercase message, a space, the Authentication class that * was on the SecurityContext at the time of method invocation, and a * boolean indicating if the Authentication object is authenticated or * not */ @Override public String makeLowerCase(String input) { Authentication auth = SecurityContextHolder.getContext().getAuthentication(); if (auth == null) { return input.toLowerCase() + " Authentication empty"; } else { return input.toLowerCase() + " " + auth.getClass().getName() + " " + auth.isAuthenticated(); } } /** * Returns the uppercase string, followed by security environment information. * @param input the message to make uppercase * @return the uppercase message, a space, the Authentication class that * was on the SecurityContext at the time of method invocation, and a * boolean indicating if the Authentication object is authenticated or * not */ @Override public String makeUpperCase(String input) { Authentication auth = SecurityContextHolder.getContext().getAuthentication(); return input.toUpperCase() + " " + auth.getClass().getName() + " " + auth.isAuthenticated(); } /** * Delegates through to the {@link #makeLowerCase(String)} method. * @param input the message to be made lower-case */ @Override public String publicMakeLowerCase(String input) { return this.makeLowerCase(input); } } ================================================ FILE: access/src/test/java/org/springframework/security/access/annotation/BusinessService.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.access.annotation; import java.io.Serializable; import java.util.List; import jakarta.annotation.security.PermitAll; import jakarta.annotation.security.RolesAllowed; import org.springframework.security.access.prepost.PreAuthorize; /** */ @Secured({ "ROLE_USER" }) @PermitAll public interface BusinessService extends Serializable { @Secured({ "ROLE_ADMIN" }) @RolesAllowed({ "ROLE_ADMIN" }) @PreAuthorize("hasRole('ROLE_ADMIN')") void someAdminMethod(); @Secured({ "ROLE_USER", "ROLE_ADMIN" }) @RolesAllowed({ "ROLE_USER", "ROLE_ADMIN" }) void someUserAndAdminMethod(); @Secured({ "ROLE_USER" }) @RolesAllowed({ "ROLE_USER" }) void someUserMethod1(); @Secured({ "ROLE_USER" }) @RolesAllowed({ "ROLE_USER" }) void someUserMethod2(); @RolesAllowed({ "USER" }) void rolesAllowedUser(); int someOther(String s); int someOther(int input); List methodReturningAList(List someList); Object[] methodReturningAnArray(Object[] someArray); List methodReturningAList(String userName, String extraParam); @RequireAdminRole @RequireUserRole default void repeatedAnnotations() { } } ================================================ FILE: access/src/test/java/org/springframework/security/access/annotation/BusinessServiceImpl.java ================================================ /* * 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 org.springframework.security.access.annotation; import java.util.ArrayList; import java.util.List; /** * @author Joe Scalise */ @SuppressWarnings("serial") public class BusinessServiceImpl implements BusinessService { @Override @Secured({ "ROLE_USER" }) public void someUserMethod1() { } @Override @Secured({ "ROLE_USER" }) public void someUserMethod2() { } @Override @Secured({ "ROLE_USER", "ROLE_ADMIN" }) public void someUserAndAdminMethod() { } @Override @Secured({ "ROLE_ADMIN" }) public void someAdminMethod() { } public E someUserMethod3(final E entity) { return entity; } @Override public int someOther(String s) { return 0; } @Override public int someOther(int input) { return input; } @Override public List methodReturningAList(List someList) { return someList; } @Override public List methodReturningAList(String userName, String arg2) { return new ArrayList<>(); } @Override public Object[] methodReturningAnArray(Object[] someArray) { return null; } @Override public void rolesAllowedUser() { } } ================================================ FILE: access/src/test/java/org/springframework/security/access/annotation/Entity.java ================================================ /* * 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 org.springframework.security.access.annotation; /** * Class to act as a superclass for annotations testing. * * @author Ben Alex * */ public class Entity { public Entity(String someParameter) { } } ================================================ FILE: access/src/test/java/org/springframework/security/access/annotation/ExpressionProtectedBusinessServiceImpl.java ================================================ /* * 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 org.springframework.security.access.annotation; import java.util.ArrayList; import java.util.List; import org.springframework.security.access.prepost.PostFilter; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreFilter; @SuppressWarnings("serial") public class ExpressionProtectedBusinessServiceImpl implements BusinessService { @Override public void someAdminMethod() { } @Override public int someOther(String s) { return 0; } @Override public int someOther(int input) { return 0; } @Override public void someUserAndAdminMethod() { } @Override public void someUserMethod1() { } @Override public void someUserMethod2() { } @Override @PreFilter(filterTarget = "someList", value = "filterObject == authentication.name or filterObject == 'sam'") @PostFilter("filterObject == 'bob'") public List methodReturningAList(List someList) { return someList; } @Override public List methodReturningAList(String userName, String arg2) { return new ArrayList<>(); } @Override @PostFilter("filterObject == 'bob'") public Object[] methodReturningAnArray(Object[] someArray) { return someArray; } @PreAuthorize("#x == 'x' and @number.intValue() == 1294 ") public void methodWithBeanNamePropertyAccessExpression(String x) { } @Override public void rolesAllowedUser() { } } ================================================ FILE: access/src/test/java/org/springframework/security/access/annotation/Jsr250BusinessServiceImpl.java ================================================ /* * 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 org.springframework.security.access.annotation; import java.util.ArrayList; import java.util.List; import jakarta.annotation.security.PermitAll; import jakarta.annotation.security.RolesAllowed; /** * @author Luke Taylor */ @PermitAll @SuppressWarnings("serial") public class Jsr250BusinessServiceImpl implements BusinessService { @Override @RolesAllowed("ROLE_USER") public void someUserMethod1() { } @Override @RolesAllowed("ROLE_USER") public void someUserMethod2() { } @Override @RolesAllowed({ "ROLE_USER", "ROLE_ADMIN" }) public void someUserAndAdminMethod() { } @Override @RolesAllowed("ROLE_ADMIN") public void someAdminMethod() { } @Override public int someOther(String input) { return 0; } @Override public int someOther(int input) { return input; } @Override public List methodReturningAList(List someList) { return someList; } @Override public List methodReturningAList(String userName, String arg2) { return new ArrayList<>(); } @Override public Object[] methodReturningAnArray(Object[] someArray) { return null; } @Override @RolesAllowed({ "USER" }) public void rolesAllowedUser() { } } ================================================ FILE: access/src/test/java/org/springframework/security/access/annotation/Jsr250MethodSecurityMetadataSourceTests.java ================================================ /* * 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 org.springframework.security.access.annotation; import java.util.Collection; import jakarta.annotation.security.PermitAll; import jakarta.annotation.security.RolesAllowed; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.access.intercept.method.MockMethodInvocation; import static org.assertj.core.api.Assertions.assertThat; /** * @author Luke Taylor * @author Ben Alex */ @SuppressWarnings("deprecation") public class Jsr250MethodSecurityMetadataSourceTests { Jsr250MethodSecurityMetadataSource mds; A a; UserAllowedClass userAllowed; @BeforeEach public void setup() { this.mds = new Jsr250MethodSecurityMetadataSource(); this.a = new A(); this.userAllowed = new UserAllowedClass(); } private ConfigAttribute[] findAttributes(String methodName) throws Exception { return this.mds.findAttributes(this.a.getClass().getMethod(methodName), null).toArray(new ConfigAttribute[0]); } @Test public void methodWithRolesAllowedHasCorrectAttribute() throws Exception { ConfigAttribute[] accessAttributes = findAttributes("adminMethod"); assertThat(accessAttributes).hasSize(1); assertThat(accessAttributes[0].toString()).isEqualTo("ROLE_ADMIN"); } @Test public void permitAllMethodHasPermitAllAttribute() throws Exception { ConfigAttribute[] accessAttributes = findAttributes("permitAllMethod"); assertThat(accessAttributes).hasSize(1); assertThat(accessAttributes[0].toString()).isEqualTo("jakarta.annotation.security.PermitAll"); } @Test public void noRoleMethodHasNoAttributes() throws Exception { Collection accessAttributes = this.mds .findAttributes(this.a.getClass().getMethod("noRoleMethod"), null); assertThat(accessAttributes).isNull(); } @Test public void classRoleIsAppliedToNoRoleMethod() throws Exception { Collection accessAttributes = this.mds .findAttributes(this.userAllowed.getClass().getMethod("noRoleMethod"), null); assertThat(accessAttributes).isNull(); } @Test public void methodRoleOverridesClassRole() throws Exception { Collection accessAttributes = this.mds .findAttributes(this.userAllowed.getClass().getMethod("adminMethod"), null); assertThat(accessAttributes).hasSize(1); assertThat(accessAttributes.toArray()[0].toString()).isEqualTo("ROLE_ADMIN"); } @Test public void customDefaultRolePrefix() throws Exception { this.mds.setDefaultRolePrefix("CUSTOMPREFIX_"); ConfigAttribute[] accessAttributes = findAttributes("adminMethod"); assertThat(accessAttributes).hasSize(1); assertThat(accessAttributes[0].toString()).isEqualTo("CUSTOMPREFIX_ADMIN"); } @Test public void emptyDefaultRolePrefix() throws Exception { this.mds.setDefaultRolePrefix(""); ConfigAttribute[] accessAttributes = findAttributes("adminMethod"); assertThat(accessAttributes).hasSize(1); assertThat(accessAttributes[0].toString()).isEqualTo("ADMIN"); } @Test public void nullDefaultRolePrefix() throws Exception { this.mds.setDefaultRolePrefix(null); ConfigAttribute[] accessAttributes = findAttributes("adminMethod"); assertThat(accessAttributes).hasSize(1); assertThat(accessAttributes[0].toString()).isEqualTo("ADMIN"); } @Test public void alreadyHasDefaultPrefix() throws Exception { ConfigAttribute[] accessAttributes = findAttributes("roleAdminMethod"); assertThat(accessAttributes).hasSize(1); assertThat(accessAttributes[0].toString()).isEqualTo("ROLE_ADMIN"); } // JSR-250 Spec Tests /** * Class-level annotations only affect the class they annotate and their members, that * is, its methods and fields. They never affect a member declared by a superclass, * even if it is not hidden or overridden by the class in question. * @throws Exception */ @Test public void classLevelAnnotationsOnlyAffectTheClassTheyAnnotateAndTheirMembers() throws Exception { Child target = new Child(); MockMethodInvocation mi = new MockMethodInvocation(target, target.getClass(), "notOverriden"); Collection accessAttributes = this.mds.getAttributes(mi); assertThat(accessAttributes).isNull(); } @Test public void classLevelAnnotationsOnlyAffectTheClassTheyAnnotateAndTheirMembersOverriden() throws Exception { Child target = new Child(); MockMethodInvocation mi = new MockMethodInvocation(target, target.getClass(), "overriden"); Collection accessAttributes = this.mds.getAttributes(mi); assertThat(accessAttributes).hasSize(1); assertThat(accessAttributes.toArray()[0].toString()).isEqualTo("ROLE_DERIVED"); } @Test public void classLevelAnnotationsImpactMemberLevel() throws Exception { Child target = new Child(); MockMethodInvocation mi = new MockMethodInvocation(target, target.getClass(), "defaults"); Collection accessAttributes = this.mds.getAttributes(mi); assertThat(accessAttributes).hasSize(1); assertThat(accessAttributes.toArray()[0].toString()).isEqualTo("ROLE_DERIVED"); } @Test public void classLevelAnnotationsIgnoredByExplicitMemberAnnotation() throws Exception { Child target = new Child(); MockMethodInvocation mi = new MockMethodInvocation(target, target.getClass(), "explicitMethod"); Collection accessAttributes = this.mds.getAttributes(mi); assertThat(accessAttributes).hasSize(1); assertThat(accessAttributes.toArray()[0].toString()).isEqualTo("ROLE_EXPLICIT"); } /** * The interfaces implemented by a class never contribute annotations to the class * itself or any of its members. * @throws Exception */ @Test public void interfacesNeverContributeAnnotationsMethodLevel() throws Exception { Parent target = new Parent(); MockMethodInvocation mi = new MockMethodInvocation(target, target.getClass(), "interfaceMethod"); Collection accessAttributes = this.mds.getAttributes(mi); assertThat(accessAttributes).isEmpty(); } @Test public void interfacesNeverContributeAnnotationsClassLevel() throws Exception { Parent target = new Parent(); MockMethodInvocation mi = new MockMethodInvocation(target, target.getClass(), "notOverriden"); Collection accessAttributes = this.mds.getAttributes(mi); assertThat(accessAttributes).isEmpty(); } @Test public void annotationsOnOverriddenMemberIgnored() throws Exception { Child target = new Child(); MockMethodInvocation mi = new MockMethodInvocation(target, target.getClass(), "overridenIgnored"); Collection accessAttributes = this.mds.getAttributes(mi); assertThat(accessAttributes).hasSize(1); assertThat(accessAttributes.toArray()[0].toString()).isEqualTo("ROLE_DERIVED"); } public static class A { public void noRoleMethod() { } @RolesAllowed("ADMIN") public void adminMethod() { } @RolesAllowed("ROLE_ADMIN") public void roleAdminMethod() { } @PermitAll public void permitAllMethod() { } } @RolesAllowed("USER") public static class UserAllowedClass { public void noRoleMethod() { } @RolesAllowed("ADMIN") public void adminMethod() { } } // JSR-250 Spec @RolesAllowed("IPARENT") interface IParent { @RolesAllowed("INTERFACEMETHOD") void interfaceMethod(); } static class Parent implements IParent { @Override public void interfaceMethod() { } public void notOverriden() { } public void overriden() { } @RolesAllowed("OVERRIDENIGNORED") public void overridenIgnored() { } } @RolesAllowed("DERIVED") class Child extends Parent { @Override public void overriden() { } @Override public void overridenIgnored() { } public void defaults() { } @RolesAllowed("EXPLICIT") public void explicitMethod() { } } } ================================================ FILE: access/src/test/java/org/springframework/security/access/annotation/Jsr250VoterTests.java ================================================ /* * 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 org.springframework.security.access.annotation; import java.util.ArrayList; import java.util.List; import org.junit.jupiter.api.Test; import org.springframework.security.access.AccessDecisionVoter; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.access.SecurityConfig; import org.springframework.security.authentication.TestingAuthenticationToken; import static org.assertj.core.api.Assertions.assertThat; /** * @author Luke Taylor */ @SuppressWarnings("deprecation") public class Jsr250VoterTests { // SEC-1443 @Test public void supportsMultipleRolesCorrectly() { List attrs = new ArrayList<>(); Jsr250Voter voter = new Jsr250Voter(); attrs.add(new Jsr250SecurityConfig("A")); attrs.add(new Jsr250SecurityConfig("B")); attrs.add(new Jsr250SecurityConfig("C")); assertThat(voter.vote(new TestingAuthenticationToken("user", "pwd", "A"), new Object(), attrs)) .isEqualTo(AccessDecisionVoter.ACCESS_GRANTED); assertThat(voter.vote(new TestingAuthenticationToken("user", "pwd", "B"), new Object(), attrs)) .isEqualTo(AccessDecisionVoter.ACCESS_GRANTED); assertThat(voter.vote(new TestingAuthenticationToken("user", "pwd", "C"), new Object(), attrs)) .isEqualTo(AccessDecisionVoter.ACCESS_GRANTED); assertThat(voter.vote(new TestingAuthenticationToken("user", "pwd", "NONE"), new Object(), attrs)) .isEqualTo(AccessDecisionVoter.ACCESS_DENIED); assertThat(voter.vote(new TestingAuthenticationToken("user", "pwd", "A"), new Object(), SecurityConfig.createList("A", "B", "C"))) .isEqualTo(AccessDecisionVoter.ACCESS_ABSTAIN); } } ================================================ FILE: access/src/test/java/org/springframework/security/access/annotation/RequireAdminRole.java ================================================ /* * 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 org.springframework.security.access.annotation; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import jakarta.annotation.security.RolesAllowed; import org.springframework.security.access.prepost.PreAuthorize; @Retention(RetentionPolicy.RUNTIME) @PreAuthorize("hasRole('ADMIN')") @RolesAllowed("ADMIN") @Secured("ADMIN") public @interface RequireAdminRole { } ================================================ FILE: access/src/test/java/org/springframework/security/access/annotation/RequireUserRole.java ================================================ /* * 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 org.springframework.security.access.annotation; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import jakarta.annotation.security.RolesAllowed; import org.springframework.security.access.prepost.PreAuthorize; @Retention(RetentionPolicy.RUNTIME) @PreAuthorize("hasRole('USER')") @RolesAllowed("USER") @Secured("USER") public @interface RequireUserRole { } ================================================ FILE: access/src/test/java/org/springframework/security/access/annotation/SecuredAnnotationSecurityMetadataSourceTests.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.access.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.reflect.Method; import java.util.Arrays; import java.util.Collection; import java.util.EnumSet; import java.util.List; import org.junit.jupiter.api.Test; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.access.SecurityConfig; import org.springframework.security.access.annotation.sec2150.MethodInvocationFactory; import org.springframework.security.access.intercept.method.MockMethodInvocation; import org.springframework.security.core.GrantedAuthority; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; /** * Tests for {@link SecuredAnnotationSecurityMetadataSource} * * @author Mark St.Godard * @author Joe Scalise * @author Ben Alex * @author Luke Taylor */ @SuppressWarnings("deprecation") public class SecuredAnnotationSecurityMetadataSourceTests { private SecuredAnnotationSecurityMetadataSource mds = new SecuredAnnotationSecurityMetadataSource(); @Test public void genericsSuperclassDeclarationsAreIncludedWhenSubclassesOverride() { Method method = null; try { method = DepartmentServiceImpl.class.getMethod("someUserMethod3", new Class[] { Department.class }); } catch (NoSuchMethodException unexpected) { fail("Should be a superMethod called 'someUserMethod3' on class!"); } Collection attrs = this.mds.findAttributes(method, DepartmentServiceImpl.class); assertThat(attrs).isNotNull(); // expect 1 attribute assertThat(attrs.size() == 1).as("Did not find 1 attribute").isTrue(); // should have 1 SecurityConfig for (ConfigAttribute sc : attrs) { assertThat(sc.getAttribute()).as("Found an incorrect role").isEqualTo("ROLE_ADMIN"); } Method superMethod = null; try { superMethod = DepartmentServiceImpl.class.getMethod("someUserMethod3", new Class[] { Entity.class }); } catch (NoSuchMethodException unexpected) { fail("Should be a superMethod called 'someUserMethod3' on class!"); } Collection superAttrs = this.mds.findAttributes(superMethod, DepartmentServiceImpl.class); assertThat(superAttrs).isNotNull(); // This part of the test relates to SEC-274 // expect 1 attribute assertThat(superAttrs).as("Did not find 1 attribute").hasSize(1); // should have 1 SecurityConfig for (ConfigAttribute sc : superAttrs) { assertThat(sc.getAttribute()).as("Found an incorrect role").isEqualTo("ROLE_ADMIN"); } } @Test public void classLevelAttributesAreFound() { Collection attrs = this.mds.findAttributes(BusinessService.class); assertThat(attrs).isNotNull(); // expect 1 annotation assertThat(attrs).hasSize(1); // should have 1 SecurityConfig SecurityConfig sc = (SecurityConfig) attrs.toArray()[0]; assertThat(sc.getAttribute()).isEqualTo("ROLE_USER"); } @Test public void methodLevelAttributesAreFound() { Method method = null; try { method = BusinessService.class.getMethod("someUserAndAdminMethod", new Class[] {}); } catch (NoSuchMethodException unexpected) { fail("Should be a method called 'someUserAndAdminMethod' on class!"); } Collection attrs = this.mds.findAttributes(method, BusinessService.class); // expect 2 attributes assertThat(attrs).hasSize(2); boolean user = false; boolean admin = false; // should have 2 SecurityConfigs for (ConfigAttribute sc : attrs) { assertThat(sc).isInstanceOf(SecurityConfig.class); if (sc.getAttribute().equals("ROLE_USER")) { user = true; } else if (sc.getAttribute().equals("ROLE_ADMIN")) { admin = true; } } // expect to have ROLE_USER and ROLE_ADMIN assertThat(user).isEqualTo(admin).isTrue(); } // SEC-1491 @Test public void customAnnotationAttributesAreFound() { SecuredAnnotationSecurityMetadataSource mds = new SecuredAnnotationSecurityMetadataSource( new CustomSecurityAnnotationMetadataExtractor()); Collection attrs = mds.findAttributes(CustomAnnotatedService.class); assertThat(attrs).containsOnly(SecurityEnum.ADMIN); } @Test public void annotatedAnnotationAtClassLevelIsDetected() throws Exception { MockMethodInvocation annotatedAtClassLevel = new MockMethodInvocation(new AnnotatedAnnotationAtClassLevel(), ReturnVoid.class, "doSomething", List.class); ConfigAttribute[] attrs = this.mds.getAttributes(annotatedAtClassLevel).toArray(new ConfigAttribute[0]); assertThat(attrs).hasSize(1); assertThat(attrs).extracting("attribute").containsOnly("CUSTOM"); } @Test public void annotatedAnnotationAtInterfaceLevelIsDetected() throws Exception { MockMethodInvocation annotatedAtInterfaceLevel = new MockMethodInvocation( new AnnotatedAnnotationAtInterfaceLevel(), ReturnVoid2.class, "doSomething", List.class); ConfigAttribute[] attrs = this.mds.getAttributes(annotatedAtInterfaceLevel).toArray(new ConfigAttribute[0]); assertThat(attrs).hasSize(1); assertThat(attrs).extracting("attribute").containsOnly("CUSTOM"); } @Test public void annotatedAnnotationAtMethodLevelIsDetected() throws Exception { MockMethodInvocation annotatedAtMethodLevel = new MockMethodInvocation(new AnnotatedAnnotationAtMethodLevel(), ReturnVoid.class, "doSomething", List.class); ConfigAttribute[] attrs = this.mds.getAttributes(annotatedAtMethodLevel).toArray(new ConfigAttribute[0]); assertThat(attrs).hasSize(1); assertThat(attrs).extracting("attribute").containsOnly("CUSTOM"); } @Test public void proxyFactoryInterfaceAttributesFound() throws Exception { MockMethodInvocation mi = MethodInvocationFactory.createSec2150MethodInvocation(); Collection attributes = this.mds.getAttributes(mi); assertThat(attributes).hasSize(1); assertThat(attributes).extracting("attribute").containsOnly("ROLE_PERSON"); } // Inner classes class Department extends Entity { Department(String name) { super(name); } } interface DepartmentService extends BusinessService { @Secured({ "ROLE_USER" }) Department someUserMethod3(Department dept); } @SuppressWarnings("serial") class DepartmentServiceImpl extends BusinessServiceImpl implements DepartmentService { @Override @Secured({ "ROLE_ADMIN" }) public Department someUserMethod3(final Department dept) { return super.someUserMethod3(dept); } } // SEC-1491 Related classes. PoC for custom annotation with enum value. @CustomSecurityAnnotation(SecurityEnum.ADMIN) interface CustomAnnotatedService { } class CustomAnnotatedServiceImpl implements CustomAnnotatedService { } enum SecurityEnum implements ConfigAttribute, GrantedAuthority { ADMIN, USER; @Override public String getAttribute() { return toString(); } @Override public String getAuthority() { return toString(); } } @Target({ ElementType.METHOD, ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) @interface CustomSecurityAnnotation { SecurityEnum[] value(); } class CustomSecurityAnnotationMetadataExtractor implements AnnotationMetadataExtractor { @Override public Collection extractAttributes(CustomSecurityAnnotation securityAnnotation) { SecurityEnum[] values = securityAnnotation.value(); return EnumSet.copyOf(Arrays.asList(values)); } } @Target({ ElementType.METHOD, ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) @Inherited @Secured("CUSTOM") public @interface AnnotatedAnnotation { } public interface ReturnVoid { void doSomething(List param); } @AnnotatedAnnotation public interface ReturnVoid2 { void doSomething(List param); } @AnnotatedAnnotation public static class AnnotatedAnnotationAtClassLevel implements ReturnVoid { @Override public void doSomething(List param) { } } public static class AnnotatedAnnotationAtInterfaceLevel implements ReturnVoid2 { @Override public void doSomething(List param) { } } public static class AnnotatedAnnotationAtMethodLevel implements ReturnVoid { @Override @AnnotatedAnnotation public void doSomething(List param) { } } } ================================================ FILE: access/src/test/java/org/springframework/security/access/annotation/sec2150/CrudRepository.java ================================================ /* * 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 org.springframework.security.access.annotation.sec2150; public interface CrudRepository { Iterable findAll(); } ================================================ FILE: access/src/test/java/org/springframework/security/access/annotation/sec2150/MethodInvocationFactory.java ================================================ /* * 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 org.springframework.security.access.annotation.sec2150; import org.springframework.aop.framework.ProxyFactory; import org.springframework.security.access.intercept.method.MockMethodInvocation; public final class MethodInvocationFactory { private MethodInvocationFactory() { } /** * In order to reproduce the bug for SEC-2150, we must have a proxy object that * implements TargetSourceAware and implements our annotated interface. * @return the mock method invocation * @throws NoSuchMethodException */ public static MockMethodInvocation createSec2150MethodInvocation() throws NoSuchMethodException { ProxyFactory factory = new ProxyFactory(new Class[] { PersonRepository.class }); factory.setTargetClass(CrudRepository.class); PersonRepository repository = (PersonRepository) factory.getProxy(); return new MockMethodInvocation(repository, PersonRepository.class, "findAll"); } } ================================================ FILE: access/src/test/java/org/springframework/security/access/annotation/sec2150/PersonRepository.java ================================================ /* * 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 org.springframework.security.access.annotation.sec2150; import org.springframework.security.access.annotation.Secured; import org.springframework.security.access.prepost.PreAuthorize; /** * Note that JSR-256 states that annotations have no impact when placed on interfaces, so * SEC-2150 is not impacted by JSR-256 support. * * @author Rob Winch * */ @Secured("ROLE_PERSON") @PreAuthorize("hasRole('ROLE_PERSON')") public interface PersonRepository extends CrudRepository { } ================================================ FILE: access/src/test/java/org/springframework/security/access/expression/method/DefaultMethodSecurityExpressionHandlerTests.java ================================================ /* * 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 org.springframework.security.access.expression.method; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.function.Supplier; import java.util.stream.Collectors; import java.util.stream.Stream; import org.aopalliance.intercept.MethodInvocation; import org.assertj.core.api.InstanceOfAssertFactories; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.expression.EvaluationContext; import org.springframework.expression.Expression; import org.springframework.expression.TypedValue; import org.springframework.security.access.expression.SecurityExpressionRoot; import org.springframework.security.authentication.AuthenticationTrustResolver; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; @ExtendWith(MockitoExtension.class) public class DefaultMethodSecurityExpressionHandlerTests { private DefaultMethodSecurityExpressionHandler handler; @Mock private Authentication authentication; @Mock private MethodInvocation methodInvocation; @Mock private AuthenticationTrustResolver trustResolver; @BeforeEach public void setup() { this.handler = new DefaultMethodSecurityExpressionHandler(); } private void setupMocks() { given(this.methodInvocation.getThis()).willReturn(new Foo()); given(this.methodInvocation.getMethod()).willReturn(Foo.class.getMethods()[0]); } @AfterEach public void cleanup() { SecurityContextHolder.clearContext(); } @Test @SuppressWarnings("deprecation") public void setTrustResolverNull() { assertThatIllegalArgumentException().isThrownBy(() -> this.handler.setTrustResolver(null)); } @Test @SuppressWarnings("deprecation") public void createEvaluationContextCustomTrustResolver() { setupMocks(); this.handler.setTrustResolver(this.trustResolver); Expression expression = this.handler.getExpressionParser().parseExpression("anonymous"); EvaluationContext context = this.handler.createEvaluationContext(this.authentication, this.methodInvocation); expression.getValue(context, Boolean.class); verify(this.trustResolver).isAnonymous(this.authentication); } @Test @SuppressWarnings("unchecked") public void filterByKeyWhenUsingMapThenFiltersMap() { setupMocks(); final Map map = new HashMap<>(); map.put("key1", "value1"); map.put("key2", "value2"); map.put("key3", "value3"); Expression expression = this.handler.getExpressionParser().parseExpression("filterObject.key eq 'key2'"); EvaluationContext context = this.handler.createEvaluationContext(this.authentication, this.methodInvocation); Object filtered = this.handler.filter(map, expression, context); assertThat(filtered == map); Map result = ((Map) filtered); assertThat(result.size() == 1); assertThat(result).containsKey("key2"); assertThat(result).containsValue("value2"); } @Test @SuppressWarnings("unchecked") public void filterByValueWhenUsingMapThenFiltersMap() { setupMocks(); final Map map = new HashMap<>(); map.put("key1", "value1"); map.put("key2", "value2"); map.put("key3", "value3"); Expression expression = this.handler.getExpressionParser().parseExpression("filterObject.value eq 'value3'"); EvaluationContext context = this.handler.createEvaluationContext(this.authentication, this.methodInvocation); Object filtered = this.handler.filter(map, expression, context); assertThat(filtered == map); Map result = ((Map) filtered); assertThat(result.size() == 1); assertThat(result).containsKey("key3"); assertThat(result).containsValue("value3"); } @Test @SuppressWarnings("unchecked") public void filterByKeyAndValueWhenUsingMapThenFiltersMap() { setupMocks(); final Map map = new HashMap<>(); map.put("key1", "value1"); map.put("key2", "value2"); map.put("key3", "value3"); Expression expression = this.handler.getExpressionParser() .parseExpression("(filterObject.key eq 'key1') or (filterObject.value eq 'value2')"); EvaluationContext context = this.handler.createEvaluationContext(this.authentication, this.methodInvocation); Object filtered = this.handler.filter(map, expression, context); assertThat(filtered == map); Map result = ((Map) filtered); assertThat(result.size() == 2); assertThat(result).containsKeys("key1", "key2"); assertThat(result).containsValues("value1", "value2"); } @Test @SuppressWarnings("unchecked") public void filterWhenUsingStreamThenFiltersStream() { setupMocks(); final Stream stream = Stream.of("1", "2", "3"); Expression expression = this.handler.getExpressionParser().parseExpression("filterObject ne '2'"); EvaluationContext context = this.handler.createEvaluationContext(this.authentication, this.methodInvocation); Object filtered = this.handler.filter(stream, expression, context); assertThat(filtered).isInstanceOf(Stream.class); List list = ((Stream) filtered).collect(Collectors.toList()); assertThat(list).containsExactly("1", "3"); } @Test public void filterStreamWhenClosedThenUpstreamGetsClosed() { setupMocks(); final Stream upstream = mock(Stream.class); doReturn(Stream.empty()).when(upstream).filter(any()); Expression expression = this.handler.getExpressionParser().parseExpression("true"); EvaluationContext context = this.handler.createEvaluationContext(this.authentication, this.methodInvocation); ((Stream) this.handler.filter(upstream, expression, context)).close(); verify(upstream).close(); } @Test public void createEvaluationContextSupplierAuthentication() { setupMocks(); Supplier mockAuthenticationSupplier = mock(); given(mockAuthenticationSupplier.get()).willReturn(this.authentication); EvaluationContext context = this.handler.createEvaluationContext(mockAuthenticationSupplier, this.methodInvocation); verifyNoInteractions(mockAuthenticationSupplier); assertThat(context.getRootObject()).extracting(TypedValue::getValue) .asInstanceOf(InstanceOfAssertFactories.type(MethodSecurityExpressionRoot.class)) .extracting(SecurityExpressionRoot::getAuthentication) .isEqualTo(this.authentication); verify(mockAuthenticationSupplier).get(); } static class Foo { void bar() { } } } ================================================ FILE: access/src/test/java/org/springframework/security/access/expression/method/ExpressionBasedPreInvocationAdviceTests.java ================================================ /* * 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 org.springframework.security.access.expression.method; import java.util.ArrayList; import java.util.List; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.security.access.intercept.method.MockMethodInvocation; import org.springframework.security.access.prepost.PreInvocationAttribute; import org.springframework.security.core.Authentication; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; /** * Tests {@link ExpressionBasedPreInvocationAdvice} * * @author Maksim Vinogradov * @since 5.2 */ @ExtendWith(MockitoExtension.class) @SuppressWarnings("deprecation") public class ExpressionBasedPreInvocationAdviceTests { @Mock private Authentication authentication; private ExpressionBasedPreInvocationAdvice expressionBasedPreInvocationAdvice; @BeforeEach public void setUp() { this.expressionBasedPreInvocationAdvice = new ExpressionBasedPreInvocationAdvice(); } @Test public void findFilterTargetNameProvidedButNotMatch() throws Exception { PreInvocationAttribute attribute = new PreInvocationExpressionAttribute("true", "filterTargetDoesNotMatch", null); MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class, "doSomethingCollection", new Class[] { List.class }, new Object[] { new ArrayList<>() }); assertThatIllegalArgumentException().isThrownBy( () -> this.expressionBasedPreInvocationAdvice.before(this.authentication, methodInvocation, attribute)); } @Test public void findFilterTargetNameProvidedArrayUnsupported() throws Exception { PreInvocationAttribute attribute = new PreInvocationExpressionAttribute("true", "param", null); MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class, "doSomethingArray", new Class[] { String[].class }, new Object[] { new String[0] }); assertThatIllegalArgumentException().isThrownBy( () -> this.expressionBasedPreInvocationAdvice.before(this.authentication, methodInvocation, attribute)); } @Test public void findFilterTargetNameProvided() throws Exception { PreInvocationAttribute attribute = new PreInvocationExpressionAttribute("true", "param", null); MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class, "doSomethingCollection", new Class[] { List.class }, new Object[] { new ArrayList<>() }); boolean result = this.expressionBasedPreInvocationAdvice.before(this.authentication, methodInvocation, attribute); assertThat(result).isTrue(); } @Test public void findFilterTargetNameNotProvidedArrayUnsupported() throws Exception { PreInvocationAttribute attribute = new PreInvocationExpressionAttribute("true", "", null); MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class, "doSomethingArray", new Class[] { String[].class }, new Object[] { new String[0] }); assertThatIllegalArgumentException().isThrownBy( () -> this.expressionBasedPreInvocationAdvice.before(this.authentication, methodInvocation, attribute)); } @Test public void findFilterTargetNameNotProvided() throws Exception { PreInvocationAttribute attribute = new PreInvocationExpressionAttribute("true", "", null); MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class, "doSomethingCollection", new Class[] { List.class }, new Object[] { new ArrayList<>() }); boolean result = this.expressionBasedPreInvocationAdvice.before(this.authentication, methodInvocation, attribute); assertThat(result).isTrue(); } @Test public void findFilterTargetNameNotProvidedTypeNotSupported() throws Exception { PreInvocationAttribute attribute = new PreInvocationExpressionAttribute("true", "", null); MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class, "doSomethingString", new Class[] { String.class }, new Object[] { "param" }); assertThatIllegalArgumentException().isThrownBy( () -> this.expressionBasedPreInvocationAdvice.before(this.authentication, methodInvocation, attribute)); } @Test public void findFilterTargetNameNotProvidedMethodAcceptMoreThenOneArgument() throws Exception { PreInvocationAttribute attribute = new PreInvocationExpressionAttribute("true", "", null); MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class, "doSomethingTwoArgs", new Class[] { String.class, List.class }, new Object[] { "param", new ArrayList<>() }); assertThatIllegalArgumentException().isThrownBy( () -> this.expressionBasedPreInvocationAdvice.before(this.authentication, methodInvocation, attribute)); } private class TestClass { public Boolean doSomethingCollection(List param) { return Boolean.TRUE; } public Boolean doSomethingArray(String[] param) { return Boolean.TRUE; } public Boolean doSomethingString(String param) { return Boolean.TRUE; } public Boolean doSomethingTwoArgs(String param, List list) { return Boolean.TRUE; } } } ================================================ FILE: access/src/test/java/org/springframework/security/access/expression/method/MethodExpressionVoterTests.java ================================================ /* * 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 org.springframework.security.access.expression.method; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; import org.aopalliance.intercept.MethodInvocation; import org.junit.jupiter.api.Test; import org.springframework.security.access.AccessDecisionVoter; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.access.prepost.PreInvocationAuthorizationAdviceVoter; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.util.SimpleMethodInvocation; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; @SuppressWarnings({ "unchecked", "deprecation" }) public class MethodExpressionVoterTests { private TestingAuthenticationToken joe = new TestingAuthenticationToken("joe", "joespass", "ROLE_blah"); private PreInvocationAuthorizationAdviceVoter am = new PreInvocationAuthorizationAdviceVoter( new ExpressionBasedPreInvocationAdvice()); @Test public void hasRoleExpressionAllowsUserWithRole() throws Exception { MethodInvocation mi = new SimpleMethodInvocation(new TargetImpl(), methodTakingAnArray()); assertThat(this.am.vote(this.joe, mi, createAttributes(new PreInvocationExpressionAttribute(null, null, "hasRole('blah')")))) .isEqualTo(AccessDecisionVoter.ACCESS_GRANTED); } @Test public void hasRoleExpressionDeniesUserWithoutRole() throws Exception { List cad = new ArrayList<>(1); cad.add(new PreInvocationExpressionAttribute(null, null, "hasRole('joedoesnt')")); MethodInvocation mi = new SimpleMethodInvocation(new TargetImpl(), methodTakingAnArray()); assertThat(this.am.vote(this.joe, mi, cad)).isEqualTo(AccessDecisionVoter.ACCESS_DENIED); } @Test public void matchingArgAgainstAuthenticationNameIsSuccessful() throws Exception { MethodInvocation mi = new SimpleMethodInvocation(new TargetImpl(), methodTakingAString(), "joe"); assertThat(this.am.vote(this.joe, mi, createAttributes(new PreInvocationExpressionAttribute(null, null, "(#argument == principal) and (principal == 'joe')")))) .isEqualTo(AccessDecisionVoter.ACCESS_GRANTED); } @Test public void accessIsGrantedIfNoPreAuthorizeAttributeIsUsed() throws Exception { Collection arg = createCollectionArg("joe", "bob", "sam"); MethodInvocation mi = new SimpleMethodInvocation(new TargetImpl(), methodTakingACollection(), arg); assertThat(this.am.vote(this.joe, mi, createAttributes(new PreInvocationExpressionAttribute("(filterObject == 'jim')", "collection", null)))) .isEqualTo(AccessDecisionVoter.ACCESS_GRANTED); // All objects should have been removed, because the expression is always false assertThat(arg).isEmpty(); } @Test public void collectionPreFilteringIsSuccessful() throws Exception { List arg = createCollectionArg("joe", "bob", "sam"); MethodInvocation mi = new SimpleMethodInvocation(new TargetImpl(), methodTakingACollection(), arg); this.am.vote(this.joe, mi, createAttributes(new PreInvocationExpressionAttribute( "(filterObject == 'joe' or filterObject == 'sam')", "collection", "permitAll"))); assertThat(arg).containsExactly("joe", "sam"); } @Test public void arraysCannotBePrefiltered() throws Exception { MethodInvocation mi = new SimpleMethodInvocation(new TargetImpl(), methodTakingAnArray(), createArrayArg("sam", "joe")); assertThatIllegalArgumentException().isThrownBy(() -> this.am.vote(this.joe, mi, createAttributes(new PreInvocationExpressionAttribute("(filterObject == 'jim')", "someArray", null)))); } @Test public void incorrectFilterTargetNameIsRejected() throws Exception { MethodInvocation mi = new SimpleMethodInvocation(new TargetImpl(), methodTakingACollection(), createCollectionArg("joe", "bob")); assertThatIllegalArgumentException().isThrownBy(() -> this.am.vote(this.joe, mi, createAttributes(new PreInvocationExpressionAttribute("(filterObject == 'joe')", "collcetion", null)))); } @Test public void nullNamedFilterTargetIsRejected() throws Exception { MethodInvocation mi = new SimpleMethodInvocation(new TargetImpl(), methodTakingACollection(), new Object[] { null }); assertThatIllegalArgumentException().isThrownBy(() -> this.am.vote(this.joe, mi, createAttributes(new PreInvocationExpressionAttribute("(filterObject == 'joe')", "collection", null)))); } @Test public void ruleDefinedInAClassMethodIsApplied() throws Exception { MethodInvocation mi = new SimpleMethodInvocation(new TargetImpl(), methodTakingAString(), "joe"); assertThat(this.am.vote(this.joe, mi, createAttributes(new PreInvocationExpressionAttribute(null, null, "T(org.springframework.security.access.expression.method.SecurityRules).isJoe(#argument)")))) .isEqualTo(AccessDecisionVoter.ACCESS_GRANTED); } private List createAttributes(ConfigAttribute... attributes) { return Arrays.asList(attributes); } private List createCollectionArg(Object... elts) { ArrayList result = new ArrayList(elts.length); result.addAll(Arrays.asList(elts)); return result; } private Object createArrayArg(Object... elts) { ArrayList result = new ArrayList(elts.length); result.addAll(Arrays.asList(elts)); return result.toArray(new Object[0]); } private Method methodTakingAnArray() throws Exception { return Target.class.getMethod("methodTakingAnArray", Object[].class); } private Method methodTakingAString() throws Exception { return Target.class.getMethod("methodTakingAString", String.class); } private Method methodTakingACollection() throws Exception { return Target.class.getMethod("methodTakingACollection", Collection.class); } private interface Target { void methodTakingAnArray(Object[] args); void methodTakingAString(String argument); Collection methodTakingACollection(Collection collection); } private static class TargetImpl implements Target { @Override public void methodTakingAnArray(Object[] args) { } @Override public void methodTakingAString(String argument) { }; @Override public Collection methodTakingACollection(Collection collection) { return collection; } } } ================================================ FILE: access/src/test/java/org/springframework/security/access/expression/method/MethodSecurityEvaluationContextTests.java ================================================ /* * 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 org.springframework.security.access.expression.method; import java.lang.reflect.Method; import org.aopalliance.intercept.MethodInvocation; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.core.ParameterNameDiscoverer; import org.springframework.security.core.Authentication; import org.springframework.util.ReflectionUtils; import static org.mockito.Mockito.doReturn; /** * @author shabarijonnalagadda * */ @ExtendWith(MockitoExtension.class) public class MethodSecurityEvaluationContextTests { @Mock private ParameterNameDiscoverer paramNameDiscoverer; @Mock private Authentication authentication; @Mock private MethodInvocation methodInvocation; @Test public void lookupVariableWhenParameterNameNullThenNotSet() { Class type = String.class; Method method = ReflectionUtils.findMethod(String.class, "contains", CharSequence.class); doReturn(new String[] { null }).when(this.paramNameDiscoverer).getParameterNames(method); doReturn(new Object[] { null }).when(this.methodInvocation).getArguments(); doReturn(type).when(this.methodInvocation).getThis(); doReturn(method).when(this.methodInvocation).getMethod(); NotNullVariableMethodSecurityEvaluationContext context = new NotNullVariableMethodSecurityEvaluationContext( this.authentication, this.methodInvocation, this.paramNameDiscoverer); context.lookupVariable("testVariable"); } private static class NotNullVariableMethodSecurityEvaluationContext extends MethodSecurityEvaluationContext { NotNullVariableMethodSecurityEvaluationContext(Authentication auth, MethodInvocation mi, ParameterNameDiscoverer parameterNameDiscoverer) { super(auth, mi, parameterNameDiscoverer); } @Override public void setVariable(String name, @Nullable Object value) { if (name == null) { throw new IllegalArgumentException("name should not be null"); } else { super.setVariable(name, value); } } } } ================================================ FILE: access/src/test/java/org/springframework/security/access/expression/method/MethodSecurityExpressionRootTests.java ================================================ /* * 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 org.springframework.security.access.expression.method; import org.aopalliance.intercept.MethodInvocation; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.expression.Expression; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.security.access.PermissionEvaluator; import org.springframework.security.access.expression.ExpressionUtils; import org.springframework.security.authentication.AuthenticationTrustResolver; import org.springframework.security.authorization.DefaultAuthorizationManagerFactory; import org.springframework.security.core.Authentication; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; /** * Tests for {@link MethodSecurityExpressionRoot} * * @author Luke Taylor */ public class MethodSecurityExpressionRootTests { SpelExpressionParser parser = new SpelExpressionParser(); MethodSecurityExpressionRoot root; StandardEvaluationContext ctx; private AuthenticationTrustResolver trustResolver; private Authentication user; @BeforeEach public void createContext() { this.user = mock(Authentication.class); this.root = new MethodSecurityExpressionRoot(() -> this.user, mock(MethodInvocation.class)); this.ctx = new StandardEvaluationContext(); this.ctx.setRootObject(this.root); this.trustResolver = mock(AuthenticationTrustResolver.class); DefaultAuthorizationManagerFactory authorizationManagerFactory = new DefaultAuthorizationManagerFactory<>(); authorizationManagerFactory.setTrustResolver(this.trustResolver); this.root.setAuthorizationManagerFactory(authorizationManagerFactory); } @Test public void canCallMethodsOnVariables() { this.ctx.setVariable("var", "somestring"); Expression e = this.parser.parseExpression("#var.length() == 10"); Assertions.assertThat(ExpressionUtils.evaluateAsBoolean(e, this.ctx)).isTrue(); } @Test public void isAnonymousReturnsTrueIfTrustResolverReportsAnonymous() { given(this.trustResolver.isAnonymous(this.user)).willReturn(true); Assertions.assertThat(this.root.isAnonymous()).isTrue(); } @Test public void isAnonymousReturnsFalseIfTrustResolverReportsNonAnonymous() { given(this.trustResolver.isAnonymous(this.user)).willReturn(false); Assertions.assertThat(this.root.isAnonymous()).isFalse(); } @Test public void hasPermissionOnDomainObjectReturnsFalseIfPermissionEvaluatorDoes() { final Object dummyDomainObject = new Object(); final PermissionEvaluator pe = mock(PermissionEvaluator.class); this.ctx.setVariable("domainObject", dummyDomainObject); this.root.setPermissionEvaluator(pe); given(pe.hasPermission(this.user, dummyDomainObject, "ignored")).willReturn(false); Assertions.assertThat(this.root.hasPermission(dummyDomainObject, "ignored")).isFalse(); } @Test public void hasPermissionOnDomainObjectReturnsTrueIfPermissionEvaluatorDoes() { final Object dummyDomainObject = new Object(); final PermissionEvaluator pe = mock(PermissionEvaluator.class); this.ctx.setVariable("domainObject", dummyDomainObject); this.root.setPermissionEvaluator(pe); given(pe.hasPermission(this.user, dummyDomainObject, "ignored")).willReturn(true); Assertions.assertThat(this.root.hasPermission(dummyDomainObject, "ignored")).isTrue(); } @Test public void hasPermissionOnDomainObjectWorksWithIntegerExpressions() { final Object dummyDomainObject = new Object(); this.ctx.setVariable("domainObject", dummyDomainObject); final PermissionEvaluator pe = mock(PermissionEvaluator.class); this.root.setPermissionEvaluator(pe); given(pe.hasPermission(eq(this.user), eq(dummyDomainObject), any(Integer.class))).willReturn(true, true, false); Expression e = this.parser.parseExpression("hasPermission(#domainObject, 0xA)"); // evaluator returns true Assertions.assertThat(ExpressionUtils.evaluateAsBoolean(e, this.ctx)).isTrue(); e = this.parser.parseExpression("hasPermission(#domainObject, 10)"); // evaluator returns true Assertions.assertThat(ExpressionUtils.evaluateAsBoolean(e, this.ctx)).isTrue(); e = this.parser.parseExpression("hasPermission(#domainObject, 0xFF)"); // evaluator returns false, make sure return value matches Assertions.assertThat(ExpressionUtils.evaluateAsBoolean(e, this.ctx)).isFalse(); } @Test public void hasPermissionWorksWithThisObject() { Object targetObject = new Object() { public String getX() { return "x"; } }; this.root.setThis(targetObject); Integer i = 2; PermissionEvaluator pe = mock(PermissionEvaluator.class); this.root.setPermissionEvaluator(pe); given(pe.hasPermission(this.user, targetObject, i)).willReturn(true, false); given(pe.hasPermission(this.user, "x", i)).willReturn(true); Expression e = this.parser.parseExpression("hasPermission(this, 2)"); Assertions.assertThat(ExpressionUtils.evaluateAsBoolean(e, this.ctx)).isTrue(); e = this.parser.parseExpression("hasPermission(this, 2)"); Assertions.assertThat(ExpressionUtils.evaluateAsBoolean(e, this.ctx)).isFalse(); e = this.parser.parseExpression("hasPermission(this.x, 2)"); Assertions.assertThat(ExpressionUtils.evaluateAsBoolean(e, this.ctx)).isTrue(); } } ================================================ FILE: access/src/test/java/org/springframework/security/access/expression/method/PrePostAnnotationSecurityMetadataSourceTests.java ================================================ /* * 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 org.springframework.security.access.expression.method; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.util.Collection; import java.util.List; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.expression.Expression; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.access.annotation.sec2150.MethodInvocationFactory; import org.springframework.security.access.intercept.method.MockMethodInvocation; import org.springframework.security.access.prepost.PostAuthorize; import org.springframework.security.access.prepost.PostFilter; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreFilter; import org.springframework.security.access.prepost.PrePostAnnotationSecurityMetadataSource; import org.springframework.test.util.ReflectionTestUtils; import static org.assertj.core.api.Assertions.assertThat; /** * @author Luke Taylor * @since 3.0 */ @SuppressWarnings("deprecation") public class PrePostAnnotationSecurityMetadataSourceTests { private PrePostAnnotationSecurityMetadataSource mds = new PrePostAnnotationSecurityMetadataSource( new ExpressionBasedAnnotationAttributeFactory(new DefaultMethodSecurityExpressionHandler())); private MockMethodInvocation voidImpl1; private MockMethodInvocation voidImpl2; private MockMethodInvocation voidImpl3; private MockMethodInvocation listImpl1; private MockMethodInvocation notherListImpl1; private MockMethodInvocation notherListImpl2; private MockMethodInvocation annotatedAtClassLevel; private MockMethodInvocation annotatedAtInterfaceLevel; private MockMethodInvocation annotatedAtMethodLevel; @BeforeEach public void setUpData() throws Exception { this.voidImpl1 = new MockMethodInvocation(new ReturnVoidImpl1(), ReturnVoid.class, "doSomething", List.class); this.voidImpl2 = new MockMethodInvocation(new ReturnVoidImpl2(), ReturnVoid.class, "doSomething", List.class); this.voidImpl3 = new MockMethodInvocation(new ReturnVoidImpl3(), ReturnVoid.class, "doSomething", List.class); this.listImpl1 = new MockMethodInvocation(new ReturnAListImpl1(), ReturnAList.class, "doSomething", List.class); this.notherListImpl1 = new MockMethodInvocation(new ReturnAnotherListImpl1(), ReturnAnotherList.class, "doSomething", List.class); this.notherListImpl2 = new MockMethodInvocation(new ReturnAnotherListImpl2(), ReturnAnotherList.class, "doSomething", List.class); this.annotatedAtClassLevel = new MockMethodInvocation(new CustomAnnotationAtClassLevel(), ReturnVoid.class, "doSomething", List.class); this.annotatedAtInterfaceLevel = new MockMethodInvocation(new CustomAnnotationAtInterfaceLevel(), ReturnVoid2.class, "doSomething", List.class); this.annotatedAtMethodLevel = new MockMethodInvocation(new CustomAnnotationAtMethodLevel(), ReturnVoid.class, "doSomething", List.class); } @Test public void classLevelPreAnnotationIsPickedUpWhenNoMethodLevelExists() { ConfigAttribute[] attrs = this.mds.getAttributes(this.voidImpl1).toArray(new ConfigAttribute[0]); assertThat(attrs).hasSize(1); assertThat(attrs[0] instanceof PreInvocationExpressionAttribute).isTrue(); PreInvocationExpressionAttribute pre = (PreInvocationExpressionAttribute) attrs[0]; assertThat(pre.getAuthorizeExpression()).isNotNull(); assertThat(pre.getAuthorizeExpression().getExpressionString()).isEqualTo("someExpression"); assertThat(pre.getFilterExpression()).isNull(); } @Test public void mixedClassAndMethodPreAnnotationsAreBothIncluded() { ConfigAttribute[] attrs = this.mds.getAttributes(this.voidImpl2).toArray(new ConfigAttribute[0]); assertThat(attrs).hasSize(1); assertThat(attrs[0] instanceof PreInvocationExpressionAttribute).isTrue(); PreInvocationExpressionAttribute pre = (PreInvocationExpressionAttribute) attrs[0]; assertThat(pre.getAuthorizeExpression().getExpressionString()).isEqualTo("someExpression"); assertThat(pre.getFilterExpression()).isNotNull(); assertThat(pre.getFilterExpression().getExpressionString()).isEqualTo("somePreFilterExpression"); } @Test public void methodWithPreFilterOnlyIsAllowed() { ConfigAttribute[] attrs = this.mds.getAttributes(this.voidImpl3).toArray(new ConfigAttribute[0]); assertThat(attrs).hasSize(1); assertThat(attrs[0] instanceof PreInvocationExpressionAttribute).isTrue(); PreInvocationExpressionAttribute pre = (PreInvocationExpressionAttribute) attrs[0]; assertThat(pre.getAuthorizeExpression().getExpressionString()).isEqualTo("permitAll"); assertThat(pre.getFilterExpression()).isNotNull(); assertThat(pre.getFilterExpression().getExpressionString()).isEqualTo("somePreFilterExpression"); } @Test public void methodWithPostFilterOnlyIsAllowed() { ConfigAttribute[] attrs = this.mds.getAttributes(this.listImpl1).toArray(new ConfigAttribute[0]); assertThat(attrs).hasSize(2); assertThat(attrs[0] instanceof PreInvocationExpressionAttribute).isTrue(); assertThat(attrs[1] instanceof PostInvocationExpressionAttribute).isTrue(); PreInvocationExpressionAttribute pre = (PreInvocationExpressionAttribute) attrs[0]; PostInvocationExpressionAttribute post = (PostInvocationExpressionAttribute) attrs[1]; assertThat(pre.getAuthorizeExpression().getExpressionString()).isEqualTo("permitAll"); assertThat(post.getFilterExpression()).isNotNull(); assertThat(post.getFilterExpression().getExpressionString()).isEqualTo("somePostFilterExpression"); } @Test public void interfaceAttributesAreIncluded() { ConfigAttribute[] attrs = this.mds.getAttributes(this.notherListImpl1).toArray(new ConfigAttribute[0]); assertThat(attrs).hasSize(1); assertThat(attrs[0] instanceof PreInvocationExpressionAttribute).isTrue(); PreInvocationExpressionAttribute pre = (PreInvocationExpressionAttribute) attrs[0]; assertThat(pre.getFilterExpression()).isNotNull(); assertThat(pre.getAuthorizeExpression()).isNotNull(); assertThat(pre.getAuthorizeExpression().getExpressionString()).isEqualTo("interfaceMethodAuthzExpression"); assertThat(pre.getFilterExpression().getExpressionString()).isEqualTo("interfacePreFilterExpression"); } @Test public void classAttributesTakesPrecedeceOverInterfaceAttributes() { ConfigAttribute[] attrs = this.mds.getAttributes(this.notherListImpl2).toArray(new ConfigAttribute[0]); assertThat(attrs).hasSize(1); assertThat(attrs[0] instanceof PreInvocationExpressionAttribute).isTrue(); PreInvocationExpressionAttribute pre = (PreInvocationExpressionAttribute) attrs[0]; assertThat(pre.getFilterExpression()).isNotNull(); assertThat(pre.getAuthorizeExpression()).isNotNull(); assertThat(pre.getAuthorizeExpression().getExpressionString()).isEqualTo("interfaceMethodAuthzExpression"); assertThat(pre.getFilterExpression().getExpressionString()).isEqualTo("classMethodPreFilterExpression"); } @Test public void customAnnotationAtClassLevelIsDetected() { ConfigAttribute[] attrs = this.mds.getAttributes(this.annotatedAtClassLevel).toArray(new ConfigAttribute[0]); assertThat(attrs).hasSize(1); } @Test public void customAnnotationAtInterfaceLevelIsDetected() { ConfigAttribute[] attrs = this.mds.getAttributes(this.annotatedAtInterfaceLevel) .toArray(new ConfigAttribute[0]); assertThat(attrs).hasSize(1); } @Test public void customAnnotationAtMethodLevelIsDetected() { ConfigAttribute[] attrs = this.mds.getAttributes(this.annotatedAtMethodLevel).toArray(new ConfigAttribute[0]); assertThat(attrs).hasSize(1); } @Test public void proxyFactoryInterfaceAttributesFound() throws Exception { MockMethodInvocation mi = MethodInvocationFactory.createSec2150MethodInvocation(); Collection attributes = this.mds.getAttributes(mi); assertThat(attributes).hasSize(1); Expression expression = (Expression) ReflectionTestUtils.getField(attributes.iterator().next(), "authorizeExpression"); assertThat(expression.getExpressionString()).isEqualTo("hasRole('ROLE_PERSON')"); } public interface ReturnVoid { void doSomething(List param); } public interface ReturnAList { List doSomething(List param); } @PreAuthorize("interfaceAuthzExpression") public interface ReturnAnotherList { @PreAuthorize("interfaceMethodAuthzExpression") @PreFilter(filterTarget = "param", value = "interfacePreFilterExpression") List doSomething(List param); } @PreAuthorize("someExpression") public static class ReturnVoidImpl1 implements ReturnVoid { @Override public void doSomething(List param) { } } @PreAuthorize("someExpression") public static class ReturnVoidImpl2 implements ReturnVoid { @Override @PreFilter(filterTarget = "param", value = "somePreFilterExpression") public void doSomething(List param) { } } public static class ReturnVoidImpl3 implements ReturnVoid { @Override @PreFilter(filterTarget = "param", value = "somePreFilterExpression") public void doSomething(List param) { } } public static class ReturnAListImpl1 implements ReturnAList { @Override @PostFilter("somePostFilterExpression") public List doSomething(List param) { return param; } } public static class ReturnAListImpl2 implements ReturnAList { @Override @PreAuthorize("someExpression") @PreFilter(filterTarget = "param", value = "somePreFilterExpression") @PostFilter("somePostFilterExpression") @PostAuthorize("somePostAuthorizeExpression") public List doSomething(List param) { return param; } } public static class ReturnAnotherListImpl1 implements ReturnAnotherList { @Override public List doSomething(List param) { return param; } } public static class ReturnAnotherListImpl2 implements ReturnAnotherList { @Override @PreFilter(filterTarget = "param", value = "classMethodPreFilterExpression") public List doSomething(List param) { return param; } } @Target({ ElementType.METHOD, ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) @Inherited @PreAuthorize("customAnnotationExpression") public @interface CustomAnnotation { } @CustomAnnotation public interface ReturnVoid2 { void doSomething(List param); } @CustomAnnotation public static class CustomAnnotationAtClassLevel implements ReturnVoid { @Override public void doSomething(List param) { } } public static class CustomAnnotationAtInterfaceLevel implements ReturnVoid2 { @Override public void doSomething(List param) { } } public static class CustomAnnotationAtMethodLevel implements ReturnVoid { @Override @CustomAnnotation public void doSomething(List param) { } } } ================================================ FILE: access/src/test/java/org/springframework/security/access/expression/method/SecurityRules.java ================================================ /* * 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 org.springframework.security.access.expression.method; public final class SecurityRules { private SecurityRules() { } public static boolean disallow() { return false; } public static boolean allow() { return false; } public static boolean isJoe(String s) { return "joe".equals(s); } } ================================================ FILE: access/src/test/java/org/springframework/security/access/intercept/AbstractSecurityInterceptorTests.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.access.intercept; import org.junit.jupiter.api.Test; import org.springframework.security.access.AccessDecisionManager; import org.springframework.security.access.SecurityMetadataSource; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.util.SimpleMethodInvocation; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.mockito.Mockito.mock; /** * Tests some {@link AbstractSecurityInterceptor} methods. Most of the testing for this * class is found in the {@code MethodSecurityInterceptorTests} class. * * @author Ben Alex */ @SuppressWarnings("deprecation") public class AbstractSecurityInterceptorTests { @Test public void detectsIfInvocationPassedIncompatibleSecureObject() { MockSecurityInterceptorWhichOnlySupportsStrings si = new MockSecurityInterceptorWhichOnlySupportsStrings(); si.setRunAsManager(mock(RunAsManager.class)); si.setAuthenticationManager(mock(AuthenticationManager.class)); si.setAfterInvocationManager(mock(AfterInvocationManager.class)); si.setAccessDecisionManager(mock(AccessDecisionManager.class)); si.setSecurityMetadataSource(mock(SecurityMetadataSource.class)); assertThatIllegalArgumentException().isThrownBy(() -> si.beforeInvocation(new SimpleMethodInvocation())); } @Test public void detectsViolationOfGetSecureObjectClassMethod() throws Exception { MockSecurityInterceptorReturnsNull si = new MockSecurityInterceptorReturnsNull(); si.setRunAsManager(mock(RunAsManager.class)); si.setAuthenticationManager(mock(AuthenticationManager.class)); si.setAfterInvocationManager(mock(AfterInvocationManager.class)); si.setAccessDecisionManager(mock(AccessDecisionManager.class)); si.setSecurityMetadataSource(mock(SecurityMetadataSource.class)); assertThatIllegalArgumentException().isThrownBy(si::afterPropertiesSet); } private class MockSecurityInterceptorReturnsNull extends AbstractSecurityInterceptor { private SecurityMetadataSource securityMetadataSource; @Override public Class getSecureObjectClass() { return null; } @Override public SecurityMetadataSource obtainSecurityMetadataSource() { return this.securityMetadataSource; } void setSecurityMetadataSource(SecurityMetadataSource securityMetadataSource) { this.securityMetadataSource = securityMetadataSource; } } private class MockSecurityInterceptorWhichOnlySupportsStrings extends AbstractSecurityInterceptor { private SecurityMetadataSource securityMetadataSource; @Override public Class getSecureObjectClass() { return String.class; } @Override public SecurityMetadataSource obtainSecurityMetadataSource() { return this.securityMetadataSource; } void setSecurityMetadataSource(SecurityMetadataSource securityMetadataSource) { this.securityMetadataSource = securityMetadataSource; } } } ================================================ FILE: access/src/test/java/org/springframework/security/access/intercept/AfterInvocationProviderManagerTests.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.access.intercept; import java.util.Collection; import java.util.List; import java.util.Vector; import org.aopalliance.intercept.MethodInvocation; import org.junit.jupiter.api.Test; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.AfterInvocationProvider; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.access.SecurityConfig; import org.springframework.security.core.Authentication; import org.springframework.security.util.SimpleMethodInvocation; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; /** * Tests {@link AfterInvocationProviderManager}. * * @author Ben Alex */ @SuppressWarnings({ "unchecked", "deprecation" }) public class AfterInvocationProviderManagerTests { @Test public void testCorrectOperation() throws Exception { AfterInvocationProviderManager manager = new AfterInvocationProviderManager(); List list = new Vector(); list.add(new MockAfterInvocationProvider("swap1", MethodInvocation.class, new SecurityConfig("GIVE_ME_SWAP1"))); list.add(new MockAfterInvocationProvider("swap2", MethodInvocation.class, new SecurityConfig("GIVE_ME_SWAP2"))); list.add(new MockAfterInvocationProvider("swap3", MethodInvocation.class, new SecurityConfig("GIVE_ME_SWAP3"))); manager.setProviders(list); assertThat(manager.getProviders()).isEqualTo(list); manager.afterPropertiesSet(); List attr1 = SecurityConfig.createList(new String[] { "GIVE_ME_SWAP1" }); List attr2 = SecurityConfig.createList(new String[] { "GIVE_ME_SWAP2" }); List attr3 = SecurityConfig.createList(new String[] { "GIVE_ME_SWAP3" }); List attr2and3 = SecurityConfig.createList(new String[] { "GIVE_ME_SWAP2", "GIVE_ME_SWAP3" }); List attr4 = SecurityConfig.createList(new String[] { "NEVER_CAUSES_SWAP" }); assertThat(manager.decide(null, new SimpleMethodInvocation(), attr1, "content-before-swapping")) .isEqualTo("swap1"); assertThat(manager.decide(null, new SimpleMethodInvocation(), attr2, "content-before-swapping")) .isEqualTo("swap2"); assertThat(manager.decide(null, new SimpleMethodInvocation(), attr3, "content-before-swapping")) .isEqualTo("swap3"); assertThat(manager.decide(null, new SimpleMethodInvocation(), attr4, "content-before-swapping")) .isEqualTo("content-before-swapping"); assertThat(manager.decide(null, new SimpleMethodInvocation(), attr2and3, "content-before-swapping")) .isEqualTo("swap3"); } @Test public void testRejectsEmptyProvidersList() { AfterInvocationProviderManager manager = new AfterInvocationProviderManager(); List list = new Vector(); assertThatIllegalArgumentException().isThrownBy(() -> manager.setProviders(list)); } @Test public void testRejectsNonAfterInvocationProviders() { AfterInvocationProviderManager manager = new AfterInvocationProviderManager(); List list = new Vector(); list.add(new MockAfterInvocationProvider("swap1", MethodInvocation.class, new SecurityConfig("GIVE_ME_SWAP1"))); list.add(45); list.add(new MockAfterInvocationProvider("swap3", MethodInvocation.class, new SecurityConfig("GIVE_ME_SWAP3"))); assertThatIllegalArgumentException().isThrownBy(() -> manager.setProviders(list)); } @Test public void testRejectsNullProvidersList() throws Exception { AfterInvocationProviderManager manager = new AfterInvocationProviderManager(); assertThatIllegalArgumentException().isThrownBy(manager::afterPropertiesSet); } @Test public void testSupportsConfigAttributeIteration() throws Exception { AfterInvocationProviderManager manager = new AfterInvocationProviderManager(); List list = new Vector(); list.add(new MockAfterInvocationProvider("swap1", MethodInvocation.class, new SecurityConfig("GIVE_ME_SWAP1"))); list.add(new MockAfterInvocationProvider("swap2", MethodInvocation.class, new SecurityConfig("GIVE_ME_SWAP2"))); list.add(new MockAfterInvocationProvider("swap3", MethodInvocation.class, new SecurityConfig("GIVE_ME_SWAP3"))); manager.setProviders(list); manager.afterPropertiesSet(); assertThat(manager.supports(new SecurityConfig("UNKNOWN_ATTRIB"))).isFalse(); assertThat(manager.supports(new SecurityConfig("GIVE_ME_SWAP2"))).isTrue(); } @Test public void testSupportsSecureObjectIteration() throws Exception { AfterInvocationProviderManager manager = new AfterInvocationProviderManager(); List list = new Vector(); list.add(new MockAfterInvocationProvider("swap1", MethodInvocation.class, new SecurityConfig("GIVE_ME_SWAP1"))); list.add(new MockAfterInvocationProvider("swap2", MethodInvocation.class, new SecurityConfig("GIVE_ME_SWAP2"))); list.add(new MockAfterInvocationProvider("swap3", MethodInvocation.class, new SecurityConfig("GIVE_ME_SWAP3"))); manager.setProviders(list); manager.afterPropertiesSet(); // assertFalse(manager.supports(FilterInvocation.class)); assertThat(manager.supports(MethodInvocation.class)).isTrue(); } /** * Always returns the constructor-defined forceReturnObject, provided the * same configuration attribute was provided. Also stores the secure object it * supports. */ private class MockAfterInvocationProvider implements AfterInvocationProvider { private Class secureObject; private ConfigAttribute configAttribute; private Object forceReturnObject; MockAfterInvocationProvider(Object forceReturnObject, Class secureObject, ConfigAttribute configAttribute) { this.forceReturnObject = forceReturnObject; this.secureObject = secureObject; this.configAttribute = configAttribute; } @Override public Object decide(Authentication authentication, Object object, Collection config, Object returnedObject) throws AccessDeniedException { if (config.contains(this.configAttribute)) { return this.forceReturnObject; } return returnedObject; } @Override public boolean supports(Class clazz) { return this.secureObject.isAssignableFrom(clazz); } @Override public boolean supports(ConfigAttribute attribute) { return attribute.equals(this.configAttribute); } } } ================================================ FILE: access/src/test/java/org/springframework/security/access/intercept/InterceptorStatusTokenTests.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.access.intercept; import java.util.List; import org.aopalliance.intercept.MethodInvocation; import org.junit.jupiter.api.Test; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.access.SecurityConfig; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.util.SimpleMethodInvocation; import static org.assertj.core.api.Assertions.assertThat; /** * Tests {@link InterceptorStatusToken}. * * @author Ben Alex */ @SuppressWarnings("deprecation") public class InterceptorStatusTokenTests { @Test public void testOperation() { List attr = SecurityConfig.createList("FOO"); MethodInvocation mi = new SimpleMethodInvocation(); SecurityContext ctx = SecurityContextHolder.createEmptyContext(); InterceptorStatusToken token = new InterceptorStatusToken(ctx, true, attr, mi); assertThat(token.isContextHolderRefreshRequired()).isTrue(); assertThat(token.getAttributes()).isEqualTo(attr); assertThat(token.getSecureObject()).isEqualTo(mi); assertThat(token.getSecurityContext()).isSameAs(ctx); } } ================================================ FILE: access/src/test/java/org/springframework/security/access/intercept/NullRunAsManagerTests.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.access.intercept; import org.junit.jupiter.api.Test; import org.springframework.security.access.SecurityConfig; import static org.assertj.core.api.Assertions.assertThat; /** * Tests {@link NullRunAsManager}. * * @author Ben Alex */ @SuppressWarnings("deprecation") public class NullRunAsManagerTests { @Test public void testAlwaysReturnsNull() { NullRunAsManager runAs = new NullRunAsManager(); assertThat(runAs.buildRunAs(null, null, null)).isNull(); } @Test public void testAlwaysSupportsClass() { NullRunAsManager runAs = new NullRunAsManager(); assertThat(runAs.supports(String.class)).isTrue(); } @Test public void testNeverSupportsAttribute() { NullRunAsManager runAs = new NullRunAsManager(); assertThat(runAs.supports(new SecurityConfig("X"))).isFalse(); } } ================================================ FILE: access/src/test/java/org/springframework/security/access/intercept/RunAsImplAuthenticationProviderTests.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.access.intercept; import org.junit.jupiter.api.Test; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.authority.AuthorityUtils; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; /** * Tests {@link RunAsImplAuthenticationProvider}. */ @SuppressWarnings("deprecation") public class RunAsImplAuthenticationProviderTests { @Test public void testAuthenticationFailDueToWrongKey() { RunAsUserToken token = new RunAsUserToken("wrong_key", "Test", "Password", AuthorityUtils.createAuthorityList("ROLE_ONE", "ROLE_TWO"), UsernamePasswordAuthenticationToken.class); RunAsImplAuthenticationProvider provider = new RunAsImplAuthenticationProvider(); provider.setKey("hello_world"); assertThatExceptionOfType(BadCredentialsException.class).isThrownBy(() -> provider.authenticate(token)); } @Test public void testAuthenticationSuccess() { RunAsUserToken token = new RunAsUserToken("my_password", "Test", "Password", AuthorityUtils.createAuthorityList("ROLE_ONE", "ROLE_TWO"), UsernamePasswordAuthenticationToken.class); RunAsImplAuthenticationProvider provider = new RunAsImplAuthenticationProvider(); provider.setKey("my_password"); Authentication result = provider.authenticate(token); assertThat(result instanceof RunAsUserToken).as("Should have returned RunAsUserToken").isTrue(); RunAsUserToken resultCast = (RunAsUserToken) result; assertThat(resultCast.getKeyHash()).isEqualTo("my_password".hashCode()); } @Test public void testStartupFailsIfNoKey() throws Exception { RunAsImplAuthenticationProvider provider = new RunAsImplAuthenticationProvider(); assertThatIllegalArgumentException().isThrownBy(provider::afterPropertiesSet); } @Test public void testStartupSuccess() throws Exception { RunAsImplAuthenticationProvider provider = new RunAsImplAuthenticationProvider(); provider.setKey("hello_world"); assertThat(provider.getKey()).isEqualTo("hello_world"); provider.afterPropertiesSet(); } @Test public void testSupports() { RunAsImplAuthenticationProvider provider = new RunAsImplAuthenticationProvider(); assertThat(provider.supports(RunAsUserToken.class)).isTrue(); assertThat(!provider.supports(TestingAuthenticationToken.class)).isTrue(); } } ================================================ FILE: access/src/test/java/org/springframework/security/access/intercept/RunAsManagerImplTests.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.access.intercept; import java.util.Set; import org.junit.jupiter.api.Test; import org.springframework.security.access.SecurityConfig; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.authority.AuthorityUtils; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.assertj.core.api.Assertions.fail; /** * Tests {@link RunAsManagerImpl}. * * @author Ben Alex */ @SuppressWarnings("deprecation") public class RunAsManagerImplTests { @Test public void testAlwaysSupportsClass() { RunAsManagerImpl runAs = new RunAsManagerImpl(); assertThat(runAs.supports(String.class)).isTrue(); } @Test public void testDoesNotReturnAdditionalAuthoritiesIfCalledWithoutARunAsSetting() { UsernamePasswordAuthenticationToken inputToken = UsernamePasswordAuthenticationToken.authenticated("Test", "Password", AuthorityUtils.createAuthorityList("ROLE_ONE", "ROLE_TWO")); RunAsManagerImpl runAs = new RunAsManagerImpl(); runAs.setKey("my_password"); Authentication resultingToken = runAs.buildRunAs(inputToken, new Object(), SecurityConfig.createList("SOMETHING_WE_IGNORE")); assertThat(resultingToken).isNull(); } @Test public void testRespectsRolePrefix() { UsernamePasswordAuthenticationToken inputToken = UsernamePasswordAuthenticationToken.authenticated("Test", "Password", AuthorityUtils.createAuthorityList("ONE", "TWO")); RunAsManagerImpl runAs = new RunAsManagerImpl(); runAs.setKey("my_password"); runAs.setRolePrefix("FOOBAR_"); Authentication result = runAs.buildRunAs(inputToken, new Object(), SecurityConfig.createList("RUN_AS_SOMETHING")); assertThat(result instanceof RunAsUserToken).withFailMessage("Should have returned a RunAsUserToken").isTrue(); assertThat(result.getPrincipal()).isEqualTo(inputToken.getPrincipal()); assertThat(result.getCredentials()).isEqualTo(inputToken.getCredentials()); Set authorities = AuthorityUtils.authorityListToSet(result.getAuthorities()); assertThat(authorities).contains("FOOBAR_RUN_AS_SOMETHING"); assertThat(authorities).contains("ONE"); assertThat(authorities).contains("TWO"); RunAsUserToken resultCast = (RunAsUserToken) result; assertThat(resultCast.getKeyHash()).isEqualTo("my_password".hashCode()); } @Test public void testReturnsAdditionalGrantedAuthorities() { UsernamePasswordAuthenticationToken inputToken = UsernamePasswordAuthenticationToken.authenticated("Test", "Password", AuthorityUtils.createAuthorityList("ROLE_ONE", "ROLE_TWO")); RunAsManagerImpl runAs = new RunAsManagerImpl(); runAs.setKey("my_password"); Authentication result = runAs.buildRunAs(inputToken, new Object(), SecurityConfig.createList("RUN_AS_SOMETHING")); if (!(result instanceof RunAsUserToken)) { fail("Should have returned a RunAsUserToken"); } assertThat(result.getPrincipal()).isEqualTo(inputToken.getPrincipal()); assertThat(result.getCredentials()).isEqualTo(inputToken.getCredentials()); Set authorities = AuthorityUtils.authorityListToSet(result.getAuthorities()); assertThat(authorities).contains("ROLE_RUN_AS_SOMETHING"); assertThat(authorities).contains("ROLE_ONE"); assertThat(authorities).contains("ROLE_TWO"); RunAsUserToken resultCast = (RunAsUserToken) result; assertThat(resultCast.getKeyHash()).isEqualTo("my_password".hashCode()); } @Test public void testStartupDetectsMissingKey() throws Exception { RunAsManagerImpl runAs = new RunAsManagerImpl(); assertThatIllegalArgumentException().isThrownBy(runAs::afterPropertiesSet); } @Test public void testStartupSuccessfulWithKey() throws Exception { RunAsManagerImpl runAs = new RunAsManagerImpl(); runAs.setKey("hello_world"); runAs.afterPropertiesSet(); assertThat(runAs.getKey()).isEqualTo("hello_world"); } @Test public void testSupports() { RunAsManager runAs = new RunAsManagerImpl(); assertThat(runAs.supports(new SecurityConfig("RUN_AS_SOMETHING"))).isTrue(); assertThat(!runAs.supports(new SecurityConfig("ROLE_WHICH_IS_IGNORED"))).isTrue(); assertThat(!runAs.supports(new SecurityConfig("role_LOWER_CASE_FAILS"))).isTrue(); } } ================================================ FILE: access/src/test/java/org/springframework/security/access/intercept/RunAsUserTokenTests.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.access.intercept; import org.junit.jupiter.api.Test; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.authority.AuthorityUtils; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; /** * Tests {@link RunAsUserToken}. * * @author Ben Alex */ @SuppressWarnings("deprecation") public class RunAsUserTokenTests { @Test public void testAuthenticationSetting() { RunAsUserToken token = new RunAsUserToken("my_password", "Test", "Password", AuthorityUtils.createAuthorityList("ROLE_ONE", "ROLE_TWO"), UsernamePasswordAuthenticationToken.class); assertThat(token.isAuthenticated()).isTrue(); token.setAuthenticated(false); assertThat(!token.isAuthenticated()).isTrue(); } @Test public void testGetters() { RunAsUserToken token = new RunAsUserToken("my_password", "Test", "Password", AuthorityUtils.createAuthorityList("ROLE_ONE", "ROLE_TWO"), UsernamePasswordAuthenticationToken.class); assertThat("Test").isEqualTo(token.getPrincipal()); assertThat("Password").isEqualTo(token.getCredentials()); assertThat("my_password".hashCode()).isEqualTo(token.getKeyHash()); assertThat(UsernamePasswordAuthenticationToken.class).isEqualTo(token.getOriginalAuthentication()); } @Test public void testNoArgConstructorDoesntExist() { assertThatExceptionOfType(NoSuchMethodException.class) .isThrownBy(() -> RunAsUserToken.class.getDeclaredConstructor((Class[]) null)); } @Test public void testToString() { RunAsUserToken token = new RunAsUserToken("my_password", "Test", "Password", AuthorityUtils.createAuthorityList("ROLE_ONE", "ROLE_TWO"), UsernamePasswordAuthenticationToken.class); assertThat(token.toString() .lastIndexOf("Original Class: " + UsernamePasswordAuthenticationToken.class.getName().toString()) != -1) .isTrue(); } // SEC-1792 @Test public void testToStringNullOriginalAuthentication() { RunAsUserToken token = new RunAsUserToken("my_password", "Test", "Password", AuthorityUtils.createAuthorityList("ROLE_ONE", "ROLE_TWO"), null); assertThat(token.toString().lastIndexOf("Original Class: null") != -1).isTrue(); } } ================================================ FILE: access/src/test/java/org/springframework/security/access/intercept/aopalliance/MethodSecurityInterceptorTests.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.access.intercept.aopalliance; import java.util.List; import org.aopalliance.intercept.MethodInvocation; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.aop.framework.ProxyFactory; import org.springframework.context.ApplicationEventPublisher; import org.springframework.security.access.AccessDecisionManager; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.access.ITargetObject; import org.springframework.security.access.SecurityConfig; import org.springframework.security.access.TargetObject; import org.springframework.security.access.event.AuthorizationFailureEvent; import org.springframework.security.access.event.AuthorizedEvent; import org.springframework.security.access.intercept.AfterInvocationManager; import org.springframework.security.access.intercept.RunAsManager; import org.springframework.security.access.intercept.RunAsUserToken; import org.springframework.security.access.method.MethodSecurityMetadataSource; import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.willThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; /** * Tests {@link MethodSecurityInterceptor}. * * @author Ben Alex * @author Rob Winch */ @SuppressWarnings({ "unchecked", "deprecation" }) public class MethodSecurityInterceptorTests { private TestingAuthenticationToken token; private MethodSecurityInterceptor interceptor; private ITargetObject realTarget; private ITargetObject advisedTarget; private AccessDecisionManager adm; private MethodSecurityMetadataSource mds; private AuthenticationManager authman; private ApplicationEventPublisher eventPublisher; @BeforeEach public final void setUp() { SecurityContextHolder.clearContext(); this.token = new TestingAuthenticationToken("Test", "Password"); this.interceptor = new MethodSecurityInterceptor(); this.adm = mock(AccessDecisionManager.class); this.authman = mock(AuthenticationManager.class); this.mds = mock(MethodSecurityMetadataSource.class); this.eventPublisher = mock(ApplicationEventPublisher.class); this.interceptor.setAccessDecisionManager(this.adm); this.interceptor.setAuthenticationManager(this.authman); this.interceptor.setSecurityMetadataSource(this.mds); this.interceptor.setApplicationEventPublisher(this.eventPublisher); createTarget(false); } @AfterEach public void tearDown() { SecurityContextHolder.clearContext(); } private void createTarget(boolean useMock) { this.realTarget = useMock ? mock(ITargetObject.class) : new TargetObject(); ProxyFactory pf = new ProxyFactory(this.realTarget); pf.addAdvice(this.interceptor); this.advisedTarget = (ITargetObject) pf.getProxy(); } @Test public void gettersReturnExpectedData() { RunAsManager runAs = mock(RunAsManager.class); AfterInvocationManager aim = mock(AfterInvocationManager.class); this.interceptor.setRunAsManager(runAs); this.interceptor.setAfterInvocationManager(aim); assertThat(this.interceptor.getAccessDecisionManager()).isEqualTo(this.adm); assertThat(this.interceptor.getRunAsManager()).isEqualTo(runAs); assertThat(this.interceptor.getAuthenticationManager()).isEqualTo(this.authman); assertThat(this.interceptor.getSecurityMetadataSource()).isEqualTo(this.mds); assertThat(this.interceptor.getAfterInvocationManager()).isEqualTo(aim); } @Test public void missingAccessDecisionManagerIsDetected() throws Exception { this.interceptor.setAccessDecisionManager(null); assertThatIllegalArgumentException().isThrownBy(() -> this.interceptor.afterPropertiesSet()); } @Test public void missingAuthenticationManagerIsDetected() throws Exception { this.interceptor.setAuthenticationManager(null); assertThatIllegalArgumentException().isThrownBy(() -> this.interceptor.afterPropertiesSet()); } @Test public void missingMethodSecurityMetadataSourceIsRejected() throws Exception { this.interceptor.setSecurityMetadataSource(null); assertThatIllegalArgumentException().isThrownBy(() -> this.interceptor.afterPropertiesSet()); } @Test public void missingRunAsManagerIsRejected() throws Exception { this.interceptor.setRunAsManager(null); assertThatIllegalArgumentException().isThrownBy(() -> this.interceptor.afterPropertiesSet()); } @Test public void initializationRejectsSecurityMetadataSourceThatDoesNotSupportMethodInvocation() throws Throwable { given(this.mds.supports(MethodInvocation.class)).willReturn(false); assertThatIllegalArgumentException().isThrownBy(() -> this.interceptor.afterPropertiesSet()); } @Test public void initializationRejectsAccessDecisionManagerThatDoesNotSupportMethodInvocation() throws Exception { given(this.mds.supports(MethodInvocation.class)).willReturn(true); given(this.adm.supports(MethodInvocation.class)).willReturn(false); assertThatIllegalArgumentException().isThrownBy(() -> this.interceptor.afterPropertiesSet()); } @Test public void intitalizationRejectsRunAsManagerThatDoesNotSupportMethodInvocation() throws Exception { final RunAsManager ram = mock(RunAsManager.class); given(ram.supports(MethodInvocation.class)).willReturn(false); this.interceptor.setRunAsManager(ram); assertThatIllegalArgumentException().isThrownBy(() -> this.interceptor.afterPropertiesSet()); } @Test public void intitalizationRejectsAfterInvocationManagerThatDoesNotSupportMethodInvocation() throws Exception { final AfterInvocationManager aim = mock(AfterInvocationManager.class); given(aim.supports(MethodInvocation.class)).willReturn(false); this.interceptor.setAfterInvocationManager(aim); assertThatIllegalArgumentException().isThrownBy(() -> this.interceptor.afterPropertiesSet()); } @Test public void initializationFailsIfAccessDecisionManagerRejectsConfigAttributes() throws Exception { given(this.adm.supports(any(ConfigAttribute.class))).willReturn(false); assertThatIllegalArgumentException().isThrownBy(() -> this.interceptor.afterPropertiesSet()); } @Test public void validationNotAttemptedIfIsValidateConfigAttributesSetToFalse() throws Exception { given(this.adm.supports(MethodInvocation.class)).willReturn(true); given(this.mds.supports(MethodInvocation.class)).willReturn(true); this.interceptor.setValidateConfigAttributes(false); this.interceptor.afterPropertiesSet(); verify(this.mds, never()).getAllConfigAttributes(); verify(this.adm, never()).supports(any(ConfigAttribute.class)); } @Test public void validationNotAttemptedIfMethodSecurityMetadataSourceReturnsNullForAttributes() throws Exception { given(this.adm.supports(MethodInvocation.class)).willReturn(true); given(this.mds.supports(MethodInvocation.class)).willReturn(true); given(this.mds.getAllConfigAttributes()).willReturn(null); this.interceptor.setValidateConfigAttributes(true); this.interceptor.afterPropertiesSet(); verify(this.adm, never()).supports(any(ConfigAttribute.class)); } @Test public void callingAPublicMethodFacadeWillNotRepeatSecurityChecksWhenPassedToTheSecuredMethodItFronts() { mdsReturnsNull(); String result = this.advisedTarget.publicMakeLowerCase("HELLO"); assertThat(result).isEqualTo("hello Authentication empty"); } @Test public void callingAPublicMethodWhenPresentingAnAuthenticationObjectDoesntChangeItsAuthenticatedProperty() { mdsReturnsNull(); SecurityContextHolder.getContext().setAuthentication(this.token); assertThat(this.advisedTarget.publicMakeLowerCase("HELLO")) .isEqualTo("hello org.springframework.security.authentication.TestingAuthenticationToken false"); assertThat(!this.token.isAuthenticated()).isTrue(); } @Test public void callIsntMadeWhenAuthenticationManagerRejectsAuthentication() { final TestingAuthenticationToken token = new TestingAuthenticationToken("Test", "Password"); SecurityContextHolder.getContext().setAuthentication(token); mdsReturnsUserRole(); given(this.authman.authenticate(token)).willThrow(new BadCredentialsException("rejected")); assertThatExceptionOfType(AuthenticationException.class) .isThrownBy(() -> this.advisedTarget.makeLowerCase("HELLO")); } @Test public void callSucceedsIfAccessDecisionManagerGrantsAccess() { this.token.setAuthenticated(true); this.interceptor.setPublishAuthorizationSuccess(true); SecurityContextHolder.getContext().setAuthentication(this.token); mdsReturnsUserRole(); String result = this.advisedTarget.makeLowerCase("HELLO"); // Note we check the isAuthenticated remained true in following line assertThat(result) .isEqualTo("hello org.springframework.security.authentication.TestingAuthenticationToken true"); verify(this.eventPublisher).publishEvent(any(AuthorizedEvent.class)); } @Test public void callIsntMadeWhenAccessDecisionManagerRejectsAccess() { SecurityContextHolder.getContext().setAuthentication(this.token); // Use mocked target to make sure invocation doesn't happen (not in expectations // so test would fail) createTarget(true); mdsReturnsUserRole(); given(this.authman.authenticate(this.token)).willReturn(this.token); willThrow(new AccessDeniedException("rejected")).given(this.adm) .decide(any(Authentication.class), any(MethodInvocation.class), any(List.class)); assertThatExceptionOfType(AccessDeniedException.class) .isThrownBy(() -> this.advisedTarget.makeUpperCase("HELLO")); verify(this.eventPublisher).publishEvent(any(AuthorizationFailureEvent.class)); } @Test public void rejectsNullSecuredObjects() throws Throwable { assertThatIllegalArgumentException().isThrownBy(() -> this.interceptor.invoke(null)); } @Test public void runAsReplacementIsCorrectlySet() { SecurityContext ctx = SecurityContextHolder.getContext(); ctx.setAuthentication(this.token); this.token.setAuthenticated(true); final RunAsManager runAs = mock(RunAsManager.class); final RunAsUserToken runAsToken = new RunAsUserToken("key", "someone", "creds", this.token.getAuthorities(), TestingAuthenticationToken.class); this.interceptor.setRunAsManager(runAs); mdsReturnsUserRole(); given(runAs.buildRunAs(eq(this.token), any(MethodInvocation.class), any(List.class))).willReturn(runAsToken); String result = this.advisedTarget.makeUpperCase("hello"); assertThat(result).isEqualTo("HELLO org.springframework.security.access.intercept.RunAsUserToken true"); // Check we've changed back assertThat(SecurityContextHolder.getContext()).isSameAs(ctx); assertThat(SecurityContextHolder.getContext().getAuthentication()).isSameAs(this.token); } // SEC-1967 @Test public void runAsReplacementCleansAfterException() { createTarget(true); given(this.realTarget.makeUpperCase(anyString())).willThrow(new RuntimeException()); SecurityContext ctx = SecurityContextHolder.getContext(); ctx.setAuthentication(this.token); this.token.setAuthenticated(true); final RunAsManager runAs = mock(RunAsManager.class); final RunAsUserToken runAsToken = new RunAsUserToken("key", "someone", "creds", this.token.getAuthorities(), TestingAuthenticationToken.class); this.interceptor.setRunAsManager(runAs); mdsReturnsUserRole(); given(runAs.buildRunAs(eq(this.token), any(MethodInvocation.class), any(List.class))).willReturn(runAsToken); assertThatExceptionOfType(RuntimeException.class).isThrownBy(() -> this.advisedTarget.makeUpperCase("hello")); // Check we've changed back assertThat(SecurityContextHolder.getContext()).isSameAs(ctx); assertThat(SecurityContextHolder.getContext().getAuthentication()).isSameAs(this.token); } @Test public void emptySecurityContextIsRejected() { mdsReturnsUserRole(); assertThatExceptionOfType(AuthenticationCredentialsNotFoundException.class) .isThrownBy(() -> this.advisedTarget.makeUpperCase("hello")); } @Test public void afterInvocationManagerIsNotInvokedIfExceptionIsRaised() throws Throwable { MethodInvocation mi = mock(MethodInvocation.class); this.token.setAuthenticated(true); SecurityContextHolder.getContext().setAuthentication(this.token); mdsReturnsUserRole(); AfterInvocationManager aim = mock(AfterInvocationManager.class); this.interceptor.setAfterInvocationManager(aim); given(mi.proceed()).willThrow(new Throwable()); assertThatExceptionOfType(Throwable.class).isThrownBy(() -> this.interceptor.invoke(mi)); verifyNoMoreInteractions(aim); } void mdsReturnsNull() { given(this.mds.getAttributes(any(MethodInvocation.class))).willReturn(null); } void mdsReturnsUserRole() { given(this.mds.getAttributes(any(MethodInvocation.class))).willReturn(SecurityConfig.createList("ROLE_USER")); } } ================================================ FILE: access/src/test/java/org/springframework/security/access/intercept/aopalliance/MethodSecurityMetadataSourceAdvisorTests.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.access.intercept.aopalliance; import java.lang.reflect.Method; import org.junit.jupiter.api.Test; import org.springframework.security.access.SecurityConfig; import org.springframework.security.access.TargetObject; import org.springframework.security.access.method.MethodSecurityMetadataSource; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; /** * Tests {@link MethodSecurityMetadataSourceAdvisor}. * * @author Ben Alex */ @SuppressWarnings("deprecation") public class MethodSecurityMetadataSourceAdvisorTests { @Test public void testAdvisorReturnsFalseWhenMethodInvocationNotDefined() throws Exception { Class clazz = TargetObject.class; Method method = clazz.getMethod("makeLowerCase", new Class[] { String.class }); MethodSecurityMetadataSource mds = mock(MethodSecurityMetadataSource.class); given(mds.getAttributes(method, clazz)).willReturn(null); MethodSecurityMetadataSourceAdvisor advisor = new MethodSecurityMetadataSourceAdvisor("", mds, ""); assertThat(advisor.getPointcut().getMethodMatcher().matches(method, clazz)).isFalse(); } @Test public void testAdvisorReturnsTrueWhenMethodInvocationIsDefined() throws Exception { Class clazz = TargetObject.class; Method method = clazz.getMethod("countLength", new Class[] { String.class }); MethodSecurityMetadataSource mds = mock(MethodSecurityMetadataSource.class); given(mds.getAttributes(method, clazz)).willReturn(SecurityConfig.createList("ROLE_A")); MethodSecurityMetadataSourceAdvisor advisor = new MethodSecurityMetadataSourceAdvisor("", mds, ""); assertThat(advisor.getPointcut().getMethodMatcher().matches(method, clazz)).isTrue(); } } ================================================ FILE: access/src/test/java/org/springframework/security/access/intercept/aspectj/AspectJMethodSecurityInterceptorTests.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.access.intercept.aspectj; import java.lang.reflect.Method; import java.util.List; import org.aopalliance.intercept.MethodInvocation; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.Signature; import org.aspectj.lang.reflect.CodeSignature; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.springframework.security.access.AccessDecisionManager; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.SecurityConfig; import org.springframework.security.access.TargetObject; import org.springframework.security.access.intercept.AfterInvocationManager; import org.springframework.security.access.intercept.RunAsManager; import org.springframework.security.access.intercept.RunAsUserToken; import org.springframework.security.access.method.MethodSecurityMetadataSource; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.util.ClassUtils; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.willThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; /** * Tests {@link AspectJMethodSecurityInterceptor}. * * @author Ben Alex * @author Luke Taylor * @author Rob Winch */ @SuppressWarnings("deprecation") public class AspectJMethodSecurityInterceptorTests { private TestingAuthenticationToken token; private AspectJMethodSecurityInterceptor interceptor; @Mock private AccessDecisionManager adm; @Mock private MethodSecurityMetadataSource mds; @Mock private AuthenticationManager authman; @Mock private AspectJCallback aspectJCallback; private ProceedingJoinPoint joinPoint; @BeforeEach public final void setUp() { MockitoAnnotations.initMocks(this); SecurityContextHolder.clearContext(); this.token = new TestingAuthenticationToken("Test", "Password"); this.interceptor = new AspectJMethodSecurityInterceptor(); this.interceptor.setAccessDecisionManager(this.adm); this.interceptor.setAuthenticationManager(this.authman); this.interceptor.setSecurityMetadataSource(this.mds); // Set up joinpoint information for the countLength method on TargetObject this.joinPoint = mock(ProceedingJoinPoint.class); // new MockJoinPoint(new // TargetObject(), method); Signature sig = mock(Signature.class); given(sig.getDeclaringType()).willReturn(TargetObject.class); JoinPoint.StaticPart staticPart = mock(JoinPoint.StaticPart.class); given(this.joinPoint.getSignature()).willReturn(sig); given(this.joinPoint.getStaticPart()).willReturn(staticPart); CodeSignature codeSig = mock(CodeSignature.class); given(codeSig.getName()).willReturn("countLength"); given(codeSig.getDeclaringType()).willReturn(TargetObject.class); given(codeSig.getParameterTypes()).willReturn(new Class[] { String.class }); given(staticPart.getSignature()).willReturn(codeSig); given(this.mds.getAttributes(any())).willReturn(SecurityConfig.createList("ROLE_USER")); given(this.authman.authenticate(this.token)).willReturn(this.token); } @AfterEach public void clearContext() { SecurityContextHolder.clearContext(); } @Test public void callbackIsInvokedWhenPermissionGranted() throws Throwable { SecurityContextHolder.getContext().setAuthentication(this.token); this.interceptor.invoke(this.joinPoint, this.aspectJCallback); verify(this.aspectJCallback).proceedWithObject(); // Just try the other method too this.interceptor.invoke(this.joinPoint); } @SuppressWarnings("unchecked") @Test public void callbackIsNotInvokedWhenPermissionDenied() { willThrow(new AccessDeniedException("denied")).given(this.adm).decide(any(), any(), any()); SecurityContextHolder.getContext().setAuthentication(this.token); assertThatExceptionOfType(AccessDeniedException.class) .isThrownBy(() -> this.interceptor.invoke(this.joinPoint, this.aspectJCallback)); verify(this.aspectJCallback, never()).proceedWithObject(); } @Test public void adapterHoldsCorrectData() { TargetObject to = new TargetObject(); Method m = ClassUtils.getMethodIfAvailable(TargetObject.class, "countLength", new Class[] { String.class }); given(this.joinPoint.getTarget()).willReturn(to); given(this.joinPoint.getArgs()).willReturn(new Object[] { "Hi" }); MethodInvocationAdapter mia = new MethodInvocationAdapter(this.joinPoint); assertThat(mia.getArguments()[0]).isEqualTo("Hi"); assertThat(mia.getStaticPart()).isEqualTo(m); assertThat(mia.getMethod()).isEqualTo(m); assertThat(mia.getThis()).isSameAs(to); } @Test public void afterInvocationManagerIsNotInvokedIfExceptionIsRaised() { this.token.setAuthenticated(true); SecurityContextHolder.getContext().setAuthentication(this.token); AfterInvocationManager aim = mock(AfterInvocationManager.class); this.interceptor.setAfterInvocationManager(aim); given(this.aspectJCallback.proceedWithObject()).willThrow(new RuntimeException()); assertThatExceptionOfType(RuntimeException.class) .isThrownBy(() -> this.interceptor.invoke(this.joinPoint, this.aspectJCallback)); verifyNoMoreInteractions(aim); } // SEC-1967 @Test @SuppressWarnings("unchecked") public void invokeWithAspectJCallbackRunAsReplacementCleansAfterException() { SecurityContext ctx = SecurityContextHolder.getContext(); ctx.setAuthentication(this.token); this.token.setAuthenticated(true); final RunAsManager runAs = mock(RunAsManager.class); final RunAsUserToken runAsToken = new RunAsUserToken("key", "someone", "creds", this.token.getAuthorities(), TestingAuthenticationToken.class); this.interceptor.setRunAsManager(runAs); given(runAs.buildRunAs(eq(this.token), any(MethodInvocation.class), any(List.class))).willReturn(runAsToken); given(this.aspectJCallback.proceedWithObject()).willThrow(new RuntimeException()); assertThatExceptionOfType(RuntimeException.class) .isThrownBy(() -> this.interceptor.invoke(this.joinPoint, this.aspectJCallback)); // Check we've changed back assertThat(SecurityContextHolder.getContext()).isSameAs(ctx); assertThat(SecurityContextHolder.getContext().getAuthentication()).isSameAs(this.token); } // SEC-1967 @Test @SuppressWarnings("unchecked") public void invokeRunAsReplacementCleansAfterException() throws Throwable { SecurityContext ctx = SecurityContextHolder.getContext(); ctx.setAuthentication(this.token); this.token.setAuthenticated(true); final RunAsManager runAs = mock(RunAsManager.class); final RunAsUserToken runAsToken = new RunAsUserToken("key", "someone", "creds", this.token.getAuthorities(), TestingAuthenticationToken.class); this.interceptor.setRunAsManager(runAs); given(runAs.buildRunAs(eq(this.token), any(MethodInvocation.class), any(List.class))).willReturn(runAsToken); given(this.joinPoint.proceed()).willThrow(new RuntimeException()); assertThatExceptionOfType(RuntimeException.class).isThrownBy(() -> this.interceptor.invoke(this.joinPoint)); // Check we've changed back assertThat(SecurityContextHolder.getContext()).isSameAs(ctx); assertThat(SecurityContextHolder.getContext().getAuthentication()).isSameAs(this.token); } } ================================================ FILE: access/src/test/java/org/springframework/security/access/intercept/method/MapBasedMethodSecurityMetadataSourceTests.java ================================================ /* * 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 org.springframework.security.access.intercept.method; import java.lang.reflect.Method; import java.util.List; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.access.SecurityConfig; import org.springframework.security.access.method.MapBasedMethodSecurityMetadataSource; import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link MapBasedMethodSecurityMetadataSource}. * * @author Luke Taylor * @since 2.0.4 */ @SuppressWarnings("deprecation") public class MapBasedMethodSecurityMetadataSourceTests { private final List ROLE_A = SecurityConfig.createList("ROLE_A"); private final List ROLE_B = SecurityConfig.createList("ROLE_B"); private MapBasedMethodSecurityMetadataSource mds; private Method someMethodString; private Method someMethodInteger; @BeforeEach public void initialize() throws Exception { this.mds = new MapBasedMethodSecurityMetadataSource(); this.someMethodString = MockService.class.getMethod("someMethod", String.class); this.someMethodInteger = MockService.class.getMethod("someMethod", Integer.class); } @Test public void wildcardedMatchIsOverwrittenByMoreSpecificMatch() { this.mds.addSecureMethod(MockService.class, "some*", this.ROLE_A); this.mds.addSecureMethod(MockService.class, "someMethod*", this.ROLE_B); assertThat(this.mds.getAttributes(this.someMethodInteger, MockService.class)).isEqualTo(this.ROLE_B); } @Test public void methodsWithDifferentArgumentsAreMatchedCorrectly() { this.mds.addSecureMethod(MockService.class, this.someMethodInteger, this.ROLE_A); this.mds.addSecureMethod(MockService.class, this.someMethodString, this.ROLE_B); assertThat(this.mds.getAttributes(this.someMethodInteger, MockService.class)).isEqualTo(this.ROLE_A); assertThat(this.mds.getAttributes(this.someMethodString, MockService.class)).isEqualTo(this.ROLE_B); } @SuppressWarnings("unused") private class MockService { public void someMethod(String s) { } public void someMethod(Integer i) { } } } ================================================ FILE: access/src/test/java/org/springframework/security/access/intercept/method/MethodInvocationPrivilegeEvaluatorTests.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.access.intercept.method; import java.util.List; import org.aopalliance.intercept.MethodInvocation; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.security.access.AccessDecisionManager; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.access.ITargetObject; import org.springframework.security.access.OtherTargetObject; import org.springframework.security.access.SecurityConfig; import org.springframework.security.access.TargetObject; import org.springframework.security.access.intercept.MethodInvocationPrivilegeEvaluator; import org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor; import org.springframework.security.access.method.MethodSecurityMetadataSource; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.util.MethodInvocationUtils; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.willThrow; import static org.mockito.Mockito.mock; /** * Tests * {@link org.springframework.security.access.intercept.MethodInvocationPrivilegeEvaluator} * . * * @author Ben Alex */ @SuppressWarnings("deprecation") public class MethodInvocationPrivilegeEvaluatorTests { private TestingAuthenticationToken token; private MethodSecurityInterceptor interceptor; private AccessDecisionManager adm; private MethodSecurityMetadataSource mds; private final List role = SecurityConfig.createList("ROLE_IGNORED"); @BeforeEach public final void setUp() { SecurityContextHolder.clearContext(); this.interceptor = new MethodSecurityInterceptor(); this.token = new TestingAuthenticationToken("Test", "Password", "ROLE_SOMETHING"); this.adm = mock(AccessDecisionManager.class); AuthenticationManager authman = mock(AuthenticationManager.class); this.mds = mock(MethodSecurityMetadataSource.class); this.interceptor.setAccessDecisionManager(this.adm); this.interceptor.setAuthenticationManager(authman); this.interceptor.setSecurityMetadataSource(this.mds); } @Test public void allowsAccessUsingCreate() throws Exception { Object object = new TargetObject(); final MethodInvocation mi = MethodInvocationUtils.create(object, "makeLowerCase", "foobar"); MethodInvocationPrivilegeEvaluator mipe = new MethodInvocationPrivilegeEvaluator(); given(this.mds.getAttributes(mi)).willReturn(this.role); mipe.setSecurityInterceptor(this.interceptor); mipe.afterPropertiesSet(); assertThat(mipe.isAllowed(mi, this.token)).isTrue(); } @Test public void allowsAccessUsingCreateFromClass() { final MethodInvocation mi = MethodInvocationUtils.createFromClass(new OtherTargetObject(), ITargetObject.class, "makeLowerCase", new Class[] { String.class }, new Object[] { "Hello world" }); MethodInvocationPrivilegeEvaluator mipe = new MethodInvocationPrivilegeEvaluator(); mipe.setSecurityInterceptor(this.interceptor); given(this.mds.getAttributes(mi)).willReturn(this.role); assertThat(mipe.isAllowed(mi, this.token)).isTrue(); } @Test public void declinesAccessUsingCreate() { Object object = new TargetObject(); final MethodInvocation mi = MethodInvocationUtils.create(object, "makeLowerCase", "foobar"); MethodInvocationPrivilegeEvaluator mipe = new MethodInvocationPrivilegeEvaluator(); mipe.setSecurityInterceptor(this.interceptor); given(this.mds.getAttributes(mi)).willReturn(this.role); willThrow(new AccessDeniedException("rejected")).given(this.adm).decide(this.token, mi, this.role); assertThat(mipe.isAllowed(mi, this.token)).isFalse(); } @Test public void declinesAccessUsingCreateFromClass() { final MethodInvocation mi = MethodInvocationUtils.createFromClass(new OtherTargetObject(), ITargetObject.class, "makeLowerCase", new Class[] { String.class }, new Object[] { "helloWorld" }); MethodInvocationPrivilegeEvaluator mipe = new MethodInvocationPrivilegeEvaluator(); mipe.setSecurityInterceptor(this.interceptor); given(this.mds.getAttributes(mi)).willReturn(this.role); willThrow(new AccessDeniedException("rejected")).given(this.adm).decide(this.token, mi, this.role); assertThat(mipe.isAllowed(mi, this.token)).isFalse(); } } ================================================ FILE: access/src/test/java/org/springframework/security/access/intercept/method/MockMethodInvocation.java ================================================ /* * 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 org.springframework.security.access.intercept.method; import java.lang.reflect.AccessibleObject; import java.lang.reflect.Method; import org.aopalliance.intercept.MethodInvocation; @SuppressWarnings("unchecked") public class MockMethodInvocation implements MethodInvocation { private Method method; private Object targetObject; private Object[] arguments = new Object[0]; public MockMethodInvocation(Object targetObject, Class clazz, String methodName, Class[] parameterTypes, Object[] arguments) throws NoSuchMethodException { this(targetObject, clazz, methodName, parameterTypes); this.arguments = arguments; } public MockMethodInvocation(Object targetObject, Class clazz, String methodName, Class... parameterTypes) throws NoSuchMethodException { this(targetObject, clazz.getMethod(methodName, parameterTypes)); this.targetObject = targetObject; } public MockMethodInvocation(Object targetObject, Method method) { this.targetObject = targetObject; this.method = method; } @Override public Object[] getArguments() { return this.arguments; } @Override public Method getMethod() { return this.method; } @Override public AccessibleObject getStaticPart() { return null; } @Override public Object getThis() { return this.targetObject; } @Override public Object proceed() { return null; } } ================================================ FILE: access/src/test/java/org/springframework/security/access/method/DelegatingMethodSecurityMetadataSourceTests.java ================================================ /* * 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 org.springframework.security.access.method; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import org.aopalliance.intercept.MethodInvocation; import org.junit.jupiter.api.Test; import org.mockito.ArgumentMatchers; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.util.SimpleMethodInvocation; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; /** * @author Luke Taylor */ @SuppressWarnings({ "unchecked", "deprecation" }) public class DelegatingMethodSecurityMetadataSourceTests { DelegatingMethodSecurityMetadataSource mds; @Test public void returnsEmptyListIfDelegateReturnsNull() throws Exception { List sources = new ArrayList(); MethodSecurityMetadataSource delegate = mock(MethodSecurityMetadataSource.class); given(delegate.getAttributes(ArgumentMatchers.any(), ArgumentMatchers.any(Class.class))) .willReturn(null); sources.add(delegate); this.mds = new DelegatingMethodSecurityMetadataSource(sources); assertThat(this.mds.getMethodSecurityMetadataSources()).isSameAs(sources); assertThat(this.mds.getAllConfigAttributes()).isEmpty(); MethodInvocation mi = new SimpleMethodInvocation(null, String.class.getMethod("toString")); assertThat(this.mds.getAttributes(mi)).isEqualTo(Collections.emptyList()); // Exercise the cached case assertThat(this.mds.getAttributes(mi)).isEqualTo(Collections.emptyList()); } @Test public void returnsDelegateAttributes() throws Exception { List sources = new ArrayList(); MethodSecurityMetadataSource delegate = mock(MethodSecurityMetadataSource.class); ConfigAttribute ca = mock(ConfigAttribute.class); List attributes = Arrays.asList(ca); Method toString = String.class.getMethod("toString"); given(delegate.getAttributes(toString, String.class)).willReturn(attributes); sources.add(delegate); this.mds = new DelegatingMethodSecurityMetadataSource(sources); assertThat(this.mds.getMethodSecurityMetadataSources()).isSameAs(sources); assertThat(this.mds.getAllConfigAttributes()).isEmpty(); MethodInvocation mi = new SimpleMethodInvocation("", toString); assertThat(this.mds.getAttributes(mi)).isSameAs(attributes); // Exercise the cached case assertThat(this.mds.getAttributes(mi)).isSameAs(attributes); assertThat(this.mds.getAttributes(new SimpleMethodInvocation(null, String.class.getMethod("length")))) .isEmpty(); } } ================================================ FILE: access/src/test/java/org/springframework/security/access/prepost/PostInvocationAdviceProviderTests.java ================================================ /* * 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 org.springframework.security.access.prepost; import org.aopalliance.intercept.MethodInvocation; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.aop.ProxyMethodInvocation; import org.springframework.security.access.intercept.aspectj.MethodInvocationAdapter; import static org.assertj.core.api.Assertions.assertThat; @ExtendWith(MockitoExtension.class) @SuppressWarnings("deprecation") public class PostInvocationAdviceProviderTests { @Mock private PostInvocationAuthorizationAdvice authorizationAdvice; private PostInvocationAdviceProvider postInvocationAdviceProvider; @BeforeEach public void setUp() { this.postInvocationAdviceProvider = new PostInvocationAdviceProvider(this.authorizationAdvice); } @Test public void supportsMethodInvocation() { assertThat(this.postInvocationAdviceProvider.supports(MethodInvocation.class)).isTrue(); } @Test public void supportsProxyMethodInvocation() { assertThat(this.postInvocationAdviceProvider.supports(ProxyMethodInvocation.class)).isTrue(); } @Test public void supportsMethodInvocationAdapter() { assertThat(this.postInvocationAdviceProvider.supports(MethodInvocationAdapter.class)).isTrue(); } } ================================================ FILE: access/src/test/java/org/springframework/security/access/prepost/PreInvocationAuthorizationAdviceVoterTests.java ================================================ /* * 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 org.springframework.security.access.prepost; import org.aopalliance.intercept.MethodInvocation; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.aop.ProxyMethodInvocation; import org.springframework.security.access.intercept.aspectj.MethodInvocationAdapter; import static org.assertj.core.api.Assertions.assertThat; @ExtendWith(MockitoExtension.class) @SuppressWarnings("deprecation") public class PreInvocationAuthorizationAdviceVoterTests { @Mock private PreInvocationAuthorizationAdvice authorizationAdvice; private PreInvocationAuthorizationAdviceVoter voter; @BeforeEach public void setUp() { this.voter = new PreInvocationAuthorizationAdviceVoter(this.authorizationAdvice); } @Test public void supportsMethodInvocation() { assertThat(this.voter.supports(MethodInvocation.class)).isTrue(); } // SEC-2031 @Test public void supportsProxyMethodInvocation() { assertThat(this.voter.supports(ProxyMethodInvocation.class)).isTrue(); } @Test public void supportsMethodInvocationAdapter() { assertThat(this.voter.supports(MethodInvocationAdapter.class)).isTrue(); } } ================================================ FILE: access/src/test/java/org/springframework/security/access/vote/AbstractAccessDecisionManagerTests.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.access.vote; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Vector; import org.junit.jupiter.api.Test; import org.springframework.security.access.AccessDecisionVoter; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.access.SecurityConfig; import org.springframework.security.core.Authentication; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; /** * Tests {@link AbstractAccessDecisionManager}. * * @author Ben Alex */ @SuppressWarnings({ "unchecked", "deprecation" }) public class AbstractAccessDecisionManagerTests { @Test public void testAllowIfAccessDecisionManagerDefaults() { List list = new Vector(); DenyAgainVoter denyVoter = new DenyAgainVoter(); list.add(denyVoter); MockDecisionManagerImpl mock = new MockDecisionManagerImpl(list); assertThat(!mock.isAllowIfAllAbstainDecisions()).isTrue(); // default mock.setAllowIfAllAbstainDecisions(true); assertThat(mock.isAllowIfAllAbstainDecisions()).isTrue(); // changed } @Test public void testDelegatesSupportsClassRequests() { List list = new Vector(); list.add(new DenyVoter()); list.add(new MockStringOnlyVoter()); MockDecisionManagerImpl mock = new MockDecisionManagerImpl(list); assertThat(mock.supports(String.class)).isTrue(); assertThat(!mock.supports(Integer.class)).isTrue(); } @Test public void testDelegatesSupportsRequests() { List list = new Vector(); DenyVoter voter = new DenyVoter(); DenyAgainVoter denyVoter = new DenyAgainVoter(); list.add(voter); list.add(denyVoter); MockDecisionManagerImpl mock = new MockDecisionManagerImpl(list); ConfigAttribute attr = new SecurityConfig("DENY_AGAIN_FOR_SURE"); assertThat(mock.supports(attr)).isTrue(); ConfigAttribute badAttr = new SecurityConfig("WE_DONT_SUPPORT_THIS"); assertThat(!mock.supports(badAttr)).isTrue(); } @Test public void testProperlyStoresListOfVoters() { List list = new Vector(); DenyVoter voter = new DenyVoter(); DenyAgainVoter denyVoter = new DenyAgainVoter(); list.add(voter); list.add(denyVoter); MockDecisionManagerImpl mock = new MockDecisionManagerImpl(list); assertThat(mock.getDecisionVoters()).hasSize(list.size()); } @Test public void testRejectsEmptyList() { assertThatIllegalArgumentException().isThrownBy(() -> new MockDecisionManagerImpl(Collections.emptyList())); } @Test public void testRejectsNullVotersList() { assertThatIllegalArgumentException().isThrownBy(() -> new MockDecisionManagerImpl(null)); } @Test public void testRoleVoterAlwaysReturnsTrueToSupports() { RoleVoter rv = new RoleVoter(); assertThat(rv.supports(String.class)).isTrue(); } @Test public void testWillNotStartIfDecisionVotersNotSet() { assertThatIllegalArgumentException().isThrownBy(() -> new MockDecisionManagerImpl(null)); } private class MockDecisionManagerImpl extends AbstractAccessDecisionManager { protected MockDecisionManagerImpl(List> decisionVoters) { super(decisionVoters); } @Override public void decide(Authentication authentication, Object object, Collection configAttributes) { } } private class MockStringOnlyVoter implements AccessDecisionVoter { @Override public boolean supports(Class clazz) { return String.class.isAssignableFrom(clazz); } @Override public boolean supports(ConfigAttribute attribute) { throw new UnsupportedOperationException("mock method not implemented"); } @Override public int vote(Authentication authentication, Object object, Collection attributes) { throw new UnsupportedOperationException("mock method not implemented"); } } } ================================================ FILE: access/src/test/java/org/springframework/security/access/vote/AbstractAclVoterTests.java ================================================ /* * 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 org.springframework.security.access.vote; import java.util.ArrayList; import java.util.Collection; import org.aopalliance.intercept.MethodInvocation; import org.junit.jupiter.api.Test; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.core.Authentication; import org.springframework.security.util.MethodInvocationUtils; import static org.assertj.core.api.Assertions.assertThat; /** * @author Luke Taylor */ @SuppressWarnings("deprecation") public class AbstractAclVoterTests { private AbstractAclVoter voter = new AbstractAclVoter() { @Override public boolean supports(ConfigAttribute attribute) { return false; } @Override public int vote(Authentication authentication, MethodInvocation object, Collection attributes) { return 0; } }; @Test public void supportsMethodInvocations() { assertThat(this.voter.supports(MethodInvocation.class)).isTrue(); assertThat(this.voter.supports(String.class)).isFalse(); } @Test public void expectedDomainObjectArgumentIsReturnedFromMethodInvocation() { this.voter.setProcessDomainObjectClass(String.class); MethodInvocation mi = MethodInvocationUtils.create(new TestClass(), "methodTakingAString", "The Argument"); assertThat(this.voter.getDomainObjectInstance(mi)).isEqualTo("The Argument"); } @Test public void correctArgumentIsSelectedFromMultipleArgs() { this.voter.setProcessDomainObjectClass(String.class); MethodInvocation mi = MethodInvocationUtils.create(new TestClass(), "methodTakingAListAndAString", new ArrayList<>(), "The Argument"); assertThat(this.voter.getDomainObjectInstance(mi)).isEqualTo("The Argument"); } @SuppressWarnings("unused") private static class TestClass { public void methodTakingAString(String arg) { } public void methodTaking2Strings(String arg1, String arg2) { } public void methodTakingAListAndAString(ArrayList arg1, String arg2) { } } } ================================================ FILE: access/src/test/java/org/springframework/security/access/vote/AffirmativeBasedTests.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.access.vote; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.security.access.AccessDecisionVoter; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.core.Authentication; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; /** * Tests {@link AffirmativeBased}. * * @author Ben Alex */ @SuppressWarnings("deprecation") public class AffirmativeBasedTests { private final List attrs = new ArrayList<>(); private final Authentication user = new TestingAuthenticationToken("somebody", "password", "ROLE_1", "ROLE_2"); private AffirmativeBased mgr; private AccessDecisionVoter grant; private AccessDecisionVoter abstain; private AccessDecisionVoter deny; @BeforeEach @SuppressWarnings("unchecked") public void setup() { this.grant = mock(AccessDecisionVoter.class); this.abstain = mock(AccessDecisionVoter.class); this.deny = mock(AccessDecisionVoter.class); given(this.grant.vote(any(Authentication.class), any(Object.class), any(List.class))) .willReturn(AccessDecisionVoter.ACCESS_GRANTED); given(this.abstain.vote(any(Authentication.class), any(Object.class), any(List.class))) .willReturn(AccessDecisionVoter.ACCESS_ABSTAIN); given(this.deny.vote(any(Authentication.class), any(Object.class), any(List.class))) .willReturn(AccessDecisionVoter.ACCESS_DENIED); } @Test public void oneAffirmativeVoteOneDenyVoteOneAbstainVoteGrantsAccess() throws Exception { this.mgr = new AffirmativeBased( Arrays.>asList(this.grant, this.deny, this.abstain)); this.mgr.afterPropertiesSet(); this.mgr.decide(this.user, new Object(), this.attrs); } @Test public void oneDenyVoteOneAbstainVoteOneAffirmativeVoteGrantsAccess() { this.mgr = new AffirmativeBased( Arrays.>asList(this.deny, this.abstain, this.grant)); this.mgr.decide(this.user, new Object(), this.attrs); } @Test public void oneAffirmativeVoteTwoAbstainVotesGrantsAccess() { this.mgr = new AffirmativeBased( Arrays.>asList(this.grant, this.abstain, this.abstain)); this.mgr.decide(this.user, new Object(), this.attrs); } @Test public void oneDenyVoteTwoAbstainVotesDeniesAccess() { this.mgr = new AffirmativeBased( Arrays.>asList(this.deny, this.abstain, this.abstain)); assertThatExceptionOfType(AccessDeniedException.class) .isThrownBy(() -> this.mgr.decide(this.user, new Object(), this.attrs)); } @Test public void onlyAbstainVotesDeniesAccessWithDefault() { this.mgr = new AffirmativeBased( Arrays.>asList(this.abstain, this.abstain, this.abstain)); assertThat(!this.mgr.isAllowIfAllAbstainDecisions()).isTrue(); // check default assertThatExceptionOfType(AccessDeniedException.class) .isThrownBy(() -> this.mgr.decide(this.user, new Object(), this.attrs)); } @Test public void testThreeAbstainVotesGrantsAccessIfAllowIfAllAbstainDecisionsIsSet() { this.mgr = new AffirmativeBased( Arrays.>asList(this.abstain, this.abstain, this.abstain)); this.mgr.setAllowIfAllAbstainDecisions(true); assertThat(this.mgr.isAllowIfAllAbstainDecisions()).isTrue(); // check changed this.mgr.decide(this.user, new Object(), this.attrs); } } ================================================ FILE: access/src/test/java/org/springframework/security/access/vote/AuthenticatedVoterTests.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.access.vote; import java.util.List; import org.junit.jupiter.api.Test; import org.springframework.security.access.AccessDecisionVoter; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.access.SecurityConfig; import org.springframework.security.authentication.AnonymousAuthenticationToken; import org.springframework.security.authentication.RememberMeAuthenticationToken; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.authority.AuthorityUtils; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; /** * Tests {@link AuthenticatedVoter}. * * @author Ben Alex */ @SuppressWarnings("deprecation") public class AuthenticatedVoterTests { private Authentication createAnonymous() { return new AnonymousAuthenticationToken("ignored", "ignored", AuthorityUtils.createAuthorityList("ignored")); } private Authentication createFullyAuthenticated() { return UsernamePasswordAuthenticationToken.authenticated("ignored", "ignored", AuthorityUtils.createAuthorityList("ignored")); } private Authentication createRememberMe() { return new RememberMeAuthenticationToken("ignored", "ignored", AuthorityUtils.createAuthorityList("ignored")); } @Test public void testAnonymousWorks() { AuthenticatedVoter voter = new AuthenticatedVoter(); List def = SecurityConfig.createList(AuthenticatedVoter.IS_AUTHENTICATED_ANONYMOUSLY); assertThat(AccessDecisionVoter.ACCESS_GRANTED).isEqualTo(voter.vote(createAnonymous(), null, def)); assertThat(AccessDecisionVoter.ACCESS_GRANTED).isEqualTo(voter.vote(createRememberMe(), null, def)); assertThat(AccessDecisionVoter.ACCESS_GRANTED).isEqualTo(voter.vote(createFullyAuthenticated(), null, def)); assertThat(AccessDecisionVoter.ACCESS_DENIED).isEqualTo(voter.vote(null, null, def)); } @Test public void testFullyWorks() { AuthenticatedVoter voter = new AuthenticatedVoter(); List def = SecurityConfig.createList(AuthenticatedVoter.IS_AUTHENTICATED_FULLY); assertThat(AccessDecisionVoter.ACCESS_DENIED).isEqualTo(voter.vote(createAnonymous(), null, def)); assertThat(AccessDecisionVoter.ACCESS_DENIED).isEqualTo(voter.vote(createRememberMe(), null, def)); assertThat(AccessDecisionVoter.ACCESS_GRANTED).isEqualTo(voter.vote(createFullyAuthenticated(), null, def)); assertThat(AccessDecisionVoter.ACCESS_DENIED).isEqualTo(voter.vote(null, null, def)); } @Test public void testRememberMeWorks() { AuthenticatedVoter voter = new AuthenticatedVoter(); List def = SecurityConfig.createList(AuthenticatedVoter.IS_AUTHENTICATED_REMEMBERED); assertThat(AccessDecisionVoter.ACCESS_DENIED).isEqualTo(voter.vote(createAnonymous(), null, def)); assertThat(AccessDecisionVoter.ACCESS_GRANTED).isEqualTo(voter.vote(createRememberMe(), null, def)); assertThat(AccessDecisionVoter.ACCESS_GRANTED).isEqualTo(voter.vote(createFullyAuthenticated(), null, def)); assertThat(AccessDecisionVoter.ACCESS_DENIED).isEqualTo(voter.vote(null, null, def)); } @Test public void testSetterRejectsNull() { AuthenticatedVoter voter = new AuthenticatedVoter(); assertThatIllegalArgumentException().isThrownBy(() -> voter.setAuthenticationTrustResolver(null)); } @Test public void testSupports() { AuthenticatedVoter voter = new AuthenticatedVoter(); assertThat(voter.supports(String.class)).isTrue(); assertThat(voter.supports(new SecurityConfig(AuthenticatedVoter.IS_AUTHENTICATED_ANONYMOUSLY))).isTrue(); assertThat(voter.supports(new SecurityConfig(AuthenticatedVoter.IS_AUTHENTICATED_FULLY))).isTrue(); assertThat(voter.supports(new SecurityConfig(AuthenticatedVoter.IS_AUTHENTICATED_REMEMBERED))).isTrue(); assertThat(voter.supports(new SecurityConfig("FOO"))).isFalse(); } } ================================================ FILE: access/src/test/java/org/springframework/security/access/vote/ConsensusBasedTests.java ================================================ /* * Copyright 2004 Acegi Technology Pty Limited * * 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 org.springframework.security.access.vote; import java.util.List; import java.util.Vector; import org.junit.jupiter.api.Test; import org.springframework.security.access.AccessDecisionVoter; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.access.SecurityConfig; import org.springframework.security.authentication.TestingAuthenticationToken; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; /** * Tests {@link ConsensusBased}. * * @author Ben Alex */ @SuppressWarnings("deprecation") public class ConsensusBasedTests { @Test public void testOneAffirmativeVoteOneDenyVoteOneAbstainVoteDeniesAccessWithoutDefault() { TestingAuthenticationToken auth = makeTestToken(); ConsensusBased mgr = makeDecisionManager(); mgr.setAllowIfEqualGrantedDeniedDecisions(false); assertThat(!mgr.isAllowIfEqualGrantedDeniedDecisions()).isTrue(); // check changed List config = SecurityConfig.createList("ROLE_1", "DENY_FOR_SURE"); assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(() -> mgr.decide(auth, new Object(), config)); } @Test public void testOneAffirmativeVoteOneDenyVoteOneAbstainVoteGrantsAccessWithDefault() { TestingAuthenticationToken auth = makeTestToken(); ConsensusBased mgr = makeDecisionManager(); assertThat(mgr.isAllowIfEqualGrantedDeniedDecisions()).isTrue(); // check default List config = SecurityConfig.createList("ROLE_1", "DENY_FOR_SURE"); mgr.decide(auth, new Object(), config); } @Test public void testOneAffirmativeVoteTwoAbstainVotesGrantsAccess() { TestingAuthenticationToken auth = makeTestToken(); ConsensusBased mgr = makeDecisionManager(); mgr.decide(auth, new Object(), SecurityConfig.createList("ROLE_2")); } @Test public void testOneDenyVoteTwoAbstainVotesDeniesAccess() { TestingAuthenticationToken auth = makeTestToken(); ConsensusBased mgr = makeDecisionManager(); assertThatExceptionOfType(AccessDeniedException.class) .isThrownBy(() -> mgr.decide(auth, new Object(), SecurityConfig.createList("ROLE_WE_DO_NOT_HAVE"))); } @Test public void testThreeAbstainVotesDeniesAccessWithDefault() { TestingAuthenticationToken auth = makeTestToken(); ConsensusBased mgr = makeDecisionManager(); assertThat(!mgr.isAllowIfAllAbstainDecisions()).isTrue(); // check default assertThatExceptionOfType(AccessDeniedException.class) .isThrownBy(() -> mgr.decide(auth, new Object(), SecurityConfig.createList("IGNORED_BY_ALL"))); } @Test public void testThreeAbstainVotesGrantsAccessWithoutDefault() { TestingAuthenticationToken auth = makeTestToken(); ConsensusBased mgr = makeDecisionManager(); mgr.setAllowIfAllAbstainDecisions(true); assertThat(mgr.isAllowIfAllAbstainDecisions()).isTrue(); // check changed mgr.decide(auth, new Object(), SecurityConfig.createList("IGNORED_BY_ALL")); } @Test public void testTwoAffirmativeVotesTwoAbstainVotesGrantsAccess() { TestingAuthenticationToken auth = makeTestToken(); ConsensusBased mgr = makeDecisionManager(); mgr.decide(auth, new Object(), SecurityConfig.createList("ROLE_1", "ROLE_2")); } private ConsensusBased makeDecisionManager() { RoleVoter roleVoter = new RoleVoter(); DenyVoter denyForSureVoter = new DenyVoter(); DenyAgainVoter denyAgainForSureVoter = new DenyAgainVoter(); List> voters = new Vector<>(); voters.add(roleVoter); voters.add(denyForSureVoter); voters.add(denyAgainForSureVoter); return new ConsensusBased(voters); } private TestingAuthenticationToken makeTestToken() { return new TestingAuthenticationToken("somebody", "password", "ROLE_1", "ROLE_2"); } } ================================================ FILE: access/src/test/java/org/springframework/security/access/vote/DenyAgainVoter.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.access.vote; import java.util.Collection; import org.springframework.security.access.AccessDecisionVoter; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.core.Authentication; /** * Implementation of an {@link AccessDecisionVoter} for unit testing. *

* If the {@link ConfigAttribute#getAttribute()} has a value of * DENY_AGAIN_FOR_SURE, the voter will vote to deny access. *

* All comparisons are case sensitive. * * @author Ben Alex */ @SuppressWarnings("deprecation") public class DenyAgainVoter implements AccessDecisionVoter { @Override public boolean supports(ConfigAttribute attribute) { return "DENY_AGAIN_FOR_SURE".equals(attribute.getAttribute()); } @Override public boolean supports(Class clazz) { return true; } @Override public int vote(Authentication authentication, Object object, Collection attributes) { for (ConfigAttribute attribute : attributes) { if (this.supports(attribute)) { return ACCESS_DENIED; } } return ACCESS_ABSTAIN; } } ================================================ FILE: access/src/test/java/org/springframework/security/access/vote/DenyVoter.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.access.vote; import java.util.Collection; import org.springframework.security.access.AccessDecisionVoter; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.core.Authentication; /** * Implementation of an {@link AccessDecisionVoter} for unit testing. *

* If the {@link ConfigAttribute#getAttribute()} has a value of DENY_FOR_SURE * , the voter will vote to deny access. *

*

* All comparisons are case sensitive. *

* * @author Ben Alex */ @SuppressWarnings("deprecation") public class DenyVoter implements AccessDecisionVoter { @Override public boolean supports(ConfigAttribute attribute) { return "DENY_FOR_SURE".equals(attribute.getAttribute()); } @Override public boolean supports(Class clazz) { return true; } @Override public int vote(Authentication authentication, Object object, Collection attributes) { for (ConfigAttribute attribute : attributes) { if (this.supports(attribute)) { return ACCESS_DENIED; } } return ACCESS_ABSTAIN; } } ================================================ FILE: access/src/test/java/org/springframework/security/access/vote/RoleHierarchyVoterTests.java ================================================ /* * 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 org.springframework.security.access.vote; import org.junit.jupiter.api.Test; import org.springframework.security.access.AccessDecisionVoter; import org.springframework.security.access.SecurityConfig; import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl; import org.springframework.security.authentication.TestingAuthenticationToken; import static org.assertj.core.api.Assertions.assertThat; @SuppressWarnings("deprecation") public class RoleHierarchyVoterTests { @Test public void hierarchicalRoleIsIncludedInDecision() { RoleHierarchyImpl roleHierarchyImpl = RoleHierarchyImpl.fromHierarchy("ROLE_A > ROLE_B"); // User has role A, role B is required TestingAuthenticationToken auth = new TestingAuthenticationToken("user", "password", "ROLE_A"); RoleHierarchyVoter voter = new RoleHierarchyVoter(roleHierarchyImpl); assertThat(voter.vote(auth, new Object(), SecurityConfig.createList("ROLE_B"))) .isEqualTo(AccessDecisionVoter.ACCESS_GRANTED); } } ================================================ FILE: access/src/test/java/org/springframework/security/access/vote/RoleVoterTests.java ================================================ /* * 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 org.springframework.security.access.vote; import org.junit.jupiter.api.Test; import org.springframework.security.access.AccessDecisionVoter; import org.springframework.security.access.SecurityConfig; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.core.Authentication; import static org.assertj.core.api.Assertions.assertThat; /** * @author Luke Taylor */ @SuppressWarnings("deprecation") public class RoleVoterTests { @Test public void oneMatchingAttributeGrantsAccess() { RoleVoter voter = new RoleVoter(); voter.setRolePrefix(""); Authentication userAB = new TestingAuthenticationToken("user", "pass", "A", "B"); // Vote on attribute list that has two attributes A and C (i.e. only one matching) assertThat(voter.vote(userAB, this, SecurityConfig.createList("A", "C"))) .isEqualTo(AccessDecisionVoter.ACCESS_GRANTED); } // SEC-3128 @Test public void nullAuthenticationDenies() { RoleVoter voter = new RoleVoter(); voter.setRolePrefix(""); Authentication notAuthenitcated = null; assertThat(voter.vote(notAuthenitcated, this, SecurityConfig.createList("A"))) .isEqualTo(AccessDecisionVoter.ACCESS_DENIED); } } ================================================ FILE: access/src/test/java/org/springframework/security/access/vote/UnanimousBasedTests.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.access.vote; import java.util.List; import java.util.Vector; import org.junit.jupiter.api.Test; import org.springframework.security.access.AccessDecisionVoter; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.access.SecurityConfig; import org.springframework.security.authentication.TestingAuthenticationToken; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; /** * Tests {@link UnanimousBased}. * * @author Ben Alex */ @SuppressWarnings("deprecation") public class UnanimousBasedTests { private UnanimousBased makeDecisionManager() { RoleVoter roleVoter = new RoleVoter(); DenyVoter denyForSureVoter = new DenyVoter(); DenyAgainVoter denyAgainForSureVoter = new DenyAgainVoter(); List> voters = new Vector<>(); voters.add(roleVoter); voters.add(denyForSureVoter); voters.add(denyAgainForSureVoter); return new UnanimousBased(voters); } private UnanimousBased makeDecisionManagerWithFooBarPrefix() { RoleVoter roleVoter = new RoleVoter(); roleVoter.setRolePrefix("FOOBAR_"); DenyVoter denyForSureVoter = new DenyVoter(); DenyAgainVoter denyAgainForSureVoter = new DenyAgainVoter(); List> voters = new Vector<>(); voters.add(roleVoter); voters.add(denyForSureVoter); voters.add(denyAgainForSureVoter); return new UnanimousBased(voters); } private TestingAuthenticationToken makeTestToken() { return new TestingAuthenticationToken("somebody", "password", "ROLE_1", "ROLE_2"); } private TestingAuthenticationToken makeTestTokenWithFooBarPrefix() { return new TestingAuthenticationToken("somebody", "password", "FOOBAR_1", "FOOBAR_2"); } @Test public void testOneAffirmativeVoteOneDenyVoteOneAbstainVoteDeniesAccess() { TestingAuthenticationToken auth = makeTestToken(); UnanimousBased mgr = makeDecisionManager(); List config = SecurityConfig.createList(new String[] { "ROLE_1", "DENY_FOR_SURE" }); assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(() -> mgr.decide(auth, new Object(), config)); } @Test public void testOneAffirmativeVoteTwoAbstainVotesGrantsAccess() { TestingAuthenticationToken auth = makeTestToken(); UnanimousBased mgr = makeDecisionManager(); List config = SecurityConfig.createList("ROLE_2"); mgr.decide(auth, new Object(), config); } @Test public void testOneDenyVoteTwoAbstainVotesDeniesAccess() { TestingAuthenticationToken auth = makeTestToken(); UnanimousBased mgr = makeDecisionManager(); List config = SecurityConfig.createList("ROLE_WE_DO_NOT_HAVE"); assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(() -> mgr.decide(auth, new Object(), config)); } @Test public void testRoleVoterPrefixObserved() { TestingAuthenticationToken auth = makeTestTokenWithFooBarPrefix(); UnanimousBased mgr = makeDecisionManagerWithFooBarPrefix(); List config = SecurityConfig.createList(new String[] { "FOOBAR_1", "FOOBAR_2" }); mgr.decide(auth, new Object(), config); } @Test public void testThreeAbstainVotesDeniesAccessWithDefault() { TestingAuthenticationToken auth = makeTestToken(); UnanimousBased mgr = makeDecisionManager(); assertThat(!mgr.isAllowIfAllAbstainDecisions()).isTrue(); // check default List config = SecurityConfig.createList("IGNORED_BY_ALL"); assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(() -> mgr.decide(auth, new Object(), config)); } @Test public void testThreeAbstainVotesGrantsAccessWithoutDefault() { TestingAuthenticationToken auth = makeTestToken(); UnanimousBased mgr = makeDecisionManager(); mgr.setAllowIfAllAbstainDecisions(true); assertThat(mgr.isAllowIfAllAbstainDecisions()).isTrue(); // check changed List config = SecurityConfig.createList("IGNORED_BY_ALL"); mgr.decide(auth, new Object(), config); } @Test public void testTwoAffirmativeVotesTwoAbstainVotesGrantsAccess() { TestingAuthenticationToken auth = makeTestToken(); UnanimousBased mgr = makeDecisionManager(); List config = SecurityConfig.createList(new String[] { "ROLE_1", "ROLE_2" }); mgr.decide(auth, new Object(), config); } } ================================================ FILE: access/src/test/java/org/springframework/security/acls/afterinvocation/AclEntryAfterInvocationCollectionFilteringProviderTests.java ================================================ /* * 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 org.springframework.security.acls.afterinvocation; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import org.junit.jupiter.api.Test; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.access.SecurityConfig; import org.springframework.security.acls.model.Acl; import org.springframework.security.acls.model.AclService; import org.springframework.security.acls.model.ObjectIdentity; import org.springframework.security.acls.model.ObjectIdentityRetrievalStrategy; import org.springframework.security.acls.model.Permission; import org.springframework.security.acls.model.SidRetrievalStrategy; import org.springframework.security.core.Authentication; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; /** * @author Luke Taylor */ @SuppressWarnings({ "unchecked", "deprecation" }) public class AclEntryAfterInvocationCollectionFilteringProviderTests { @Test public void objectsAreRemovedIfPermissionDenied() { AclService service = mock(AclService.class); Acl acl = mock(Acl.class); given(acl.isGranted(any(), any(), anyBoolean())).willReturn(false); given(service.readAclById(any(), any())).willReturn(acl); AclEntryAfterInvocationCollectionFilteringProvider provider = new AclEntryAfterInvocationCollectionFilteringProvider( service, Arrays.asList(mock(Permission.class))); provider.setObjectIdentityRetrievalStrategy(mock(ObjectIdentityRetrievalStrategy.class)); provider.setProcessDomainObjectClass(Object.class); provider.setSidRetrievalStrategy(mock(SidRetrievalStrategy.class)); Object returned = provider.decide(mock(Authentication.class), new Object(), SecurityConfig.createList("AFTER_ACL_COLLECTION_READ"), new ArrayList(Arrays.asList(new Object(), new Object()))); assertThat(returned).isInstanceOf(List.class); assertThat(((List) returned)).isEmpty(); returned = provider.decide(mock(Authentication.class), new Object(), SecurityConfig.createList("UNSUPPORTED", "AFTER_ACL_COLLECTION_READ"), new Object[] { new Object(), new Object() }); assertThat(returned instanceof Object[]).isTrue(); assertThat(((Object[]) returned).length == 0).isTrue(); } @Test public void accessIsGrantedIfNoAttributesDefined() { AclEntryAfterInvocationCollectionFilteringProvider provider = new AclEntryAfterInvocationCollectionFilteringProvider( mock(AclService.class), Arrays.asList(mock(Permission.class))); Object returned = new Object(); assertThat(returned).isSameAs(provider.decide(mock(Authentication.class), new Object(), Collections.emptyList(), returned)); } @Test public void nullReturnObjectIsIgnored() { AclService service = mock(AclService.class); AclEntryAfterInvocationCollectionFilteringProvider provider = new AclEntryAfterInvocationCollectionFilteringProvider( service, Arrays.asList(mock(Permission.class))); assertThat(provider.decide(mock(Authentication.class), new Object(), SecurityConfig.createList("AFTER_ACL_COLLECTION_READ"), null)) .isNull(); verify(service, never()).readAclById(any(ObjectIdentity.class), any(List.class)); } } ================================================ FILE: access/src/test/java/org/springframework/security/acls/afterinvocation/AclEntryAfterInvocationProviderTests.java ================================================ /* * 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 org.springframework.security.acls.afterinvocation; import java.util.Arrays; import java.util.Collections; import java.util.List; import org.junit.jupiter.api.Test; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.access.SecurityConfig; import org.springframework.security.acls.model.Acl; import org.springframework.security.acls.model.AclService; import org.springframework.security.acls.model.NotFoundException; import org.springframework.security.acls.model.ObjectIdentity; import org.springframework.security.acls.model.ObjectIdentityRetrievalStrategy; import org.springframework.security.acls.model.Permission; import org.springframework.security.acls.model.SidRetrievalStrategy; import org.springframework.security.core.Authentication; import org.springframework.security.core.SpringSecurityMessageSource; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; /** * @author Luke Taylor */ @SuppressWarnings({ "unchecked", "deprecation" }) public class AclEntryAfterInvocationProviderTests { @Test public void rejectsMissingPermissions() { assertThatIllegalArgumentException() .isThrownBy(() -> new AclEntryAfterInvocationProvider(mock(AclService.class), null)); assertThatIllegalArgumentException().isThrownBy( () -> new AclEntryAfterInvocationProvider(mock(AclService.class), Collections.emptyList())); } @Test public void accessIsAllowedIfPermissionIsGranted() { AclService service = mock(AclService.class); Acl acl = mock(Acl.class); given(acl.isGranted(any(List.class), any(List.class), anyBoolean())).willReturn(true); given(service.readAclById(any(), any())).willReturn(acl); AclEntryAfterInvocationProvider provider = new AclEntryAfterInvocationProvider(service, Arrays.asList(mock(Permission.class))); provider.setMessageSource(new SpringSecurityMessageSource()); provider.setObjectIdentityRetrievalStrategy(mock(ObjectIdentityRetrievalStrategy.class)); provider.setProcessDomainObjectClass(Object.class); provider.setSidRetrievalStrategy(mock(SidRetrievalStrategy.class)); Object returned = new Object(); assertThat(returned).isSameAs(provider.decide(mock(Authentication.class), new Object(), SecurityConfig.createList("AFTER_ACL_READ"), returned)); } @Test public void accessIsGrantedIfNoAttributesDefined() { AclEntryAfterInvocationProvider provider = new AclEntryAfterInvocationProvider(mock(AclService.class), Arrays.asList(mock(Permission.class))); Object returned = new Object(); assertThat(returned).isSameAs(provider.decide(mock(Authentication.class), new Object(), Collections.emptyList(), returned)); } @Test public void accessIsGrantedIfObjectTypeNotSupported() { AclEntryAfterInvocationProvider provider = new AclEntryAfterInvocationProvider(mock(AclService.class), Arrays.asList(mock(Permission.class))); provider.setProcessDomainObjectClass(String.class); // Not a String Object returned = new Object(); assertThat(returned).isSameAs(provider.decide(mock(Authentication.class), new Object(), SecurityConfig.createList("AFTER_ACL_READ"), returned)); } @Test public void accessIsDeniedIfPermissionIsNotGranted() { AclService service = mock(AclService.class); Acl acl = mock(Acl.class); given(acl.isGranted(any(List.class), any(List.class), anyBoolean())).willReturn(false); // Try a second time with no permissions found given(acl.isGranted(any(), any(List.class), anyBoolean())).willThrow(new NotFoundException("")); given(service.readAclById(any(), any())).willReturn(acl); AclEntryAfterInvocationProvider provider = new AclEntryAfterInvocationProvider(service, Arrays.asList(mock(Permission.class))); provider.setProcessConfigAttribute("MY_ATTRIBUTE"); provider.setMessageSource(new SpringSecurityMessageSource()); provider.setObjectIdentityRetrievalStrategy(mock(ObjectIdentityRetrievalStrategy.class)); provider.setProcessDomainObjectClass(Object.class); provider.setSidRetrievalStrategy(mock(SidRetrievalStrategy.class)); assertThatExceptionOfType(AccessDeniedException.class) .isThrownBy(() -> provider.decide(mock(Authentication.class), new Object(), SecurityConfig.createList("UNSUPPORTED", "MY_ATTRIBUTE"), new Object())); // Second scenario with no acls found assertThatExceptionOfType(AccessDeniedException.class) .isThrownBy(() -> provider.decide(mock(Authentication.class), new Object(), SecurityConfig.createList("UNSUPPORTED", "MY_ATTRIBUTE"), new Object())); } @Test public void nullReturnObjectIsIgnored() { AclService service = mock(AclService.class); AclEntryAfterInvocationProvider provider = new AclEntryAfterInvocationProvider(service, Arrays.asList(mock(Permission.class))); assertThat(provider.decide(mock(Authentication.class), new Object(), SecurityConfig.createList("AFTER_ACL_COLLECTION_READ"), null)) .isNull(); verify(service, never()).readAclById(any(ObjectIdentity.class), any(List.class)); } } ================================================ FILE: access/src/test/java/org/springframework/security/messaging/access/expression/ExpressionBasedMessageSecurityMetadataSourceFactoryTests.java ================================================ /* * 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 org.springframework.security.messaging.access.expression; import java.util.Collection; import java.util.LinkedHashMap; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.messaging.Message; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.core.Authentication; import org.springframework.security.messaging.access.intercept.MessageSecurityMetadataSource; import org.springframework.security.messaging.util.matcher.MessageMatcher; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.BDDMockito.given; @ExtendWith(MockitoExtension.class) @SuppressWarnings("deprecation") public class ExpressionBasedMessageSecurityMetadataSourceFactoryTests { @Mock MessageMatcher matcher1; @Mock MessageMatcher matcher2; @Mock Message message; @Mock Authentication authentication; String expression1; String expression2; LinkedHashMap, String> matcherToExpression; MessageSecurityMetadataSource source; MessageSecurityExpressionRoot rootObject; @BeforeEach public void setup() { this.expression1 = "permitAll"; this.expression2 = "denyAll"; this.matcherToExpression = new LinkedHashMap<>(); this.matcherToExpression.put(this.matcher1, this.expression1); this.matcherToExpression.put(this.matcher2, this.expression2); this.source = ExpressionBasedMessageSecurityMetadataSourceFactory .createExpressionMessageMetadataSource(this.matcherToExpression); this.rootObject = new MessageSecurityExpressionRoot<>(this.authentication, this.message); } @Test public void createExpressionMessageMetadataSourceNoMatch() { Collection attrs = this.source.getAttributes(this.message); assertThat(attrs).isEmpty(); } @Test public void createExpressionMessageMetadataSourceMatchFirst() { given(this.matcher1.matches(this.message)).willReturn(true); Collection attrs = this.source.getAttributes(this.message); assertThat(attrs).hasSize(1); ConfigAttribute attr = attrs.iterator().next(); assertThat(attr).isInstanceOf(MessageExpressionConfigAttribute.class); assertThat(((MessageExpressionConfigAttribute) attr).getAuthorizeExpression().getValue(this.rootObject)) .isEqualTo(true); } @Test public void createExpressionMessageMetadataSourceMatchSecond() { given(this.matcher2.matches(this.message)).willReturn(true); Collection attrs = this.source.getAttributes(this.message); assertThat(attrs).hasSize(1); ConfigAttribute attr = attrs.iterator().next(); assertThat(attr).isInstanceOf(MessageExpressionConfigAttribute.class); assertThat(((MessageExpressionConfigAttribute) attr).getAuthorizeExpression().getValue(this.rootObject)) .isEqualTo(false); } } ================================================ FILE: access/src/test/java/org/springframework/security/messaging/access/expression/MessageExpressionConfigAttributeTests.java ================================================ /* * 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 org.springframework.security.messaging.access.expression; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.expression.EvaluationContext; import org.springframework.expression.Expression; import org.springframework.messaging.Message; import org.springframework.messaging.simp.SimpMessageHeaderAccessor; import org.springframework.messaging.support.MessageBuilder; import org.springframework.security.messaging.util.matcher.MessageMatcher; import org.springframework.security.messaging.util.matcher.PathPatternMessageMatcher; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @ExtendWith(MockitoExtension.class) @SuppressWarnings("deprecation") public class MessageExpressionConfigAttributeTests { @Mock Expression expression; @Mock MessageMatcher matcher; MessageExpressionConfigAttribute attribute; @BeforeEach public void setup() { this.attribute = new MessageExpressionConfigAttribute(this.expression, this.matcher); } @Test public void constructorNullExpression() { assertThatIllegalArgumentException().isThrownBy(() -> new MessageExpressionConfigAttribute(null, this.matcher)); } @Test public void constructorNullMatcher() { assertThatIllegalArgumentException() .isThrownBy(() -> new MessageExpressionConfigAttribute(this.expression, null)); } @Test public void getAuthorizeExpression() { assertThat(this.attribute.getAuthorizeExpression()).isSameAs(this.expression); } @Test public void getAttribute() { assertThat(this.attribute.getAttribute()).isNull(); } @Test public void toStringUsesExpressionString() { given(this.expression.getExpressionString()).willReturn("toString"); assertThat(this.attribute.toString()).isEqualTo(this.expression.getExpressionString()); } @Test public void postProcessContext() { PathPatternMessageMatcher matcher = PathPatternMessageMatcher.withDefaults().matcher("/topics/{topic}/**"); // @formatter:off Message message = MessageBuilder.withPayload("M") .setHeader(SimpMessageHeaderAccessor.DESTINATION_HEADER, "/topics/someTopic/sub1") .build(); // @formatter:on EvaluationContext context = mock(EvaluationContext.class); this.attribute = new MessageExpressionConfigAttribute(this.expression, matcher); this.attribute.postProcess(context, message); verify(context).setVariable("topic", "someTopic"); } } ================================================ FILE: access/src/test/java/org/springframework/security/messaging/access/expression/MessageExpressionVoterTests.java ================================================ /* * 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 org.springframework.security.messaging.access.expression; import java.util.Arrays; import java.util.Collection; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.expression.EvaluationContext; import org.springframework.expression.Expression; import org.springframework.messaging.Message; import org.springframework.security.access.AccessDecisionVoter; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.access.SecurityConfig; import org.springframework.security.access.expression.SecurityExpressionHandler; import org.springframework.security.core.Authentication; import org.springframework.security.messaging.util.matcher.MessageMatcher; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @ExtendWith(MockitoExtension.class) @SuppressWarnings("deprecation") public class MessageExpressionVoterTests { @Mock Authentication authentication; @Mock Message message; Collection attributes; @Mock Expression expression; @Mock MessageMatcher matcher; @Mock SecurityExpressionHandler expressionHandler; @Mock EvaluationContext evaluationContext; MessageExpressionVoter voter; @BeforeEach public void setup() { this.attributes = Arrays .asList(new MessageExpressionConfigAttribute(this.expression, this.matcher)); this.voter = new MessageExpressionVoter(); } @Test @SuppressWarnings("unchecked") public void voteGranted() { given(this.expression.getValue(any(EvaluationContext.class), eq(Boolean.class))).willReturn(true); given(this.matcher.matcher(any())).willCallRealMethod(); assertThat(this.voter.vote(this.authentication, this.message, this.attributes)) .isEqualTo(AccessDecisionVoter.ACCESS_GRANTED); } @Test @SuppressWarnings("unchecked") public void voteDenied() { given(this.expression.getValue(any(EvaluationContext.class), eq(Boolean.class))).willReturn(false); given(this.matcher.matcher(any())).willCallRealMethod(); assertThat(this.voter.vote(this.authentication, this.message, this.attributes)) .isEqualTo(AccessDecisionVoter.ACCESS_DENIED); } @Test @SuppressWarnings("unchecked") public void voteAbstain() { this.attributes = Arrays.asList(new SecurityConfig("ROLE_USER")); assertThat(this.voter.vote(this.authentication, this.message, this.attributes)) .isEqualTo(AccessDecisionVoter.ACCESS_ABSTAIN); } @Test @SuppressWarnings("unchecked") public void supportsObjectClassFalse() { assertThat(this.voter.supports(Object.class)).isFalse(); } @Test @SuppressWarnings("unchecked") public void supportsMessageClassTrue() { assertThat(this.voter.supports(Message.class)).isTrue(); } @Test public void supportsSecurityConfigFalse() { assertThat(this.voter.supports(new SecurityConfig("ROLE_USER"))).isFalse(); } @Test public void supportsMessageExpressionConfigAttributeTrue() { assertThat(this.voter.supports(new MessageExpressionConfigAttribute(this.expression, this.matcher))).isTrue(); } @Test @SuppressWarnings("unchecked") public void setExpressionHandlerNull() { assertThatIllegalArgumentException().isThrownBy(() -> this.voter.setExpressionHandler(null)); } @Test @SuppressWarnings("unchecked") public void customExpressionHandler() { this.voter.setExpressionHandler(this.expressionHandler); given(this.expressionHandler.createEvaluationContext(this.authentication, this.message)) .willReturn(this.evaluationContext); given(this.expression.getValue(this.evaluationContext, Boolean.class)).willReturn(true); given(this.matcher.matcher(any())).willCallRealMethod(); assertThat(this.voter.vote(this.authentication, this.message, this.attributes)) .isEqualTo(AccessDecisionVoter.ACCESS_GRANTED); verify(this.expressionHandler).createEvaluationContext(this.authentication, this.message); } @Test @SuppressWarnings("unchecked") public void postProcessEvaluationContext() { final MessageExpressionConfigAttribute configAttribute = mock(MessageExpressionConfigAttribute.class); this.voter.setExpressionHandler(this.expressionHandler); given(this.expressionHandler.createEvaluationContext(this.authentication, this.message)) .willReturn(this.evaluationContext); given(configAttribute.getAuthorizeExpression()).willReturn(this.expression); this.attributes = Arrays.asList(configAttribute); given(configAttribute.postProcess(this.evaluationContext, this.message)).willReturn(this.evaluationContext); given(this.expression.getValue(any(EvaluationContext.class), eq(Boolean.class))).willReturn(true); assertThat(this.voter.vote(this.authentication, this.message, this.attributes)) .isEqualTo(AccessDecisionVoter.ACCESS_GRANTED); verify(configAttribute).postProcess(this.evaluationContext, this.message); } } ================================================ FILE: access/src/test/java/org/springframework/security/messaging/access/intercept/ChannelSecurityInterceptorTests.java ================================================ /* * 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 org.springframework.security.messaging.access.intercept; import java.util.Arrays; import java.util.Collection; import java.util.List; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.messaging.Message; import org.springframework.messaging.MessageChannel; import org.springframework.security.access.AccessDecisionManager; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.access.SecurityConfig; import org.springframework.security.access.intercept.RunAsManager; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.willThrow; @ExtendWith(MockitoExtension.class) @SuppressWarnings("deprecation") public class ChannelSecurityInterceptorTests { @Mock Message message; @Mock MessageChannel channel; @Mock MessageSecurityMetadataSource source; @Mock AccessDecisionManager accessDecisionManager; @Mock RunAsManager runAsManager; @Mock Authentication runAs; Authentication originalAuth; List attrs; ChannelSecurityInterceptor interceptor; @BeforeEach public void setup() { this.attrs = Arrays.asList(new SecurityConfig("ROLE_USER")); this.interceptor = new ChannelSecurityInterceptor(this.source); this.interceptor.setAccessDecisionManager(this.accessDecisionManager); this.interceptor.setRunAsManager(this.runAsManager); this.originalAuth = new TestingAuthenticationToken("user", "pass", "ROLE_USER"); SecurityContextHolder.getContext().setAuthentication(this.originalAuth); } @AfterEach public void cleanup() { SecurityContextHolder.clearContext(); } @Test public void constructorMessageSecurityMetadataSourceNull() { assertThatIllegalArgumentException().isThrownBy(() -> new ChannelSecurityInterceptor(null)); } @Test public void getSecureObjectClass() { assertThat(this.interceptor.getSecureObjectClass()).isEqualTo(Message.class); } @Test public void obtainSecurityMetadataSource() { assertThat(this.interceptor.obtainSecurityMetadataSource()).isEqualTo(this.source); } @Test public void preSendNullAttributes() { assertThat(this.interceptor.preSend(this.message, this.channel)).isSameAs(this.message); } @Test public void preSendGrant() { given(this.source.getAttributes(this.message)).willReturn(this.attrs); Message result = this.interceptor.preSend(this.message, this.channel); assertThat(result).isSameAs(this.message); } @Test public void preSendDeny() { given(this.source.getAttributes(this.message)).willReturn(this.attrs); willThrow(new AccessDeniedException("")).given(this.accessDecisionManager) .decide(any(Authentication.class), eq(this.message), eq(this.attrs)); assertThatExceptionOfType(AccessDeniedException.class) .isThrownBy(() -> this.interceptor.preSend(this.message, this.channel)); } @SuppressWarnings("unchecked") @Test public void preSendPostSendRunAs() { given(this.source.getAttributes(this.message)).willReturn(this.attrs); given(this.runAsManager.buildRunAs(any(Authentication.class), any(), any(Collection.class))) .willReturn(this.runAs); Message preSend = this.interceptor.preSend(this.message, this.channel); assertThat(SecurityContextHolder.getContext().getAuthentication()).isSameAs(this.runAs); this.interceptor.postSend(preSend, this.channel, true); assertThat(SecurityContextHolder.getContext().getAuthentication()).isSameAs(this.originalAuth); } @Test public void afterSendCompletionNotTokenMessageNoExceptionThrown() { this.interceptor.afterSendCompletion(this.message, this.channel, true, null); } @SuppressWarnings("unchecked") @Test public void preSendFinallySendRunAs() { given(this.source.getAttributes(this.message)).willReturn(this.attrs); given(this.runAsManager.buildRunAs(any(Authentication.class), any(), any(Collection.class))) .willReturn(this.runAs); Message preSend = this.interceptor.preSend(this.message, this.channel); assertThat(SecurityContextHolder.getContext().getAuthentication()).isSameAs(this.runAs); this.interceptor.afterSendCompletion(preSend, this.channel, true, new RuntimeException()); assertThat(SecurityContextHolder.getContext().getAuthentication()).isSameAs(this.originalAuth); } @Test public void preReceive() { assertThat(this.interceptor.preReceive(this.channel)).isTrue(); } @Test public void postReceive() { assertThat(this.interceptor.postReceive(this.message, this.channel)).isSameAs(this.message); } @Test public void afterReceiveCompletionNullExceptionNoExceptionThrown() { this.interceptor.afterReceiveCompletion(this.message, this.channel, null); } } ================================================ FILE: access/src/test/java/org/springframework/security/messaging/access/intercept/DefaultMessageSecurityMetadataSourceTests.java ================================================ /* * 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 org.springframework.security.messaging.access.intercept; import java.util.Arrays; import java.util.Collection; import java.util.LinkedHashMap; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.messaging.Message; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.access.SecurityConfig; import org.springframework.security.core.Authentication; import org.springframework.security.messaging.util.matcher.MessageMatcher; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.BDDMockito.given; @ExtendWith(MockitoExtension.class) @SuppressWarnings("deprecation") public class DefaultMessageSecurityMetadataSourceTests { @Mock MessageMatcher matcher1; @Mock MessageMatcher matcher2; @Mock Message message; @Mock Authentication authentication; SecurityConfig config1; SecurityConfig config2; LinkedHashMap, Collection> messageMap; MessageSecurityMetadataSource source; @BeforeEach public void setup() { this.messageMap = new LinkedHashMap<>(); this.messageMap.put(this.matcher1, Arrays.asList(this.config1)); this.messageMap.put(this.matcher2, Arrays.asList(this.config2)); this.source = new DefaultMessageSecurityMetadataSource(this.messageMap); } @Test public void getAttributesEmpty() { assertThat(this.source.getAttributes(this.message)).isEmpty(); } @Test public void getAttributesFirst() { given(this.matcher1.matches(this.message)).willReturn(true); assertThat(this.source.getAttributes(this.message)).containsOnly(this.config1); } @Test public void getAttributesSecond() { given(this.matcher1.matches(this.message)).willReturn(true); assertThat(this.source.getAttributes(this.message)).containsOnly(this.config2); } @Test public void getAllConfigAttributes() { assertThat(this.source.getAllConfigAttributes()).containsOnly(this.config1, this.config2); } @Test public void supportsFalse() { assertThat(this.source.supports(Object.class)).isFalse(); } @Test public void supportsTrue() { assertThat(this.source.supports(Message.class)).isTrue(); } } ================================================ FILE: access/src/test/java/org/springframework/security/web/access/DefaultWebInvocationPrivilegeEvaluatorTests.java ================================================ /* * 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 org.springframework.security.web.access; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; import org.mockito.ArgumentMatchers; import org.mockito.BDDMockito; import org.mockito.Mockito; import org.springframework.context.ApplicationEventPublisher; import org.springframework.mock.web.MockServletContext; import org.springframework.security.access.AccessDecisionManager; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.intercept.RunAsManager; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.FilterInvocation; import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource; import org.springframework.security.web.access.intercept.FilterSecurityInterceptor; /** * Tests * {@link org.springframework.security.web.access.DefaultWebInvocationPrivilegeEvaluator}. * * @author Ben Alex */ @SuppressWarnings("deprecation") public class DefaultWebInvocationPrivilegeEvaluatorTests { private AccessDecisionManager adm; private FilterInvocationSecurityMetadataSource ods; private RunAsManager ram; private FilterSecurityInterceptor interceptor; @BeforeEach public final void setUp() { this.interceptor = new FilterSecurityInterceptor(); this.ods = Mockito.mock(FilterInvocationSecurityMetadataSource.class); this.adm = Mockito.mock(AccessDecisionManager.class); this.ram = Mockito.mock(RunAsManager.class); this.interceptor.setAuthenticationManager(Mockito.mock(AuthenticationManager.class)); this.interceptor.setSecurityMetadataSource(this.ods); this.interceptor.setAccessDecisionManager(this.adm); this.interceptor.setRunAsManager(this.ram); this.interceptor.setApplicationEventPublisher(Mockito.mock(ApplicationEventPublisher.class)); SecurityContextHolder.clearContext(); } @Test public void permitsAccessIfNoMatchingAttributesAndPublicInvocationsAllowed() { DefaultWebInvocationPrivilegeEvaluator wipe = new DefaultWebInvocationPrivilegeEvaluator(this.interceptor); BDDMockito.given(this.ods.getAttributes(ArgumentMatchers.any())).willReturn(null); Assertions.assertThat(wipe.isAllowed("/context", "/foo/index.jsp", "GET", Mockito.mock(Authentication.class))) .isTrue(); } @Test public void deniesAccessIfNoMatchingAttributesAndPublicInvocationsNotAllowed() { DefaultWebInvocationPrivilegeEvaluator wipe = new DefaultWebInvocationPrivilegeEvaluator(this.interceptor); BDDMockito.given(this.ods.getAttributes(ArgumentMatchers.any())).willReturn(null); this.interceptor.setRejectPublicInvocations(true); Assertions.assertThat(wipe.isAllowed("/context", "/foo/index.jsp", "GET", Mockito.mock(Authentication.class))) .isFalse(); } @Test public void deniesAccessIfAuthenticationIsNull() { DefaultWebInvocationPrivilegeEvaluator wipe = new DefaultWebInvocationPrivilegeEvaluator(this.interceptor); Assertions.assertThat(wipe.isAllowed("/foo/index.jsp", null)).isFalse(); } @Test public void allowsAccessIfAccessDecisionManagerDoes() { Authentication token = new TestingAuthenticationToken("test", "Password", "MOCK_INDEX"); DefaultWebInvocationPrivilegeEvaluator wipe = new DefaultWebInvocationPrivilegeEvaluator(this.interceptor); Assertions.assertThat(wipe.isAllowed("/foo/index.jsp", token)).isTrue(); } @SuppressWarnings("unchecked") @Test public void deniesAccessIfAccessDecisionManagerDoes() { Authentication token = new TestingAuthenticationToken("test", "Password", "MOCK_INDEX"); DefaultWebInvocationPrivilegeEvaluator wipe = new DefaultWebInvocationPrivilegeEvaluator(this.interceptor); BDDMockito.willThrow(new AccessDeniedException("")) .given(this.adm) .decide(ArgumentMatchers.any(Authentication.class), ArgumentMatchers.any(), ArgumentMatchers.anyList()); Assertions.assertThat(wipe.isAllowed("/foo/index.jsp", token)).isFalse(); } @Test public void isAllowedWhenServletContextIsSetThenPassedFilterInvocationHasServletContext() { Authentication token = new TestingAuthenticationToken("test", "Password", "MOCK_INDEX"); MockServletContext servletContext = new MockServletContext(); ArgumentCaptor filterInvocationArgumentCaptor = ArgumentCaptor .forClass(FilterInvocation.class); DefaultWebInvocationPrivilegeEvaluator wipe = new DefaultWebInvocationPrivilegeEvaluator(this.interceptor); wipe.setServletContext(servletContext); wipe.isAllowed("/foo/index.jsp", token); Mockito.verify(this.adm) .decide(ArgumentMatchers.eq(token), filterInvocationArgumentCaptor.capture(), ArgumentMatchers.any()); Assertions.assertThat(filterInvocationArgumentCaptor.getValue().getRequest().getServletContext()).isNotNull(); } } ================================================ FILE: access/src/test/java/org/springframework/security/web/access/channel/ChannelDecisionManagerImplTests.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.web.access.channel; import java.io.IOException; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.Vector; import jakarta.servlet.FilterChain; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.access.SecurityConfig; import org.springframework.security.web.FilterInvocation; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.assertj.core.api.Assertions.fail; import static org.mockito.Mockito.mock; /** * Tests {@link ChannelDecisionManagerImpl}. * * @author Ben Alex */ @SuppressWarnings({ "unchecked", "deprecation" }) public class ChannelDecisionManagerImplTests { @Test public void testCannotSetEmptyChannelProcessorsList() throws Exception { ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl(); assertThatIllegalArgumentException().isThrownBy(() -> { cdm.setChannelProcessors(new Vector()); cdm.afterPropertiesSet(); }).withMessage("A list of ChannelProcessors is required"); } @Test public void testCannotSetIncorrectObjectTypesIntoChannelProcessorsList() { ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl(); List list = new Vector(); list.add("THIS IS NOT A CHANNELPROCESSOR"); assertThatIllegalArgumentException().isThrownBy(() -> cdm.setChannelProcessors(list)); } @Test public void testCannotSetNullChannelProcessorsList() throws Exception { ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl(); assertThatIllegalArgumentException().isThrownBy(() -> { cdm.setChannelProcessors(null); cdm.afterPropertiesSet(); }).withMessage("A list of ChannelProcessors is required"); } @Test public void testDecideIsOperational() throws Exception { ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl(); MockChannelProcessor cpXyz = new MockChannelProcessor("xyz", false); MockChannelProcessor cpAbc = new MockChannelProcessor("abc", true); List list = new Vector(); list.add(cpXyz); list.add(cpAbc); cdm.setChannelProcessors(list); cdm.afterPropertiesSet(); MockHttpServletRequest request = new MockHttpServletRequest(); MockHttpServletResponse response = new MockHttpServletResponse(); FilterInvocation fi = new FilterInvocation(request, response, mock(FilterChain.class)); List cad = SecurityConfig.createList("xyz"); cdm.decide(fi, cad); Assertions.assertThat(fi.getResponse().isCommitted()).isTrue(); } @Test public void testAnyChannelAttributeCausesProcessorsToBeSkipped() throws Exception { ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl(); MockChannelProcessor cpAbc = new MockChannelProcessor("abc", true); List list = new Vector(); list.add(cpAbc); cdm.setChannelProcessors(list); cdm.afterPropertiesSet(); MockHttpServletRequest request = new MockHttpServletRequest(); MockHttpServletResponse response = new MockHttpServletResponse(); FilterInvocation fi = new FilterInvocation(request, response, mock(FilterChain.class)); cdm.decide(fi, SecurityConfig.createList(new String[] { "abc", "ANY_CHANNEL" })); Assertions.assertThat(fi.getResponse().isCommitted()).isFalse(); } @Test public void testDecideIteratesAllProcessorsIfNoneCommitAResponse() throws Exception { ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl(); MockChannelProcessor cpXyz = new MockChannelProcessor("xyz", false); MockChannelProcessor cpAbc = new MockChannelProcessor("abc", false); List list = new Vector(); list.add(cpXyz); list.add(cpAbc); cdm.setChannelProcessors(list); cdm.afterPropertiesSet(); MockHttpServletRequest request = new MockHttpServletRequest(); MockHttpServletResponse response = new MockHttpServletResponse(); FilterInvocation fi = new FilterInvocation(request, response, mock(FilterChain.class)); cdm.decide(fi, SecurityConfig.createList("SOME_ATTRIBUTE_NO_PROCESSORS_SUPPORT")); Assertions.assertThat(fi.getResponse().isCommitted()).isFalse(); } @Test public void testDelegatesSupports() throws Exception { ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl(); MockChannelProcessor cpXyz = new MockChannelProcessor("xyz", false); MockChannelProcessor cpAbc = new MockChannelProcessor("abc", false); List list = new Vector(); list.add(cpXyz); list.add(cpAbc); cdm.setChannelProcessors(list); cdm.afterPropertiesSet(); assertThat(cdm.supports(new SecurityConfig("xyz"))).isTrue(); assertThat(cdm.supports(new SecurityConfig("abc"))).isTrue(); assertThat(cdm.supports(new SecurityConfig("UNSUPPORTED"))).isFalse(); } @Test public void testGettersSetters() { ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl(); assertThat(cdm.getChannelProcessors()).isNull(); MockChannelProcessor cpXyz = new MockChannelProcessor("xyz", false); MockChannelProcessor cpAbc = new MockChannelProcessor("abc", false); List list = new Vector(); list.add(cpXyz); list.add(cpAbc); cdm.setChannelProcessors(list); assertThat(cdm.getChannelProcessors()).isEqualTo(list); } @Test public void testStartupFailsWithEmptyChannelProcessorsList() throws Exception { ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl(); assertThatIllegalArgumentException().isThrownBy(cdm::afterPropertiesSet) .withMessage("A list of ChannelProcessors is required"); } private class MockChannelProcessor implements ChannelProcessor { private String configAttribute; private boolean failIfCalled; MockChannelProcessor(String configAttribute, boolean failIfCalled) { this.configAttribute = configAttribute; this.failIfCalled = failIfCalled; } @Override public void decide(FilterInvocation invocation, Collection config) throws IOException { Iterator iter = config.iterator(); if (this.failIfCalled) { fail("Should not have called this channel processor: " + this.configAttribute); } while (iter.hasNext()) { ConfigAttribute attr = (ConfigAttribute) iter.next(); if (attr.getAttribute().equals(this.configAttribute)) { invocation.getHttpResponse().sendRedirect("/redirected"); return; } } } @Override public boolean supports(ConfigAttribute attribute) { return attribute.getAttribute().equals(this.configAttribute); } } } ================================================ FILE: access/src/test/java/org/springframework/security/web/access/channel/ChannelProcessingFilterTests.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.web.access.channel; import java.io.IOException; import java.util.Collection; import jakarta.servlet.FilterChain; import org.junit.jupiter.api.Test; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.access.SecurityConfig; import org.springframework.security.web.FilterInvocation; import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource; import org.springframework.security.web.servlet.TestMockHttpServletRequests; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.mockito.Mockito.mock; /** * Tests {@link ChannelProcessingFilter}. * * @author Ben Alex */ @SuppressWarnings("deprecation") public class ChannelProcessingFilterTests { @Test public void testDetectsMissingChannelDecisionManager() { ChannelProcessingFilter filter = new ChannelProcessingFilter(); MockFilterInvocationDefinitionMap fids = new MockFilterInvocationDefinitionMap("/path", true, "MOCK"); filter.setSecurityMetadataSource(fids); assertThatIllegalArgumentException().isThrownBy(filter::afterPropertiesSet); } @Test public void testDetectsMissingFilterInvocationSecurityMetadataSource() { ChannelProcessingFilter filter = new ChannelProcessingFilter(); filter.setChannelDecisionManager(new MockChannelDecisionManager(false, "MOCK")); assertThatIllegalArgumentException().isThrownBy(filter::afterPropertiesSet); } @Test public void testDetectsSupportedConfigAttribute() { ChannelProcessingFilter filter = new ChannelProcessingFilter(); filter.setChannelDecisionManager(new MockChannelDecisionManager(false, "SUPPORTS_MOCK_ONLY")); MockFilterInvocationDefinitionMap fids = new MockFilterInvocationDefinitionMap("/path", true, "SUPPORTS_MOCK_ONLY"); filter.setSecurityMetadataSource(fids); filter.afterPropertiesSet(); } @Test public void testDetectsUnsupportedConfigAttribute() { ChannelProcessingFilter filter = new ChannelProcessingFilter(); filter.setChannelDecisionManager(new MockChannelDecisionManager(false, "SUPPORTS_MOCK_ONLY")); MockFilterInvocationDefinitionMap fids = new MockFilterInvocationDefinitionMap("/path", true, "SUPPORTS_MOCK_ONLY", "INVALID_ATTRIBUTE"); filter.setSecurityMetadataSource(fids); assertThatIllegalArgumentException().isThrownBy(filter::afterPropertiesSet); } @Test public void testDoFilterWhenManagerDoesCommitResponse() throws Exception { ChannelProcessingFilter filter = new ChannelProcessingFilter(); filter.setChannelDecisionManager(new MockChannelDecisionManager(true, "SOME_ATTRIBUTE")); MockFilterInvocationDefinitionMap fids = new MockFilterInvocationDefinitionMap("/path", true, "SOME_ATTRIBUTE"); filter.setSecurityMetadataSource(fids); MockHttpServletRequest request = TestMockHttpServletRequests.get("/path").build(); request.setQueryString("info=now"); MockHttpServletResponse response = new MockHttpServletResponse(); filter.doFilter(request, response, mock(FilterChain.class)); } @Test public void testDoFilterWhenManagerDoesNotCommitResponse() throws Exception { ChannelProcessingFilter filter = new ChannelProcessingFilter(); filter.setChannelDecisionManager(new MockChannelDecisionManager(false, "SOME_ATTRIBUTE")); MockFilterInvocationDefinitionMap fids = new MockFilterInvocationDefinitionMap("/path", true, "SOME_ATTRIBUTE"); filter.setSecurityMetadataSource(fids); MockHttpServletRequest request = TestMockHttpServletRequests.get("/path").build(); request.setQueryString("info=now"); MockHttpServletResponse response = new MockHttpServletResponse(); filter.doFilter(request, response, mock(FilterChain.class)); } @Test public void testDoFilterWhenNullConfigAttributeReturned() throws Exception { ChannelProcessingFilter filter = new ChannelProcessingFilter(); filter.setChannelDecisionManager(new MockChannelDecisionManager(false, "NOT_USED")); MockFilterInvocationDefinitionMap fids = new MockFilterInvocationDefinitionMap("/path", true, "NOT_USED"); filter.setSecurityMetadataSource(fids); MockHttpServletRequest request = TestMockHttpServletRequests.get("/PATH_NOT_MATCHING_CONFIG_ATTRIBUTE").build(); request.setQueryString("info=now"); MockHttpServletResponse response = new MockHttpServletResponse(); filter.doFilter(request, response, mock(FilterChain.class)); } @Test public void testGetterSetters() { ChannelProcessingFilter filter = new ChannelProcessingFilter(); filter.setChannelDecisionManager(new MockChannelDecisionManager(false, "MOCK")); assertThat(filter.getChannelDecisionManager() != null).isTrue(); MockFilterInvocationDefinitionMap fids = new MockFilterInvocationDefinitionMap("/path", false, "MOCK"); filter.setSecurityMetadataSource(fids); assertThat(filter.getSecurityMetadataSource()).isSameAs(fids); filter.afterPropertiesSet(); } private class MockChannelDecisionManager implements ChannelDecisionManager { private String supportAttribute; private boolean commitAResponse; MockChannelDecisionManager(boolean commitAResponse, String supportAttribute) { this.commitAResponse = commitAResponse; this.supportAttribute = supportAttribute; } @Override public void decide(FilterInvocation invocation, Collection config) throws IOException { if (this.commitAResponse) { invocation.getHttpResponse().sendRedirect("/redirected"); } } @Override public boolean supports(ConfigAttribute attribute) { return attribute.getAttribute().equals(this.supportAttribute); } } private class MockFilterInvocationDefinitionMap implements FilterInvocationSecurityMetadataSource { private Collection toReturn; private String servletPath; private boolean provideIterator; MockFilterInvocationDefinitionMap(String servletPath, boolean provideIterator, String... toReturn) { this.servletPath = servletPath; this.toReturn = SecurityConfig.createList(toReturn); this.provideIterator = provideIterator; } @Override public Collection getAttributes(Object object) throws IllegalArgumentException { FilterInvocation fi = (FilterInvocation) object; if (this.servletPath.equals(fi.getHttpRequest().getServletPath())) { return this.toReturn; } else { return null; } } @Override public Collection getAllConfigAttributes() { if (!this.provideIterator) { return null; } return this.toReturn; } @Override public boolean supports(Class clazz) { return true; } } } ================================================ FILE: access/src/test/java/org/springframework/security/web/access/channel/InsecureChannelProcessorTests.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.web.access.channel; import jakarta.servlet.FilterChain; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.security.access.SecurityConfig; import org.springframework.security.web.FilterInvocation; import org.springframework.security.web.servlet.TestMockHttpServletRequests; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.mockito.Mockito.mock; /** * Tests {@link InsecureChannelProcessor}. * * @author Ben Alex */ @SuppressWarnings("deprecation") public class InsecureChannelProcessorTests { @Test public void testDecideDetectsAcceptableChannel() throws Exception { MockHttpServletRequest request = TestMockHttpServletRequests.get("http://localhost:8080") .requestUri("/bigapp", "/servlet", null) .queryString("info=true") .build(); MockHttpServletResponse response = new MockHttpServletResponse(); FilterInvocation fi = new FilterInvocation(request, response, mock(FilterChain.class)); InsecureChannelProcessor processor = new InsecureChannelProcessor(); processor.decide(fi, SecurityConfig.createList("SOME_IGNORED_ATTRIBUTE", "REQUIRES_INSECURE_CHANNEL")); Assertions.assertThat(fi.getResponse().isCommitted()).isFalse(); } @Test public void testDecideDetectsUnacceptableChannel() throws Exception { MockHttpServletRequest request = TestMockHttpServletRequests.get("https://localhost:8443") .requestUri("/bigapp", "/servlet", null) .queryString("info=true") .build(); MockHttpServletResponse response = new MockHttpServletResponse(); FilterInvocation fi = new FilterInvocation(request, response, mock(FilterChain.class)); InsecureChannelProcessor processor = new InsecureChannelProcessor(); processor.decide(fi, SecurityConfig.createList(new String[] { "SOME_IGNORED_ATTRIBUTE", "REQUIRES_INSECURE_CHANNEL" })); Assertions.assertThat(fi.getResponse().isCommitted()).isTrue(); } @Test public void testDecideRejectsNulls() throws Exception { InsecureChannelProcessor processor = new InsecureChannelProcessor(); processor.afterPropertiesSet(); assertThatIllegalArgumentException().isThrownBy(() -> processor.decide(null, null)); } @Test public void testGettersSetters() { InsecureChannelProcessor processor = new InsecureChannelProcessor(); assertThat(processor.getInsecureKeyword()).isEqualTo("REQUIRES_INSECURE_CHANNEL"); processor.setInsecureKeyword("X"); assertThat(processor.getInsecureKeyword()).isEqualTo("X"); assertThat(processor.getEntryPoint() != null).isTrue(); processor.setEntryPoint(null); assertThat(processor.getEntryPoint() == null).isTrue(); } @Test public void testMissingEntryPoint() throws Exception { InsecureChannelProcessor processor = new InsecureChannelProcessor(); processor.setEntryPoint(null); assertThatIllegalArgumentException().isThrownBy(processor::afterPropertiesSet) .withMessage("entryPoint required"); } @Test public void testMissingSecureChannelKeyword() throws Exception { InsecureChannelProcessor processor = new InsecureChannelProcessor(); processor.setInsecureKeyword(null); assertThatIllegalArgumentException().isThrownBy(processor::afterPropertiesSet) .withMessage("insecureKeyword required"); processor.setInsecureKeyword(""); assertThatIllegalArgumentException().isThrownBy(processor::afterPropertiesSet) .withMessage("insecureKeyword required"); } @Test public void testSupports() { InsecureChannelProcessor processor = new InsecureChannelProcessor(); assertThat(processor.supports(new SecurityConfig("REQUIRES_INSECURE_CHANNEL"))).isTrue(); assertThat(processor.supports(null)).isFalse(); assertThat(processor.supports(new SecurityConfig("NOT_SUPPORTED"))).isFalse(); } } ================================================ FILE: access/src/test/java/org/springframework/security/web/access/channel/RetryWithHttpEntryPointTests.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.web.access.channel; import java.util.HashMap; import java.util.Map; import org.junit.jupiter.api.Test; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.security.web.PortMapper; import org.springframework.security.web.PortMapperImpl; import org.springframework.security.web.RedirectStrategy; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.mockito.Mockito.mock; /** * Tests {@link RetryWithHttpEntryPoint}. * * @author Ben Alex */ @SuppressWarnings("deprecation") public class RetryWithHttpEntryPointTests { @Test public void testDetectsMissingPortMapper() { RetryWithHttpEntryPoint ep = new RetryWithHttpEntryPoint(); assertThatIllegalArgumentException().isThrownBy(() -> ep.setPortMapper(null)); } @Test public void testGettersSetters() { RetryWithHttpEntryPoint ep = new RetryWithHttpEntryPoint(); PortMapper portMapper = mock(PortMapper.class); RedirectStrategy redirector = mock(RedirectStrategy.class); ep.setPortMapper(portMapper); ep.setRedirectStrategy(redirector); assertThat(ep.getPortMapper()).isSameAs(portMapper); assertThat(ep.getRedirectStrategy()).isSameAs(redirector); } @Test public void testNormalOperation() throws Exception { MockHttpServletRequest request = new MockHttpServletRequest("GET", "/bigWebApp/hello/pathInfo.html"); request.setQueryString("open=true"); request.setScheme("https"); request.setServerName("localhost"); request.setServerPort(443); MockHttpServletResponse response = new MockHttpServletResponse(); RetryWithHttpEntryPoint ep = new RetryWithHttpEntryPoint(); ep.setPortMapper(new PortMapperImpl()); ep.commence(request, response); assertThat(response.getRedirectedUrl()).isEqualTo("http://localhost/bigWebApp/hello/pathInfo.html?open=true"); } @Test public void testNormalOperationWithNullQueryString() throws Exception { MockHttpServletRequest request = new MockHttpServletRequest("GET", "/bigWebApp/hello"); request.setScheme("https"); request.setServerName("localhost"); request.setServerPort(443); MockHttpServletResponse response = new MockHttpServletResponse(); RetryWithHttpEntryPoint ep = new RetryWithHttpEntryPoint(); ep.setPortMapper(new PortMapperImpl()); ep.commence(request, response); assertThat(response.getRedirectedUrl()).isEqualTo("http://localhost/bigWebApp/hello"); } @Test public void testOperationWhenTargetPortIsUnknown() throws Exception { MockHttpServletRequest request = new MockHttpServletRequest("GET", "/bigWebApp"); request.setQueryString("open=true"); request.setScheme("https"); request.setServerName("www.example.com"); request.setServerPort(8768); MockHttpServletResponse response = new MockHttpServletResponse(); RetryWithHttpEntryPoint ep = new RetryWithHttpEntryPoint(); ep.setPortMapper(new PortMapperImpl()); ep.commence(request, response); assertThat(response.getRedirectedUrl()).isEqualTo("/bigWebApp?open=true"); } @Test public void testOperationWithNonStandardPort() throws Exception { MockHttpServletRequest request = new MockHttpServletRequest("GET", "/bigWebApp/hello/pathInfo.html"); request.setQueryString("open=true"); request.setScheme("https"); request.setServerName("localhost"); request.setServerPort(9999); MockHttpServletResponse response = new MockHttpServletResponse(); PortMapperImpl portMapper = new PortMapperImpl(); Map map = new HashMap<>(); map.put("8888", "9999"); portMapper.setPortMappings(map); RetryWithHttpEntryPoint ep = new RetryWithHttpEntryPoint(); ep.setPortMapper(portMapper); ep.commence(request, response); assertThat(response.getRedirectedUrl()) .isEqualTo("http://localhost:8888/bigWebApp/hello/pathInfo.html?open=true"); } } ================================================ FILE: access/src/test/java/org/springframework/security/web/access/channel/RetryWithHttpsEntryPointTests.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.web.access.channel; import java.util.HashMap; import java.util.Map; import org.junit.jupiter.api.Test; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.security.web.PortMapperImpl; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; /** * Tests {@link RetryWithHttpsEntryPoint}. * * @author Ben Alex */ @SuppressWarnings("deprecation") public class RetryWithHttpsEntryPointTests { @Test public void testDetectsMissingPortMapper() { RetryWithHttpsEntryPoint ep = new RetryWithHttpsEntryPoint(); assertThatIllegalArgumentException().isThrownBy(() -> ep.setPortMapper(null)); } @Test public void testGettersSetters() { RetryWithHttpsEntryPoint ep = new RetryWithHttpsEntryPoint(); ep.setPortMapper(new PortMapperImpl()); assertThat(ep.getPortMapper() != null).isTrue(); } @Test public void testNormalOperation() throws Exception { MockHttpServletRequest request = new MockHttpServletRequest("GET", "/bigWebApp/hello/pathInfo.html"); request.setQueryString("open=true"); request.setScheme("http"); request.setServerName("www.example.com"); request.setServerPort(80); MockHttpServletResponse response = new MockHttpServletResponse(); RetryWithHttpsEntryPoint ep = new RetryWithHttpsEntryPoint(); ep.setPortMapper(new PortMapperImpl()); ep.commence(request, response); assertThat(response.getRedirectedUrl()) .isEqualTo("https://www.example.com/bigWebApp/hello/pathInfo.html?open=true"); } @Test public void testNormalOperationWithNullQueryString() throws Exception { MockHttpServletRequest request = new MockHttpServletRequest("GET", "/bigWebApp/hello"); request.setScheme("http"); request.setServerName("www.example.com"); request.setServerPort(80); MockHttpServletResponse response = new MockHttpServletResponse(); RetryWithHttpsEntryPoint ep = new RetryWithHttpsEntryPoint(); ep.setPortMapper(new PortMapperImpl()); ep.commence(request, response); assertThat(response.getRedirectedUrl()).isEqualTo("https://www.example.com/bigWebApp/hello"); } @Test public void testOperationWhenTargetPortIsUnknown() throws Exception { MockHttpServletRequest request = new MockHttpServletRequest("GET", "/bigWebApp"); request.setQueryString("open=true"); request.setScheme("http"); request.setServerName("www.example.com"); request.setServerPort(8768); MockHttpServletResponse response = new MockHttpServletResponse(); RetryWithHttpsEntryPoint ep = new RetryWithHttpsEntryPoint(); ep.setPortMapper(new PortMapperImpl()); ep.commence(request, response); assertThat(response.getRedirectedUrl()).isEqualTo("/bigWebApp?open=true"); } @Test public void testOperationWithNonStandardPort() throws Exception { MockHttpServletRequest request = new MockHttpServletRequest("GET", "/bigWebApp/hello/pathInfo.html"); request.setQueryString("open=true"); request.setScheme("http"); request.setServerName("www.example.com"); request.setServerPort(8888); MockHttpServletResponse response = new MockHttpServletResponse(); PortMapperImpl portMapper = new PortMapperImpl(); Map map = new HashMap<>(); map.put("8888", "9999"); portMapper.setPortMappings(map); RetryWithHttpsEntryPoint ep = new RetryWithHttpsEntryPoint(); ep.setPortMapper(portMapper); ep.commence(request, response); assertThat(response.getRedirectedUrl()) .isEqualTo("https://www.example.com:9999/bigWebApp/hello/pathInfo.html?open=true"); } } ================================================ FILE: access/src/test/java/org/springframework/security/web/access/channel/SecureChannelProcessorTests.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.web.access.channel; import jakarta.servlet.FilterChain; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.security.access.SecurityConfig; import org.springframework.security.web.FilterInvocation; import org.springframework.security.web.servlet.TestMockHttpServletRequests; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.mockito.Mockito.mock; /** * Tests {@link SecureChannelProcessor}. * * @author Ben Alex */ @SuppressWarnings("deprecation") public class SecureChannelProcessorTests { @Test public void testDecideDetectsAcceptableChannel() throws Exception { MockHttpServletRequest request = TestMockHttpServletRequests.get("https://localhost:8443") .requestUri("/bigapp", "/servlet", null) .queryString("info=true") .build(); MockHttpServletResponse response = new MockHttpServletResponse(); FilterInvocation fi = new FilterInvocation(request, response, mock(FilterChain.class)); SecureChannelProcessor processor = new SecureChannelProcessor(); processor.decide(fi, SecurityConfig.createList("SOME_IGNORED_ATTRIBUTE", "REQUIRES_SECURE_CHANNEL")); Assertions.assertThat(fi.getResponse().isCommitted()).isFalse(); } @Test public void testDecideDetectsUnacceptableChannel() throws Exception { MockHttpServletRequest request = TestMockHttpServletRequests.get("http://localhost:8080") .requestUri("/bigapp", "/servlet", null) .queryString("info=true") .build(); MockHttpServletResponse response = new MockHttpServletResponse(); FilterInvocation fi = new FilterInvocation(request, response, mock(FilterChain.class)); SecureChannelProcessor processor = new SecureChannelProcessor(); processor.decide(fi, SecurityConfig.createList(new String[] { "SOME_IGNORED_ATTRIBUTE", "REQUIRES_SECURE_CHANNEL" })); Assertions.assertThat(fi.getResponse().isCommitted()).isTrue(); } @Test public void testDecideRejectsNulls() throws Exception { SecureChannelProcessor processor = new SecureChannelProcessor(); processor.afterPropertiesSet(); assertThatIllegalArgumentException().isThrownBy(() -> processor.decide(null, null)); } @Test public void testGettersSetters() { SecureChannelProcessor processor = new SecureChannelProcessor(); assertThat(processor.getSecureKeyword()).isEqualTo("REQUIRES_SECURE_CHANNEL"); processor.setSecureKeyword("X"); assertThat(processor.getSecureKeyword()).isEqualTo("X"); assertThat(processor.getEntryPoint() != null).isTrue(); processor.setEntryPoint(null); assertThat(processor.getEntryPoint() == null).isTrue(); } @Test public void testMissingEntryPoint() throws Exception { SecureChannelProcessor processor = new SecureChannelProcessor(); processor.setEntryPoint(null); assertThatIllegalArgumentException().isThrownBy(processor::afterPropertiesSet) .withMessage("entryPoint required"); } @Test public void testMissingSecureChannelKeyword() throws Exception { SecureChannelProcessor processor = new SecureChannelProcessor(); processor.setSecureKeyword(null); assertThatIllegalArgumentException().isThrownBy(processor::afterPropertiesSet) .withMessage("secureKeyword required"); processor.setSecureKeyword(""); assertThatIllegalArgumentException().isThrownBy(() -> processor.afterPropertiesSet()) .withMessage("secureKeyword required"); } @Test public void testSupports() { SecureChannelProcessor processor = new SecureChannelProcessor(); assertThat(processor.supports(new SecurityConfig("REQUIRES_SECURE_CHANNEL"))).isTrue(); assertThat(processor.supports(null)).isFalse(); assertThat(processor.supports(new SecurityConfig("NOT_SUPPORTED"))).isFalse(); } } ================================================ FILE: access/src/test/java/org/springframework/security/web/access/expression/DefaultWebSecurityExpressionHandlerTests.java ================================================ /* * 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 org.springframework.security.web.access.expression; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.context.support.StaticApplicationContext; import org.springframework.expression.EvaluationContext; import org.springframework.expression.Expression; import org.springframework.expression.ExpressionParser; import org.springframework.security.access.SecurityConfig; import org.springframework.security.authentication.AuthenticationTrustResolver; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.FilterInvocation; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @ExtendWith(MockitoExtension.class) public class DefaultWebSecurityExpressionHandlerTests { @Mock private AuthenticationTrustResolver trustResolver; @Mock private Authentication authentication; @Mock private FilterInvocation invocation; private DefaultWebSecurityExpressionHandler handler; @BeforeEach public void setup() { this.handler = new DefaultWebSecurityExpressionHandler(); } @AfterEach public void cleanup() { SecurityContextHolder.clearContext(); } @Test @SuppressWarnings("deprecation") public void expressionPropertiesAreResolvedAgainstAppContextBeans() { StaticApplicationContext appContext = new StaticApplicationContext(); RootBeanDefinition bean = new RootBeanDefinition(SecurityConfig.class); bean.getConstructorArgumentValues().addGenericArgumentValue("ROLE_A"); appContext.registerBeanDefinition("role", bean); this.handler.setApplicationContext(appContext); EvaluationContext ctx = this.handler.createEvaluationContext(mock(Authentication.class), mock(FilterInvocation.class)); ExpressionParser parser = this.handler.getExpressionParser(); assertThat(parser.parseExpression("@role.getAttribute() == 'ROLE_A'").getValue(ctx, Boolean.class)).isTrue(); assertThat(parser.parseExpression("@role.attribute == 'ROLE_A'").getValue(ctx, Boolean.class)).isTrue(); } @Test @SuppressWarnings("deprecation") public void setTrustResolverNull() { assertThatIllegalArgumentException().isThrownBy(() -> this.handler.setTrustResolver(null)); } @Test @SuppressWarnings("deprecation") public void createEvaluationContextCustomTrustResolver() { this.handler.setTrustResolver(this.trustResolver); Expression expression = this.handler.getExpressionParser().parseExpression("anonymous"); EvaluationContext context = this.handler.createEvaluationContext(this.authentication, this.invocation); assertThat(expression.getValue(context, Boolean.class)).isFalse(); verify(this.trustResolver).isAnonymous(this.authentication); } } ================================================ FILE: access/src/test/java/org/springframework/security/web/access/expression/ExpressionBasedFilterInvocationSecurityMetadataSourceTests.java ================================================ /* * 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 org.springframework.security.web.access.expression; import java.util.Collection; import java.util.LinkedHashMap; import org.junit.jupiter.api.Test; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.access.SecurityConfig; import org.springframework.security.web.FilterInvocation; import org.springframework.security.web.util.matcher.AnyRequestMatcher; import org.springframework.security.web.util.matcher.RequestMatcher; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; /** * @author Luke Taylor */ @SuppressWarnings("deprecation") public class ExpressionBasedFilterInvocationSecurityMetadataSourceTests { @Test public void expectedAttributeIsReturned() { final String expression = "hasRole('X')"; LinkedHashMap> requestMap = new LinkedHashMap<>(); requestMap.put(AnyRequestMatcher.INSTANCE, SecurityConfig.createList(expression)); ExpressionBasedFilterInvocationSecurityMetadataSource mds = new ExpressionBasedFilterInvocationSecurityMetadataSource( requestMap, new DefaultWebSecurityExpressionHandler()); assertThat(mds.getAllConfigAttributes()).hasSize(1); Collection attrs = mds.getAttributes(new FilterInvocation("/path", "GET")); assertThat(attrs).hasSize(1); WebExpressionConfigAttribute attribute = (WebExpressionConfigAttribute) attrs.toArray()[0]; assertThat(attribute.getAttribute()).isNull(); assertThat(attribute.getAuthorizeExpression().getExpressionString()).isEqualTo(expression); assertThat(attribute.toString()).isEqualTo(expression); } @Test public void invalidExpressionIsRejected() { LinkedHashMap> requestMap = new LinkedHashMap<>(); requestMap.put(AnyRequestMatcher.INSTANCE, SecurityConfig.createList("hasRole('X'")); assertThatIllegalArgumentException() .isThrownBy(() -> new ExpressionBasedFilterInvocationSecurityMetadataSource(requestMap, new DefaultWebSecurityExpressionHandler())); } } ================================================ FILE: access/src/test/java/org/springframework/security/web/access/expression/WebExpressionVoterTests.java ================================================ /* * 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 org.springframework.security.web.access.expression; import java.util.ArrayList; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; import org.aopalliance.intercept.MethodInvocation; import org.junit.jupiter.api.Test; import org.springframework.expression.EvaluationContext; import org.springframework.expression.Expression; import org.springframework.security.access.AccessDecisionVoter; import org.springframework.security.access.SecurityConfig; import org.springframework.security.access.expression.SecurityExpressionHandler; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.web.FilterInvocation; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; /** * @author Luke Taylor */ @SuppressWarnings({ "unchecked", "deprecation" }) public class WebExpressionVoterTests { private Authentication user = new TestingAuthenticationToken("user", "pass", "X"); @Test public void supportsWebConfigAttributeAndFilterInvocation() { WebExpressionVoter voter = new WebExpressionVoter(); assertThat(voter.supports( new WebExpressionConfigAttribute(mock(Expression.class), mock(EvaluationContextPostProcessor.class)))) .isTrue(); assertThat(voter.supports(FilterInvocation.class)).isTrue(); assertThat(voter.supports(MethodInvocation.class)).isFalse(); } @Test public void abstainsIfNoAttributeFound() { WebExpressionVoter voter = new WebExpressionVoter(); assertThat( voter.vote(this.user, new FilterInvocation("/path", "GET"), SecurityConfig.createList("A", "B", "C"))) .isEqualTo(AccessDecisionVoter.ACCESS_ABSTAIN); } @Test public void grantsAccessIfExpressionIsTrueDeniesIfFalse() { WebExpressionVoter voter = new WebExpressionVoter(); Expression ex = mock(Expression.class); EvaluationContextPostProcessor postProcessor = mock(EvaluationContextPostProcessor.class); given(postProcessor.postProcess(any(EvaluationContext.class), any(FilterInvocation.class))) .willAnswer((invocation) -> invocation.getArgument(0)); WebExpressionConfigAttribute weca = new WebExpressionConfigAttribute(ex, postProcessor); EvaluationContext ctx = mock(EvaluationContext.class); SecurityExpressionHandler eh = mock(SecurityExpressionHandler.class); FilterInvocation fi = new FilterInvocation("/path", "GET"); voter.setExpressionHandler(eh); given(eh.createEvaluationContext(this.user, fi)).willReturn(ctx); given(ex.getValue(ctx, Boolean.class)).willReturn(Boolean.TRUE, Boolean.FALSE); ArrayList attributes = new ArrayList(); attributes.addAll(SecurityConfig.createList("A", "B", "C")); attributes.add(weca); assertThat(voter.vote(this.user, fi, attributes)).isEqualTo(AccessDecisionVoter.ACCESS_GRANTED); // Second time false assertThat(voter.vote(this.user, fi, attributes)).isEqualTo(AccessDecisionVoter.ACCESS_DENIED); } // SEC-2507 @Test public void supportFilterInvocationSubClass() { WebExpressionVoter voter = new WebExpressionVoter(); assertThat(voter.supports(FilterInvocationChild.class)).isTrue(); } @Test public void supportFilterInvocation() { WebExpressionVoter voter = new WebExpressionVoter(); assertThat(voter.supports(FilterInvocation.class)).isTrue(); } @Test public void supportsObjectIsFalse() { WebExpressionVoter voter = new WebExpressionVoter(); assertThat(voter.supports(Object.class)).isFalse(); } private static class FilterInvocationChild extends FilterInvocation { FilterInvocationChild(ServletRequest request, ServletResponse response, FilterChain chain) { super(request, response, chain); } } } ================================================ FILE: access/src/test/java/org/springframework/security/web/access/intercept/DefaultFilterInvocationSecurityMetadataSourceTests.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.web.access.intercept; import java.util.Collection; import java.util.LinkedHashMap; import jakarta.servlet.FilterChain; import org.junit.jupiter.api.Test; import org.springframework.http.HttpMethod; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.access.SecurityConfig; import org.springframework.security.web.FilterInvocation; import org.springframework.security.web.servlet.TestMockHttpServletRequests; import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher; import org.springframework.security.web.util.matcher.RequestMatcher; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; /** * Tests {@link DefaultFilterInvocationSecurityMetadataSource}. * * @author Ben Alex */ @SuppressWarnings("deprecation") public class DefaultFilterInvocationSecurityMetadataSourceTests { private DefaultFilterInvocationSecurityMetadataSource fids; private Collection def = SecurityConfig.createList("ROLE_ONE"); private void createFids(String pattern, HttpMethod method) { LinkedHashMap> requestMap = new LinkedHashMap<>(); requestMap.put(PathPatternRequestMatcher.pathPattern(method, pattern), this.def); this.fids = new DefaultFilterInvocationSecurityMetadataSource(requestMap); } @Test public void lookupNotRequiringExactMatchSucceedsIfNotMatching() { createFids("/secure/super/**", null); FilterInvocation fi = createFilterInvocation("/secure/super/somefile.html", null, null, "GET"); assertThat(this.fids.getAttributes(fi)).isEqualTo(this.def); } /** * SEC-501. Note that as of 2.0, lower case comparisons are the default for this * class. */ @Test public void lookupNotRequiringExactMatchSucceedsIfSecureUrlPathContainsUpperCase() { createFids("/secure/super/**", null); FilterInvocation fi = createFilterInvocation("/secure", "/super/somefile.html", null, "GET"); Collection response = this.fids.getAttributes(fi); assertThat(response).isEqualTo(this.def); } @Test public void lookupRequiringExactMatchIsSuccessful() { createFids("/SeCurE/super/**", null); FilterInvocation fi = createFilterInvocation("/SeCurE/super/somefile.html", null, null, "GET"); Collection response = this.fids.getAttributes(fi); assertThat(response).isEqualTo(this.def); } @Test public void lookupRequiringExactMatchWithAdditionalSlashesIsSuccessful() { createFids("/someAdminPage.html**", null); FilterInvocation fi = createFilterInvocation("/someAdminPage.html", null, "a=/test", "GET"); Collection response = this.fids.getAttributes(fi); assertThat(response); // see SEC-161 (it should truncate after ? // sign).isEqualTo(def) } @Test public void httpMethodLookupSucceeds() { createFids("/somepage**", HttpMethod.GET); FilterInvocation fi = createFilterInvocation("/somepage", null, null, "GET"); Collection attrs = this.fids.getAttributes(fi); assertThat(attrs).isEqualTo(this.def); } @Test public void generalMatchIsUsedIfNoMethodSpecificMatchExists() { createFids("/somepage**", null); FilterInvocation fi = createFilterInvocation("/somepage", null, null, "GET"); Collection attrs = this.fids.getAttributes(fi); assertThat(attrs).isEqualTo(this.def); } @Test public void requestWithDifferentHttpMethodDoesntMatch() { createFids("/somepage**", HttpMethod.GET); FilterInvocation fi = createFilterInvocation("/somepage", null, null, "POST"); Collection attrs = this.fids.getAttributes(fi); assertThat(attrs).isEmpty(); } // SEC-1236 @Test public void mixingPatternsWithAndWithoutHttpMethodsIsSupported() { LinkedHashMap> requestMap = new LinkedHashMap<>(); Collection userAttrs = SecurityConfig.createList("A"); requestMap.put(PathPatternRequestMatcher.pathPattern("/user/**"), userAttrs); requestMap.put(PathPatternRequestMatcher.pathPattern(HttpMethod.GET, "/teller/**"), SecurityConfig.createList("B")); this.fids = new DefaultFilterInvocationSecurityMetadataSource(requestMap); FilterInvocation fi = createFilterInvocation("/user", null, null, "GET"); Collection attrs = this.fids.getAttributes(fi); assertThat(attrs).isEqualTo(userAttrs); } /** * Check fixes for SEC-321 */ @Test public void extraQuestionMarkStillMatches() { createFids("/someAdminPage.html*", null); FilterInvocation fi = createFilterInvocation("/someAdminPage.html", null, null, "GET"); Collection response = this.fids.getAttributes(fi); assertThat(response).isEqualTo(this.def); fi = createFilterInvocation("/someAdminPage.html", null, "?", "GET"); response = this.fids.getAttributes(fi); assertThat(response).isEqualTo(this.def); } private FilterInvocation createFilterInvocation(String servletPath, String pathInfo, String queryString, String method) { MockHttpServletRequest request = TestMockHttpServletRequests.request(method) .requestUri(null, servletPath, pathInfo) .queryString(queryString) .build(); return new FilterInvocation(request, new MockHttpServletResponse(), mock(FilterChain.class)); } } ================================================ FILE: access/src/test/java/org/springframework/security/web/access/intercept/FilterSecurityInterceptorTests.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.web.access.intercept; import jakarta.servlet.FilterChain; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.context.ApplicationEventPublisher; import org.springframework.mock.web.MockFilterChain; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.security.access.AccessDecisionManager; import org.springframework.security.access.SecurityConfig; import org.springframework.security.access.event.AuthorizedEvent; import org.springframework.security.access.intercept.AfterInvocationManager; import org.springframework.security.access.intercept.RunAsManager; import org.springframework.security.access.intercept.RunAsUserToken; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.FilterInvocation; import org.springframework.security.web.servlet.TestMockHttpServletRequests; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyCollection; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.willThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; /** * Tests {@link FilterSecurityInterceptor}. * * @author Ben Alex * @author Luke Taylor * @author Rob Winch */ @SuppressWarnings("deprecation") public class FilterSecurityInterceptorTests { private AuthenticationManager am; private AccessDecisionManager adm; private FilterInvocationSecurityMetadataSource ods; private RunAsManager ram; private FilterSecurityInterceptor interceptor; private ApplicationEventPublisher publisher; @BeforeEach public final void setUp() { this.interceptor = new FilterSecurityInterceptor(); this.am = mock(AuthenticationManager.class); this.ods = mock(FilterInvocationSecurityMetadataSource.class); this.adm = mock(AccessDecisionManager.class); this.ram = mock(RunAsManager.class); this.publisher = mock(ApplicationEventPublisher.class); this.interceptor.setAuthenticationManager(this.am); this.interceptor.setSecurityMetadataSource(this.ods); this.interceptor.setAccessDecisionManager(this.adm); this.interceptor.setRunAsManager(this.ram); this.interceptor.setApplicationEventPublisher(this.publisher); SecurityContextHolder.clearContext(); } @AfterEach public void tearDown() { SecurityContextHolder.clearContext(); } @Test public void testEnsuresAccessDecisionManagerSupportsFilterInvocationClass() throws Exception { given(this.adm.supports(FilterInvocation.class)).willReturn(true); assertThatIllegalArgumentException().isThrownBy(this.interceptor::afterPropertiesSet); } @Test public void testEnsuresRunAsManagerSupportsFilterInvocationClass() throws Exception { given(this.adm.supports(FilterInvocation.class)).willReturn(false); assertThatIllegalArgumentException().isThrownBy(this.interceptor::afterPropertiesSet); } /** * We just test invocation works in a success event. There is no need to test access * denied events as the abstract parent enforces that logic, which is extensively * tested separately. */ @Test public void testSuccessfulInvocation() throws Throwable { // Setup a Context Authentication token = new TestingAuthenticationToken("Test", "Password", "NOT_USED"); SecurityContextHolder.getContext().setAuthentication(token); FilterInvocation fi = createinvocation(); given(this.ods.getAttributes(fi)).willReturn(SecurityConfig.createList("MOCK_OK")); this.interceptor.invoke(fi); // SEC-1697 verify(this.publisher, never()).publishEvent(any(AuthorizedEvent.class)); } @Test public void afterInvocationIsNotInvokedIfExceptionThrown() throws Exception { Authentication token = new TestingAuthenticationToken("Test", "Password", "NOT_USED"); SecurityContextHolder.getContext().setAuthentication(token); FilterInvocation fi = createinvocation(); FilterChain chain = fi.getChain(); willThrow(new RuntimeException()).given(chain) .doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class)); given(this.ods.getAttributes(fi)).willReturn(SecurityConfig.createList("MOCK_OK")); AfterInvocationManager aim = mock(AfterInvocationManager.class); this.interceptor.setAfterInvocationManager(aim); assertThatExceptionOfType(RuntimeException.class).isThrownBy(() -> this.interceptor.invoke(fi)); verifyNoMoreInteractions(aim); } // SEC-1967 @Test @SuppressWarnings("unchecked") public void finallyInvocationIsInvokedIfExceptionThrown() throws Exception { SecurityContext ctx = SecurityContextHolder.getContext(); Authentication token = new TestingAuthenticationToken("Test", "Password", "NOT_USED"); token.setAuthenticated(true); ctx.setAuthentication(token); RunAsManager runAsManager = mock(RunAsManager.class); given(runAsManager.buildRunAs(eq(token), any(), anyCollection())) .willReturn(new RunAsUserToken("key", "someone", "creds", token.getAuthorities(), token.getClass())); this.interceptor.setRunAsManager(runAsManager); FilterInvocation fi = createinvocation(); FilterChain chain = fi.getChain(); willThrow(new RuntimeException()).given(chain) .doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class)); given(this.ods.getAttributes(fi)).willReturn(SecurityConfig.createList("MOCK_OK")); AfterInvocationManager aim = mock(AfterInvocationManager.class); this.interceptor.setAfterInvocationManager(aim); assertThatExceptionOfType(RuntimeException.class).isThrownBy(() -> this.interceptor.invoke(fi)); // Check we've changed back assertThat(SecurityContextHolder.getContext()).isSameAs(ctx); assertThat(SecurityContextHolder.getContext().getAuthentication()).isSameAs(token); } @Test // gh-4997 public void doFilterWhenObserveOncePerRequestThenAttributeNotSet() throws Exception { this.interceptor.setObserveOncePerRequest(false); MockHttpServletResponse response = new MockHttpServletResponse(); MockHttpServletRequest request = new MockHttpServletRequest(); this.interceptor.doFilter(request, response, new MockFilterChain()); assertThat(request.getAttributeNames().hasMoreElements()).isFalse(); } @Test public void doFilterWhenObserveOncePerRequestFalseAndInvokedTwiceThenObserveTwice() throws Throwable { Authentication token = new TestingAuthenticationToken("Test", "Password", "NOT_USED"); SecurityContextHolder.getContext().setAuthentication(token); FilterInvocation fi = createinvocation(); given(this.ods.getAttributes(fi)).willReturn(SecurityConfig.createList("MOCK_OK")); this.interceptor.invoke(fi); this.interceptor.invoke(fi); verify(this.adm, times(2)).decide(any(), any(), any()); } private FilterInvocation createinvocation() { MockHttpServletResponse response = new MockHttpServletResponse(); MockHttpServletRequest request = TestMockHttpServletRequests.get("/secure/page.html").build(); FilterChain chain = mock(FilterChain.class); FilterInvocation fi = new FilterInvocation(request, response, chain); return fi; } } ================================================ FILE: acl/spring-security-acl.gradle ================================================ plugins { id 'compile-warnings-error' id 'javadoc-warnings-error' id 'security-nullability' } apply plugin: 'io.spring.convention.spring-module' dependencies { management platform(project(":spring-security-dependencies")) api project(':spring-security-core') api 'org.springframework:spring-aop' api 'org.springframework:spring-context' api 'org.springframework:spring-core' api 'org.springframework:spring-jdbc' api 'org.springframework:spring-tx' testImplementation "org.assertj:assertj-core" testImplementation "org.junit.jupiter:junit-jupiter-api" testImplementation "org.junit.jupiter:junit-jupiter-params" testImplementation "org.junit.jupiter:junit-jupiter-engine" testImplementation "org.mockito:mockito-core" testImplementation "org.mockito:mockito-junit-jupiter" testImplementation 'org.springframework:spring-beans' testImplementation 'org.springframework:spring-context-support' testImplementation "org.springframework:spring-test" testRuntimeOnly 'org.hsqldb:hsqldb' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' } ================================================ FILE: acl/src/main/java/org/springframework/security/acls/AclPermissionCacheOptimizer.java ================================================ /* * 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 org.springframework.security.acls; import java.util.ArrayList; import java.util.Collection; import java.util.List; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.core.log.LogMessage; import org.springframework.security.access.PermissionCacheOptimizer; import org.springframework.security.acls.domain.ObjectIdentityRetrievalStrategyImpl; import org.springframework.security.acls.domain.SidRetrievalStrategyImpl; import org.springframework.security.acls.model.AclService; import org.springframework.security.acls.model.ObjectIdentity; import org.springframework.security.acls.model.ObjectIdentityRetrievalStrategy; import org.springframework.security.acls.model.Sid; import org.springframework.security.acls.model.SidRetrievalStrategy; import org.springframework.security.core.Authentication; /** * Batch loads ACLs for collections of objects to allow optimised filtering. * * @author Luke Taylor * @since 3.1 */ public class AclPermissionCacheOptimizer implements PermissionCacheOptimizer { private final Log logger = LogFactory.getLog(getClass()); private final AclService aclService; private SidRetrievalStrategy sidRetrievalStrategy = new SidRetrievalStrategyImpl(); private ObjectIdentityRetrievalStrategy oidRetrievalStrategy = new ObjectIdentityRetrievalStrategyImpl(); public AclPermissionCacheOptimizer(AclService aclService) { this.aclService = aclService; } @Override public void cachePermissionsFor(Authentication authentication, Collection objects) { if (objects.isEmpty()) { return; } List oidsToCache = new ArrayList<>(objects.size()); for (Object domainObject : objects) { if (domainObject != null) { ObjectIdentity oid = this.oidRetrievalStrategy.getObjectIdentity(domainObject); oidsToCache.add(oid); } } List sids = this.sidRetrievalStrategy.getSids(authentication); this.logger.debug(LogMessage.of(() -> "Eagerly loading Acls for " + oidsToCache.size() + " objects")); this.aclService.readAclsById(oidsToCache, sids); } public void setObjectIdentityRetrievalStrategy(ObjectIdentityRetrievalStrategy objectIdentityRetrievalStrategy) { this.oidRetrievalStrategy = objectIdentityRetrievalStrategy; } public void setSidRetrievalStrategy(SidRetrievalStrategy sidRetrievalStrategy) { this.sidRetrievalStrategy = sidRetrievalStrategy; } } ================================================ FILE: acl/src/main/java/org/springframework/security/acls/AclPermissionEvaluator.java ================================================ /* * 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 org.springframework.security.acls; import java.io.Serializable; import java.util.Arrays; import java.util.List; import java.util.Locale; 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.PermissionEvaluator; import org.springframework.security.acls.domain.DefaultPermissionFactory; import org.springframework.security.acls.domain.ObjectIdentityRetrievalStrategyImpl; import org.springframework.security.acls.domain.PermissionFactory; import org.springframework.security.acls.domain.SidRetrievalStrategyImpl; import org.springframework.security.acls.model.Acl; import org.springframework.security.acls.model.AclService; import org.springframework.security.acls.model.NotFoundException; import org.springframework.security.acls.model.ObjectIdentity; import org.springframework.security.acls.model.ObjectIdentityGenerator; import org.springframework.security.acls.model.ObjectIdentityRetrievalStrategy; import org.springframework.security.acls.model.Permission; import org.springframework.security.acls.model.Sid; import org.springframework.security.acls.model.SidRetrievalStrategy; import org.springframework.security.core.Authentication; /** * Used by Spring Security's expression-based access control implementation to evaluate * permissions for a particular object using the ACL module. Similar in behaviour to * org.springframework.security.acls.AclEntryVoter AclEntryVoter * * @author Luke Taylor * @since 3.0 */ public class AclPermissionEvaluator implements PermissionEvaluator { private final Log logger = LogFactory.getLog(getClass()); private final AclService aclService; private ObjectIdentityRetrievalStrategy objectIdentityRetrievalStrategy = new ObjectIdentityRetrievalStrategyImpl(); private ObjectIdentityGenerator objectIdentityGenerator = new ObjectIdentityRetrievalStrategyImpl(); private SidRetrievalStrategy sidRetrievalStrategy = new SidRetrievalStrategyImpl(); private PermissionFactory permissionFactory = new DefaultPermissionFactory(); public AclPermissionEvaluator(AclService aclService) { this.aclService = aclService; } /** * Determines whether the user has the given permission(s) on the domain object using * the ACL configuration. If the domain object is null, returns false (this can always * be overridden using a null check in the expression itself). */ @Override public boolean hasPermission(Authentication authentication, @Nullable Object domainObject, Object permission) { if (domainObject == null) { return false; } ObjectIdentity objectIdentity = this.objectIdentityRetrievalStrategy.getObjectIdentity(domainObject); return checkPermission(authentication, objectIdentity, permission); } @Override public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) { ObjectIdentity objectIdentity = this.objectIdentityGenerator.createObjectIdentity(targetId, targetType); return checkPermission(authentication, objectIdentity, permission); } private boolean checkPermission(Authentication authentication, ObjectIdentity oid, Object permission) { // Obtain the SIDs applicable to the principal List sids = this.sidRetrievalStrategy.getSids(authentication); List requiredPermission = resolvePermission(permission); this.logger.debug(LogMessage.of(() -> "Checking permission '" + permission + "' for object '" + oid + "'")); try { // Lookup only ACLs for SIDs we're interested in Acl acl = this.aclService.readAclById(oid, sids); if (acl.isGranted(requiredPermission, sids, false)) { this.logger.debug("Access is granted"); return true; } this.logger.debug("Returning false - ACLs returned, but insufficient permissions for this principal"); } catch (NotFoundException nfe) { this.logger.debug("Returning false - no ACLs apply for this principal"); } return false; } List resolvePermission(Object permission) { if (permission instanceof Integer) { return Arrays.asList(this.permissionFactory.buildFromMask((Integer) permission)); } if (permission instanceof Permission) { return Arrays.asList((Permission) permission); } if (permission instanceof Permission[]) { return Arrays.asList((Permission[]) permission); } if (permission instanceof String permString) { Permission p = buildPermission(permString); if (p != null) { return Arrays.asList(p); } } throw new IllegalArgumentException("Unsupported permission: " + permission); } private Permission buildPermission(String permString) { try { return this.permissionFactory.buildFromName(permString); } catch (IllegalArgumentException notfound) { return this.permissionFactory.buildFromName(permString.toUpperCase(Locale.ENGLISH)); } } public void setObjectIdentityRetrievalStrategy(ObjectIdentityRetrievalStrategy objectIdentityRetrievalStrategy) { this.objectIdentityRetrievalStrategy = objectIdentityRetrievalStrategy; } public void setObjectIdentityGenerator(ObjectIdentityGenerator objectIdentityGenerator) { this.objectIdentityGenerator = objectIdentityGenerator; } public void setSidRetrievalStrategy(SidRetrievalStrategy sidRetrievalStrategy) { this.sidRetrievalStrategy = sidRetrievalStrategy; } public void setPermissionFactory(PermissionFactory permissionFactory) { this.permissionFactory = permissionFactory; } } ================================================ FILE: acl/src/main/java/org/springframework/security/acls/aot/hint/AclRuntimeHints.java ================================================ /* * 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 org.springframework.security.acls.aot.hint; import java.util.stream.Stream; import org.jspecify.annotations.Nullable; import org.springframework.aot.hint.MemberCategory; import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.RuntimeHintsRegistrar; import org.springframework.aot.hint.TypeReference; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; import org.springframework.security.acls.domain.AclImpl; import org.springframework.security.acls.domain.AuditLogger; import org.springframework.security.acls.domain.BasePermission; import org.springframework.security.acls.domain.GrantedAuthoritySid; import org.springframework.security.acls.domain.ObjectIdentityImpl; import org.springframework.security.acls.domain.PrincipalSid; import org.springframework.security.acls.model.AccessControlEntry; import org.springframework.security.acls.model.Acl; import org.springframework.security.acls.model.AuditableAccessControlEntry; import org.springframework.security.acls.model.ObjectIdentity; import org.springframework.security.acls.model.Sid; /** * {@link RuntimeHintsRegistrar} for ACL (Access Control List) classes. * * @author Josh Long */ class AclRuntimeHints implements RuntimeHintsRegistrar { @Override public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) { registerAclDomainHints(hints); registerJdbcSchemaHints(hints); } private void registerAclDomainHints(RuntimeHints hints) { // Register core ACL domain types Stream .of(Acl.class, AccessControlEntry.class, AuditableAccessControlEntry.class, ObjectIdentity.class, Sid.class, AclImpl.class, AccessControlEntry.class, AuditLogger.class, ObjectIdentityImpl.class, PrincipalSid.class, GrantedAuthoritySid.class, BasePermission.class) .forEach((c) -> hints.reflection() .registerType(TypeReference.of(c), (builder) -> builder.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_DECLARED_METHODS, MemberCategory.ACCESS_DECLARED_FIELDS))); } private void registerJdbcSchemaHints(RuntimeHints hints) { String[] sqlFiles = new String[] { "createAclSchema.sql", "createAclSchemaMySQL.sql", "createAclSchemaOracle.sql", "createAclSchemaPostgres.sql", "createAclSchemaSqlServer.sql", "createAclSchemaWithAclClassIdType.sql", "select.sql" }; for (String sqlFile : sqlFiles) { Resource sqlResource = new ClassPathResource(sqlFile); if (sqlResource.exists()) { hints.resources().registerResource(sqlResource); } } } } ================================================ FILE: acl/src/main/java/org/springframework/security/acls/aot/hint/package-info.java ================================================ /* * 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. */ /** * AOT and native image hint support for ACLs. */ @NullMarked package org.springframework.security.acls.aot.hint; import org.jspecify.annotations.NullMarked; ================================================ FILE: acl/src/main/java/org/springframework/security/acls/domain/AbstractPermission.java ================================================ /* * 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 org.springframework.security.acls.domain; import org.springframework.security.acls.model.Permission; /** * Provides an abstract superclass for {@link Permission} implementations. * * @author Ben Alex * @since 2.0.3 */ public abstract class AbstractPermission implements Permission { protected final char code; protected int mask; /** * Sets the permission mask and uses the '*' character to represent active bits when * represented as a bit pattern string. * @param mask the integer bit mask for the permission */ protected AbstractPermission(int mask) { this.mask = mask; this.code = '*'; } /** * Sets the permission mask and uses the specified character for active bits. * @param mask the integer bit mask for the permission * @param code the character to print for each active bit in the mask (see * {@link Permission#getPattern()}) */ protected AbstractPermission(int mask, char code) { this.mask = mask; this.code = code; } @Override public final boolean equals(Object obj) { if (obj == null) { return false; } if (!(obj instanceof Permission other)) { return false; } return (this.mask == other.getMask()); } @Override public final int hashCode() { return this.mask; } @Override public final String toString() { return this.getClass().getSimpleName() + "[" + getPattern() + "=" + this.mask + "]"; } @Override public final int getMask() { return this.mask; } @Override public String getPattern() { return AclFormattingUtils.printBinary(this.mask, this.code); } } ================================================ FILE: acl/src/main/java/org/springframework/security/acls/domain/AccessControlEntryImpl.java ================================================ /* * 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 org.springframework.security.acls.domain; import java.io.Serializable; import org.jspecify.annotations.Nullable; import org.springframework.security.acls.model.AccessControlEntry; import org.springframework.security.acls.model.Acl; import org.springframework.security.acls.model.AuditableAccessControlEntry; import org.springframework.security.acls.model.Permission; import org.springframework.security.acls.model.Sid; import org.springframework.util.Assert; /** * An immutable default implementation of AccessControlEntry. * * @author Ben Alex */ public class AccessControlEntryImpl implements AccessControlEntry, AuditableAccessControlEntry { private final Acl acl; private Permission permission; private final @Nullable Serializable id; private final Sid sid; private boolean auditFailure = false; private boolean auditSuccess = false; private final boolean granting; public AccessControlEntryImpl(@Nullable Serializable id, Acl acl, Sid sid, Permission permission, boolean granting, boolean auditSuccess, boolean auditFailure) { Assert.notNull(acl, "Acl required"); Assert.notNull(sid, "Sid required"); Assert.notNull(permission, "Permission required"); this.id = id; this.acl = acl; // can be null this.sid = sid; this.permission = permission; this.granting = granting; this.auditSuccess = auditSuccess; this.auditFailure = auditFailure; } @Override public boolean equals(Object arg0) { if (!(arg0 instanceof AccessControlEntryImpl)) { return false; } AccessControlEntryImpl other = (AccessControlEntryImpl) arg0; if (this.acl == null) { if (other.getAcl() != null) { return false; } // Both this.acl and rhs.acl are null and thus equal } else { // this.acl is non-null if (other.getAcl() == null) { return false; } // Both this.acl and rhs.acl are non-null, so do a comparison if (this.acl.getObjectIdentity() == null) { if (other.acl.getObjectIdentity() != null) { return false; } // Both this.acl and rhs.acl are null and thus equal } else { // Both this.acl.objectIdentity and rhs.acl.objectIdentity are non-null if (!this.acl.getObjectIdentity().equals(other.getAcl().getObjectIdentity())) { return false; } } } if (this.id == null) { if (other.id != null) { return false; } // Both this.id and rhs.id are null and thus equal } else { // this.id is non-null if (other.id == null) { return false; } // Both this.id and rhs.id are non-null if (!this.id.equals(other.id)) { return false; } } if ((this.auditFailure != other.isAuditFailure()) || (this.auditSuccess != other.isAuditSuccess()) || (this.granting != other.isGranting()) || !this.permission.equals(other.getPermission()) || !this.sid.equals(other.getSid())) { return false; } return true; } @Override public int hashCode() { int result = this.permission.hashCode(); result = 31 * result + ((this.id != null) ? this.id.hashCode() : 0); result = 31 * result + (this.sid.hashCode()); result = 31 * result + (this.auditFailure ? 1 : 0); result = 31 * result + (this.auditSuccess ? 1 : 0); result = 31 * result + (this.granting ? 1 : 0); return result; } @Override public Acl getAcl() { return this.acl; } @Override public @Nullable Serializable getId() { return this.id; } @Override public Permission getPermission() { return this.permission; } @Override public Sid getSid() { return this.sid; } @Override public boolean isAuditFailure() { return this.auditFailure; } @Override public boolean isAuditSuccess() { return this.auditSuccess; } @Override public boolean isGranting() { return this.granting; } void setAuditFailure(boolean auditFailure) { this.auditFailure = auditFailure; } void setAuditSuccess(boolean auditSuccess) { this.auditSuccess = auditSuccess; } void setPermission(Permission permission) { Assert.notNull(permission, "Permission required"); this.permission = permission; } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("AccessControlEntryImpl["); sb.append("id: ").append(this.id).append("; "); sb.append("granting: ").append(this.granting).append("; "); sb.append("sid: ").append(this.sid).append("; "); sb.append("permission: ").append(this.permission).append("; "); sb.append("auditSuccess: ").append(this.auditSuccess).append("; "); sb.append("auditFailure: ").append(this.auditFailure); sb.append("]"); return sb.toString(); } } ================================================ FILE: acl/src/main/java/org/springframework/security/acls/domain/AclAuthorizationStrategy.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.acls.domain; import org.springframework.security.acls.model.Acl; /** * Strategy used by {@link AclImpl} to determine whether a principal is permitted to call * administrative methods on the AclImpl. * * @author Ben Alex */ public interface AclAuthorizationStrategy { int CHANGE_OWNERSHIP = 0; int CHANGE_AUDITING = 1; int CHANGE_GENERAL = 2; void securityCheck(Acl acl, int changeType); } ================================================ FILE: acl/src/main/java/org/springframework/security/acls/domain/AclAuthorizationStrategyImpl.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.acls.domain; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Set; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.hierarchicalroles.NullRoleHierarchy; import org.springframework.security.access.hierarchicalroles.RoleHierarchy; import org.springframework.security.acls.model.Acl; import org.springframework.security.acls.model.Sid; import org.springframework.security.acls.model.SidRetrievalStrategy; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.util.Assert; /** * Default implementation of {@link AclAuthorizationStrategy}. *

* Permission will be granted if at least one of the following conditions is true for the * current principal. *

    *
  • is the owner (as defined by the ACL).
  • *
  • holds the relevant system-wide {@link GrantedAuthority} injected into the * constructor.
  • *
  • has {@link BasePermission#ADMINISTRATION} permission (as defined by the ACL).
  • *
* * @author Ben Alex */ public class AclAuthorizationStrategyImpl implements AclAuthorizationStrategy { private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder .getContextHolderStrategy(); private final GrantedAuthority gaGeneralChanges; private final GrantedAuthority gaModifyAuditing; private final GrantedAuthority gaTakeOwnership; private SidRetrievalStrategy sidRetrievalStrategy = new SidRetrievalStrategyImpl(); private RoleHierarchy roleHierarchy = new NullRoleHierarchy(); /** * Constructor. The only mandatory parameter relates to the system-wide * {@link GrantedAuthority} instances that can be held to always permit ACL changes. * @param auths the GrantedAuthoritys that have special permissions * (index 0 is the authority needed to change ownership, index 1 is the authority * needed to modify auditing details, index 2 is the authority needed to change other * ACL and ACE details) (required) *

* Alternatively, a single value can be supplied for all three permissions. */ public AclAuthorizationStrategyImpl(GrantedAuthority... auths) { Assert.isTrue(auths != null && (auths.length == 3 || auths.length == 1), "One or three GrantedAuthority instances required"); if (auths.length == 3) { this.gaTakeOwnership = auths[0]; this.gaModifyAuditing = auths[1]; this.gaGeneralChanges = auths[2]; } else { this.gaTakeOwnership = auths[0]; this.gaModifyAuditing = auths[0]; this.gaGeneralChanges = auths[0]; } } @Override public void securityCheck(Acl acl, int changeType) { SecurityContext context = this.securityContextHolderStrategy.getContext(); if ((context == null) || (context.getAuthentication() == null) || !context.getAuthentication().isAuthenticated()) { throw new AccessDeniedException("Authenticated principal required to operate with ACLs"); } Authentication authentication = context.getAuthentication(); // Check if authorized by virtue of ACL ownership Sid currentUser = createCurrentUser(authentication); Sid owner = acl.getOwner(); if (owner != null && currentUser.equals(owner) && ((changeType == CHANGE_GENERAL) || (changeType == CHANGE_OWNERSHIP))) { return; } // Iterate this principal's authorities to determine right Collection reachableGrantedAuthorities = this.roleHierarchy .getReachableGrantedAuthorities(authentication.getAuthorities()); Set authorities = AuthorityUtils.authorityListToSet(reachableGrantedAuthorities); if (owner instanceof GrantedAuthoritySid && authorities.contains(((GrantedAuthoritySid) owner).getGrantedAuthority())) { return; } // Not authorized by ACL ownership; try via adminstrative permissions GrantedAuthority requiredAuthority = getRequiredAuthority(changeType); if (authorities.contains(requiredAuthority.getAuthority())) { return; } // Try to get permission via ACEs within the ACL List sids = this.sidRetrievalStrategy.getSids(authentication); if (acl.isGranted(Arrays.asList(BasePermission.ADMINISTRATION), sids, false)) { return; } throw new AccessDeniedException( "Principal does not have required ACL permissions to perform requested operation"); } private GrantedAuthority getRequiredAuthority(int changeType) { if (changeType == CHANGE_AUDITING) { return this.gaModifyAuditing; } if (changeType == CHANGE_GENERAL) { return this.gaGeneralChanges; } if (changeType == CHANGE_OWNERSHIP) { return this.gaTakeOwnership; } throw new IllegalArgumentException("Unknown change type"); } /** * Creates a principal-like sid from the authentication information. * @param authentication the authentication information that can provide principal and * thus the sid's id will be dependant on the value inside * @return a sid with the ID taken from the authentication information */ protected Sid createCurrentUser(Authentication authentication) { return new PrincipalSid(authentication); } public void setSidRetrievalStrategy(SidRetrievalStrategy sidRetrievalStrategy) { Assert.notNull(sidRetrievalStrategy, "SidRetrievalStrategy required"); this.sidRetrievalStrategy = sidRetrievalStrategy; } /** * Sets the {@link SecurityContextHolderStrategy} to use. The default action is to use * the {@link SecurityContextHolderStrategy} stored in {@link SecurityContextHolder}. * * @since 5.8 */ public void setSecurityContextHolderStrategy(SecurityContextHolderStrategy securityContextHolderStrategy) { Assert.notNull(securityContextHolderStrategy, "securityContextHolderStrategy cannot be null"); this.securityContextHolderStrategy = securityContextHolderStrategy; } /** * Sets the {@link RoleHierarchy} to use. The default is to use a * {@link NullRoleHierarchy} * @since 6.4 */ public void setRoleHierarchy(RoleHierarchy roleHierarchy) { Assert.notNull(roleHierarchy, "roleHierarchy cannot be null"); this.roleHierarchy = roleHierarchy; } } ================================================ FILE: acl/src/main/java/org/springframework/security/acls/domain/AclFormattingUtils.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.acls.domain; import org.springframework.security.acls.model.Permission; import org.springframework.util.Assert; /** * Utility methods for displaying ACL information. * * @author Ben Alex */ public abstract class AclFormattingUtils { public static String demergePatterns(String original, String removeBits) { Assert.notNull(original, "Original string required"); Assert.notNull(removeBits, "Bits To Remove string required"); Assert.isTrue(original.length() == removeBits.length(), "Original and Bits To Remove strings must be identical length"); char[] replacement = new char[original.length()]; for (int i = 0; i < original.length(); i++) { if (removeBits.charAt(i) == Permission.RESERVED_OFF) { replacement[i] = original.charAt(i); } else { replacement[i] = Permission.RESERVED_OFF; } } return new String(replacement); } public static String mergePatterns(String original, String extraBits) { Assert.notNull(original, "Original string required"); Assert.notNull(extraBits, "Extra Bits string required"); Assert.isTrue(original.length() == extraBits.length(), "Original and Extra Bits strings must be identical length"); char[] replacement = new char[extraBits.length()]; for (int i = 0; i < extraBits.length(); i++) { if (extraBits.charAt(i) == Permission.RESERVED_OFF) { replacement[i] = original.charAt(i); } else { replacement[i] = extraBits.charAt(i); } } return new String(replacement); } /** * Returns a representation of the active bits in the presented mask, with each active * bit being denoted by character '*'. *

* Inactive bits will be denoted by character {@link Permission#RESERVED_OFF}. * @param i the integer bit mask to print the active bits for * @return a 32-character representation of the bit mask */ public static String printBinary(int i) { return printBinary(i, '*', Permission.RESERVED_OFF); } /** * Returns a representation of the active bits in the presented mask, with each active * bit being denoted by the passed character. *

* Inactive bits will be denoted by character {@link Permission#RESERVED_OFF}. * @param mask the integer bit mask to print the active bits for * @param code the character to print when an active bit is detected * @return a 32-character representation of the bit mask */ public static String printBinary(int mask, char code) { Assert.doesNotContain(Character.toString(code), Character.toString(Permission.RESERVED_ON), () -> Permission.RESERVED_ON + " is a reserved character code"); Assert.doesNotContain(Character.toString(code), Character.toString(Permission.RESERVED_OFF), () -> Permission.RESERVED_OFF + " is a reserved character code"); return printBinary(mask, Permission.RESERVED_ON, Permission.RESERVED_OFF).replace(Permission.RESERVED_ON, code); } private static String printBinary(int i, char on, char off) { String s = Integer.toBinaryString(i); String pattern = Permission.THIRTY_TWO_RESERVED_OFF; String temp2 = pattern.substring(0, pattern.length() - s.length()) + s; return temp2.replace('0', off).replace('1', on); } } ================================================ FILE: acl/src/main/java/org/springframework/security/acls/domain/AclImpl.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.acls.domain; import java.io.Serializable; import java.util.ArrayList; import java.util.List; import org.jspecify.annotations.Nullable; import org.springframework.security.acls.model.AccessControlEntry; import org.springframework.security.acls.model.Acl; import org.springframework.security.acls.model.AuditableAcl; import org.springframework.security.acls.model.MutableAcl; import org.springframework.security.acls.model.NotFoundException; import org.springframework.security.acls.model.ObjectIdentity; import org.springframework.security.acls.model.OwnershipAcl; import org.springframework.security.acls.model.Permission; import org.springframework.security.acls.model.PermissionGrantingStrategy; import org.springframework.security.acls.model.Sid; import org.springframework.security.acls.model.UnloadedSidException; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; /** * Base implementation of Acl. * * @author Ben Alex */ public class AclImpl implements Acl, MutableAcl, AuditableAcl, OwnershipAcl { private @Nullable Acl parentAcl; private transient AclAuthorizationStrategy aclAuthorizationStrategy; private transient PermissionGrantingStrategy permissionGrantingStrategy; private final List aces = new ArrayList<>(); private ObjectIdentity objectIdentity; private Serializable id; // OwnershipAcl private @Nullable Sid owner; // includes all SIDs the WHERE clause covered, even if there was no ACE for a SID private @Nullable List loadedSids = null; private boolean entriesInheriting = true; /** * Minimal constructor, which should be used * {@link org.springframework.security.acls.model.MutableAclService#createAcl(ObjectIdentity)} * . * @param objectIdentity the object identity this ACL relates to (required) * @param id the primary key assigned to this ACL (required) * @param aclAuthorizationStrategy authorization strategy (required) * @param auditLogger audit logger (required) */ public AclImpl(ObjectIdentity objectIdentity, Serializable id, AclAuthorizationStrategy aclAuthorizationStrategy, AuditLogger auditLogger) { Assert.notNull(objectIdentity, "Object Identity required"); Assert.notNull(id, "Id required"); Assert.notNull(aclAuthorizationStrategy, "AclAuthorizationStrategy required"); Assert.notNull(auditLogger, "AuditLogger required"); this.objectIdentity = objectIdentity; this.id = id; this.aclAuthorizationStrategy = aclAuthorizationStrategy; this.permissionGrantingStrategy = new DefaultPermissionGrantingStrategy(auditLogger); } /** * Full constructor, which should be used by persistence tools that do not provide * field-level access features. * @param objectIdentity the object identity this ACL relates to * @param id the primary key assigned to this ACL * @param aclAuthorizationStrategy authorization strategy * @param grantingStrategy the {@code PermissionGrantingStrategy} which will be used * by the {@code isGranted()} method * @param parentAcl the parent (may be may be {@code null}) * @param loadedSids the loaded SIDs if only a subset were loaded (may be {@code null} * ) * @param entriesInheriting if ACEs from the parent should inherit into this ACL * @param owner the owner (required) */ public AclImpl(ObjectIdentity objectIdentity, Serializable id, AclAuthorizationStrategy aclAuthorizationStrategy, PermissionGrantingStrategy grantingStrategy, @Nullable Acl parentAcl, @Nullable List loadedSids, boolean entriesInheriting, Sid owner) { Assert.notNull(objectIdentity, "Object Identity required"); Assert.notNull(id, "Id required"); Assert.notNull(aclAuthorizationStrategy, "AclAuthorizationStrategy required"); Assert.notNull(owner, "Owner required"); this.objectIdentity = objectIdentity; this.id = id; this.aclAuthorizationStrategy = aclAuthorizationStrategy; this.parentAcl = parentAcl; // may be null this.loadedSids = loadedSids; // may be null this.entriesInheriting = entriesInheriting; this.owner = owner; this.permissionGrantingStrategy = grantingStrategy; } /** * Private no-argument constructor for use by reflection-based persistence tools along * with field-level access. */ @SuppressWarnings({ "unused", "NullAway.Init" }) private AclImpl() { } @Override public void deleteAce(int aceIndex) throws NotFoundException { this.aclAuthorizationStrategy.securityCheck(this, AclAuthorizationStrategy.CHANGE_GENERAL); verifyAceIndexExists(aceIndex); synchronized (this.aces) { this.aces.remove(aceIndex); } } private void verifyAceIndexExists(int aceIndex) { if (aceIndex < 0) { throw new NotFoundException("aceIndex must be greater than or equal to zero"); } if (aceIndex >= this.aces.size()) { throw new NotFoundException("aceIndex must refer to an index of the AccessControlEntry list. " + "List size is " + this.aces.size() + ", index was " + aceIndex); } } @Override public void insertAce(int atIndexLocation, Permission permission, Sid sid, boolean granting) throws NotFoundException { this.aclAuthorizationStrategy.securityCheck(this, AclAuthorizationStrategy.CHANGE_GENERAL); Assert.notNull(permission, "Permission required"); Assert.notNull(sid, "Sid required"); if (atIndexLocation < 0) { throw new NotFoundException("atIndexLocation must be greater than or equal to zero"); } if (atIndexLocation > this.aces.size()) { throw new NotFoundException( "atIndexLocation must be less than or equal to the size of the AccessControlEntry collection"); } AccessControlEntryImpl ace = new AccessControlEntryImpl(null, this, sid, permission, granting, false, false); synchronized (this.aces) { this.aces.add(atIndexLocation, ace); } } @Override public List getEntries() { // Can safely return AccessControlEntry directly, as they're immutable outside the // ACL package return new ArrayList<>(this.aces); } @Override public Serializable getId() { return this.id; } @Override public ObjectIdentity getObjectIdentity() { return this.objectIdentity; } @Override public boolean isEntriesInheriting() { return this.entriesInheriting; } /** * Delegates to the {@link PermissionGrantingStrategy}. * @throws UnloadedSidException if the passed SIDs are unknown to this ACL because the * ACL was only loaded for a subset of SIDs * @see DefaultPermissionGrantingStrategy */ @Override public boolean isGranted(List permission, List sids, boolean administrativeMode) throws NotFoundException, UnloadedSidException { Assert.notEmpty(permission, "Permissions required"); Assert.notEmpty(sids, "SIDs required"); if (!this.isSidLoaded(sids)) { throw new UnloadedSidException("ACL was not loaded for one or more SID"); } return this.permissionGrantingStrategy.isGranted(this, permission, sids, administrativeMode); } @Override public boolean isSidLoaded(@Nullable List sids) { // If loadedSides is null, this indicates all SIDs were loaded // Also return true if the caller didn't specify a SID to find if ((this.loadedSids == null) || (sids == null) || sids.isEmpty()) { return true; } // This ACL applies to a SID subset only. Iterate to check it applies. for (Sid sid : sids) { boolean found = false; for (Sid loadedSid : this.loadedSids) { if (sid.equals(loadedSid)) { // this SID is OK found = true; break; // out of loadedSids for loop } } if (!found) { return false; } } return true; } @Override public void setEntriesInheriting(boolean entriesInheriting) { this.aclAuthorizationStrategy.securityCheck(this, AclAuthorizationStrategy.CHANGE_GENERAL); this.entriesInheriting = entriesInheriting; } @Override public void setOwner(Sid newOwner) { this.aclAuthorizationStrategy.securityCheck(this, AclAuthorizationStrategy.CHANGE_OWNERSHIP); Assert.notNull(newOwner, "Owner required"); this.owner = newOwner; } @Override public @Nullable Sid getOwner() { return this.owner; } @Override public void setParent(@Nullable Acl newParent) { this.aclAuthorizationStrategy.securityCheck(this, AclAuthorizationStrategy.CHANGE_GENERAL); Assert.isTrue(newParent == null || !newParent.equals(this), "Cannot be the parent of yourself"); this.parentAcl = newParent; } @Override public @Nullable Acl getParentAcl() { return this.parentAcl; } @Override public void updateAce(int aceIndex, Permission permission) throws NotFoundException { this.aclAuthorizationStrategy.securityCheck(this, AclAuthorizationStrategy.CHANGE_GENERAL); verifyAceIndexExists(aceIndex); synchronized (this.aces) { AccessControlEntryImpl ace = (AccessControlEntryImpl) this.aces.get(aceIndex); ace.setPermission(permission); } } @Override public void updateAuditing(int aceIndex, boolean auditSuccess, boolean auditFailure) { this.aclAuthorizationStrategy.securityCheck(this, AclAuthorizationStrategy.CHANGE_AUDITING); verifyAceIndexExists(aceIndex); synchronized (this.aces) { AccessControlEntryImpl ace = (AccessControlEntryImpl) this.aces.get(aceIndex); ace.setAuditSuccess(auditSuccess); ace.setAuditFailure(auditFailure); } } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (obj == null || !(obj instanceof AclImpl)) { return false; } AclImpl other = (AclImpl) obj; boolean result = true; result = result && this.aces.equals(other.aces); result = result && ObjectUtils.nullSafeEquals(this.parentAcl, other.parentAcl); result = result && ObjectUtils.nullSafeEquals(this.objectIdentity, other.objectIdentity); result = result && ObjectUtils.nullSafeEquals(this.id, other.id); result = result && ObjectUtils.nullSafeEquals(this.owner, other.owner); result = result && this.entriesInheriting == other.entriesInheriting; result = result && ObjectUtils.nullSafeEquals(this.loadedSids, other.loadedSids); return result; } @Override public int hashCode() { int result = (this.parentAcl != null) ? this.parentAcl.hashCode() : 0; result = 31 * result + this.aclAuthorizationStrategy.hashCode(); result = 31 * result + ((this.permissionGrantingStrategy != null) ? this.permissionGrantingStrategy.hashCode() : 0); result = 31 * result + ((this.aces != null) ? this.aces.hashCode() : 0); result = 31 * result + this.objectIdentity.hashCode(); result = 31 * result + this.id.hashCode(); result = 31 * result + ((this.owner != null) ? this.owner.hashCode() : 0); result = 31 * result + ((this.loadedSids != null) ? this.loadedSids.hashCode() : 0); result = 31 * result + (this.entriesInheriting ? 1 : 0); return result; } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("AclImpl["); sb.append("id: ").append(this.id).append("; "); sb.append("objectIdentity: ").append(this.objectIdentity).append("; "); sb.append("owner: ").append(this.owner).append("; "); int count = 0; for (AccessControlEntry ace : this.aces) { count++; if (count == 1) { sb.append("\n"); } sb.append(ace).append("\n"); } if (count == 0) { sb.append("no ACEs; "); } sb.append("inheriting: ").append(this.entriesInheriting).append("; "); sb.append("parent: ").append((this.parentAcl == null) ? "Null" : this.parentAcl.getObjectIdentity().toString()); sb.append("; "); sb.append("aclAuthorizationStrategy: ").append(this.aclAuthorizationStrategy).append("; "); sb.append("permissionGrantingStrategy: ").append(this.permissionGrantingStrategy); sb.append("]"); return sb.toString(); } } ================================================ FILE: acl/src/main/java/org/springframework/security/acls/domain/AuditLogger.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.acls.domain; import org.springframework.security.acls.model.AccessControlEntry; /** * Used by AclImpl to log audit events. * * @author Ben Alex */ public interface AuditLogger { void logIfNeeded(boolean granted, AccessControlEntry ace); } ================================================ FILE: acl/src/main/java/org/springframework/security/acls/domain/BasePermission.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.acls.domain; import org.springframework.security.acls.model.Permission; /** * A set of standard permissions. * *

* You may subclass this class to add additional permissions, or use this class as a guide * for creating your own permission classes. *

* * @author Ben Alex */ public class BasePermission extends AbstractPermission { public static final Permission READ = new BasePermission(1 << 0, 'R'); // 1 public static final Permission WRITE = new BasePermission(1 << 1, 'W'); // 2 public static final Permission CREATE = new BasePermission(1 << 2, 'C'); // 4 public static final Permission DELETE = new BasePermission(1 << 3, 'D'); // 8 public static final Permission ADMINISTRATION = new BasePermission(1 << 4, 'A'); // 16 protected BasePermission(int mask) { super(mask); } protected BasePermission(int mask, char code) { super(mask, code); } } ================================================ FILE: acl/src/main/java/org/springframework/security/acls/domain/ConsoleAuditLogger.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.acls.domain; import org.springframework.security.acls.model.AccessControlEntry; import org.springframework.security.acls.model.AuditableAccessControlEntry; import org.springframework.util.Assert; /** * A basic implementation of {@link AuditLogger}. * * @author Ben Alex */ public class ConsoleAuditLogger implements AuditLogger { @Override public void logIfNeeded(boolean granted, AccessControlEntry ace) { Assert.notNull(ace, "AccessControlEntry required"); if (ace instanceof AuditableAccessControlEntry) { AuditableAccessControlEntry auditableAce = (AuditableAccessControlEntry) ace; if (granted && auditableAce.isAuditSuccess()) { System.out.println("GRANTED due to ACE: " + ace); } else if (!granted && auditableAce.isAuditFailure()) { System.out.println("DENIED due to ACE: " + ace); } } } } ================================================ FILE: acl/src/main/java/org/springframework/security/acls/domain/CumulativePermission.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.acls.domain; import org.springframework.security.acls.model.Permission; /** * Represents a Permission that is constructed at runtime from other * permissions. * *

* Methods return this, in order to facilitate method chaining. *

* * @author Ben Alex */ public class CumulativePermission extends AbstractPermission { private String pattern = THIRTY_TWO_RESERVED_OFF; public CumulativePermission() { super(0, ' '); } public CumulativePermission clear(Permission permission) { this.mask &= ~permission.getMask(); this.pattern = AclFormattingUtils.demergePatterns(this.pattern, permission.getPattern()); return this; } public CumulativePermission clear() { this.mask = 0; this.pattern = THIRTY_TWO_RESERVED_OFF; return this; } public CumulativePermission set(Permission permission) { this.mask |= permission.getMask(); this.pattern = AclFormattingUtils.mergePatterns(this.pattern, permission.getPattern()); return this; } @Override public String getPattern() { return this.pattern; } } ================================================ FILE: acl/src/main/java/org/springframework/security/acls/domain/DefaultPermissionFactory.java ================================================ /* * 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 org.springframework.security.acls.domain; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import org.springframework.security.acls.model.Permission; import org.springframework.util.Assert; /** * Default implementation of {@link PermissionFactory}. *

* Used as a strategy by classes which wish to map integer masks and permission names to * Permission instances for use with the ACL implementation. *

* Maintains a registry of permission names and masks to Permission instances. * * @author Ben Alex * @author Luke Taylor * @since 2.0.3 */ public class DefaultPermissionFactory implements PermissionFactory { private final Map registeredPermissionsByInteger = new HashMap<>(); private final Map registeredPermissionsByName = new HashMap<>(); /** * Registers the Permission fields from the BasePermission class. */ public DefaultPermissionFactory() { registerPublicPermissions(BasePermission.class); } /** * Registers the Permission fields from the supplied class. */ public DefaultPermissionFactory(Class permissionClass) { registerPublicPermissions(permissionClass); } /** * Registers a map of named Permission instances. * @param namedPermissions the map of Permissions, keyed by name. */ public DefaultPermissionFactory(Map namedPermissions) { for (String name : namedPermissions.keySet()) { registerPermission(namedPermissions.get(name), name); } } /** * Registers the public static fields of type {@link Permission} for a give class. *

* These permissions will be registered under the name of the field. See * {@link BasePermission} for an example. * @param clazz a {@link Permission} class with public static fields to register */ protected void registerPublicPermissions(Class clazz) { Assert.notNull(clazz, "Class required"); Field[] fields = clazz.getFields(); for (Field field : fields) { try { Object fieldValue = field.get(null); if (Permission.class.isAssignableFrom(fieldValue.getClass())) { // Found a Permission static field Permission perm = (Permission) fieldValue; String permissionName = field.getName(); registerPermission(perm, permissionName); } } catch (Exception ex) { } } } protected void registerPermission(Permission perm, String permissionName) { Assert.notNull(perm, "Permission required"); Assert.hasText(permissionName, "Permission name required"); Integer mask = perm.getMask(); // Ensure no existing Permission uses this integer or code Assert.isTrue(!this.registeredPermissionsByInteger.containsKey(mask), () -> "An existing Permission already provides mask " + mask); Assert.isTrue(!this.registeredPermissionsByName.containsKey(permissionName), () -> "An existing Permission already provides name '" + permissionName + "'"); // Register the new Permission this.registeredPermissionsByInteger.put(mask, perm); this.registeredPermissionsByName.put(permissionName, perm); } @Override public Permission buildFromMask(int mask) { if (this.registeredPermissionsByInteger.containsKey(mask)) { // The requested mask has an exact match against a statically-defined // Permission, so return it return this.registeredPermissionsByInteger.get(mask); } // To get this far, we have to use a CumulativePermission CumulativePermission permission = new CumulativePermission(); for (int i = 0; i < 32; i++) { int permissionToCheck = 1 << i; if ((mask & permissionToCheck) == permissionToCheck) { Permission p = this.registeredPermissionsByInteger.get(permissionToCheck); Assert.state(p != null, () -> "Mask '" + permissionToCheck + "' does not have a corresponding static Permission"); permission.set(p); } } return permission; } @Override public Permission buildFromName(String name) { Permission p = this.registeredPermissionsByName.get(name); Assert.notNull(p, "Unknown permission '" + name + "'"); return p; } @Override public List buildFromNames(List names) { if ((names == null) || names.isEmpty()) { return Collections.emptyList(); } List permissions = new ArrayList<>(names.size()); for (String name : names) { permissions.add(buildFromName(name)); } return permissions; } } ================================================ FILE: acl/src/main/java/org/springframework/security/acls/domain/DefaultPermissionGrantingStrategy.java ================================================ /* * 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 org.springframework.security.acls.domain; import java.util.List; import org.springframework.security.acls.model.AccessControlEntry; import org.springframework.security.acls.model.Acl; import org.springframework.security.acls.model.NotFoundException; import org.springframework.security.acls.model.Permission; import org.springframework.security.acls.model.PermissionGrantingStrategy; import org.springframework.security.acls.model.Sid; import org.springframework.util.Assert; public class DefaultPermissionGrantingStrategy implements PermissionGrantingStrategy { private final transient AuditLogger auditLogger; /** * Creates an instance with the logger which will be used to record granting and * denial of requested permissions. */ public DefaultPermissionGrantingStrategy(AuditLogger auditLogger) { Assert.notNull(auditLogger, "auditLogger cannot be null"); this.auditLogger = auditLogger; } /** * Determines authorization. The order of the permission and * sid arguments is extremely important! The method will iterate * through each of the permissions in the order specified. For each * iteration, all of the sids will be considered, again in the order they * are presented. A search will then be performed for the first * {@link AccessControlEntry} object that directly matches that * permission:sid combination. When the first full match is * found (ie an ACE that has the SID currently being searched for and the exact * permission bit mask being search for), the grant or deny flag for that ACE will * prevail. If the ACE specifies to grant access, the method will return * true. If the ACE specifies to deny access, the loop will stop and the * next permission iteration will be performed. If each permission * indicates to deny access, the first deny ACE found will be considered the reason * for the failure (as it was the first match found, and is therefore the one most * logically requiring changes - although not always). If absolutely no matching ACE * was found at all for any permission, the parent ACL will be tried (provided that * there is a parent and {@link Acl#isEntriesInheriting()} is true. The * parent ACL will also scan its parent and so on. If ultimately no matching ACE is * found, a NotFoundException will be thrown and the caller will need to * decide how to handle the permission check. Similarly, if any of the SID arguments * presented to the method were not loaded by the ACL, * UnloadedSidException will be thrown. * @param permission the exact permissions to scan for (order is important) * @param sids the exact SIDs to scan for (order is important) * @param administrativeMode if true denotes the query is for * administrative purposes and no auditing will be undertaken * @return true if one of the permissions has been granted, * false if one of the permissions has been specifically revoked * @throws NotFoundException if an exact ACE for one of the permission bit masks and * SID combination could not be found */ @Override public boolean isGranted(Acl acl, List permission, List sids, boolean administrativeMode) throws NotFoundException { List aces = acl.getEntries(); AccessControlEntry firstRejection = null; for (Permission p : permission) { for (Sid sid : sids) { // Attempt to find exact match for this permission mask and SID boolean scanNextSid = true; for (AccessControlEntry ace : aces) { if (isGranted(ace, p) && ace.getSid().equals(sid)) { // Found a matching ACE, so its authorization decision will // prevail if (ace.isGranting()) { // Success if (!administrativeMode) { this.auditLogger.logIfNeeded(true, ace); } return true; } // Failure for this permission, so stop search // We will see if they have a different permission // (this permission is 100% rejected for this SID) if (firstRejection == null) { // Store first rejection for auditing reasons firstRejection = ace; } scanNextSid = false; // helps break the loop break; // exit aces loop } } if (!scanNextSid) { break; // exit SID for loop (now try next permission) } } } if (firstRejection != null) { // We found an ACE to reject the request at this point, as no // other ACEs were found that granted a different permission if (!administrativeMode) { this.auditLogger.logIfNeeded(false, firstRejection); } return false; } // No matches have been found so far if (acl.isEntriesInheriting() && (acl.getParentAcl() != null)) { // We have a parent, so let them try to find a matching ACE return acl.getParentAcl().isGranted(permission, sids, false); } // We either have no parent, or we're the uppermost parent throw new NotFoundException("Unable to locate a matching ACE for passed permissions and SIDs"); } /** * Compares an ACE Permission to the given Permission. By default, we compare the * Permission masks for exact match. Subclasses of this strategy can override this * behavior and implement more sophisticated comparisons, e.g. a bitwise comparison * for ACEs that grant access.

{@code
	 * if (ace.isGranting() && p.getMask() != 0) {
	 *    return (ace.getPermission().getMask() & p.getMask()) != 0;
	 * } else {
	 *    return ace.getPermission().getMask() == p.getMask();
	 * }
	 * }
* @param ace the ACE from the Acl holding the mask. * @param p the Permission we are checking against. * @return true, if the respective masks are considered to be equal. */ protected boolean isGranted(AccessControlEntry ace, Permission p) { return ace.getPermission().getMask() == p.getMask(); } } ================================================ FILE: acl/src/main/java/org/springframework/security/acls/domain/GrantedAuthoritySid.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.acls.domain; import org.springframework.security.acls.model.Sid; import org.springframework.security.core.GrantedAuthority; import org.springframework.util.Assert; /** * Represents a GrantedAuthority as a Sid. *

* This is a basic implementation that simply uses the String-based principal * for Sid comparison. More complex principal objects may wish to provide an * alternative Sid implementation that uses some other identifier. *

* * @author Ben Alex */ public class GrantedAuthoritySid implements Sid { private final String grantedAuthority; public GrantedAuthoritySid(String grantedAuthority) { Assert.hasText(grantedAuthority, "GrantedAuthority required"); this.grantedAuthority = grantedAuthority; } public GrantedAuthoritySid(GrantedAuthority grantedAuthority) { Assert.notNull(grantedAuthority, "GrantedAuthority required"); Assert.notNull(grantedAuthority.getAuthority(), "This Sid is only compatible with GrantedAuthority that provide a non-null getAuthority()"); this.grantedAuthority = grantedAuthority.getAuthority(); } @Override public boolean equals(Object object) { if ((object == null) || !(object instanceof GrantedAuthoritySid)) { return false; } // Delegate to getGrantedAuthority() to perform actual comparison (both should be // identical) return ((GrantedAuthoritySid) object).getGrantedAuthority().equals(this.getGrantedAuthority()); } @Override public int hashCode() { return this.getGrantedAuthority().hashCode(); } public String getGrantedAuthority() { return this.grantedAuthority; } @Override public String toString() { return "GrantedAuthoritySid[" + this.grantedAuthority + "]"; } } ================================================ FILE: acl/src/main/java/org/springframework/security/acls/domain/IdentityUnavailableException.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.acls.domain; /** * Thrown if an ACL identity could not be extracted from an object. * * @author Ben Alex */ public class IdentityUnavailableException extends RuntimeException { /** * Constructs an IdentityUnavailableException with the specified message. * @param msg the detail message */ public IdentityUnavailableException(String msg) { super(msg); } /** * Constructs an IdentityUnavailableException with the specified message * and root cause. * @param msg the detail message * @param cause root cause */ public IdentityUnavailableException(String msg, Throwable cause) { super(msg, cause); } } ================================================ FILE: acl/src/main/java/org/springframework/security/acls/domain/ObjectIdentityImpl.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.acls.domain; import java.io.Serializable; import java.lang.reflect.Method; import org.springframework.security.acls.model.ObjectIdentity; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; /** * Simple implementation of {@link ObjectIdentity}. *

* Uses Strings to store the identity of the domain object instance. Also * offers a constructor that uses reflection to build the identity information. * * @author Ben Alex */ public class ObjectIdentityImpl implements ObjectIdentity { private final String type; private Serializable identifier; public ObjectIdentityImpl(String type, Serializable identifier) { Assert.hasText(type, "Type required"); Assert.notNull(identifier, "identifier required"); this.identifier = identifier; this.type = type; } /** * Constructor which uses the name of the supplied class as the type * property. */ public ObjectIdentityImpl(Class javaType, Serializable identifier) { Assert.notNull(javaType, "Java Type required"); Assert.notNull(identifier, "identifier required"); this.type = javaType.getName(); this.identifier = identifier; } /** * Creates the ObjectIdentityImpl based on the passed object instance. * The passed object must provide a getId() method, otherwise an * exception will be thrown. *

* The class name of the object passed will be considered the {@link #type}, so if * more control is required, a different constructor should be used. * @param object the domain object instance to create an identity for. * @throws IdentityUnavailableException if identity could not be extracted */ public ObjectIdentityImpl(Object object) throws IdentityUnavailableException { Assert.notNull(object, "object cannot be null"); Class typeClass = ClassUtils.getUserClass(object.getClass()); this.type = typeClass.getName(); Object result = invokeGetIdMethod(object, typeClass); Assert.notNull(result, "getId() is required to return a non-null value"); Assert.isInstanceOf(Serializable.class, result, "Getter must provide a return value of type Serializable"); this.identifier = (Serializable) result; } private Object invokeGetIdMethod(Object object, Class typeClass) { try { Method method = typeClass.getMethod("getId", new Class[] {}); return method.invoke(object); } catch (Exception ex) { throw new IdentityUnavailableException("Could not extract identity from object " + object, ex); } } /** * Important so caching operates properly. *

* Considers an object of the same class equal if it has the same * classname and id properties. *

* Numeric identities (Integer and Long values) are considered equal if they are * numerically equal. Other serializable types are evaluated using a simple equality. * @param obj object to compare * @return true if the presented object matches this object */ @Override public boolean equals(Object obj) { if (obj == null || !(obj instanceof ObjectIdentityImpl)) { return false; } ObjectIdentityImpl other = (ObjectIdentityImpl) obj; if (this.identifier instanceof Number && other.identifier instanceof Number) { // Integers and Longs with same value should be considered equal if (((Number) this.identifier).longValue() != ((Number) other.identifier).longValue()) { return false; } } else { // Use plain equality for other serializable types if (!this.identifier.equals(other.identifier)) { return false; } } return this.type.equals(other.type); } @Override public Serializable getIdentifier() { return this.identifier; } @Override public String getType() { return this.type; } /** * Important so caching operates properly. * @return the hash */ @Override public int hashCode() { int result = this.type.hashCode(); result = 31 * result + this.identifier.hashCode(); return result; } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append(this.getClass().getName()).append("["); sb.append("Type: ").append(this.type); sb.append("; Identifier: ").append(this.identifier).append("]"); return sb.toString(); } } ================================================ FILE: acl/src/main/java/org/springframework/security/acls/domain/ObjectIdentityRetrievalStrategyImpl.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.acls.domain; import java.io.Serializable; import org.springframework.security.acls.model.ObjectIdentity; import org.springframework.security.acls.model.ObjectIdentityGenerator; import org.springframework.security.acls.model.ObjectIdentityRetrievalStrategy; /** * Basic implementation of {@link ObjectIdentityRetrievalStrategy} and * ObjectIdentityGenerator that uses the constructors of * {@link ObjectIdentityImpl} to create the {@link ObjectIdentity}. * * @author Ben Alex */ public class ObjectIdentityRetrievalStrategyImpl implements ObjectIdentityRetrievalStrategy, ObjectIdentityGenerator { @Override public ObjectIdentity getObjectIdentity(Object domainObject) { return new ObjectIdentityImpl(domainObject); } @Override public ObjectIdentity createObjectIdentity(Serializable id, String type) { return new ObjectIdentityImpl(type, id); } } ================================================ FILE: acl/src/main/java/org/springframework/security/acls/domain/PermissionFactory.java ================================================ /* * 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 org.springframework.security.acls.domain; import java.util.List; import org.springframework.security.acls.model.Permission; /** * Provides a simple mechanism to retrieve {@link Permission} instances from integer * masks. * * @author Ben Alex * @since 2.0.3 */ public interface PermissionFactory { /** * Dynamically creates a CumulativePermission or * BasePermission representing the active bits in the passed mask. * @param mask to build * @return a Permission representing the requested object */ Permission buildFromMask(int mask); Permission buildFromName(String name); List buildFromNames(List names); } ================================================ FILE: acl/src/main/java/org/springframework/security/acls/domain/PrincipalSid.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.acls.domain; import org.springframework.security.acls.model.Sid; import org.springframework.security.core.Authentication; import org.springframework.util.Assert; /** * Represents an Authentication.getPrincipal() as a Sid. *

* This is a basic implementation that simply uses the String-based principal * for Sid comparison. More complex principal objects may wish to provide an * alternative Sid implementation that uses some other identifier. *

* * @author Ben Alex */ public class PrincipalSid implements Sid { private final String principal; public PrincipalSid(String principal) { Assert.hasText(principal, "Principal required"); this.principal = principal; } public PrincipalSid(Authentication authentication) { Assert.notNull(authentication, "Authentication required"); Assert.notNull(authentication.getPrincipal(), "Principal required"); this.principal = authentication.getName(); } @Override public boolean equals(Object object) { if ((object == null) || !(object instanceof PrincipalSid)) { return false; } // Delegate to getPrincipal() to perform actual comparison (both should be // identical) return ((PrincipalSid) object).getPrincipal().equals(this.getPrincipal()); } @Override public int hashCode() { return this.getPrincipal().hashCode(); } public String getPrincipal() { return this.principal; } @Override public String toString() { return "PrincipalSid[" + this.principal + "]"; } } ================================================ FILE: acl/src/main/java/org/springframework/security/acls/domain/SidRetrievalStrategyImpl.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.acls.domain; import java.util.ArrayList; import java.util.Collection; import java.util.List; import org.springframework.security.access.hierarchicalroles.NullRoleHierarchy; import org.springframework.security.access.hierarchicalroles.RoleHierarchy; import org.springframework.security.acls.model.Sid; import org.springframework.security.acls.model.SidRetrievalStrategy; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.util.Assert; /** * Basic implementation of {@link SidRetrievalStrategy} that creates a {@link Sid} for the * principal, as well as every granted authority the principal holds. Can optionally have * a RoleHierarchy injected in order to determine the extended list of * authorities that the principal is assigned. *

* The returned array will always contain the {@link PrincipalSid} before any * {@link GrantedAuthoritySid} elements. * * @author Ben Alex */ public class SidRetrievalStrategyImpl implements SidRetrievalStrategy { private RoleHierarchy roleHierarchy = new NullRoleHierarchy(); public SidRetrievalStrategyImpl() { } public SidRetrievalStrategyImpl(RoleHierarchy roleHierarchy) { Assert.notNull(roleHierarchy, "RoleHierarchy must not be null"); this.roleHierarchy = roleHierarchy; } @Override public List getSids(Authentication authentication) { Collection authorities = this.roleHierarchy .getReachableGrantedAuthorities(authentication.getAuthorities()); List sids = new ArrayList<>(authorities.size() + 1); sids.add(new PrincipalSid(authentication)); for (GrantedAuthority authority : authorities) { sids.add(new GrantedAuthoritySid(authority)); } return sids; } } ================================================ FILE: acl/src/main/java/org/springframework/security/acls/domain/SpringCacheBasedAclCache.java ================================================ /* * 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 org.springframework.security.acls.domain; import java.io.Serializable; import org.jspecify.annotations.Nullable; import org.springframework.cache.Cache; import org.springframework.security.acls.model.AclCache; import org.springframework.security.acls.model.MutableAcl; import org.springframework.security.acls.model.ObjectIdentity; import org.springframework.security.acls.model.PermissionGrantingStrategy; import org.springframework.security.util.FieldUtils; import org.springframework.util.Assert; /** * Simple implementation of {@link org.springframework.security.acls.model.AclCache} that * delegates to {@link Cache} implementation. *

* Designed to handle the transient fields in * {@link org.springframework.security.acls.domain.AclImpl}. Note that this implementation * assumes all {@link org.springframework.security.acls.domain.AclImpl} instances share * the same {@link org.springframework.security.acls.model.PermissionGrantingStrategy} and * {@link org.springframework.security.acls.domain.AclAuthorizationStrategy} instances. * * @author Marten Deinum * @since 3.2 */ public class SpringCacheBasedAclCache implements AclCache { private final Cache cache; private PermissionGrantingStrategy permissionGrantingStrategy; private AclAuthorizationStrategy aclAuthorizationStrategy; public SpringCacheBasedAclCache(Cache cache, PermissionGrantingStrategy permissionGrantingStrategy, AclAuthorizationStrategy aclAuthorizationStrategy) { Assert.notNull(cache, "Cache required"); Assert.notNull(permissionGrantingStrategy, "PermissionGrantingStrategy required"); Assert.notNull(aclAuthorizationStrategy, "AclAuthorizationStrategy required"); this.cache = cache; this.permissionGrantingStrategy = permissionGrantingStrategy; this.aclAuthorizationStrategy = aclAuthorizationStrategy; } @Override public void evictFromCache(Serializable pk) { Assert.notNull(pk, "Primary key (identifier) required"); MutableAcl acl = getFromCache(pk); if (acl != null) { this.cache.evict(acl.getId()); this.cache.evict(acl.getObjectIdentity()); } } @Override public void evictFromCache(ObjectIdentity objectIdentity) { Assert.notNull(objectIdentity, "ObjectIdentity required"); MutableAcl acl = getFromCache(objectIdentity); if (acl != null) { this.cache.evict(acl.getId()); this.cache.evict(acl.getObjectIdentity()); } } @Override public @Nullable MutableAcl getFromCache(ObjectIdentity objectIdentity) { Assert.notNull(objectIdentity, "ObjectIdentity required"); return getFromCache((Object) objectIdentity); } @Override public @Nullable MutableAcl getFromCache(Serializable pk) { Assert.notNull(pk, "Primary key (identifier) required"); return getFromCache((Object) pk); } @Override public void putInCache(MutableAcl acl) { Assert.notNull(acl, "Acl required"); Assert.notNull(acl.getObjectIdentity(), "ObjectIdentity required"); Assert.notNull(acl.getId(), "ID required"); if ((acl.getParentAcl() != null) && (acl.getParentAcl() instanceof MutableAcl)) { putInCache((MutableAcl) acl.getParentAcl()); } this.cache.put(acl.getObjectIdentity(), acl); this.cache.put(acl.getId(), acl); } private @Nullable MutableAcl getFromCache(Object key) { Cache.ValueWrapper element = this.cache.get(key); if (element == null) { return null; } Object value = element.get(); if (value == null) { return null; } return initializeTransientFields((MutableAcl) value); } private MutableAcl initializeTransientFields(MutableAcl value) { if (value instanceof AclImpl) { FieldUtils.setProtectedFieldValue("aclAuthorizationStrategy", value, this.aclAuthorizationStrategy); FieldUtils.setProtectedFieldValue("permissionGrantingStrategy", value, this.permissionGrantingStrategy); } if (value.getParentAcl() != null) { initializeTransientFields((MutableAcl) value.getParentAcl()); } return value; } @Override public void clearCache() { this.cache.clear(); } } ================================================ FILE: acl/src/main/java/org/springframework/security/acls/domain/package-info.java ================================================ /* * 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. */ /** * Basic implementation of access control lists (ACLs) interfaces. */ @NullMarked package org.springframework.security.acls.domain; import org.jspecify.annotations.NullMarked; ================================================ FILE: acl/src/main/java/org/springframework/security/acls/jdbc/AclClassIdUtils.java ================================================ /* * 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 org.springframework.security.acls.jdbc; import java.io.Serializable; import java.sql.ResultSet; import java.sql.SQLException; import java.util.UUID; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.jspecify.annotations.Nullable; import org.springframework.core.convert.ConversionFailedException; import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.support.GenericConversionService; import org.springframework.security.acls.model.ObjectIdentity; import org.springframework.util.Assert; /** * Utility class for helping convert database representations of * {@link ObjectIdentity#getIdentifier()} into the correct Java type as specified by * acl_class.class_id_type. * * @author paulwheeler */ class AclClassIdUtils { private static final String DEFAULT_CLASS_ID_TYPE_COLUMN_NAME = "class_id_type"; private static final Log log = LogFactory.getLog(AclClassIdUtils.class); private ConversionService conversionService; AclClassIdUtils() { GenericConversionService genericConversionService = new GenericConversionService(); genericConversionService.addConverter(String.class, Long.class, new StringToLongConverter()); genericConversionService.addConverter(String.class, UUID.class, new StringToUUIDConverter()); this.conversionService = genericConversionService; } AclClassIdUtils(ConversionService conversionService) { Assert.notNull(conversionService, "conversionService must not be null"); this.conversionService = conversionService; } /** * Converts the raw type from the database into the right Java type. For most * applications the 'raw type' will be Long, for some applications it could be String. * @param identifier The identifier from the database * @param resultSet Result set of the query * @return The identifier in the appropriate target Java type. Typically Long or UUID. * @throws SQLException */ @Nullable Serializable identifierFrom(Serializable identifier, ResultSet resultSet) throws SQLException { Class classIdType = classIdTypeFrom(resultSet); if (isString(identifier) && classIdType != null && canConvertFromStringTo(classIdType)) { return convertFromStringTo((String) identifier, classIdType); } // Assume it should be a Long type return convertToLong(identifier); } private boolean hasValidClassIdType(ResultSet resultSet) { try { return classIdTypeFrom(resultSet) != null; } catch (SQLException ex) { log.debug("Unable to obtain the class id type", ex); return false; } } private @Nullable Class classIdTypeFrom(ResultSet resultSet) throws SQLException { try { return classIdTypeFrom(resultSet.getString(DEFAULT_CLASS_ID_TYPE_COLUMN_NAME)); } catch (SQLException ex) { log.debug("Unable to obtain the class id type", ex); return null; } } private @Nullable Class classIdTypeFrom(String className) { if (className == null) { return null; } try { return Class.forName(className).asSubclass(Serializable.class); } catch (ClassNotFoundException ex) { log.debug("Unable to find class id type on classpath", ex); return null; } catch (ClassCastException ex) { log.debug("Class id type is not a Serializable type", ex); return null; } } private boolean canConvertFromStringTo(Class targetType) { return this.conversionService.canConvert(String.class, targetType); } private @Nullable T convertFromStringTo(String identifier, Class targetType) { return this.conversionService.convert(identifier, targetType); } /** * Converts to a {@link Long}, attempting to use the {@link ConversionService} if * available. * @param identifier The identifier * @return Long version of the identifier * @throws NumberFormatException if the string cannot be parsed to a long. * @throws org.springframework.core.convert.ConversionException if a conversion * exception occurred * @throws IllegalArgumentException if targetType is null */ private @Nullable Long convertToLong(Serializable identifier) { if (this.conversionService.canConvert(identifier.getClass(), Long.class)) { return this.conversionService.convert(identifier, Long.class); } return Long.valueOf(identifier.toString()); } private boolean isString(Serializable object) { return object.getClass().isAssignableFrom(String.class); } void setConversionService(ConversionService conversionService) { Assert.notNull(conversionService, "conversionService must not be null"); this.conversionService = conversionService; } private static class StringToLongConverter implements Converter { @Override public Long convert(@Nullable String identifierAsString) { if (identifierAsString == null) { throw new ConversionFailedException(TypeDescriptor.valueOf(String.class), TypeDescriptor.valueOf(Long.class), identifierAsString, new NullPointerException()); } return Long.parseLong(identifierAsString); } } private static class StringToUUIDConverter implements Converter { @Override public UUID convert(@Nullable String identifierAsString) { if (identifierAsString == null) { throw new ConversionFailedException(TypeDescriptor.valueOf(String.class), TypeDescriptor.valueOf(UUID.class), identifierAsString, new NullPointerException()); } return UUID.fromString(identifierAsString); } } } ================================================ FILE: acl/src/main/java/org/springframework/security/acls/jdbc/BasicLookupStrategy.java ================================================ /* * Copyright 2004, 2005, 2006, 2017 Acegi Technology Pty Limited * * 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 org.springframework.security.acls.jdbc; import java.io.Serializable; import java.lang.reflect.Field; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.sql.DataSource; import org.jspecify.annotations.Nullable; import org.springframework.core.convert.ConversionException; import org.springframework.core.convert.ConversionService; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.ResultSetExtractor; import org.springframework.security.acls.domain.AccessControlEntryImpl; import org.springframework.security.acls.domain.AclAuthorizationStrategy; import org.springframework.security.acls.domain.AclImpl; import org.springframework.security.acls.domain.AuditLogger; import org.springframework.security.acls.domain.DefaultPermissionFactory; import org.springframework.security.acls.domain.DefaultPermissionGrantingStrategy; import org.springframework.security.acls.domain.GrantedAuthoritySid; import org.springframework.security.acls.domain.ObjectIdentityRetrievalStrategyImpl; import org.springframework.security.acls.domain.PermissionFactory; import org.springframework.security.acls.domain.PrincipalSid; import org.springframework.security.acls.model.AccessControlEntry; import org.springframework.security.acls.model.Acl; import org.springframework.security.acls.model.AclCache; import org.springframework.security.acls.model.MutableAcl; import org.springframework.security.acls.model.NotFoundException; import org.springframework.security.acls.model.ObjectIdentity; import org.springframework.security.acls.model.ObjectIdentityGenerator; import org.springframework.security.acls.model.Permission; import org.springframework.security.acls.model.PermissionGrantingStrategy; import org.springframework.security.acls.model.Sid; import org.springframework.security.acls.model.UnloadedSidException; import org.springframework.security.util.FieldUtils; import org.springframework.util.Assert; /** * Performs lookups in a manner that is compatible with ANSI SQL. *

* NB: This implementation does attempt to provide reasonably optimised lookups - within * the constraints of a normalised database and standard ANSI SQL features. If you are * willing to sacrifice either of these constraints (e.g. use a particular database * feature such as hierarchical queries or materialized views, or reduce normalisation) * you are likely to achieve better performance. In such situations you will need to * provide your own custom LookupStrategy. This class does not support * subclassing, as it is likely to change in future releases and therefore subclassing is * unsupported. *

* There are two SQL queries executed, one in the lookupPrimaryKeys method and * one in lookupObjectIdentities. These are built from the same select and "order * by" clause, using a different where clause in each case. In order to use custom schema * or column names, each of these SQL clauses can be customized, but they must be * consistent with each other and with the expected result set generated by the default * values. * * @author Ben Alex */ public class BasicLookupStrategy implements LookupStrategy { private static final String DEFAULT_SELECT_CLAUSE_COLUMNS = "select acl_object_identity.object_id_identity, " + "acl_entry.ace_order, " + "acl_object_identity.id as acl_id, " + "acl_object_identity.parent_object, " + "acl_object_identity.entries_inheriting, " + "acl_entry.id as ace_id, " + "acl_entry.mask, " + "acl_entry.granting, " + "acl_entry.audit_success, " + "acl_entry.audit_failure, " + "acl_sid.principal as ace_principal, " + "acl_sid.sid as ace_sid, " + "acli_sid.principal as acl_principal, " + "acli_sid.sid as acl_sid, " + "acl_class.class "; private static final String DEFAULT_SELECT_CLAUSE_ACL_CLASS_ID_TYPE_COLUMN = ", acl_class.class_id_type "; private static final String DEFAULT_SELECT_CLAUSE_FROM = "from acl_object_identity " + "left join acl_sid acli_sid on acli_sid.id = acl_object_identity.owner_sid " + "left join acl_class on acl_class.id = acl_object_identity.object_id_class " + "left join acl_entry on acl_object_identity.id = acl_entry.acl_object_identity " + "left join acl_sid on acl_entry.sid = acl_sid.id " + "where ( "; public static final String DEFAULT_SELECT_CLAUSE = DEFAULT_SELECT_CLAUSE_COLUMNS + DEFAULT_SELECT_CLAUSE_FROM; public static final String DEFAULT_ACL_CLASS_ID_SELECT_CLAUSE = DEFAULT_SELECT_CLAUSE_COLUMNS + DEFAULT_SELECT_CLAUSE_ACL_CLASS_ID_TYPE_COLUMN + DEFAULT_SELECT_CLAUSE_FROM; private static final String DEFAULT_LOOKUP_KEYS_WHERE_CLAUSE = "(acl_object_identity.id = ?)"; private static final String DEFAULT_LOOKUP_IDENTITIES_WHERE_CLAUSE = "(acl_object_identity.object_id_identity = ? and acl_class.class = ?)"; public static final String DEFAULT_ORDER_BY_CLAUSE = ") order by acl_object_identity.object_id_identity" + " asc, acl_entry.ace_order asc"; private final AclAuthorizationStrategy aclAuthorizationStrategy; private ObjectIdentityGenerator objectIdentityGenerator; private PermissionFactory permissionFactory = new DefaultPermissionFactory(); private final AclCache aclCache; private final PermissionGrantingStrategy grantingStrategy; private final JdbcTemplate jdbcTemplate; private int batchSize = 50; private final Field fieldAces = FieldUtils.getField(AclImpl.class, "aces"); private final Field fieldAcl = FieldUtils.getField(AccessControlEntryImpl.class, "acl"); // SQL Customization fields private String selectClause = DEFAULT_SELECT_CLAUSE; private String lookupPrimaryKeysWhereClause = DEFAULT_LOOKUP_KEYS_WHERE_CLAUSE; private String lookupObjectIdentitiesWhereClause = DEFAULT_LOOKUP_IDENTITIES_WHERE_CLAUSE; private String orderByClause = DEFAULT_ORDER_BY_CLAUSE; private AclClassIdUtils aclClassIdUtils; /** * Constructor accepting mandatory arguments * @param dataSource to access the database * @param aclCache the cache where fully-loaded elements can be stored * @param aclAuthorizationStrategy authorization strategy (required) */ public BasicLookupStrategy(DataSource dataSource, AclCache aclCache, AclAuthorizationStrategy aclAuthorizationStrategy, AuditLogger auditLogger) { this(dataSource, aclCache, aclAuthorizationStrategy, new DefaultPermissionGrantingStrategy(auditLogger)); } /** * Creates a new instance * @param dataSource to access the database * @param aclCache the cache where fully-loaded elements can be stored * @param aclAuthorizationStrategy authorization strategy (required) * @param grantingStrategy the PermissionGrantingStrategy */ public BasicLookupStrategy(DataSource dataSource, AclCache aclCache, AclAuthorizationStrategy aclAuthorizationStrategy, PermissionGrantingStrategy grantingStrategy) { Assert.notNull(dataSource, "DataSource required"); Assert.notNull(aclCache, "AclCache required"); Assert.notNull(aclAuthorizationStrategy, "AclAuthorizationStrategy required"); Assert.notNull(grantingStrategy, "grantingStrategy required"); this.jdbcTemplate = new JdbcTemplate(dataSource); this.aclCache = aclCache; this.aclAuthorizationStrategy = aclAuthorizationStrategy; this.grantingStrategy = grantingStrategy; this.objectIdentityGenerator = new ObjectIdentityRetrievalStrategyImpl(); this.aclClassIdUtils = new AclClassIdUtils(); this.fieldAces.setAccessible(true); this.fieldAcl.setAccessible(true); } private String computeRepeatingSql(String repeatingSql, int requiredRepetitions) { Assert.isTrue(requiredRepetitions > 0, "requiredRepetitions must be > 0"); String startSql = this.selectClause; String endSql = this.orderByClause; StringBuilder sqlStringBldr = new StringBuilder( startSql.length() + endSql.length() + requiredRepetitions * (repeatingSql.length() + 4)); sqlStringBldr.append(startSql); for (int i = 1; i <= requiredRepetitions; i++) { sqlStringBldr.append(repeatingSql); if (i != requiredRepetitions) { sqlStringBldr.append(" or "); } } sqlStringBldr.append(endSql); return sqlStringBldr.toString(); } @SuppressWarnings("unchecked") private List readAces(AclImpl acl) { try { return (List) this.fieldAces.get(acl); } catch (IllegalAccessException ex) { throw new IllegalStateException("Could not obtain AclImpl.aces field", ex); } } private void setAclOnAce(AccessControlEntryImpl ace, AclImpl acl) { try { this.fieldAcl.set(ace, acl); } catch (IllegalAccessException ex) { throw new IllegalStateException("Could not or set AclImpl on AccessControlEntryImpl fields", ex); } } private void setAces(AclImpl acl, List aces) { try { this.fieldAces.set(acl, aces); } catch (IllegalAccessException ex) { throw new IllegalStateException("Could not set AclImpl entries", ex); } } /** * Locates the primary key IDs specified in "findNow", adding AclImpl instances with * StubAclParents to the "acls" Map. * @param acls the AclImpls (with StubAclParents) * @param findNow Long-based primary keys to retrieve * @param sids */ private void lookupPrimaryKeys(final Map acls, final Set findNow, final @Nullable List sids) { Assert.notNull(acls, "ACLs are required"); Assert.notEmpty(findNow, "Items to find now required"); String sql = computeRepeatingSql(this.lookupPrimaryKeysWhereClause, findNow.size()); Set parentsToLookup = this.jdbcTemplate.query(sql, (ps) -> setKeys(ps, findNow), new ProcessResultSet(acls, sids)); // Lookup the parents, now that our JdbcTemplate has released the database // connection (SEC-547) if (parentsToLookup.size() > 0) { lookupPrimaryKeys(acls, parentsToLookup, sids); } } private void setKeys(PreparedStatement ps, Set findNow) throws SQLException { int i = 0; for (Long toFind : findNow) { i++; ps.setLong(i, toFind); } } /** * The main method. *

* WARNING: This implementation completely disregards the "sids" argument! Every item * in the cache is expected to contain all SIDs. If you have serious performance needs * (e.g. a very large number of SIDs per object identity), you'll probably want to * develop a custom {@link LookupStrategy} implementation instead. *

* The implementation works in batch sizes specified by {@link #batchSize}. * @param objects the identities to lookup (required) * @param sids the SIDs for which identities are required (ignored by this * implementation) * @return a Map where keys represent the {@link ObjectIdentity} of the * located {@link Acl} and values are the located {@link Acl} (never null * although some entries may be missing; this method should not throw * {@link NotFoundException}, as a chain of {@link LookupStrategy}s may be used to * automatically create entries if required) */ @Override public final Map readAclsById(List objects, @Nullable List sids) { Assert.isTrue(this.batchSize >= 1, "BatchSize must be >= 1"); Assert.notEmpty(objects, "Objects to lookup required"); // Map // contains FULLY loaded Acl objects Map result = new HashMap<>(); Set currentBatchToLoad = new HashSet<>(); for (int i = 0; i < objects.size(); i++) { final ObjectIdentity oid = objects.get(i); boolean aclFound = false; // Check we don't already have this ACL in the results if (result.containsKey(oid)) { aclFound = true; } // Check cache for the present ACL entry if (!aclFound) { Acl acl = this.aclCache.getFromCache(oid); // Ensure any cached element supports all the requested SIDs // (they should always, as our base impl doesn't filter on SID) if (acl != null) { Assert.state(acl.isSidLoaded(sids), "Error: SID-filtered element detected when implementation does not perform SID filtering " + "- have you added something to the cache manually?"); result.put(acl.getObjectIdentity(), acl); aclFound = true; } } // Load the ACL from the database if (!aclFound) { currentBatchToLoad.add(oid); } // Is it time to load from JDBC the currentBatchToLoad? if ((currentBatchToLoad.size() == this.batchSize) || ((i + 1) == objects.size())) { if (currentBatchToLoad.size() > 0) { Map loadedBatch = lookupObjectIdentities(currentBatchToLoad, sids); // Add loaded batch (all elements 100% initialized) to results result.putAll(loadedBatch); // Add the loaded batch to the cache for (Acl loadedAcl : loadedBatch.values()) { this.aclCache.putInCache((AclImpl) loadedAcl); } currentBatchToLoad.clear(); } } } return result; } /** * Looks up a batch of ObjectIdentitys directly from the database. *

* The caller is responsible for optimization issues, such as selecting the identities * to lookup, ensuring the cache doesn't contain them already, and adding the returned * elements to the cache etc. *

* This subclass is required to return fully valid Acls, including * properly-configured parent ACLs. */ private Map lookupObjectIdentities(final Collection objectIdentities, @Nullable List sids) { Assert.notEmpty(objectIdentities, "Must provide identities to lookup"); // contains Acls with StubAclParents Map acls = new HashMap<>(); // Make the "acls" map contain all requested objectIdentities // (including markers to each parent in the hierarchy) String sql = computeRepeatingSql(this.lookupObjectIdentitiesWhereClause, objectIdentities.size()); Set parentsToLookup = this.jdbcTemplate.query(sql, (ps) -> setupLookupObjectIdentitiesStatement(ps, objectIdentities), new ProcessResultSet(acls, sids)); // Lookup the parents, now that our JdbcTemplate has released the database // connection (SEC-547) if (parentsToLookup.size() > 0) { lookupPrimaryKeys(acls, parentsToLookup, sids); } // Finally, convert our "acls" containing StubAclParents into true Acls Map resultMap = new HashMap<>(); for (Acl inputAcl : acls.values()) { Assert.isInstanceOf(AclImpl.class, inputAcl, "Map should have contained an AclImpl"); Assert.isInstanceOf(Long.class, ((AclImpl) inputAcl).getId(), "Acl.getId() must be Long"); Acl result = convert(acls, (Long) ((AclImpl) inputAcl).getId()); resultMap.put(result.getObjectIdentity(), result); } return resultMap; } private void setupLookupObjectIdentitiesStatement(PreparedStatement ps, Collection objectIdentities) throws SQLException { int i = 0; for (ObjectIdentity oid : objectIdentities) { // Determine prepared statement values for this iteration String type = oid.getType(); // No need to check for nulls, as guaranteed non-null by // ObjectIdentity.getIdentifier() interface contract String identifier = oid.getIdentifier().toString(); // Inject values ps.setString((2 * i) + 1, identifier); ps.setString((2 * i) + 2, type); i++; } } /** * The final phase of converting the Map of AclImpl * instances which contain StubAclParents into proper, valid * AclImpls with correct ACL parents. * @param inputMap the unconverted AclImpls * @param currentIdentity the currentAcl that we wish to convert (this * may be */ private AclImpl convert(Map inputMap, Long currentIdentity) { Assert.notEmpty(inputMap, "InputMap required"); Assert.notNull(currentIdentity, "CurrentIdentity required"); // Retrieve this Acl from the InputMap Acl uncastAcl = inputMap.get(currentIdentity); Assert.isInstanceOf(AclImpl.class, uncastAcl, "The inputMap contained a non-AclImpl"); AclImpl inputAcl = (AclImpl) uncastAcl; Acl parent = inputAcl.getParentAcl(); if ((parent != null) && parent instanceof StubAclParent) { // Lookup the parent StubAclParent stubAclParent = (StubAclParent) parent; parent = convert(inputMap, stubAclParent.getId()); } // Now we have the parent (if there is one), create the true AclImpl Sid owner = inputAcl.getOwner(); Assert.isTrue(owner != null, "Owner is required"); AclImpl result = new AclImpl(inputAcl.getObjectIdentity(), inputAcl.getId(), this.aclAuthorizationStrategy, this.grantingStrategy, parent, null, inputAcl.isEntriesInheriting(), owner); // Copy the "aces" from the input to the destination // Obtain the "aces" from the input ACL List aces = readAces(inputAcl); // Create a list in which to store the "aces" for the "result" AclImpl instance List acesNew = new ArrayList<>(); // Iterate over the "aces" input and replace each nested // AccessControlEntryImpl.getAcl() with the new "result" AclImpl instance // This ensures StubAclParent instances are removed, as per SEC-951 for (AccessControlEntryImpl ace : aces) { setAclOnAce(ace, result); acesNew.add(ace); } // Finally, now that the "aces" have been converted to have the "result" AclImpl // instance, modify the "result" AclImpl instance setAces(result, acesNew); return result; } /** * Creates a particular implementation of {@link Sid} depending on the arguments. * @param sid the name of the sid representing its unique identifier. In typical ACL * database schema it's located in table {@code acl_sid} table, {@code sid} column. * @param isPrincipal whether it's a user or granted authority like role * @return the instance of Sid with the {@code sidName} as an identifier */ protected Sid createSid(boolean isPrincipal, String sid) { if (isPrincipal) { return new PrincipalSid(sid); } return new GrantedAuthoritySid(sid); } /** * Sets the {@code PermissionFactory} instance which will be used to convert loaded * permission data values to {@code Permission}s. A {@code DefaultPermissionFactory} * will be used by default. * @param permissionFactory */ public final void setPermissionFactory(PermissionFactory permissionFactory) { this.permissionFactory = permissionFactory; } public final void setBatchSize(int batchSize) { this.batchSize = batchSize; } /** * The SQL for the select clause. If customizing in order to modify column names, * schema etc, the other SQL customization fields must also be set to match. * @param selectClause the select clause, which defaults to * {@link #DEFAULT_SELECT_CLAUSE}. */ public final void setSelectClause(String selectClause) { this.selectClause = selectClause; } /** * The SQL for the where clause used in the lookupPrimaryKey method. */ public final void setLookupPrimaryKeysWhereClause(String lookupPrimaryKeysWhereClause) { this.lookupPrimaryKeysWhereClause = lookupPrimaryKeysWhereClause; } /** * The SQL for the where clause used in the lookupObjectIdentities method. */ public final void setLookupObjectIdentitiesWhereClause(String lookupObjectIdentitiesWhereClause) { this.lookupObjectIdentitiesWhereClause = lookupObjectIdentitiesWhereClause; } /** * The SQL for the "order by" clause used in both queries. */ public final void setOrderByClause(String orderByClause) { this.orderByClause = orderByClause; } public final void setAclClassIdSupported(boolean aclClassIdSupported) { if (aclClassIdSupported) { Assert.isTrue(this.selectClause.equals(DEFAULT_SELECT_CLAUSE), "Cannot set aclClassIdSupported and override the select clause; " + "just override the select clause"); this.selectClause = DEFAULT_ACL_CLASS_ID_SELECT_CLAUSE; } } public final void setObjectIdentityGenerator(ObjectIdentityGenerator objectIdentityGenerator) { Assert.notNull(objectIdentityGenerator, "objectIdentityGenerator cannot be null"); this.objectIdentityGenerator = objectIdentityGenerator; } public final void setConversionService(ConversionService conversionService) { this.aclClassIdUtils = new AclClassIdUtils(conversionService); } private class ProcessResultSet implements ResultSetExtractor> { private final Map acls; private final @Nullable List sids; ProcessResultSet(Map acls, @Nullable List sids) { Assert.notNull(acls, "ACLs cannot be null"); this.acls = acls; this.sids = sids; // can be null } /** * Implementation of {@link ResultSetExtractor#extractData(ResultSet)}. Creates an * {@link Acl} for each row in the {@link ResultSet} and ensures it is in member * field acls. Any {@link Acl} with a parent will have the parents id * returned in a set. The returned set of ids may requires further processing. * @param rs The {@link ResultSet} to be processed * @return a list of parent IDs remaining to be looked up (may be empty, but never * null) * @throws SQLException */ @Override public Set extractData(ResultSet rs) throws SQLException { Set parentIdsToLookup = new HashSet<>(); // Set of parent_id Longs while (rs.next()) { // Convert current row into an Acl (albeit with a StubAclParent) convertCurrentResultIntoObject(this.acls, rs); // Figure out if this row means we need to lookup another parent long parentId = rs.getLong("parent_object"); if (parentId != 0) { // See if it's already in the "acls" if (this.acls.containsKey(parentId)) { continue; // skip this while iteration } // Now try to find it in the cache MutableAcl cached = BasicLookupStrategy.this.aclCache.getFromCache(parentId); if ((cached == null) || !cached.isSidLoaded(this.sids)) { parentIdsToLookup.add(parentId); } else { // Pop into the acls map, so our convert method doesn't // need to deal with an unsynchronized AclCache this.acls.put(cached.getId(), cached); } } } // Return the parents left to lookup to the caller return parentIdsToLookup; } /** * Accepts the current ResultSet row, and converts it into an * AclImpl that contains a StubAclParent * @param acls the Map we should add the converted Acl to * @param rs the ResultSet focused on a current row * @throws SQLException if something goes wrong converting values * @throws ConversionException if can't convert to the desired Java type */ private void convertCurrentResultIntoObject(Map acls, ResultSet rs) throws SQLException { Long id = rs.getLong("acl_id"); // If we already have an ACL for this ID, just create the ACE Acl acl = acls.get(id); if (acl == null) { // Make an AclImpl and pop it into the Map // If the Java type is a String, check to see if we can convert it to the // target id type, e.g. UUID. Serializable identifier = (Serializable) rs.getObject("object_id_identity"); identifier = BasicLookupStrategy.this.aclClassIdUtils.identifierFrom(identifier, rs); if (identifier == null) { throw new IllegalStateException("Identifier cannot be null"); } ObjectIdentity objectIdentity = BasicLookupStrategy.this.objectIdentityGenerator .createObjectIdentity(identifier, rs.getString("class")); Acl parentAcl = null; long parentAclId = rs.getLong("parent_object"); if (parentAclId != 0) { parentAcl = new StubAclParent(parentAclId); } boolean entriesInheriting = rs.getBoolean("entries_inheriting"); Sid owner = createSid(rs.getBoolean("acl_principal"), rs.getString("acl_sid")); acl = new AclImpl(objectIdentity, id, BasicLookupStrategy.this.aclAuthorizationStrategy, BasicLookupStrategy.this.grantingStrategy, parentAcl, null, entriesInheriting, owner); acls.put(id, acl); } // Add an extra ACE to the ACL (ORDER BY maintains the ACE list order) // It is permissible to have no ACEs in an ACL (which is detected by a null // ACE_SID) if (rs.getString("ace_sid") != null) { Long aceId = rs.getLong("ace_id"); Sid recipient = createSid(rs.getBoolean("ace_principal"), rs.getString("ace_sid")); int mask = rs.getInt("mask"); Permission permission = BasicLookupStrategy.this.permissionFactory.buildFromMask(mask); boolean granting = rs.getBoolean("granting"); boolean auditSuccess = rs.getBoolean("audit_success"); boolean auditFailure = rs.getBoolean("audit_failure"); AccessControlEntryImpl ace = new AccessControlEntryImpl(aceId, acl, recipient, permission, granting, auditSuccess, auditFailure); // Field acesField = FieldUtils.getField(AclImpl.class, "aces"); List aces = readAces((AclImpl) acl); // Add the ACE if it doesn't already exist in the ACL.aces field if (!aces.contains(ace)) { aces.add(ace); } } } } private static class StubAclParent implements Acl { private final Long id; StubAclParent(Long id) { this.id = id; } Long getId() { return this.id; } @Override public List getEntries() { throw new UnsupportedOperationException("Stub only"); } @Override public ObjectIdentity getObjectIdentity() { throw new UnsupportedOperationException("Stub only"); } @Override public Sid getOwner() { throw new UnsupportedOperationException("Stub only"); } @Override public Acl getParentAcl() { throw new UnsupportedOperationException("Stub only"); } @Override public boolean isEntriesInheriting() { throw new UnsupportedOperationException("Stub only"); } @Override public boolean isGranted(List permission, List sids, boolean administrativeMode) throws NotFoundException, UnloadedSidException { throw new UnsupportedOperationException("Stub only"); } @Override public boolean isSidLoaded(@Nullable List sids) { throw new UnsupportedOperationException("Stub only"); } } } ================================================ FILE: acl/src/main/java/org/springframework/security/acls/jdbc/JdbcAclService.java ================================================ /* * Copyright 2004, 2005, 2006, 2017, 2018 Acegi Technology Pty Limited * * 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 org.springframework.security.acls.jdbc; import java.io.Serializable; import java.sql.ResultSet; import java.sql.SQLException; import java.util.Collections; import java.util.List; import java.util.Map; import javax.sql.DataSource; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.jspecify.annotations.Nullable; import org.springframework.core.convert.ConversionService; import org.springframework.jdbc.core.JdbcOperations; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.security.acls.domain.ObjectIdentityRetrievalStrategyImpl; import org.springframework.security.acls.model.Acl; import org.springframework.security.acls.model.AclService; import org.springframework.security.acls.model.NotFoundException; import org.springframework.security.acls.model.ObjectIdentity; import org.springframework.security.acls.model.ObjectIdentityGenerator; import org.springframework.security.acls.model.Sid; import org.springframework.util.Assert; /** * Simple JDBC-based implementation of AclService. *

* Requires the "dirty" flags in {@link org.springframework.security.acls.domain.AclImpl} * and {@link org.springframework.security.acls.domain.AccessControlEntryImpl} to be set, * so that the implementation can detect changed parameters easily. * * @author Ben Alex */ public class JdbcAclService implements AclService { protected static final Log log = LogFactory.getLog(JdbcAclService.class); private static final String DEFAULT_SELECT_ACL_CLASS_COLUMNS = "class.class as class"; private static final String DEFAULT_SELECT_ACL_CLASS_COLUMNS_WITH_ID_TYPE = DEFAULT_SELECT_ACL_CLASS_COLUMNS + ", class.class_id_type as class_id_type"; private static final String DEFAULT_SELECT_ACL_WITH_PARENT_SQL = "select obj.object_id_identity as obj_id, " + DEFAULT_SELECT_ACL_CLASS_COLUMNS + " from acl_object_identity obj, acl_object_identity parent, acl_class class " + "where obj.parent_object = parent.id and obj.object_id_class = class.id " + "and parent.object_id_identity = ? and parent.object_id_class = (" + "select id FROM acl_class where acl_class.class = ?)"; private static final String DEFAULT_SELECT_ACL_WITH_PARENT_SQL_WITH_CLASS_ID_TYPE = "select obj.object_id_identity as obj_id, " + DEFAULT_SELECT_ACL_CLASS_COLUMNS_WITH_ID_TYPE + " from acl_object_identity obj, acl_object_identity parent, acl_class class " + "where obj.parent_object = parent.id and obj.object_id_class = class.id " + "and parent.object_id_identity = ? and parent.object_id_class = (" + "select id FROM acl_class where acl_class.class = ?)"; protected final JdbcOperations jdbcOperations; private final LookupStrategy lookupStrategy; private boolean aclClassIdSupported; private String findChildrenSql = DEFAULT_SELECT_ACL_WITH_PARENT_SQL; private AclClassIdUtils aclClassIdUtils; private ObjectIdentityGenerator objectIdentityGenerator; public JdbcAclService(DataSource dataSource, LookupStrategy lookupStrategy) { this(new JdbcTemplate(dataSource), lookupStrategy); } public JdbcAclService(JdbcOperations jdbcOperations, LookupStrategy lookupStrategy) { Assert.notNull(jdbcOperations, "JdbcOperations required"); Assert.notNull(lookupStrategy, "LookupStrategy required"); this.jdbcOperations = jdbcOperations; this.lookupStrategy = lookupStrategy; this.aclClassIdUtils = new AclClassIdUtils(); this.objectIdentityGenerator = new ObjectIdentityRetrievalStrategyImpl(); } @Override public @Nullable List findChildren(ObjectIdentity parentIdentity) { Object[] args = { parentIdentity.getIdentifier().toString(), parentIdentity.getType() }; List objects = this.jdbcOperations.query(this.findChildrenSql, (rs, rowNum) -> mapObjectIdentityRow(rs), args); return (!objects.isEmpty()) ? objects : null; } private ObjectIdentity mapObjectIdentityRow(ResultSet rs) throws SQLException { String javaType = rs.getString("class"); Serializable identifier = (Serializable) rs.getObject("obj_id"); identifier = this.aclClassIdUtils.identifierFrom(identifier, rs); if (identifier == null) { throw new IllegalStateException("Identifier cannot be null"); } return this.objectIdentityGenerator.createObjectIdentity(identifier, javaType); } @Override public Acl readAclById(ObjectIdentity object, @Nullable List sids) throws NotFoundException { Map map = readAclsById(Collections.singletonList(object), sids); Assert.isTrue(map.containsKey(object), () -> "There should have been an Acl entry for ObjectIdentity " + object); return map.get(object); } @Override public Acl readAclById(ObjectIdentity object) throws NotFoundException { return readAclById(object, null); } @Override public Map readAclsById(List objects) throws NotFoundException { return readAclsById(objects, null); } @Override public Map readAclsById(List objects, @Nullable List sids) throws NotFoundException { Map result = this.lookupStrategy.readAclsById(objects, sids); // Check every requested object identity was found (throw NotFoundException if // needed) for (ObjectIdentity oid : objects) { if (!result.containsKey(oid)) { throw new NotFoundException("Unable to find ACL information for object identity '" + oid + "'"); } } return result; } /** * Allows customization of the SQL query used to find child object identities. * @param findChildrenSql */ public void setFindChildrenQuery(String findChildrenSql) { this.findChildrenSql = findChildrenSql; } public void setAclClassIdSupported(boolean aclClassIdSupported) { this.aclClassIdSupported = aclClassIdSupported; if (aclClassIdSupported) { // Change the default children select if it hasn't been overridden if (this.findChildrenSql.equals(DEFAULT_SELECT_ACL_WITH_PARENT_SQL)) { this.findChildrenSql = DEFAULT_SELECT_ACL_WITH_PARENT_SQL_WITH_CLASS_ID_TYPE; } else { log.debug("Find children statement has already been overridden, so not overriding the default"); } } } public void setConversionService(ConversionService conversionService) { this.aclClassIdUtils = new AclClassIdUtils(conversionService); } public void setObjectIdentityGenerator(ObjectIdentityGenerator objectIdentityGenerator) { Assert.notNull(objectIdentityGenerator, "objectIdentityGenerator cannot be null"); this.objectIdentityGenerator = objectIdentityGenerator; } protected boolean isAclClassIdSupported() { return this.aclClassIdSupported; } } ================================================ FILE: acl/src/main/java/org/springframework/security/acls/jdbc/JdbcMutableAclService.java ================================================ /* * Copyright 2004, 2005, 2006, 2017, 2018 Acegi Technology Pty Limited * * 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 org.springframework.security.acls.jdbc; import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.List; import javax.sql.DataSource; import org.jspecify.annotations.Nullable; import org.springframework.dao.DataAccessException; import org.springframework.jdbc.core.BatchPreparedStatementSetter; import org.springframework.security.acls.domain.AccessControlEntryImpl; import org.springframework.security.acls.domain.GrantedAuthoritySid; import org.springframework.security.acls.domain.ObjectIdentityImpl; import org.springframework.security.acls.domain.PrincipalSid; import org.springframework.security.acls.model.AccessControlEntry; import org.springframework.security.acls.model.Acl; import org.springframework.security.acls.model.AclCache; import org.springframework.security.acls.model.AlreadyExistsException; import org.springframework.security.acls.model.ChildrenExistException; import org.springframework.security.acls.model.MutableAcl; import org.springframework.security.acls.model.MutableAclService; import org.springframework.security.acls.model.NotFoundException; import org.springframework.security.acls.model.ObjectIdentity; import org.springframework.security.acls.model.Sid; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.transaction.support.TransactionSynchronizationManager; import org.springframework.util.Assert; /** * Provides a base JDBC implementation of {@link MutableAclService}. *

* The default settings are for HSQLDB. If you are using a different database you will * probably need to set the {@link #setSidIdentityQuery(String) sidIdentityQuery} and * {@link #setClassIdentityQuery(String) classIdentityQuery} properties appropriately. The * other queries, SQL inserts and updates can also be customized to accommodate schema * variations, but must produce results consistent with those expected by the defaults. *

* See the appendix of the Spring Security reference manual for more information on the * expected schema and how it is used. Information on using PostgreSQL is also included. * * @author Ben Alex * @author Johannes Zlattinger */ public class JdbcMutableAclService extends JdbcAclService implements MutableAclService { private static final String DEFAULT_INSERT_INTO_ACL_CLASS = "insert into acl_class (class) values (?)"; private static final String DEFAULT_INSERT_INTO_ACL_CLASS_WITH_ID = "insert into acl_class (class, class_id_type) values (?, ?)"; private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder .getContextHolderStrategy(); private boolean foreignKeysInDatabase = true; private final AclCache aclCache; private String deleteEntryByObjectIdentityForeignKey = "delete from acl_entry where acl_object_identity=?"; private String deleteObjectIdentityByPrimaryKey = "delete from acl_object_identity where id=?"; private String classIdentityQuery = "call identity()"; private String sidIdentityQuery = "call identity()"; private String insertClass = DEFAULT_INSERT_INTO_ACL_CLASS; private String insertEntry = "insert into acl_entry " + "(acl_object_identity, ace_order, sid, mask, granting, audit_success, audit_failure)" + "values (?, ?, ?, ?, ?, ?, ?)"; private String insertObjectIdentity = "insert into acl_object_identity " + "(object_id_class, object_id_identity, owner_sid, entries_inheriting) " + "values (?, ?, ?, ?)"; private String insertSid = "insert into acl_sid (principal, sid) values (?, ?)"; private String selectClassPrimaryKey = "select id from acl_class where class=?"; private String selectObjectIdentityPrimaryKey = "select acl_object_identity.id from acl_object_identity, acl_class " + "where acl_object_identity.object_id_class = acl_class.id and acl_class.class=? " + "and acl_object_identity.object_id_identity = ?"; private String selectSidPrimaryKey = "select id from acl_sid where principal=? and sid=?"; private String updateObjectIdentity = "update acl_object_identity set " + "parent_object = ?, owner_sid = ?, entries_inheriting = ?" + " where id = ?"; public JdbcMutableAclService(DataSource dataSource, LookupStrategy lookupStrategy, AclCache aclCache) { super(dataSource, lookupStrategy); Assert.notNull(aclCache, "AclCache required"); this.aclCache = aclCache; } @Override public MutableAcl createAcl(ObjectIdentity objectIdentity) throws AlreadyExistsException { Assert.notNull(objectIdentity, "Object Identity required"); // Check this object identity hasn't already been persisted if (retrieveObjectIdentityPrimaryKey(objectIdentity) != null) { throw new AlreadyExistsException("Object identity '" + objectIdentity + "' already exists"); } // Need to retrieve the current principal, in order to know who "owns" this ACL // (can be changed later on) Authentication auth = this.securityContextHolderStrategy.getContext().getAuthentication(); Assert.isTrue(auth != null, "Authentication required"); PrincipalSid sid = new PrincipalSid(auth); // Create the acl_object_identity row createObjectIdentity(objectIdentity, sid); // Retrieve the ACL via superclass (ensures cache registration, proper retrieval // etc) Acl acl = readAclById(objectIdentity); Assert.isInstanceOf(MutableAcl.class, acl, "MutableAcl should be been returned"); return (MutableAcl) acl; } /** * Creates a new row in acl_entry for every ACE defined in the passed MutableAcl * object. * @param acl containing the ACEs to insert */ protected void createEntries(final MutableAcl acl) { if (acl.getEntries().isEmpty()) { return; } this.jdbcOperations.batchUpdate(this.insertEntry, new BatchPreparedStatementSetter() { @Override public int getBatchSize() { return acl.getEntries().size(); } @Override public void setValues(PreparedStatement stmt, int i) throws SQLException { AccessControlEntry entry_ = acl.getEntries().get(i); Assert.isTrue(entry_ instanceof AccessControlEntryImpl, "Unknown ACE class"); AccessControlEntryImpl entry = (AccessControlEntryImpl) entry_; Assert.state(acl.getId() != null, "ACL ID cannot be null"); stmt.setLong(1, (Long) acl.getId()); stmt.setInt(2, i); Long sidPrimaryKey = createOrRetrieveSidPrimaryKey(entry.getSid(), true); Assert.state(sidPrimaryKey != null, "SID primary key cannot be null"); stmt.setLong(3, sidPrimaryKey); stmt.setInt(4, entry.getPermission().getMask()); stmt.setBoolean(5, entry.isGranting()); stmt.setBoolean(6, entry.isAuditSuccess()); stmt.setBoolean(7, entry.isAuditFailure()); } }); } /** * Creates an entry in the acl_object_identity table for the passed ObjectIdentity. * The Sid is also necessary, as acl_object_identity has defined the sid column as * non-null. * @param object to represent an acl_object_identity for * @param owner for the SID column (will be created if there is no acl_sid entry for * this particular Sid already) */ protected void createObjectIdentity(ObjectIdentity object, Sid owner) { Long sidId = createOrRetrieveSidPrimaryKey(owner, true); Long classId = createOrRetrieveClassPrimaryKey(object.getType(), true, object.getIdentifier().getClass()); this.jdbcOperations.update(this.insertObjectIdentity, classId, object.getIdentifier().toString(), sidId, Boolean.TRUE); } /** * Retrieves the primary key from {@code acl_class}, creating a new row if needed and * the {@code allowCreate} property is {@code true}. * @param type to find or create an entry for (often the fully-qualified class name) * @param allowCreate true if creation is permitted if not found * @return the primary key or null if not found */ protected @Nullable Long createOrRetrieveClassPrimaryKey(String type, boolean allowCreate, Class idType) { List<@Nullable Long> classIds = this.jdbcOperations.queryForList(this.selectClassPrimaryKey, Long.class, type); if (!classIds.isEmpty()) { Long result = classIds.get(0); if (result != null) { return result; } } if (allowCreate) { if (!isAclClassIdSupported()) { this.jdbcOperations.update(this.insertClass, type); } else { this.jdbcOperations.update(this.insertClass, type, idType.getCanonicalName()); } Assert.isTrue(TransactionSynchronizationManager.isSynchronizationActive(), "Transaction must be running"); Long result = this.jdbcOperations.queryForObject(this.classIdentityQuery, Long.class); Assert.state(result != null, "Failed to retrieve class primary key"); return result; } return null; } /** * Retrieves the primary key from acl_sid, creating a new row if needed and the * allowCreate property is true. * @param sid to find or create * @param allowCreate true if creation is permitted if not found * @return the primary key or null if not found * @throws IllegalArgumentException if the Sid is not a recognized * implementation. */ protected @Nullable Long createOrRetrieveSidPrimaryKey(Sid sid, boolean allowCreate) { Assert.notNull(sid, "Sid required"); if (sid instanceof PrincipalSid) { String sidName = ((PrincipalSid) sid).getPrincipal(); return createOrRetrieveSidPrimaryKey(sidName, true, allowCreate); } if (sid instanceof GrantedAuthoritySid) { String sidName = ((GrantedAuthoritySid) sid).getGrantedAuthority(); return createOrRetrieveSidPrimaryKey(sidName, false, allowCreate); } throw new IllegalArgumentException("Unsupported implementation of Sid"); } /** * Retrieves the primary key from acl_sid, creating a new row if needed and the * allowCreate property is true. * @param sidName name of Sid to find or to create * @param sidIsPrincipal whether it's a user or granted authority like role * @param allowCreate true if creation is permitted if not found * @return the primary key or null if not found */ protected @Nullable Long createOrRetrieveSidPrimaryKey(String sidName, boolean sidIsPrincipal, boolean allowCreate) { List<@Nullable Long> sidIds = this.jdbcOperations.queryForList(this.selectSidPrimaryKey, Long.class, sidIsPrincipal, sidName); if (!sidIds.isEmpty()) { Long result = sidIds.get(0); if (result != null) { return result; } } if (allowCreate) { this.jdbcOperations.update(this.insertSid, sidIsPrincipal, sidName); Assert.isTrue(TransactionSynchronizationManager.isSynchronizationActive(), "Transaction must be running"); Long result = this.jdbcOperations.queryForObject(this.sidIdentityQuery, Long.class); Assert.state(result != null, "Failed to retrieve sid primary key"); return result; } return null; } @Override public void deleteAcl(ObjectIdentity objectIdentity, boolean deleteChildren) throws ChildrenExistException { Assert.notNull(objectIdentity, "Object Identity required"); Assert.notNull(objectIdentity.getIdentifier(), "Object Identity doesn't provide an identifier"); if (deleteChildren) { List children = findChildren(objectIdentity); if (children != null) { for (ObjectIdentity child : children) { deleteAcl(child, true); } } } else { if (!this.foreignKeysInDatabase) { // We need to perform a manual verification for what a FK would normally // do. We generally don't do this, in the interests of deadlock management List children = findChildren(objectIdentity); if (children != null) { throw new ChildrenExistException( "Cannot delete '" + objectIdentity + "' (has " + children.size() + " children)"); } } } Long oidPrimaryKey = retrieveObjectIdentityPrimaryKey(objectIdentity); if (oidPrimaryKey == null) { throw new NotFoundException("Object identity not found: " + objectIdentity); } // Delete this ACL's ACEs in the acl_entry table deleteEntries(oidPrimaryKey); // Delete this ACL's acl_object_identity row deleteObjectIdentity(oidPrimaryKey); // Clear the cache this.aclCache.evictFromCache(objectIdentity); } /** * Deletes all ACEs defined in the acl_entry table belonging to the presented * ObjectIdentity primary key. * @param oidPrimaryKey the rows in acl_entry to delete */ protected void deleteEntries(Long oidPrimaryKey) { this.jdbcOperations.update(this.deleteEntryByObjectIdentityForeignKey, oidPrimaryKey); } /** * Deletes a single row from acl_object_identity that is associated with the presented * ObjectIdentity primary key. *

* We do not delete any entries from acl_class, even if no classes are using that * class any longer. This is a deadlock avoidance approach. * @param oidPrimaryKey to delete the acl_object_identity */ protected void deleteObjectIdentity(Long oidPrimaryKey) { // Delete the acl_object_identity row this.jdbcOperations.update(this.deleteObjectIdentityByPrimaryKey, oidPrimaryKey); } /** * Retrieves the primary key from the acl_object_identity table for the passed * ObjectIdentity. Unlike some other methods in this implementation, this method will * NOT create a row (use {@link #createObjectIdentity(ObjectIdentity, Sid)} instead). * @param oid to find * @return the object identity or null if not found */ protected @Nullable Long retrieveObjectIdentityPrimaryKey(ObjectIdentity oid) { try { Long result = this.jdbcOperations.queryForObject(this.selectObjectIdentityPrimaryKey, Long.class, oid.getType(), oid.getIdentifier().toString()); return result; } catch (DataAccessException notFound) { return null; } } /** * This implementation will simply delete all ACEs in the database and recreate them * on each invocation of this method. A more comprehensive implementation might use * dirty state checking, or more likely use ORM capabilities for create, update and * delete operations of {@link MutableAcl}. */ @Override public MutableAcl updateAcl(MutableAcl acl) throws NotFoundException { Assert.notNull(acl.getId(), "Object Identity doesn't provide an identifier"); // Delete this ACL's ACEs in the acl_entry table Long oidPrimaryKey = retrieveObjectIdentityPrimaryKey(acl.getObjectIdentity()); if (oidPrimaryKey == null) { throw new NotFoundException("Object identity not found for ACL: " + acl.getObjectIdentity()); } deleteEntries(oidPrimaryKey); // Create this ACL's ACEs in the acl_entry table createEntries(acl); // Change the mutable columns in acl_object_identity updateObjectIdentity(acl); // Clear the cache, including children clearCacheIncludingChildren(acl.getObjectIdentity()); // Retrieve the ACL via superclass (ensures cache registration, proper retrieval // etc) return (MutableAcl) super.readAclById(acl.getObjectIdentity()); } private void clearCacheIncludingChildren(ObjectIdentity objectIdentity) { Assert.notNull(objectIdentity, "ObjectIdentity required"); List children = findChildren(objectIdentity); if (children != null) { for (ObjectIdentity child : children) { clearCacheIncludingChildren(child); } } this.aclCache.evictFromCache(objectIdentity); } /** * Updates an existing acl_object_identity row, with new information presented in the * passed MutableAcl object. Also will create an acl_sid entry if needed for the Sid * that owns the MutableAcl. * @param acl to modify (a row must already exist in acl_object_identity) * @throws NotFoundException if the ACL could not be found to update. */ protected void updateObjectIdentity(MutableAcl acl) { Long parentId = null; if (acl.getParentAcl() != null) { Assert.isInstanceOf(ObjectIdentityImpl.class, acl.getParentAcl().getObjectIdentity(), "Implementation only supports ObjectIdentityImpl"); ObjectIdentityImpl oii = (ObjectIdentityImpl) acl.getParentAcl().getObjectIdentity(); parentId = retrieveObjectIdentityPrimaryKey(oii); } Assert.notNull(acl.getOwner(), "Owner is required in this implementation"); Long ownerSid = createOrRetrieveSidPrimaryKey(acl.getOwner(), true); int count = this.jdbcOperations.update(this.updateObjectIdentity, parentId, ownerSid, acl.isEntriesInheriting(), acl.getId()); if (count != 1) { throw new NotFoundException("Unable to locate ACL to update"); } } /** * Sets the query that will be used to retrieve the identity of a newly created row in * the acl_class table. * @param classIdentityQuery the query, which should return the identifier. Defaults * to call identity() */ public void setClassIdentityQuery(String classIdentityQuery) { Assert.hasText(classIdentityQuery, "New classIdentityQuery query is required"); this.classIdentityQuery = classIdentityQuery; } /** * Sets the query that will be used to retrieve the identity of a newly created row in * the acl_sid table. * @param sidIdentityQuery the query, which should return the identifier. Defaults to * call identity() */ public void setSidIdentityQuery(String sidIdentityQuery) { Assert.hasText(sidIdentityQuery, "New sidIdentityQuery query is required"); this.sidIdentityQuery = sidIdentityQuery; } public void setDeleteEntryByObjectIdentityForeignKeySql(String deleteEntryByObjectIdentityForeignKey) { this.deleteEntryByObjectIdentityForeignKey = deleteEntryByObjectIdentityForeignKey; } public void setDeleteObjectIdentityByPrimaryKeySql(String deleteObjectIdentityByPrimaryKey) { this.deleteObjectIdentityByPrimaryKey = deleteObjectIdentityByPrimaryKey; } public void setInsertClassSql(String insertClass) { this.insertClass = insertClass; } public void setInsertEntrySql(String insertEntry) { this.insertEntry = insertEntry; } public void setInsertObjectIdentitySql(String insertObjectIdentity) { this.insertObjectIdentity = insertObjectIdentity; } public void setInsertSidSql(String insertSid) { this.insertSid = insertSid; } public void setClassPrimaryKeyQuery(String selectClassPrimaryKey) { this.selectClassPrimaryKey = selectClassPrimaryKey; } public void setObjectIdentityPrimaryKeyQuery(String selectObjectIdentityPrimaryKey) { this.selectObjectIdentityPrimaryKey = selectObjectIdentityPrimaryKey; } public void setSidPrimaryKeyQuery(String selectSidPrimaryKey) { this.selectSidPrimaryKey = selectSidPrimaryKey; } public void setUpdateObjectIdentity(String updateObjectIdentity) { this.updateObjectIdentity = updateObjectIdentity; } /** * @param foreignKeysInDatabase if false this class will perform additional FK * constrain checking, which may cause deadlocks (the default is true, so deadlocks * are avoided but the database is expected to enforce FKs) */ public void setForeignKeysInDatabase(boolean foreignKeysInDatabase) { this.foreignKeysInDatabase = foreignKeysInDatabase; } @Override public void setAclClassIdSupported(boolean aclClassIdSupported) { super.setAclClassIdSupported(aclClassIdSupported); if (aclClassIdSupported) { // Change the default insert if it hasn't been overridden if (this.insertClass.equals(DEFAULT_INSERT_INTO_ACL_CLASS)) { this.insertClass = DEFAULT_INSERT_INTO_ACL_CLASS_WITH_ID; } else { log.debug("Insert class statement has already been overridden, so not overriding the default"); } } } /** * Sets the {@link SecurityContextHolderStrategy} to use. The default action is to use * the {@link SecurityContextHolderStrategy} stored in {@link SecurityContextHolder}. * * @since 5.8 */ public void setSecurityContextHolderStrategy(SecurityContextHolderStrategy securityContextHolderStrategy) { Assert.notNull(securityContextHolderStrategy, "securityContextHolderStrategy cannot be null"); this.securityContextHolderStrategy = securityContextHolderStrategy; } } ================================================ FILE: acl/src/main/java/org/springframework/security/acls/jdbc/LookupStrategy.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.acls.jdbc; import java.util.List; import java.util.Map; import org.jspecify.annotations.Nullable; import org.springframework.security.acls.model.Acl; import org.springframework.security.acls.model.NotFoundException; import org.springframework.security.acls.model.ObjectIdentity; import org.springframework.security.acls.model.Sid; /** * Performs lookups for {@link org.springframework.security.acls.model.AclService}. * * @author Ben Alex */ public interface LookupStrategy { /** * Perform database-specific optimized lookup. * @param objects the identities to lookup (required) * @param sids the SIDs for which identities are required (may be null - * implementations may elect not to provide SID optimisations) * @return a Map where keys represent the {@link ObjectIdentity} of the * located {@link Acl} and values are the located {@link Acl} (never null * although some entries may be missing; this method should not throw * {@link NotFoundException}, as a chain of {@link LookupStrategy}s may be used to * automatically create entries if required) */ Map readAclsById(List objects, @Nullable List sids); } ================================================ FILE: acl/src/main/java/org/springframework/security/acls/jdbc/package-info.java ================================================ /* * 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. */ /** * JDBC-based persistence of ACL information */ @NullMarked package org.springframework.security.acls.jdbc; import org.jspecify.annotations.NullMarked; ================================================ FILE: acl/src/main/java/org/springframework/security/acls/model/AccessControlEntry.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.acls.model; import java.io.Serializable; import org.jspecify.annotations.Nullable; /** * Represents an individual permission assignment within an {@link Acl}. * *

* Instances MUST be immutable, as they are returned by Acl and should not * allow client modification. *

* * @author Ben Alex */ public interface AccessControlEntry extends Serializable { Acl getAcl(); /** * Obtains an identifier that represents this ACE. * @return the identifier, or null if unsaved */ @Nullable Serializable getId(); Permission getPermission(); Sid getSid(); /** * Indicates the permission is being granted to the relevant Sid. If false, indicates * the permission is being revoked/blocked. * @return true if being granted, false otherwise */ boolean isGranting(); } ================================================ FILE: acl/src/main/java/org/springframework/security/acls/model/Acl.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.acls.model; import java.io.Serializable; import java.util.List; import org.jspecify.annotations.Nullable; /** * Represents an access control list (ACL) for a domain object. * *

* An Acl represents all ACL entries for a given domain object. In order to avoid * needing references to the domain object itself, this interface handles indirection * between a domain object and an ACL object identity via the * {@link org.springframework.security.acls.model.ObjectIdentity} interface. *

* *

* Implementing classes may elect to return instances that represent * {@link org.springframework.security.acls.model.Permission} information for either some * OR all {@link org.springframework.security.acls.model.Sid} instances. Therefore, an * instance may NOT necessarily contain ALL Sids for a given domain object. *

* * @author Ben Alex */ public interface Acl extends Serializable { /** * Returns all of the entries represented by the present Acl. Entries * associated with the Acl parents are not returned. * *

* This method is typically used for administrative purposes. *

* *

* The order that entries appear in the array is important for methods declared in the * {@link MutableAcl} interface. Furthermore, some implementations MAY use ordering as * part of advanced permission checking. *

* *

* Do NOT use this method for making authorization decisions. Instead use * {@link #isGranted(List, List, boolean)}. *

* *

* This method must operate correctly even if the Acl only represents a * subset of Sids. The caller is responsible for correctly handling the * result if only a subset of Sids is represented. *

* @return the list of entries represented by the Acl, or null if * there are no entries presently associated with this Acl. */ List getEntries(); /** * Obtains the domain object this Acl provides entries for. This is immutable * once an Acl is created. * @return the object identity (never null) */ ObjectIdentity getObjectIdentity(); /** * Determines the owner of the Acl. The meaning of ownership varies by * implementation and is unspecified. * @return the owner (may be null if the implementation does not use * ownership concepts) */ @Nullable Sid getOwner(); /** * A domain object may have a parent for the purpose of ACL inheritance. If there is a * parent, its ACL can be accessed via this method. In turn, the parent's parent * (grandparent) can be accessed and so on. * *

* This method solely represents the presence of a navigation hierarchy between the * parent Acl and this Acl. For actual inheritance to take place, * the {@link #isEntriesInheriting()} must also be true. *

* *

* This method must operate correctly even if the Acl only represents a * subset of Sids. The caller is responsible for correctly handling the * result if only a subset of Sids is represented. *

* @return the parent Acl (may be null if this Acl does not * have a parent) */ @Nullable Acl getParentAcl(); /** * Indicates whether the ACL entries from the {@link #getParentAcl()} should flow down * into the current Acl. *

* The mere link between an Acl and a parent Acl on its own is * insufficient to cause ACL entries to inherit down. This is because a domain object * may wish to have entirely independent entries, but maintain the link with the * parent for navigation purposes. Thus, this method denotes whether or not the * navigation relationship also extends to the actual inheritance of entries. *

* @return true if parent ACL entries inherit into the current Acl */ boolean isEntriesInheriting(); /** * This is the actual authorization logic method, and must be used whenever ACL * authorization decisions are required. * *

* An array of Sids are presented, representing security identifies of the * current principal. In addition, an array of Permissions is presented which * will have one or more bits set in order to indicate the permissions needed for an * affirmative authorization decision. An array is presented because holding * any of the Permissions inside the array will be sufficient for an * affirmative authorization. *

* *

* The actual approach used to make authorization decisions is left to the * implementation and is not specified by this interface. For example, an * implementation MAY search the current ACL in the order the ACL entries * have been stored. If a single entry is found that has the same active bits as are * shown in a passed Permission, that entry's grant or deny state may * determine the authorization decision. If the case of a deny state, the deny * decision will only be relevant if all other Permissions passed in the * array have also been unsuccessfully searched. If no entry is found that match the * bits in the current ACL, provided that {@link #isEntriesInheriting()} is * true, the authorization decision may be passed to the parent ACL. If there * is no matching entry, the implementation MAY throw an exception, or make a * predefined authorization decision. *

* *

* This method must operate correctly even if the Acl only represents a * subset of Sids, although the implementation is permitted to throw one of * the signature-defined exceptions if the method is called requesting an * authorization decision for a {@link Sid} that was never loaded in this Acl * . *

* @param permission the permission or permissions required (at least one entry * required) * @param sids the security identities held by the principal (at least one entry * required) * @param administrativeMode if true denotes the query is for administrative * purposes and no logging or auditing (if supported by the implementation) should be * undertaken * @return true if authorization is granted * @throws NotFoundException MUST be thrown if an implementation cannot make an * authoritative authorization decision, usually because there is no ACL information * for this particular permission and/or SID * @throws UnloadedSidException thrown if the Acl does not have details for * one or more of the Sids passed as arguments */ boolean isGranted(List permission, List sids, boolean administrativeMode) throws NotFoundException, UnloadedSidException; /** * For efficiency reasons an Acl may be loaded and not contain * entries for every Sid in the system. If an Acl has been loaded * and does not represent every Sid, all methods of the Acl can only * be used within the limited scope of the Sid instances it actually * represents. *

* It is normal to load an Acl for only particular Sids if read-only * authorization decisions are being made. However, if user interface reporting or * modification of Acls are desired, an Acl should be loaded with * all Sids. This method denotes whether or not the specified Sids * have been loaded or not. *

* @param sids one or more security identities the caller is interest in knowing * whether this Sid supports * @return true if every passed Sid is represented by this * Acl instance */ boolean isSidLoaded(@Nullable List sids); } ================================================ FILE: acl/src/main/java/org/springframework/security/acls/model/AclCache.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.acls.model; import java.io.Serializable; import org.jspecify.annotations.Nullable; import org.springframework.security.acls.jdbc.JdbcAclService; /** * A caching layer for {@link JdbcAclService}. * * @author Ben Alex */ public interface AclCache { void evictFromCache(Serializable pk); void evictFromCache(ObjectIdentity objectIdentity); @Nullable MutableAcl getFromCache(ObjectIdentity objectIdentity); @Nullable MutableAcl getFromCache(Serializable pk); void putInCache(MutableAcl acl); void clearCache(); } ================================================ FILE: acl/src/main/java/org/springframework/security/acls/model/AclDataAccessException.java ================================================ /* * 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 org.springframework.security.acls.model; /** * Abstract base class for Acl data operations. * * @author Luke Taylor * @since 3.0 */ public abstract class AclDataAccessException extends RuntimeException { /** * Constructs an AclDataAccessException with the specified message and * root cause. * @param msg the detail message * @param cause the root cause */ public AclDataAccessException(String msg, Throwable cause) { super(msg, cause); } /** * Constructs an AclDataAccessException with the specified message and no * root cause. * @param msg the detail message */ public AclDataAccessException(String msg) { super(msg); } } ================================================ FILE: acl/src/main/java/org/springframework/security/acls/model/AclService.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.acls.model; import java.util.List; import java.util.Map; import org.jspecify.annotations.Nullable; /** * Provides retrieval of {@link Acl} instances. * * @author Ben Alex */ public interface AclService { /** * Locates all object identities that use the specified parent. This is useful for * administration tools. * @param parentIdentity to locate children of * @return the children (or null if none were found) */ @Nullable List findChildren(ObjectIdentity parentIdentity); /** * Same as {@link #readAclsById(List)} except it returns only a single Acl. *

* This method should not be called as it does not leverage the underlying * implementation's potential ability to filter Acl entries based on a * {@link Sid} parameter. *

* @param object to locate an {@link Acl} for * @return the {@link Acl} for the requested {@link ObjectIdentity} (never * null) * @throws NotFoundException if an {@link Acl} was not found for the requested * {@link ObjectIdentity} */ Acl readAclById(ObjectIdentity object) throws NotFoundException; /** * Same as {@link #readAclsById(List, List)} except it returns only a single Acl. * @param object to locate an {@link Acl} for * @param sids the security identities for which {@link Acl} information is required * (may be null to denote all entries) * @return the {@link Acl} for the requested {@link ObjectIdentity} (never * null) * @throws NotFoundException if an {@link Acl} was not found for the requested * {@link ObjectIdentity} */ Acl readAclById(ObjectIdentity object, @Nullable List sids) throws NotFoundException; /** * Obtains all the Acls that apply for the passed Objects. *

* The returned map is keyed on the passed objects, with the values being the * Acl instances. Any unknown objects will not have a map key. *

* @param objects the objects to find {@link Acl} information for * @return a map with exactly one element for each {@link ObjectIdentity} passed as an * argument (never null) * @throws NotFoundException if an {@link Acl} was not found for each requested * {@link ObjectIdentity} */ Map readAclsById(List objects) throws NotFoundException; /** * Obtains all the Acls that apply for the passed Objects, but only * for the security identifies passed. *

* Implementations MAY provide a subset of the ACLs via this method although * this is NOT a requirement. This is intended to allow performance optimisations * within implementations. Callers should therefore use this method in preference to * the alternative overloaded version which does not have performance optimisation * opportunities. *

*

* The returned map is keyed on the passed objects, with the values being the * Acl instances. Any unknown objects (or objects for which the interested * Sids do not have entries) will not have a map key. *

* @param objects the objects to find {@link Acl} information for * @param sids the security identities for which {@link Acl} information is required * (may be null to denote all entries) * @return a map with exactly one element for each {@link ObjectIdentity} passed as an * argument (never null) * @throws NotFoundException if an {@link Acl} was not found for each requested * {@link ObjectIdentity} */ Map readAclsById(List objects, @Nullable List sids) throws NotFoundException; } ================================================ FILE: acl/src/main/java/org/springframework/security/acls/model/AlreadyExistsException.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.acls.model; /** * Thrown if an Acl entry already exists for the object. * * @author Ben Alex */ public class AlreadyExistsException extends AclDataAccessException { /** * Constructs an AlreadyExistsException with the specified message. * @param msg the detail message */ public AlreadyExistsException(String msg) { super(msg); } /** * Constructs an AlreadyExistsException with the specified message and * root cause. * @param msg the detail message * @param cause root cause */ public AlreadyExistsException(String msg, Throwable cause) { super(msg, cause); } } ================================================ FILE: acl/src/main/java/org/springframework/security/acls/model/AuditableAccessControlEntry.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.acls.model; /** * Represents an ACE that provides auditing information. * * @author Ben Alex */ public interface AuditableAccessControlEntry extends AccessControlEntry { boolean isAuditFailure(); boolean isAuditSuccess(); } ================================================ FILE: acl/src/main/java/org/springframework/security/acls/model/AuditableAcl.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.acls.model; /** * A mutable ACL that provides audit capabilities. * * @author Ben Alex */ public interface AuditableAcl extends MutableAcl { void updateAuditing(int aceIndex, boolean auditSuccess, boolean auditFailure); } ================================================ FILE: acl/src/main/java/org/springframework/security/acls/model/ChildrenExistException.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.acls.model; /** * Thrown if an {@link Acl} cannot be deleted because children Acls exist. * * @author Ben Alex */ public class ChildrenExistException extends AclDataAccessException { /** * Constructs an ChildrenExistException with the specified message. * @param msg the detail message */ public ChildrenExistException(String msg) { super(msg); } /** * Constructs an ChildrenExistException with the specified message and * root cause. * @param msg the detail message * @param cause root cause */ public ChildrenExistException(String msg, Throwable cause) { super(msg, cause); } } ================================================ FILE: acl/src/main/java/org/springframework/security/acls/model/MutableAcl.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.acls.model; import java.io.Serializable; /** * A mutable Acl. *

* A mutable ACL must ensure that appropriate security checks are performed before * allowing access to its methods. * * @author Ben Alex */ public interface MutableAcl extends Acl { void deleteAce(int aceIndex) throws NotFoundException; /** * Obtains an identifier that represents this MutableAcl. * @return the identifier, or null if unsaved */ Serializable getId(); void insertAce(int atIndexLocation, Permission permission, Sid sid, boolean granting) throws NotFoundException; /** * Changes the present owner to a different owner. * @param newOwner the new owner (mandatory; cannot be null) */ void setOwner(Sid newOwner); /** * Change the value returned by {@link Acl#isEntriesInheriting()}. * @param entriesInheriting the new value */ void setEntriesInheriting(boolean entriesInheriting); /** * Changes the parent of this ACL. * @param newParent the new parent */ void setParent(Acl newParent); void updateAce(int aceIndex, Permission permission) throws NotFoundException; } ================================================ FILE: acl/src/main/java/org/springframework/security/acls/model/MutableAclService.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.acls.model; /** * Provides support for creating and storing Acl instances. * * @author Ben Alex */ public interface MutableAclService extends AclService { /** * Creates an empty Acl object in the database. It will have no entries. * The returned object will then be used to add entries. * @param objectIdentity the object identity to create * @return an ACL object with its ID set * @throws AlreadyExistsException if the passed object identity already has a record */ MutableAcl createAcl(ObjectIdentity objectIdentity) throws AlreadyExistsException; /** * Removes the specified entry from the database. * @param objectIdentity the object identity to remove * @param deleteChildren whether to cascade the delete to children * @throws ChildrenExistException if the deleteChildren argument was * false but children exist */ void deleteAcl(ObjectIdentity objectIdentity, boolean deleteChildren) throws ChildrenExistException; /** * Changes an existing Acl in the database. * @param acl to modify * @throws NotFoundException if the relevant record could not be found (did you * remember to use {@link #createAcl(ObjectIdentity)} to create the object, rather * than creating it with the new keyword?) */ MutableAcl updateAcl(MutableAcl acl) throws NotFoundException; } ================================================ FILE: acl/src/main/java/org/springframework/security/acls/model/NotFoundException.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.acls.model; /** * Thrown if an ACL-related object cannot be found. * * @author Ben Alex */ public class NotFoundException extends AclDataAccessException { /** * Constructs an NotFoundException with the specified message. * @param msg the detail message */ public NotFoundException(String msg) { super(msg); } /** * Constructs an NotFoundException with the specified message and root * cause. * @param msg the detail message * @param cause root cause */ public NotFoundException(String msg, Throwable cause) { super(msg, cause); } } ================================================ FILE: acl/src/main/java/org/springframework/security/acls/model/ObjectIdentity.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.acls.model; import java.io.Serializable; /** * Represents the identity of an individual domain object instance. * *

* As implementations of ObjectIdentity are used as the key to represent domain * objects in the ACL subsystem, it is essential that implementations provide methods so * that object-equality rather than reference-equality can be relied upon reliably. In * other words, the ACL subsystem can consider two ObjectIdentitys equal if * identity1.equals(identity2), rather than reference-equality of * identity1==identity2. *

* * @author Ben Alex */ public interface ObjectIdentity extends Serializable { /** * @param obj to be compared * @return true if the objects are equal, false otherwise * @see Object#equals(Object) */ @Override boolean equals(Object obj); /** * Obtains the actual identifier. This identifier must not be reused to represent * other domain objects with the same javaType. * *

* Because ACLs are largely immutable, it is strongly recommended to use a synthetic * identifier (such as a database sequence number for the primary key). Do not use an * identifier with business meaning, as that business meaning may change in the future * such change will cascade to the ACL subsystem data. *

* @return the identifier (unique within this type; never null) */ Serializable getIdentifier(); /** * Obtains the "type" metadata for the domain object. This will often be a Java type * name (an interface or a class) – traditionally it is the name of the domain * object implementation class. * @return the "type" of the domain object (never null). */ String getType(); /** * @return a hash code representation of the ObjectIdentity * @see Object#hashCode() */ @Override int hashCode(); } ================================================ FILE: acl/src/main/java/org/springframework/security/acls/model/ObjectIdentityGenerator.java ================================================ /* * 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 org.springframework.security.acls.model; import java.io.Serializable; /** * Strategy which creates an {@link ObjectIdentity} from an object identifier (such as a * primary key) and type information. *

* Differs from {@link ObjectIdentityRetrievalStrategy} in that it is used in situations * when the actual object instance isn't available. * * @author Luke Taylor * @since 3.0 */ public interface ObjectIdentityGenerator { /** * @param id the identifier of the domain object, not null * @param type the type of the object (often a class name), not null * @return the identity constructed using the supplied identifier and type * information. */ ObjectIdentity createObjectIdentity(Serializable id, String type); } ================================================ FILE: acl/src/main/java/org/springframework/security/acls/model/ObjectIdentityRetrievalStrategy.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.acls.model; /** * Strategy interface that provides the ability to determine which {@link ObjectIdentity} * will be returned for a particular domain object * * @author Ben Alex */ public interface ObjectIdentityRetrievalStrategy { ObjectIdentity getObjectIdentity(Object domainObject); } ================================================ FILE: acl/src/main/java/org/springframework/security/acls/model/OwnershipAcl.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.acls.model; /** * A mutable ACL that provides ownership capabilities. * *

* Generally the owner of an ACL is able to call any ACL mutator method, as well as assign * a new owner. * * @author Ben Alex */ public interface OwnershipAcl extends MutableAcl { @Override void setOwner(Sid newOwner); } ================================================ FILE: acl/src/main/java/org/springframework/security/acls/model/Permission.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.acls.model; import java.io.Serializable; /** * Represents a permission granted to a Sid for a given domain object. * * @author Ben Alex */ public interface Permission extends Serializable { char RESERVED_ON = '~'; char RESERVED_OFF = '.'; String THIRTY_TWO_RESERVED_OFF = "................................"; /** * Returns the bits that represents the permission. * @return the bits that represent the permission */ int getMask(); /** * Returns a 32-character long bit pattern String representing this * permission. *

* Implementations are free to format the pattern as they see fit, although under no * circumstances may {@link #RESERVED_OFF} or {@link #RESERVED_ON} be used within the * pattern. An exemption is in the case of {@link #RESERVED_OFF} which is used to * denote a bit that is off (clear). Implementations may also elect to use * {@link #RESERVED_ON} internally for computation purposes, although this method may * not return any String containing {@link #RESERVED_ON}. *

* The returned String must be 32 characters in length. *

* This method is only used for user interface and logging purposes. It is not used in * any permission calculations. Therefore, duplication of characters within the output * is permitted. * @return a 32-character bit pattern */ String getPattern(); } ================================================ FILE: acl/src/main/java/org/springframework/security/acls/model/PermissionGrantingStrategy.java ================================================ /* * 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 org.springframework.security.acls.model; import java.util.List; /** * Allow customization of the logic for determining whether a permission or permissions * are granted to a particular sid or sids by an {@link Acl}. * * @author Luke Taylor * @since 3.0.2 */ public interface PermissionGrantingStrategy { /** * Returns true if the supplied strategy decides that the supplied {@code Acl} grants * access based on the supplied list of permissions and sids. */ boolean isGranted(Acl acl, List permission, List sids, boolean administrativeMode); } ================================================ FILE: acl/src/main/java/org/springframework/security/acls/model/Sid.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.acls.model; import java.io.Serializable; /** * A security identity recognised by the ACL system. * *

* This interface provides indirection between actual security objects (eg principals, * roles, groups etc) and what is stored inside an Acl. This is because an * Acl will not store an entire security object, but only an abstraction of * it. This interface therefore provides a simple way to compare these abstracted security * identities with other security identities and actual security objects. *

* * @author Ben Alex */ public interface Sid extends Serializable { /** * Refer to the java.lang.Object documentation for the interface * contract. * @param obj to be compared * @return true if the objects are equal, false otherwise */ @Override boolean equals(Object obj); /** * Refer to the java.lang.Object documentation for the interface * contract. * @return a hash code representation of this object */ @Override int hashCode(); } ================================================ FILE: acl/src/main/java/org/springframework/security/acls/model/SidRetrievalStrategy.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.acls.model; import java.util.List; import org.springframework.security.core.Authentication; /** * Strategy interface that provides an ability to determine the {@link Sid} instances * applicable for an {@link Authentication}. * * @author Ben Alex */ public interface SidRetrievalStrategy { List getSids(Authentication authentication); } ================================================ FILE: acl/src/main/java/org/springframework/security/acls/model/UnloadedSidException.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.acls.model; /** * Thrown if an {@link Acl} cannot perform an operation because it only loaded a subset of * Sids and the caller has requested details for an unloaded Sid * . * * @author Ben Alex */ public class UnloadedSidException extends AclDataAccessException { /** * Constructs an NotFoundException with the specified message. * @param msg the detail message */ public UnloadedSidException(String msg) { super(msg); } /** * Constructs an NotFoundException with the specified message and root * cause. * @param msg the detail message * @param cause root cause */ public UnloadedSidException(String msg, Throwable cause) { super(msg, cause); } } ================================================ FILE: acl/src/main/java/org/springframework/security/acls/model/package-info.java ================================================ /* * 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. */ /** * Interfaces and shared classes to manage access control lists (ACLs) for domain object * instances. */ @NullMarked package org.springframework.security.acls.model; import org.jspecify.annotations.NullMarked; ================================================ FILE: acl/src/main/java/org/springframework/security/acls/package-info.java ================================================ /* * 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. */ /** * The Spring Security ACL package which implements instance-based security for domain * objects. *

* Consider using the annotation based approach ({@code @PreAuthorize}, * {@code @PostFilter} annotations) combined with a * {@link org.springframework.security.acls.AclPermissionEvaluator} in preference to the * older and more verbose attribute/voter/after-invocation approach from versions before * Spring Security 3.0. */ @NullMarked package org.springframework.security.acls; import org.jspecify.annotations.NullMarked; ================================================ FILE: acl/src/main/resources/META-INF/spring/aot.factories ================================================ org.springframework.aot.hint.RuntimeHintsRegistrar=\ org.springframework.security.acls.aot.hint.AclRuntimeHints ================================================ FILE: acl/src/main/resources/createAclSchema.sql ================================================ -- ACL schema sql used in HSQLDB -- drop table acl_entry; -- drop table acl_object_identity; -- drop table acl_class; -- drop table acl_sid; create table acl_sid( id bigint generated by default as identity(start with 100) not null primary key, principal boolean not null, sid varchar_ignorecase(100) not null, constraint unique_uk_1 unique(sid,principal) ); create table acl_class( id bigint generated by default as identity(start with 100) not null primary key, class varchar_ignorecase(100) not null, constraint unique_uk_2 unique(class) ); create table acl_object_identity( id bigint generated by default as identity(start with 100) not null primary key, object_id_class bigint not null, object_id_identity bigint not null, parent_object bigint, owner_sid bigint, entries_inheriting boolean not null, constraint unique_uk_3 unique(object_id_class,object_id_identity), constraint foreign_fk_1 foreign key(parent_object)references acl_object_identity(id), constraint foreign_fk_2 foreign key(object_id_class)references acl_class(id), constraint foreign_fk_3 foreign key(owner_sid)references acl_sid(id) ); create table acl_entry( id bigint generated by default as identity(start with 100) not null primary key, acl_object_identity bigint not null, ace_order int not null, sid bigint not null, mask integer not null, granting boolean not null, audit_success boolean not null, audit_failure boolean not null, constraint unique_uk_4 unique(acl_object_identity,ace_order), constraint foreign_fk_4 foreign key(acl_object_identity) references acl_object_identity(id), constraint foreign_fk_5 foreign key(sid) references acl_sid(id) ); ================================================ FILE: acl/src/main/resources/createAclSchemaMySQL.sql ================================================ -- ACL Schema SQL for MySQL 5.5+ / MariaDB equivalent -- drop table acl_entry; -- drop table acl_object_identity; -- drop table acl_class; -- drop table acl_sid; CREATE TABLE acl_sid ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, principal BOOLEAN NOT NULL, sid VARCHAR(100) NOT NULL, UNIQUE KEY unique_acl_sid (sid, principal) ) ENGINE=InnoDB; CREATE TABLE acl_class ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, class VARCHAR(100) NOT NULL, UNIQUE KEY uk_acl_class (class) ) ENGINE=InnoDB; CREATE TABLE acl_object_identity ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, object_id_class BIGINT UNSIGNED NOT NULL, object_id_identity VARCHAR(36) NOT NULL, parent_object BIGINT UNSIGNED, owner_sid BIGINT UNSIGNED, entries_inheriting BOOLEAN NOT NULL, UNIQUE KEY uk_acl_object_identity (object_id_class, object_id_identity), CONSTRAINT fk_acl_object_identity_parent FOREIGN KEY (parent_object) REFERENCES acl_object_identity (id), CONSTRAINT fk_acl_object_identity_class FOREIGN KEY (object_id_class) REFERENCES acl_class (id), CONSTRAINT fk_acl_object_identity_owner FOREIGN KEY (owner_sid) REFERENCES acl_sid (id) ) ENGINE=InnoDB; CREATE TABLE acl_entry ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, acl_object_identity BIGINT UNSIGNED NOT NULL, ace_order INTEGER NOT NULL, sid BIGINT UNSIGNED NOT NULL, mask INTEGER UNSIGNED NOT NULL, granting BOOLEAN NOT NULL, audit_success BOOLEAN NOT NULL, audit_failure BOOLEAN NOT NULL, UNIQUE KEY unique_acl_entry (acl_object_identity, ace_order), CONSTRAINT fk_acl_entry_object FOREIGN KEY (acl_object_identity) REFERENCES acl_object_identity (id), CONSTRAINT fk_acl_entry_acl FOREIGN KEY (sid) REFERENCES acl_sid (id) ) ENGINE=InnoDB; ================================================ FILE: acl/src/main/resources/createAclSchemaOracle.sql ================================================ -- ACL Schema SQL for Oracle Database 10g+ -- drop trigger acl_sid_id_trigger; -- drop trigger acl_class_id_trigger; -- drop trigger acl_object_identity_id_trigger; -- drop trigger acl_entry_id_trigger; -- drop sequence acl_sid_sequence; -- drop sequence acl_class_sequence; -- drop sequence acl_object_identity_sequence; -- drop sequence acl_entry_sequence; -- drop table acl_entry; -- drop table acl_object_identity; -- drop table acl_class; -- drop table acl_sid; CREATE TABLE acl_sid ( id NUMBER(38) NOT NULL PRIMARY KEY, principal NUMBER(1) NOT NULL CHECK (principal in (0, 1)), sid NVARCHAR2(100) NOT NULL, CONSTRAINT unique_acl_sid UNIQUE (sid, principal) ); CREATE SEQUENCE acl_sid_sequence START WITH 1 INCREMENT BY 1 NOMAXVALUE; CREATE OR REPLACE TRIGGER acl_sid_id_trigger BEFORE INSERT ON acl_sid FOR EACH ROW BEGIN SELECT acl_sid_sequence.nextval INTO :new.id FROM dual; END; CREATE TABLE acl_class ( id NUMBER(38) NOT NULL PRIMARY KEY, class NVARCHAR2(100) NOT NULL, CONSTRAINT uk_acl_class UNIQUE (class) ); CREATE SEQUENCE acl_class_sequence START WITH 1 INCREMENT BY 1 NOMAXVALUE; CREATE OR REPLACE TRIGGER acl_class_id_trigger BEFORE INSERT ON acl_class FOR EACH ROW BEGIN SELECT acl_class_sequence.nextval INTO :new.id FROM dual; END; CREATE TABLE acl_object_identity ( id NUMBER(38) NOT NULL PRIMARY KEY, object_id_class NUMBER(38) NOT NULL, object_id_identity NVARCHAR2(36) NOT NULL, parent_object NUMBER(38), owner_sid NUMBER(38), entries_inheriting NUMBER(1) NOT NULL CHECK (entries_inheriting in (0, 1)), CONSTRAINT uk_acl_object_identity UNIQUE (object_id_class, object_id_identity), CONSTRAINT fk_acl_object_identity_parent FOREIGN KEY (parent_object) REFERENCES acl_object_identity (id), CONSTRAINT fk_acl_object_identity_class FOREIGN KEY (object_id_class) REFERENCES acl_class (id), CONSTRAINT fk_acl_object_identity_owner FOREIGN KEY (owner_sid) REFERENCES acl_sid (id) ); CREATE SEQUENCE acl_object_identity_sequence START WITH 1 INCREMENT BY 1 NOMAXVALUE; CREATE OR REPLACE TRIGGER acl_object_identity_id_trigger BEFORE INSERT ON acl_object_identity FOR EACH ROW BEGIN SELECT acl_object_identity_sequence.nextval INTO :new.id FROM dual; END; CREATE TABLE acl_entry ( id NUMBER(38) NOT NULL PRIMARY KEY, acl_object_identity NUMBER(38) NOT NULL, ace_order INTEGER NOT NULL, sid NUMBER(38) NOT NULL, mask INTEGER NOT NULL, granting NUMBER(1) NOT NULL CHECK (granting in (0, 1)), audit_success NUMBER(1) NOT NULL CHECK (audit_success in (0, 1)), audit_failure NUMBER(1) NOT NULL CHECK (audit_failure in (0, 1)), CONSTRAINT unique_acl_entry UNIQUE (acl_object_identity, ace_order), CONSTRAINT fk_acl_entry_object FOREIGN KEY (acl_object_identity) REFERENCES acl_object_identity (id), CONSTRAINT fk_acl_entry_acl FOREIGN KEY (sid) REFERENCES acl_sid (id) ); CREATE SEQUENCE acl_entry_sequence START WITH 1 INCREMENT BY 1 NOMAXVALUE; CREATE OR REPLACE TRIGGER acl_entry_id_trigger BEFORE INSERT ON acl_entry FOR EACH ROW BEGIN SELECT acl_entry_sequence.nextval INTO :new.id FROM dual; END; ================================================ FILE: acl/src/main/resources/createAclSchemaPostgres.sql ================================================ -- ACL Schema SQL for PostgreSQL -- drop table acl_entry; -- drop table acl_object_identity; -- drop table acl_class; -- drop table acl_sid; create table acl_sid( id bigserial not null primary key, principal boolean not null, sid varchar(100) not null, constraint unique_uk_1 unique(sid,principal) ); create table acl_class( id bigserial not null primary key, class varchar(100) not null, class_id_type varchar(100), constraint unique_uk_2 unique(class) ); create table acl_object_identity( id bigserial primary key, object_id_class bigint not null, object_id_identity varchar(36) not null, parent_object bigint, owner_sid bigint, entries_inheriting boolean not null, constraint unique_uk_3 unique(object_id_class,object_id_identity), constraint foreign_fk_1 foreign key(parent_object)references acl_object_identity(id), constraint foreign_fk_2 foreign key(object_id_class)references acl_class(id), constraint foreign_fk_3 foreign key(owner_sid)references acl_sid(id) ); create table acl_entry( id bigserial primary key, acl_object_identity bigint not null, ace_order int not null, sid bigint not null, mask integer not null, granting boolean not null, audit_success boolean not null, audit_failure boolean not null, constraint unique_uk_4 unique(acl_object_identity,ace_order), constraint foreign_fk_4 foreign key(acl_object_identity) references acl_object_identity(id), constraint foreign_fk_5 foreign key(sid) references acl_sid(id) ); ================================================ FILE: acl/src/main/resources/createAclSchemaSqlServer.sql ================================================ -- ACL Schema SQL for Microsoft SQL Server 2008+ -- drop table acl_entry; -- drop table acl_object_identity; -- drop table acl_class; -- drop table acl_sid; CREATE TABLE acl_sid ( id BIGINT NOT NULL IDENTITY PRIMARY KEY, principal BIT NOT NULL, sid VARCHAR(100) NOT NULL, CONSTRAINT unique_acl_sid UNIQUE (sid, principal) ); CREATE TABLE acl_class ( id BIGINT NOT NULL IDENTITY PRIMARY KEY, class VARCHAR(100) NOT NULL, CONSTRAINT uk_acl_class UNIQUE (class) ); CREATE TABLE acl_object_identity ( id BIGINT NOT NULL IDENTITY PRIMARY KEY, object_id_class BIGINT NOT NULL, object_id_identity VARCHAR(36) NOT NULL, parent_object BIGINT, owner_sid BIGINT, entries_inheriting BIT NOT NULL, CONSTRAINT uk_acl_object_identity UNIQUE (object_id_class, object_id_identity), CONSTRAINT fk_acl_object_identity_parent FOREIGN KEY (parent_object) REFERENCES acl_object_identity (id), CONSTRAINT fk_acl_object_identity_class FOREIGN KEY (object_id_class) REFERENCES acl_class (id), CONSTRAINT fk_acl_object_identity_owner FOREIGN KEY (owner_sid) REFERENCES acl_sid (id) ); CREATE TABLE acl_entry ( id BIGINT NOT NULL IDENTITY PRIMARY KEY, acl_object_identity BIGINT NOT NULL, ace_order INTEGER NOT NULL, sid BIGINT NOT NULL, mask INTEGER NOT NULL, granting BIT NOT NULL, audit_success BIT NOT NULL, audit_failure BIT NOT NULL, CONSTRAINT unique_acl_entry UNIQUE (acl_object_identity, ace_order), CONSTRAINT fk_acl_entry_object FOREIGN KEY (acl_object_identity) REFERENCES acl_object_identity (id), CONSTRAINT fk_acl_entry_acl FOREIGN KEY (sid) REFERENCES acl_sid (id) ); ================================================ FILE: acl/src/main/resources/createAclSchemaWithAclClassIdType.sql ================================================ -- ACL schema sql used in HSQLDB -- drop table acl_entry; -- drop table acl_object_identity; -- drop table acl_class; -- drop table acl_sid; create table acl_sid( id bigint generated by default as identity(start with 100) not null primary key, principal boolean not null, sid varchar_ignorecase(100) not null, constraint unique_uk_1 unique(sid,principal) ); create table acl_class( id bigint generated by default as identity(start with 100) not null primary key, class varchar_ignorecase(100) not null, class_id_type varchar_ignorecase(100), constraint unique_uk_2 unique(class) ); create table acl_object_identity( id bigint generated by default as identity(start with 100) not null primary key, object_id_class bigint not null, object_id_identity varchar_ignorecase(36) not null, parent_object bigint, owner_sid bigint, entries_inheriting boolean not null, constraint unique_uk_3 unique(object_id_class,object_id_identity), constraint foreign_fk_1 foreign key(parent_object)references acl_object_identity(id), constraint foreign_fk_2 foreign key(object_id_class)references acl_class(id), constraint foreign_fk_3 foreign key(owner_sid)references acl_sid(id) ); create table acl_entry( id bigint generated by default as identity(start with 100) not null primary key, acl_object_identity bigint not null, ace_order int not null, sid bigint not null, mask integer not null, granting boolean not null, audit_success boolean not null, audit_failure boolean not null, constraint unique_uk_4 unique(acl_object_identity,ace_order), constraint foreign_fk_4 foreign key(acl_object_identity) references acl_object_identity(id), constraint foreign_fk_5 foreign key(sid) references acl_sid(id) ); ================================================ FILE: acl/src/main/resources/select.sql ================================================ -- Not required. Just shows the sort of queries being sent to DB. select acl_object_identity.object_id_identity, acl_entry.ace_order, acl_object_identity.id as acl_id, acl_object_identity.parent_object, acl_object_identity, entries_inheriting, acl_entry.id as ace_id, acl_entry.mask, acl_entry.granting, acl_entry.audit_success, acl_entry.audit_failure, acl_sid.principal as ace_principal, acl_sid.sid as ace_sid, acli_sid.principal as acl_principal, acli_sid.sid as acl_sid, acl_class.class from acl_object_identity, acl_sid acli_sid, acl_class left join acl_entry on acl_object_identity.id = acl_entry.acl_object_identity left join acl_sid on acl_entry.sid = acl_sid.id where acli_sid.id = acl_object_identity.owner_sid and acl_class.id = acl_object_identity.object_id_class and ( (acl_object_identity.object_id_identity = 1 and acl_class.class = 'sample.contact.contact') or (acl_object_identity.object_id_identity = 2000 and acl_class.class = 'sample.contact.contact') ) order by acl_object_identity.object_id_identity asc, acl_entry.ace_order asc ================================================ FILE: acl/src/test/java/org/springframework/security/acls/AclFormattingUtilsTests.java ================================================ /* * 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 org.springframework.security.acls; import org.junit.jupiter.api.Test; import org.springframework.security.acls.domain.AclFormattingUtils; import org.springframework.security.acls.model.Permission; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.assertj.core.api.Assertions.assertThatNoException; /** * Tests for {@link AclFormattingUtils}. * * @author Andrei Stefan */ public class AclFormattingUtilsTests { @Test public final void testDemergePatternsParametersConstraints() { assertThatIllegalArgumentException().isThrownBy(() -> AclFormattingUtils.demergePatterns(null, "SOME STRING")); assertThatIllegalArgumentException().isThrownBy(() -> AclFormattingUtils.demergePatterns("SOME STRING", null)); assertThatIllegalArgumentException() .isThrownBy(() -> AclFormattingUtils.demergePatterns("SOME STRING", "LONGER SOME STRING")); assertThatNoException().isThrownBy(() -> AclFormattingUtils.demergePatterns("SOME STRING", "SAME LENGTH")); } @Test public final void testDemergePatterns() { String original = "...........................A...R"; String removeBits = "...............................R"; assertThat(AclFormattingUtils.demergePatterns(original, removeBits)) .isEqualTo("...........................A...."); assertThat(AclFormattingUtils.demergePatterns("ABCDEF", "......")).isEqualTo("ABCDEF"); assertThat(AclFormattingUtils.demergePatterns("ABCDEF", "GHIJKL")).isEqualTo("......"); } @Test public final void testMergePatternsParametersConstraints() { assertThatIllegalArgumentException().isThrownBy(() -> AclFormattingUtils.mergePatterns(null, "SOME STRING")); assertThatIllegalArgumentException().isThrownBy(() -> AclFormattingUtils.mergePatterns("SOME STRING", null)); assertThatIllegalArgumentException() .isThrownBy(() -> AclFormattingUtils.mergePatterns("SOME STRING", "LONGER SOME STRING")); assertThatNoException().isThrownBy(() -> AclFormattingUtils.mergePatterns("SOME STRING", "SAME LENGTH")); } @Test public final void testMergePatterns() { String original = "...............................R"; String extraBits = "...........................A...."; assertThat(AclFormattingUtils.mergePatterns(original, extraBits)).isEqualTo("...........................A...R"); assertThat(AclFormattingUtils.mergePatterns("ABCDEF", "......")).isEqualTo("ABCDEF"); assertThat(AclFormattingUtils.mergePatterns("ABCDEF", "GHIJKL")).isEqualTo("GHIJKL"); } @Test public final void testBinaryPrints() { assertThat(AclFormattingUtils.printBinary(15)).isEqualTo("............................****"); assertThatIllegalArgumentException() .isThrownBy(() -> AclFormattingUtils.printBinary(15, Permission.RESERVED_ON)); assertThatIllegalArgumentException() .isThrownBy(() -> AclFormattingUtils.printBinary(15, Permission.RESERVED_OFF)); assertThat(AclFormattingUtils.printBinary(15, 'x')).isEqualTo("............................xxxx"); } @Test public void testPrintBinaryNegative() { assertThat(AclFormattingUtils.printBinary(0x80000000)).isEqualTo("*..............................."); } @Test public void testPrintBinaryMinusOne() { assertThat(AclFormattingUtils.printBinary(0xffffffff)).isEqualTo("********************************"); } } ================================================ FILE: acl/src/test/java/org/springframework/security/acls/AclPermissionCacheOptimizerTests.java ================================================ /* * 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 org.springframework.security.acls; import java.util.Arrays; import java.util.Collections; import java.util.List; import org.junit.jupiter.api.Test; import org.springframework.security.acls.domain.ObjectIdentityImpl; import org.springframework.security.acls.model.AclService; import org.springframework.security.acls.model.ObjectIdentity; import org.springframework.security.acls.model.ObjectIdentityRetrievalStrategy; import org.springframework.security.acls.model.SidRetrievalStrategy; import org.springframework.security.core.Authentication; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; /** * @author Luke Taylor */ @SuppressWarnings({ "unchecked" }) public class AclPermissionCacheOptimizerTests { @Test public void eagerlyLoadsRequiredAcls() { AclService service = mock(AclService.class); AclPermissionCacheOptimizer pco = new AclPermissionCacheOptimizer(service); ObjectIdentityRetrievalStrategy oidStrat = mock(ObjectIdentityRetrievalStrategy.class); SidRetrievalStrategy sidStrat = mock(SidRetrievalStrategy.class); pco.setObjectIdentityRetrievalStrategy(oidStrat); pco.setSidRetrievalStrategy(sidStrat); Object[] dos = { new Object(), null, new Object() }; ObjectIdentity[] oids = { new ObjectIdentityImpl("A", "1"), new ObjectIdentityImpl("A", "2") }; given(oidStrat.getObjectIdentity(dos[0])).willReturn(oids[0]); given(oidStrat.getObjectIdentity(dos[2])).willReturn(oids[1]); pco.cachePermissionsFor(mock(Authentication.class), Arrays.asList(dos)); // AclService should be invoked with the list of required Oids verify(service).readAclsById(eq(Arrays.asList(oids)), any(List.class)); } @Test public void ignoresEmptyCollection() { AclService service = mock(AclService.class); AclPermissionCacheOptimizer pco = new AclPermissionCacheOptimizer(service); ObjectIdentityRetrievalStrategy oids = mock(ObjectIdentityRetrievalStrategy.class); SidRetrievalStrategy sids = mock(SidRetrievalStrategy.class); pco.setObjectIdentityRetrievalStrategy(oids); pco.setSidRetrievalStrategy(sids); pco.cachePermissionsFor(mock(Authentication.class), Collections.emptyList()); verifyNoMoreInteractions(service, sids, oids); } } ================================================ FILE: acl/src/test/java/org/springframework/security/acls/AclPermissionEvaluatorTests.java ================================================ /* * 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 org.springframework.security.acls; import java.util.Locale; import org.junit.jupiter.api.Test; import org.springframework.security.acls.model.Acl; import org.springframework.security.acls.model.AclService; import org.springframework.security.acls.model.ObjectIdentity; import org.springframework.security.acls.model.ObjectIdentityRetrievalStrategy; import org.springframework.security.acls.model.SidRetrievalStrategy; import org.springframework.security.core.Authentication; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyList; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; /** * @author Luke Taylor * @since 3.0 */ public class AclPermissionEvaluatorTests { @Test public void hasPermissionReturnsTrueIfAclGrantsPermission() { AclService service = mock(AclService.class); AclPermissionEvaluator pe = new AclPermissionEvaluator(service); ObjectIdentity oid = mock(ObjectIdentity.class); ObjectIdentityRetrievalStrategy oidStrategy = mock(ObjectIdentityRetrievalStrategy.class); given(oidStrategy.getObjectIdentity(any(Object.class))).willReturn(oid); pe.setObjectIdentityRetrievalStrategy(oidStrategy); pe.setSidRetrievalStrategy(mock(SidRetrievalStrategy.class)); Acl acl = mock(Acl.class); given(service.readAclById(any(ObjectIdentity.class), anyList())).willReturn(acl); given(acl.isGranted(anyList(), anyList(), eq(false))).willReturn(true); assertThat(pe.hasPermission(mock(Authentication.class), new Object(), "READ")).isTrue(); } @Test public void resolvePermissionNonEnglishLocale() { Locale systemLocale = Locale.getDefault(); Locale.setDefault(new Locale("tr")); AclService service = mock(AclService.class); AclPermissionEvaluator pe = new AclPermissionEvaluator(service); ObjectIdentity oid = mock(ObjectIdentity.class); ObjectIdentityRetrievalStrategy oidStrategy = mock(ObjectIdentityRetrievalStrategy.class); given(oidStrategy.getObjectIdentity(any(Object.class))).willReturn(oid); pe.setObjectIdentityRetrievalStrategy(oidStrategy); pe.setSidRetrievalStrategy(mock(SidRetrievalStrategy.class)); Acl acl = mock(Acl.class); given(service.readAclById(any(ObjectIdentity.class), anyList())).willReturn(acl); given(acl.isGranted(anyList(), anyList(), eq(false))).willReturn(true); assertThat(pe.hasPermission(mock(Authentication.class), new Object(), "write")).isTrue(); Locale.setDefault(systemLocale); } } ================================================ FILE: acl/src/test/java/org/springframework/security/acls/TargetObject.java ================================================ /* * 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 org.springframework.security.acls; /** * Dummy domain object class * * @author Luke Taylor */ public final class TargetObject { } ================================================ FILE: acl/src/test/java/org/springframework/security/acls/TargetObjectWithUUID.java ================================================ /* * 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 org.springframework.security.acls; import java.util.UUID; /** * Dummy domain object class with a {@link UUID} for the Id. * * @author Luke Taylor */ public final class TargetObjectWithUUID { private UUID id; public UUID getId() { return this.id; } public void setId(UUID id) { this.id = id; } } ================================================ FILE: acl/src/test/java/org/springframework/security/acls/domain/AccessControlImplEntryTests.java ================================================ /* * 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 org.springframework.security.acls.domain; import org.junit.jupiter.api.Test; import org.springframework.security.acls.model.AccessControlEntry; import org.springframework.security.acls.model.Acl; import org.springframework.security.acls.model.AuditableAccessControlEntry; import org.springframework.security.acls.model.ObjectIdentity; import org.springframework.security.acls.model.Sid; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; /** * Tests for {@link AccessControlEntryImpl}. * * @author Andrei Stefan */ public class AccessControlImplEntryTests { @Test public void testConstructorRequiredFields() { // Check Acl field is present assertThatIllegalArgumentException().isThrownBy(() -> new AccessControlEntryImpl(null, null, new PrincipalSid("johndoe"), BasePermission.ADMINISTRATION, true, true, true)); // Check Sid field is present assertThatIllegalArgumentException().isThrownBy(() -> new AccessControlEntryImpl(null, mock(Acl.class), null, BasePermission.ADMINISTRATION, true, true, true)); // Check Permission field is present assertThatIllegalArgumentException().isThrownBy(() -> new AccessControlEntryImpl(null, mock(Acl.class), new PrincipalSid("johndoe"), null, true, true, true)); } @Test public void testAccessControlEntryImplGetters() { Acl mockAcl = mock(Acl.class); Sid sid = new PrincipalSid("johndoe"); // Create a sample entry AccessControlEntry ace = new AccessControlEntryImpl(1L, mockAcl, sid, BasePermission.ADMINISTRATION, true, true, true); // and check every get() method assertThat(ace.getId()).isEqualTo(1L); assertThat(ace.getAcl()).isEqualTo(mockAcl); assertThat(ace.getSid()).isEqualTo(sid); assertThat(ace.isGranting()).isTrue(); assertThat(ace.getPermission()).isEqualTo(BasePermission.ADMINISTRATION); assertThat(((AuditableAccessControlEntry) ace).isAuditFailure()).isTrue(); assertThat(((AuditableAccessControlEntry) ace).isAuditSuccess()).isTrue(); } @Test public void testEquals() { final Acl mockAcl = mock(Acl.class); final ObjectIdentity oid = mock(ObjectIdentity.class); given(mockAcl.getObjectIdentity()).willReturn(oid); Sid sid = new PrincipalSid("johndoe"); AccessControlEntry ace = new AccessControlEntryImpl(1L, mockAcl, sid, BasePermission.ADMINISTRATION, true, true, true); assertThat(ace).isNotNull(); assertThat(ace).isNotEqualTo(100L); assertThat(ace).isEqualTo(ace); assertThat(ace) .isEqualTo(new AccessControlEntryImpl(1L, mockAcl, sid, BasePermission.ADMINISTRATION, true, true, true)); assertThat(ace).isNotEqualTo( new AccessControlEntryImpl(2L, mockAcl, sid, BasePermission.ADMINISTRATION, true, true, true)); assertThat(ace).isNotEqualTo(new AccessControlEntryImpl(1L, mockAcl, new PrincipalSid("scott"), BasePermission.ADMINISTRATION, true, true, true)); assertThat(ace) .isNotEqualTo(new AccessControlEntryImpl(1L, mockAcl, sid, BasePermission.WRITE, true, true, true)); assertThat(ace).isNotEqualTo( new AccessControlEntryImpl(1L, mockAcl, sid, BasePermission.ADMINISTRATION, false, true, true)); assertThat(ace).isNotEqualTo( new AccessControlEntryImpl(1L, mockAcl, sid, BasePermission.ADMINISTRATION, true, false, true)); assertThat(ace).isNotEqualTo( new AccessControlEntryImpl(1L, mockAcl, sid, BasePermission.ADMINISTRATION, true, true, false)); } } ================================================ FILE: acl/src/test/java/org/springframework/security/acls/domain/AclAuthorizationStrategyImplTests.java ================================================ /* * 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 org.springframework.security.acls.domain; import java.util.Arrays; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl; import org.springframework.security.acls.model.Acl; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.security.core.context.SecurityContextImpl; import static org.assertj.core.api.Assertions.assertThatNoException; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.verify; /** * @author Rob Winch * */ @ExtendWith(MockitoExtension.class) public class AclAuthorizationStrategyImplTests { SecurityContext context; @Mock Acl acl; @Mock SecurityContextHolderStrategy securityContextHolderStrategy; GrantedAuthority authority; AclAuthorizationStrategyImpl strategy; @BeforeEach public void setup() { this.authority = new SimpleGrantedAuthority("ROLE_AUTH"); TestingAuthenticationToken authentication = new TestingAuthenticationToken("foo", "bar", Arrays.asList(this.authority)); authentication.setAuthenticated(true); this.context = new SecurityContextImpl(authentication); SecurityContextHolder.setContext(this.context); } @AfterEach public void cleanup() { SecurityContextHolder.clearContext(); } // gh-4085 @Test public void securityCheckWhenCustomAuthorityThenNameIsUsed() { this.strategy = new AclAuthorizationStrategyImpl(new CustomAuthority()); this.strategy.securityCheck(this.acl, AclAuthorizationStrategy.CHANGE_GENERAL); } // gh-9425 @Test public void securityCheckWhenAclOwnedByGrantedAuthority() { given(this.acl.getOwner()).willReturn(new GrantedAuthoritySid("ROLE_AUTH")); this.strategy = new AclAuthorizationStrategyImpl(new SimpleGrantedAuthority("ROLE_SYSTEM_ADMIN")); this.strategy.securityCheck(this.acl, AclAuthorizationStrategy.CHANGE_GENERAL); } @Test public void securityCheckWhenRoleReachableByHierarchyThenAuthorized() { given(this.acl.getOwner()).willReturn(new GrantedAuthoritySid("ROLE_AUTH_B")); this.strategy = new AclAuthorizationStrategyImpl(new SimpleGrantedAuthority("ROLE_SYSTEM_ADMIN")); this.strategy.setRoleHierarchy(RoleHierarchyImpl.fromHierarchy("ROLE_AUTH > ROLE_AUTH_B")); assertThatNoException() .isThrownBy(() -> this.strategy.securityCheck(this.acl, AclAuthorizationStrategy.CHANGE_GENERAL)); } @Test public void securityCheckWhenCustomSecurityContextHolderStrategyThenUses() { given(this.securityContextHolderStrategy.getContext()).willReturn(this.context); given(this.acl.getOwner()).willReturn(new GrantedAuthoritySid("ROLE_AUTH")); this.strategy = new AclAuthorizationStrategyImpl(new SimpleGrantedAuthority("ROLE_SYSTEM_ADMIN")); this.strategy.setSecurityContextHolderStrategy(this.securityContextHolderStrategy); this.strategy.securityCheck(this.acl, AclAuthorizationStrategy.CHANGE_GENERAL); verify(this.securityContextHolderStrategy).getContext(); } @SuppressWarnings("serial") class CustomAuthority implements GrantedAuthority { @Override public String getAuthority() { return AclAuthorizationStrategyImplTests.this.authority.getAuthority(); } } } ================================================ FILE: acl/src/test/java/org/springframework/security/acls/domain/AclImplTests.java ================================================ /* * 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 org.springframework.security.acls.domain; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.security.acls.model.AccessControlEntry; import org.springframework.security.acls.model.Acl; import org.springframework.security.acls.model.AlreadyExistsException; import org.springframework.security.acls.model.AuditableAccessControlEntry; import org.springframework.security.acls.model.AuditableAcl; import org.springframework.security.acls.model.ChildrenExistException; import org.springframework.security.acls.model.MutableAcl; import org.springframework.security.acls.model.MutableAclService; import org.springframework.security.acls.model.NotFoundException; import org.springframework.security.acls.model.ObjectIdentity; import org.springframework.security.acls.model.Permission; import org.springframework.security.acls.model.PermissionGrantingStrategy; import org.springframework.security.acls.model.Sid; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.util.FieldUtils; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.mockito.Mockito.mock; /** * Tests for {@link AclImpl}. * * @author Andrei Stefan */ public class AclImplTests { private static final String TARGET_CLASS = "org.springframework.security.acls.TargetObject"; private static final List READ = Arrays.asList(BasePermission.READ); private static final List WRITE = Arrays.asList(BasePermission.WRITE); private static final List CREATE = Arrays.asList(BasePermission.CREATE); private static final List DELETE = Arrays.asList(BasePermission.DELETE); private static final List SCOTT = Arrays.asList((Sid) new PrincipalSid("scott")); private static final List BEN = Arrays.asList((Sid) new PrincipalSid("ben")); Authentication auth = new TestingAuthenticationToken("joe", "ignored", "ROLE_ADMINISTRATOR"); AclAuthorizationStrategy authzStrategy; PermissionGrantingStrategy pgs; AuditLogger mockAuditLogger; ObjectIdentity objectIdentity = new ObjectIdentityImpl(TARGET_CLASS, 100); private DefaultPermissionFactory permissionFactory; @BeforeEach public void setUp() { SecurityContextHolder.getContext().setAuthentication(this.auth); this.authzStrategy = mock(AclAuthorizationStrategy.class); this.mockAuditLogger = mock(AuditLogger.class); this.pgs = new DefaultPermissionGrantingStrategy(this.mockAuditLogger); this.auth.setAuthenticated(true); this.permissionFactory = new DefaultPermissionFactory(); } @AfterEach public void tearDown() { SecurityContextHolder.clearContext(); } @Test public void constructorsRejectNullObjectIdentity() { assertThatIllegalArgumentException().isThrownBy( () -> new AclImpl(null, 1, this.authzStrategy, this.pgs, null, null, true, new PrincipalSid("joe"))); assertThatIllegalArgumentException() .isThrownBy(() -> new AclImpl(null, 1, this.authzStrategy, this.mockAuditLogger)); } @Test public void constructorsRejectNullId() { assertThatIllegalArgumentException().isThrownBy(() -> new AclImpl(this.objectIdentity, null, this.authzStrategy, this.pgs, null, null, true, new PrincipalSid("joe"))); assertThatIllegalArgumentException() .isThrownBy(() -> new AclImpl(this.objectIdentity, null, this.authzStrategy, this.mockAuditLogger)); } @Test public void constructorsRejectNullAclAuthzStrategy() { assertThatIllegalArgumentException().isThrownBy(() -> new AclImpl(this.objectIdentity, 1, null, new DefaultPermissionGrantingStrategy(this.mockAuditLogger), null, null, true, new PrincipalSid("joe"))); assertThatIllegalArgumentException() .isThrownBy(() -> new AclImpl(this.objectIdentity, 1, null, this.mockAuditLogger)); } @Test public void insertAceRejectsNullParameters() { MutableAcl acl = new AclImpl(this.objectIdentity, 1, this.authzStrategy, this.pgs, null, null, true, new PrincipalSid("joe")); assertThatIllegalArgumentException() .isThrownBy(() -> acl.insertAce(0, null, new GrantedAuthoritySid("ROLE_IGNORED"), true)); assertThatIllegalArgumentException().isThrownBy(() -> acl.insertAce(0, BasePermission.READ, null, true)); } @Test public void insertAceAddsElementAtCorrectIndex() { MutableAcl acl = new AclImpl(this.objectIdentity, 1, this.authzStrategy, this.pgs, null, null, true, new PrincipalSid("joe")); MockAclService service = new MockAclService(); // Insert one permission acl.insertAce(0, BasePermission.READ, new GrantedAuthoritySid("ROLE_TEST1"), true); service.updateAcl(acl); // Check it was successfully added assertThat(acl.getEntries()).hasSize(1); assertThat(acl).isEqualTo(acl.getEntries().get(0).getAcl()); assertThat(BasePermission.READ).isEqualTo(acl.getEntries().get(0).getPermission()); assertThat(acl.getEntries().get(0).getSid()).isEqualTo(new GrantedAuthoritySid("ROLE_TEST1")); // Add a second permission acl.insertAce(1, BasePermission.READ, new GrantedAuthoritySid("ROLE_TEST2"), true); service.updateAcl(acl); // Check it was added on the last position assertThat(acl.getEntries()).hasSize(2); assertThat(acl).isEqualTo(acl.getEntries().get(1).getAcl()); assertThat(BasePermission.READ).isEqualTo(acl.getEntries().get(1).getPermission()); assertThat(acl.getEntries().get(1).getSid()).isEqualTo(new GrantedAuthoritySid("ROLE_TEST2")); // Add a third permission, after the first one acl.insertAce(1, BasePermission.WRITE, new GrantedAuthoritySid("ROLE_TEST3"), false); service.updateAcl(acl); assertThat(acl.getEntries()).hasSize(3); // Check the third entry was added between the two existent ones assertThat(BasePermission.READ).isEqualTo(acl.getEntries().get(0).getPermission()); assertThat(acl.getEntries().get(0).getSid()).isEqualTo(new GrantedAuthoritySid("ROLE_TEST1")); assertThat(BasePermission.WRITE).isEqualTo(acl.getEntries().get(1).getPermission()); assertThat(acl.getEntries().get(1).getSid()).isEqualTo(new GrantedAuthoritySid("ROLE_TEST3")); assertThat(BasePermission.READ).isEqualTo(acl.getEntries().get(2).getPermission()); assertThat(acl.getEntries().get(2).getSid()).isEqualTo(new GrantedAuthoritySid("ROLE_TEST2")); } @Test public void insertAceFailsForNonExistentElement() { MutableAcl acl = new AclImpl(this.objectIdentity, 1, this.authzStrategy, this.pgs, null, null, true, new PrincipalSid("joe")); MockAclService service = new MockAclService(); // Insert one permission acl.insertAce(0, BasePermission.READ, new GrantedAuthoritySid("ROLE_TEST1"), true); service.updateAcl(acl); assertThatExceptionOfType(NotFoundException.class) .isThrownBy(() -> acl.insertAce(55, BasePermission.READ, new GrantedAuthoritySid("ROLE_TEST2"), true)); } @Test public void deleteAceKeepsInitialOrdering() { MutableAcl acl = new AclImpl(this.objectIdentity, 1, this.authzStrategy, this.pgs, null, null, true, new PrincipalSid("joe")); MockAclService service = new MockAclService(); // Add several permissions acl.insertAce(0, BasePermission.READ, new GrantedAuthoritySid("ROLE_TEST1"), true); acl.insertAce(1, BasePermission.READ, new GrantedAuthoritySid("ROLE_TEST2"), true); acl.insertAce(2, BasePermission.READ, new GrantedAuthoritySid("ROLE_TEST3"), true); service.updateAcl(acl); // Delete first permission and check the order of the remaining permissions is // kept acl.deleteAce(0); assertThat(acl.getEntries()).hasSize(2); assertThat(acl.getEntries().get(0).getSid()).isEqualTo(new GrantedAuthoritySid("ROLE_TEST2")); assertThat(acl.getEntries().get(1).getSid()).isEqualTo(new GrantedAuthoritySid("ROLE_TEST3")); // Add one more permission and remove the permission in the middle acl.insertAce(2, BasePermission.READ, new GrantedAuthoritySid("ROLE_TEST4"), true); service.updateAcl(acl); acl.deleteAce(1); assertThat(acl.getEntries()).hasSize(2); assertThat(acl.getEntries().get(0).getSid()).isEqualTo(new GrantedAuthoritySid("ROLE_TEST2")); assertThat(acl.getEntries().get(1).getSid()).isEqualTo(new GrantedAuthoritySid("ROLE_TEST4")); // Remove remaining permissions acl.deleteAce(1); acl.deleteAce(0); assertThat(acl.getEntries()).isEmpty(); } @Test public void deleteAceFailsForNonExistentElement() { AclAuthorizationStrategyImpl strategy = new AclAuthorizationStrategyImpl( new SimpleGrantedAuthority("ROLE_OWNERSHIP"), new SimpleGrantedAuthority("ROLE_AUDITING"), new SimpleGrantedAuthority("ROLE_GENERAL")); MutableAcl acl = new AclImpl(this.objectIdentity, (1), strategy, this.pgs, null, null, true, new PrincipalSid("joe")); assertThatExceptionOfType(NotFoundException.class).isThrownBy(() -> acl.deleteAce(99)); } @Test public void isGrantingRejectsEmptyParameters() { MutableAcl acl = new AclImpl(this.objectIdentity, 1, this.authzStrategy, this.pgs, null, null, true, new PrincipalSid("joe")); Sid ben = new PrincipalSid("ben"); assertThatIllegalArgumentException() .isThrownBy(() -> acl.isGranted(new ArrayList<>(0), Arrays.asList(ben), false)); assertThatIllegalArgumentException().isThrownBy(() -> acl.isGranted(READ, new ArrayList<>(0), false)); } @Test public void isGrantingGrantsAccessForAclWithNoParent() { Authentication auth = new TestingAuthenticationToken("ben", "ignored", "ROLE_GENERAL", "ROLE_GUEST"); auth.setAuthenticated(true); SecurityContextHolder.getContext().setAuthentication(auth); ObjectIdentity rootOid = new ObjectIdentityImpl(TARGET_CLASS, 100); // Create an ACL which owner is not the authenticated principal MutableAcl rootAcl = new AclImpl(rootOid, 1, this.authzStrategy, this.pgs, null, null, false, new PrincipalSid("joe")); // Grant some permissions rootAcl.insertAce(0, BasePermission.READ, new PrincipalSid("ben"), false); rootAcl.insertAce(1, BasePermission.WRITE, new PrincipalSid("scott"), true); rootAcl.insertAce(2, BasePermission.WRITE, new PrincipalSid("rod"), false); rootAcl.insertAce(3, BasePermission.WRITE, new GrantedAuthoritySid("WRITE_ACCESS_ROLE"), true); // Check permissions granting List permissions = Arrays.asList(BasePermission.READ, BasePermission.CREATE); List sids = Arrays.asList(new PrincipalSid("ben"), new GrantedAuthoritySid("ROLE_GUEST")); assertThat(rootAcl.isGranted(permissions, sids, false)).isFalse(); assertThatExceptionOfType(NotFoundException.class) .isThrownBy(() -> rootAcl.isGranted(permissions, SCOTT, false)); assertThat(rootAcl.isGranted(WRITE, SCOTT, false)).isTrue(); assertThat(rootAcl.isGranted(WRITE, Arrays.asList(new PrincipalSid("rod"), new GrantedAuthoritySid("WRITE_ACCESS_ROLE")), false)) .isFalse(); assertThat(rootAcl.isGranted(WRITE, Arrays.asList(new GrantedAuthoritySid("WRITE_ACCESS_ROLE"), new PrincipalSid("rod")), false)) .isTrue(); // Change the type of the Sid and check the granting process assertThatExceptionOfType(NotFoundException.class).isThrownBy(() -> rootAcl.isGranted(WRITE, Arrays.asList(new GrantedAuthoritySid("rod"), new PrincipalSid("WRITE_ACCESS_ROLE")), false)); } @Test public void isGrantingGrantsAccessForInheritableAcls() { Authentication auth = new TestingAuthenticationToken("ben", "ignored", "ROLE_GENERAL"); auth.setAuthenticated(true); SecurityContextHolder.getContext().setAuthentication(auth); ObjectIdentity grandParentOid = new ObjectIdentityImpl(TARGET_CLASS, 100); ObjectIdentity parentOid1 = new ObjectIdentityImpl(TARGET_CLASS, 101); ObjectIdentity parentOid2 = new ObjectIdentityImpl(TARGET_CLASS, 102); ObjectIdentity childOid1 = new ObjectIdentityImpl(TARGET_CLASS, 103); ObjectIdentity childOid2 = new ObjectIdentityImpl(TARGET_CLASS, 104); // Create ACLs PrincipalSid joe = new PrincipalSid("joe"); MutableAcl grandParentAcl = new AclImpl(grandParentOid, 1, this.authzStrategy, this.pgs, null, null, false, joe); MutableAcl parentAcl1 = new AclImpl(parentOid1, 2, this.authzStrategy, this.pgs, null, null, true, joe); MutableAcl parentAcl2 = new AclImpl(parentOid2, 3, this.authzStrategy, this.pgs, null, null, true, joe); MutableAcl childAcl1 = new AclImpl(childOid1, 4, this.authzStrategy, this.pgs, null, null, true, joe); MutableAcl childAcl2 = new AclImpl(childOid2, 4, this.authzStrategy, this.pgs, null, null, false, joe); // Create hierarchies childAcl2.setParent(childAcl1); childAcl1.setParent(parentAcl1); parentAcl2.setParent(grandParentAcl); parentAcl1.setParent(grandParentAcl); // Add some permissions grandParentAcl.insertAce(0, BasePermission.READ, new GrantedAuthoritySid("ROLE_USER_READ"), true); grandParentAcl.insertAce(1, BasePermission.WRITE, new PrincipalSid("ben"), true); grandParentAcl.insertAce(2, BasePermission.DELETE, new PrincipalSid("ben"), false); grandParentAcl.insertAce(3, BasePermission.DELETE, new PrincipalSid("scott"), true); parentAcl1.insertAce(0, BasePermission.READ, new PrincipalSid("scott"), true); parentAcl1.insertAce(1, BasePermission.DELETE, new PrincipalSid("scott"), false); parentAcl2.insertAce(0, BasePermission.CREATE, new PrincipalSid("ben"), true); childAcl1.insertAce(0, BasePermission.CREATE, new PrincipalSid("scott"), true); // Check granting process for parent1 assertThat(parentAcl1.isGranted(READ, SCOTT, false)).isTrue(); assertThat(parentAcl1.isGranted(READ, Arrays.asList((Sid) new GrantedAuthoritySid("ROLE_USER_READ")), false)) .isTrue(); assertThat(parentAcl1.isGranted(WRITE, BEN, false)).isTrue(); assertThat(parentAcl1.isGranted(DELETE, BEN, false)).isFalse(); assertThat(parentAcl1.isGranted(DELETE, SCOTT, false)).isFalse(); // Check granting process for parent2 assertThat(parentAcl2.isGranted(CREATE, BEN, false)).isTrue(); assertThat(parentAcl2.isGranted(WRITE, BEN, false)).isTrue(); assertThat(parentAcl2.isGranted(DELETE, BEN, false)).isFalse(); // Check granting process for child1 assertThat(childAcl1.isGranted(CREATE, SCOTT, false)).isTrue(); assertThat(childAcl1.isGranted(READ, Arrays.asList((Sid) new GrantedAuthoritySid("ROLE_USER_READ")), false)) .isTrue(); assertThat(childAcl1.isGranted(DELETE, BEN, false)).isFalse(); // Check granting process for child2 (doesn't inherit the permissions from its // parent) assertThatExceptionOfType(NotFoundException.class).isThrownBy(() -> childAcl2.isGranted(CREATE, SCOTT, false)); assertThatExceptionOfType(NotFoundException.class) .isThrownBy(() -> childAcl2.isGranted(CREATE, Arrays.asList((Sid) new PrincipalSid("joe")), false)); } @Test public void updatedAceValuesAreCorrectlyReflectedInAcl() { Authentication auth = new TestingAuthenticationToken("ben", "ignored", "ROLE_GENERAL"); auth.setAuthenticated(true); SecurityContextHolder.getContext().setAuthentication(auth); MutableAcl acl = new AclImpl(this.objectIdentity, 1, this.authzStrategy, this.pgs, null, null, false, new PrincipalSid("joe")); MockAclService service = new MockAclService(); acl.insertAce(0, BasePermission.READ, new GrantedAuthoritySid("ROLE_USER_READ"), true); acl.insertAce(1, BasePermission.WRITE, new GrantedAuthoritySid("ROLE_USER_READ"), true); acl.insertAce(2, BasePermission.CREATE, new PrincipalSid("ben"), true); service.updateAcl(acl); assertThat(BasePermission.READ).isEqualTo(acl.getEntries().get(0).getPermission()); assertThat(BasePermission.WRITE).isEqualTo(acl.getEntries().get(1).getPermission()); assertThat(BasePermission.CREATE).isEqualTo(acl.getEntries().get(2).getPermission()); // Change each permission acl.updateAce(0, BasePermission.CREATE); acl.updateAce(1, BasePermission.DELETE); acl.updateAce(2, BasePermission.READ); // Check the change was successfully made assertThat(BasePermission.CREATE).isEqualTo(acl.getEntries().get(0).getPermission()); assertThat(BasePermission.DELETE).isEqualTo(acl.getEntries().get(1).getPermission()); assertThat(BasePermission.READ).isEqualTo(acl.getEntries().get(2).getPermission()); } @Test public void auditableEntryFlagsAreUpdatedCorrectly() { Authentication auth = new TestingAuthenticationToken("ben", "ignored", "ROLE_AUDITING", "ROLE_GENERAL"); auth.setAuthenticated(true); SecurityContextHolder.getContext().setAuthentication(auth); MutableAcl acl = new AclImpl(this.objectIdentity, 1, this.authzStrategy, this.pgs, null, null, false, new PrincipalSid("joe")); MockAclService service = new MockAclService(); acl.insertAce(0, BasePermission.READ, new GrantedAuthoritySid("ROLE_USER_READ"), true); acl.insertAce(1, BasePermission.WRITE, new GrantedAuthoritySid("ROLE_USER_READ"), true); service.updateAcl(acl); assertThat(((AuditableAccessControlEntry) acl.getEntries().get(0)).isAuditFailure()).isFalse(); assertThat(((AuditableAccessControlEntry) acl.getEntries().get(1)).isAuditFailure()).isFalse(); assertThat(((AuditableAccessControlEntry) acl.getEntries().get(0)).isAuditSuccess()).isFalse(); assertThat(((AuditableAccessControlEntry) acl.getEntries().get(1)).isAuditSuccess()).isFalse(); // Change each permission ((AuditableAcl) acl).updateAuditing(0, true, true); ((AuditableAcl) acl).updateAuditing(1, true, true); // Check the change was successfuly made assertThat(acl.getEntries()).extracting("auditSuccess").containsOnly(true, true); assertThat(acl.getEntries()).extracting("auditFailure").containsOnly(true, true); } @Test public void gettersAndSettersAreConsistent() { Authentication auth = new TestingAuthenticationToken("ben", "ignored", "ROLE_GENERAL"); auth.setAuthenticated(true); SecurityContextHolder.getContext().setAuthentication(auth); ObjectIdentity identity = new ObjectIdentityImpl(TARGET_CLASS, (100)); ObjectIdentity identity2 = new ObjectIdentityImpl(TARGET_CLASS, (101)); MutableAcl acl = new AclImpl(identity, 1, this.authzStrategy, this.pgs, null, null, true, new PrincipalSid("joe")); MutableAcl parentAcl = new AclImpl(identity2, 2, this.authzStrategy, this.pgs, null, null, true, new PrincipalSid("joe")); MockAclService service = new MockAclService(); acl.insertAce(0, BasePermission.READ, new GrantedAuthoritySid("ROLE_USER_READ"), true); acl.insertAce(1, BasePermission.WRITE, new GrantedAuthoritySid("ROLE_USER_READ"), true); service.updateAcl(acl); assertThat(1).isEqualTo(acl.getId()); assertThat(identity).isEqualTo(acl.getObjectIdentity()); assertThat(new PrincipalSid("joe")).isEqualTo(acl.getOwner()); assertThat(acl.getParentAcl()).isNull(); assertThat(acl.isEntriesInheriting()).isTrue(); assertThat(acl.getEntries()).hasSize(2); acl.setParent(parentAcl); assertThat(parentAcl).isEqualTo(acl.getParentAcl()); acl.setEntriesInheriting(false); assertThat(acl.isEntriesInheriting()).isFalse(); acl.setOwner(new PrincipalSid("ben")); assertThat(new PrincipalSid("ben")).isEqualTo(acl.getOwner()); } @Test public void isSidLoadedBehavesAsExpected() { List loadedSids = Arrays.asList(new PrincipalSid("ben"), new GrantedAuthoritySid("ROLE_IGNORED")); MutableAcl acl = new AclImpl(this.objectIdentity, 1, this.authzStrategy, this.pgs, null, loadedSids, true, new PrincipalSid("joe")); assertThat(acl.isSidLoaded(loadedSids)).isTrue(); assertThat(acl.isSidLoaded(Arrays.asList(new GrantedAuthoritySid("ROLE_IGNORED"), new PrincipalSid("ben")))) .isTrue(); assertThat(acl.isSidLoaded(Arrays.asList((Sid) new GrantedAuthoritySid("ROLE_IGNORED")))).isTrue(); assertThat(acl.isSidLoaded(BEN)).isTrue(); assertThat(acl.isSidLoaded(null)).isTrue(); assertThat(acl.isSidLoaded(new ArrayList<>(0))).isTrue(); assertThat(acl.isSidLoaded( Arrays.asList(new GrantedAuthoritySid("ROLE_IGNORED"), new GrantedAuthoritySid("ROLE_IGNORED")))) .isTrue(); assertThat(acl.isSidLoaded( Arrays.asList(new GrantedAuthoritySid("ROLE_GENERAL"), new GrantedAuthoritySid("ROLE_IGNORED")))) .isFalse(); assertThat(acl.isSidLoaded( Arrays.asList(new GrantedAuthoritySid("ROLE_IGNORED"), new GrantedAuthoritySid("ROLE_GENERAL")))) .isFalse(); } @Test public void insertAceRaisesNotFoundExceptionForIndexLessThanZero() { AclImpl acl = new AclImpl(this.objectIdentity, 1, this.authzStrategy, this.pgs, null, null, true, new PrincipalSid("joe")); assertThatExceptionOfType(NotFoundException.class) .isThrownBy(() -> acl.insertAce(-1, mock(Permission.class), mock(Sid.class), true)); } @Test public void deleteAceRaisesNotFoundExceptionForIndexLessThanZero() { AclImpl acl = new AclImpl(this.objectIdentity, 1, this.authzStrategy, this.pgs, null, null, true, new PrincipalSid("joe")); assertThatExceptionOfType(NotFoundException.class).isThrownBy(() -> acl.deleteAce(-1)); } @Test public void insertAceRaisesNotFoundExceptionForIndexGreaterThanSize() { AclImpl acl = new AclImpl(this.objectIdentity, 1, this.authzStrategy, this.pgs, null, null, true, new PrincipalSid("joe")); // Insert at zero, OK. acl.insertAce(0, mock(Permission.class), mock(Sid.class), true); // Size is now 1 assertThatExceptionOfType(NotFoundException.class) .isThrownBy(() -> acl.insertAce(2, mock(Permission.class), mock(Sid.class), true)); } // SEC-1151 @Test public void deleteAceRaisesNotFoundExceptionForIndexEqualToSize() { AclImpl acl = new AclImpl(this.objectIdentity, 1, this.authzStrategy, this.pgs, null, null, true, new PrincipalSid("joe")); acl.insertAce(0, mock(Permission.class), mock(Sid.class), true); // Size is now 1 assertThatExceptionOfType(NotFoundException.class).isThrownBy(() -> acl.deleteAce(1)); } // SEC-1795 @Test public void changingParentIsSuccessful() { AclImpl parentAcl = new AclImpl(this.objectIdentity, 1L, this.authzStrategy, this.mockAuditLogger); AclImpl childAcl = new AclImpl(this.objectIdentity, 2L, this.authzStrategy, this.mockAuditLogger); AclImpl changeParentAcl = new AclImpl(this.objectIdentity, 3L, this.authzStrategy, this.mockAuditLogger); childAcl.setParent(parentAcl); childAcl.setParent(changeParentAcl); } // SEC-2342 @Test public void maskPermissionGrantingStrategy() { DefaultPermissionGrantingStrategy maskPgs = new MaskPermissionGrantingStrategy(this.mockAuditLogger); MockAclService service = new MockAclService(); AclImpl acl = new AclImpl(this.objectIdentity, 1, this.authzStrategy, maskPgs, null, null, true, new PrincipalSid("joe")); Permission permission = this.permissionFactory .buildFromMask(BasePermission.READ.getMask() | BasePermission.WRITE.getMask()); Sid sid = new PrincipalSid("ben"); acl.insertAce(0, permission, sid, true); service.updateAcl(acl); List permissions = Arrays.asList(BasePermission.READ); List sids = Arrays.asList(sid); assertThat(acl.isGranted(permissions, sids, false)).isTrue(); } @Test @SuppressWarnings("unchecked") public void hashCodeWithoutStackOverFlow() throws Exception { Sid sid = new PrincipalSid("pSid"); ObjectIdentity oid = new ObjectIdentityImpl("type", 1); AclAuthorizationStrategy authStrategy = new AclAuthorizationStrategyImpl(new SimpleGrantedAuthority("role")); PermissionGrantingStrategy grantingStrategy = new DefaultPermissionGrantingStrategy(new ConsoleAuditLogger()); AclImpl acl = new AclImpl(oid, 1L, authStrategy, grantingStrategy, null, null, false, sid); AccessControlEntryImpl ace = new AccessControlEntryImpl(1L, acl, sid, BasePermission.READ, true, true, true); Field fieldAces = FieldUtils.getField(AclImpl.class, "aces"); fieldAces.setAccessible(true); List aces = (List) fieldAces.get(acl); aces.add(ace); ace.hashCode(); } private static class MaskPermissionGrantingStrategy extends DefaultPermissionGrantingStrategy { MaskPermissionGrantingStrategy(AuditLogger auditLogger) { super(auditLogger); } @Override protected boolean isGranted(AccessControlEntry ace, Permission p) { if (p.getMask() != 0) { return (p.getMask() & ace.getPermission().getMask()) != 0; } return super.isGranted(ace, p); } } private class MockAclService implements MutableAclService { @Override public MutableAcl createAcl(ObjectIdentity objectIdentity) throws AlreadyExistsException { return null; } @Override public void deleteAcl(ObjectIdentity objectIdentity, boolean deleteChildren) throws ChildrenExistException { } /* * Mock implementation that populates the aces list with fully initialized * AccessControlEntries * * @see org.springframework.security.acls.MutableAclService#updateAcl(org. * springframework .security.acls.MutableAcl) */ @Override @SuppressWarnings("unchecked") public MutableAcl updateAcl(MutableAcl acl) throws NotFoundException { List oldAces = acl.getEntries(); Field acesField = FieldUtils.getField(AclImpl.class, "aces"); acesField.setAccessible(true); List newAces; try { newAces = (List) acesField.get(acl); newAces.clear(); for (int i = 0; i < oldAces.size(); i++) { AccessControlEntry ac = oldAces.get(i); // Just give an ID to all this acl's aces, rest of the fields are just // copied newAces.add(new AccessControlEntryImpl((i + 1), ac.getAcl(), ac.getSid(), ac.getPermission(), ac.isGranting(), ((AuditableAccessControlEntry) ac).isAuditSuccess(), ((AuditableAccessControlEntry) ac).isAuditFailure())); } } catch (IllegalAccessException ex) { ex.printStackTrace(); } return acl; } @Override public List findChildren(ObjectIdentity parentIdentity) { return null; } @Override public Acl readAclById(ObjectIdentity object) throws NotFoundException { return null; } @Override public Acl readAclById(ObjectIdentity object, List sids) throws NotFoundException { return null; } @Override public Map readAclsById(List objects) throws NotFoundException { return null; } @Override public Map readAclsById(List objects, List sids) throws NotFoundException { return null; } } } ================================================ FILE: acl/src/test/java/org/springframework/security/acls/domain/AclImplementationSecurityCheckTests.java ================================================ /* * 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 org.springframework.security.acls.domain; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.acls.model.Acl; import org.springframework.security.acls.model.MutableAcl; import org.springframework.security.acls.model.NotFoundException; import org.springframework.security.acls.model.ObjectIdentity; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatNoException; /** * Test class for {@link AclAuthorizationStrategyImpl} and {@link AclImpl} security * checks. * * @author Andrei Stefan */ public class AclImplementationSecurityCheckTests { private static final String TARGET_CLASS = "org.springframework.security.acls.TargetObject"; @BeforeEach public void setUp() { SecurityContextHolder.clearContext(); } @AfterEach public void tearDown() { SecurityContextHolder.clearContext(); } @Test public void testSecurityCheckNoACEs() { Authentication auth = new TestingAuthenticationToken("user", "password", "ROLE_GENERAL", "ROLE_AUDITING", "ROLE_OWNERSHIP"); auth.setAuthenticated(true); SecurityContextHolder.getContext().setAuthentication(auth); ObjectIdentity identity = new ObjectIdentityImpl(TARGET_CLASS, 100L); AclAuthorizationStrategy aclAuthorizationStrategy = new AclAuthorizationStrategyImpl( new SimpleGrantedAuthority("ROLE_OWNERSHIP"), new SimpleGrantedAuthority("ROLE_AUDITING"), new SimpleGrantedAuthority("ROLE_GENERAL")); Acl acl = new AclImpl(identity, 1L, aclAuthorizationStrategy, new ConsoleAuditLogger()); aclAuthorizationStrategy.securityCheck(acl, AclAuthorizationStrategy.CHANGE_GENERAL); aclAuthorizationStrategy.securityCheck(acl, AclAuthorizationStrategy.CHANGE_AUDITING); aclAuthorizationStrategy.securityCheck(acl, AclAuthorizationStrategy.CHANGE_OWNERSHIP); // Create another authorization strategy AclAuthorizationStrategy aclAuthorizationStrategy2 = new AclAuthorizationStrategyImpl( new SimpleGrantedAuthority("ROLE_ONE"), new SimpleGrantedAuthority("ROLE_TWO"), new SimpleGrantedAuthority("ROLE_THREE")); Acl acl2 = new AclImpl(identity, 1L, aclAuthorizationStrategy2, new ConsoleAuditLogger()); // Check access in case the principal has no authorization rights assertThatExceptionOfType(NotFoundException.class) .isThrownBy(() -> aclAuthorizationStrategy2.securityCheck(acl2, AclAuthorizationStrategy.CHANGE_GENERAL)); assertThatExceptionOfType(NotFoundException.class) .isThrownBy(() -> aclAuthorizationStrategy2.securityCheck(acl2, AclAuthorizationStrategy.CHANGE_AUDITING)); assertThatExceptionOfType(NotFoundException.class) .isThrownBy(() -> aclAuthorizationStrategy2.securityCheck(acl2, AclAuthorizationStrategy.CHANGE_OWNERSHIP)); } @Test public void testSecurityCheckWithMultipleACEs() { // Create a simple authentication with ROLE_GENERAL Authentication auth = new TestingAuthenticationToken("user", "password", "ROLE_GENERAL"); auth.setAuthenticated(true); SecurityContextHolder.getContext().setAuthentication(auth); ObjectIdentity identity = new ObjectIdentityImpl(TARGET_CLASS, 100L); // Authorization strategy will require a different role for each access AclAuthorizationStrategy aclAuthorizationStrategy = new AclAuthorizationStrategyImpl( new SimpleGrantedAuthority("ROLE_OWNERSHIP"), new SimpleGrantedAuthority("ROLE_AUDITING"), new SimpleGrantedAuthority("ROLE_GENERAL")); // Let's give the principal the ADMINISTRATION permission, without // granting access MutableAcl aclFirstDeny = new AclImpl(identity, 1L, aclAuthorizationStrategy, new ConsoleAuditLogger()); aclFirstDeny.insertAce(0, BasePermission.ADMINISTRATION, new PrincipalSid(auth), false); // The CHANGE_GENERAL test should pass as the principal has ROLE_GENERAL aclAuthorizationStrategy.securityCheck(aclFirstDeny, AclAuthorizationStrategy.CHANGE_GENERAL); // The CHANGE_AUDITING and CHANGE_OWNERSHIP should fail since the // principal doesn't have these authorities, // nor granting access assertThatExceptionOfType(AccessDeniedException.class).isThrownBy( () -> aclAuthorizationStrategy.securityCheck(aclFirstDeny, AclAuthorizationStrategy.CHANGE_AUDITING)); assertThatExceptionOfType(AccessDeniedException.class).isThrownBy( () -> aclAuthorizationStrategy.securityCheck(aclFirstDeny, AclAuthorizationStrategy.CHANGE_OWNERSHIP)); // Add granting access to this principal aclFirstDeny.insertAce(1, BasePermission.ADMINISTRATION, new PrincipalSid(auth), true); // and try again for CHANGE_AUDITING - the first ACE's granting flag // (false) will deny this access assertThatExceptionOfType(AccessDeniedException.class).isThrownBy( () -> aclAuthorizationStrategy.securityCheck(aclFirstDeny, AclAuthorizationStrategy.CHANGE_AUDITING)); // Create another ACL and give the principal the ADMINISTRATION // permission, with granting access MutableAcl aclFirstAllow = new AclImpl(identity, 1L, aclAuthorizationStrategy, new ConsoleAuditLogger()); aclFirstAllow.insertAce(0, BasePermission.ADMINISTRATION, new PrincipalSid(auth), true); // The CHANGE_AUDITING test should pass as there is one ACE with // granting access aclAuthorizationStrategy.securityCheck(aclFirstAllow, AclAuthorizationStrategy.CHANGE_AUDITING); // Add a deny ACE and test again for CHANGE_AUDITING aclFirstAllow.insertAce(1, BasePermission.ADMINISTRATION, new PrincipalSid(auth), false); assertThatNoException().isThrownBy( () -> aclAuthorizationStrategy.securityCheck(aclFirstAllow, AclAuthorizationStrategy.CHANGE_AUDITING)); // Create an ACL with no ACE MutableAcl aclNoACE = new AclImpl(identity, 1L, aclAuthorizationStrategy, new ConsoleAuditLogger()); assertThatExceptionOfType(NotFoundException.class).isThrownBy( () -> aclAuthorizationStrategy.securityCheck(aclNoACE, AclAuthorizationStrategy.CHANGE_AUDITING)); // and still grant access for CHANGE_GENERAL assertThatNoException().isThrownBy( () -> aclAuthorizationStrategy.securityCheck(aclNoACE, AclAuthorizationStrategy.CHANGE_GENERAL)); } @Test public void testSecurityCheckWithInheritableACEs() { // Create a simple authentication with ROLE_GENERAL Authentication auth = new TestingAuthenticationToken("user", "password", "ROLE_GENERAL"); auth.setAuthenticated(true); SecurityContextHolder.getContext().setAuthentication(auth); ObjectIdentity identity = new ObjectIdentityImpl(TARGET_CLASS, 100); // Authorization strategy will require a different role for each access AclAuthorizationStrategy aclAuthorizationStrategy = new AclAuthorizationStrategyImpl( new SimpleGrantedAuthority("ROLE_ONE"), new SimpleGrantedAuthority("ROLE_TWO"), new SimpleGrantedAuthority("ROLE_GENERAL")); // Let's give the principal an ADMINISTRATION permission, with granting // access MutableAcl parentAcl = new AclImpl(identity, 1, aclAuthorizationStrategy, new ConsoleAuditLogger()); parentAcl.insertAce(0, BasePermission.ADMINISTRATION, new PrincipalSid(auth), true); MutableAcl childAcl = new AclImpl(identity, 2, aclAuthorizationStrategy, new ConsoleAuditLogger()); // Check against the 'child' acl, which doesn't offer any authorization // rights on CHANGE_OWNERSHIP assertThatExceptionOfType(NotFoundException.class).isThrownBy( () -> aclAuthorizationStrategy.securityCheck(childAcl, AclAuthorizationStrategy.CHANGE_OWNERSHIP)); // Link the child with its parent and test again against the // CHANGE_OWNERSHIP right childAcl.setParent(parentAcl); childAcl.setEntriesInheriting(true); assertThatNoException().isThrownBy( () -> aclAuthorizationStrategy.securityCheck(childAcl, AclAuthorizationStrategy.CHANGE_OWNERSHIP)); // Create a root parent and link it to the middle parent MutableAcl rootParentAcl = new AclImpl(identity, 1, aclAuthorizationStrategy, new ConsoleAuditLogger()); parentAcl = new AclImpl(identity, 1, aclAuthorizationStrategy, new ConsoleAuditLogger()); rootParentAcl.insertAce(0, BasePermission.ADMINISTRATION, new PrincipalSid(auth), true); parentAcl.setEntriesInheriting(true); parentAcl.setParent(rootParentAcl); childAcl.setParent(parentAcl); assertThatNoException().isThrownBy( () -> aclAuthorizationStrategy.securityCheck(childAcl, AclAuthorizationStrategy.CHANGE_OWNERSHIP)); } @Test public void testSecurityCheckPrincipalOwner() { Authentication auth = new TestingAuthenticationToken("user", "password", "ROLE_ONE"); auth.setAuthenticated(true); SecurityContextHolder.getContext().setAuthentication(auth); ObjectIdentity identity = new ObjectIdentityImpl(TARGET_CLASS, 100); AclAuthorizationStrategy aclAuthorizationStrategy = new AclAuthorizationStrategyImpl( new SimpleGrantedAuthority("ROLE_OWNERSHIP"), new SimpleGrantedAuthority("ROLE_AUDITING"), new SimpleGrantedAuthority("ROLE_GENERAL")); Acl acl = new AclImpl(identity, 1, aclAuthorizationStrategy, new DefaultPermissionGrantingStrategy(new ConsoleAuditLogger()), null, null, false, new PrincipalSid(auth)); assertThatNoException() .isThrownBy(() -> aclAuthorizationStrategy.securityCheck(acl, AclAuthorizationStrategy.CHANGE_GENERAL)); assertThatExceptionOfType(NotFoundException.class) .isThrownBy(() -> aclAuthorizationStrategy.securityCheck(acl, AclAuthorizationStrategy.CHANGE_AUDITING)); assertThatNoException() .isThrownBy(() -> aclAuthorizationStrategy.securityCheck(acl, AclAuthorizationStrategy.CHANGE_OWNERSHIP)); } } ================================================ FILE: acl/src/test/java/org/springframework/security/acls/domain/AuditLoggerTests.java ================================================ /* * 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 org.springframework.security.acls.domain; import java.io.ByteArrayOutputStream; import java.io.PrintStream; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.security.acls.model.AccessControlEntry; import org.springframework.security.acls.model.AuditableAccessControlEntry; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; /** * Test class for {@link ConsoleAuditLogger}. * * @author Andrei Stefan */ public class AuditLoggerTests { private PrintStream console; private ByteArrayOutputStream bytes = new ByteArrayOutputStream(); private ConsoleAuditLogger logger; private AuditableAccessControlEntry ace; @BeforeEach public void setUp() { this.logger = new ConsoleAuditLogger(); this.ace = mock(AuditableAccessControlEntry.class); this.console = System.out; System.setOut(new PrintStream(this.bytes)); } @AfterEach public void tearDown() { System.setOut(this.console); this.bytes.reset(); } @Test public void nonAuditableAceIsIgnored() { AccessControlEntry ace = mock(AccessControlEntry.class); this.logger.logIfNeeded(true, ace); assertThat(this.bytes.size()).isZero(); } @Test public void successIsNotLoggedIfAceDoesntRequireSuccessAudit() { given(this.ace.isAuditSuccess()).willReturn(false); this.logger.logIfNeeded(true, this.ace); assertThat(this.bytes.size()).isZero(); } @Test public void successIsLoggedIfAceRequiresSuccessAudit() { given(this.ace.isAuditSuccess()).willReturn(true); this.logger.logIfNeeded(true, this.ace); assertThat(this.bytes.toString()).startsWith("GRANTED due to ACE"); } @Test public void failureIsntLoggedIfAceDoesntRequireFailureAudit() { given(this.ace.isAuditFailure()).willReturn(false); this.logger.logIfNeeded(false, this.ace); assertThat(this.bytes.size()).isZero(); } @Test public void failureIsLoggedIfAceRequiresFailureAudit() { given(this.ace.isAuditFailure()).willReturn(true); this.logger.logIfNeeded(false, this.ace); assertThat(this.bytes.toString()).startsWith("DENIED due to ACE"); } } ================================================ FILE: acl/src/test/java/org/springframework/security/acls/domain/ObjectIdentityImplTests.java ================================================ /* * 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 org.springframework.security.acls.domain; import org.junit.jupiter.api.Test; import org.springframework.security.acls.model.ObjectIdentity; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.assertj.core.api.Assertions.assertThatNoException; /** * Tests for {@link ObjectIdentityImpl}. * * @author Andrei Stefan */ @SuppressWarnings("unused") public class ObjectIdentityImplTests { private static final String DOMAIN_CLASS = "org.springframework.security.acls.domain.ObjectIdentityImplTests$MockIdDomainObject"; @Test public void constructorsRespectRequiredFields() { // Check one-argument constructor required field assertThatIllegalArgumentException().isThrownBy(() -> new ObjectIdentityImpl(null)); // Check String-Serializable constructor required field assertThatIllegalArgumentException().isThrownBy(() -> new ObjectIdentityImpl("", 1L)); // Check Serializable parameter is not null assertThatIllegalArgumentException().isThrownBy(() -> new ObjectIdentityImpl(DOMAIN_CLASS, null)); // The correct way of using String-Serializable constructor assertThatNoException().isThrownBy(() -> new ObjectIdentityImpl(DOMAIN_CLASS, 1L)); // Check the Class-Serializable constructor assertThatIllegalArgumentException().isThrownBy(() -> new ObjectIdentityImpl(MockIdDomainObject.class, null)); } @Test public void gettersReturnExpectedValues() { ObjectIdentity obj = new ObjectIdentityImpl(DOMAIN_CLASS, 1L); assertThat(obj.getIdentifier()).isEqualTo(1L); assertThat(obj.getType()).isEqualTo(MockIdDomainObject.class.getName()); } @Test public void testGetIdMethodConstraints() { // Check the getId() method is present assertThatExceptionOfType(IdentityUnavailableException.class) .isThrownBy(() -> new ObjectIdentityImpl("A_STRING_OBJECT")); // getId() should return a non-null value MockIdDomainObject mockId = new MockIdDomainObject(); assertThatIllegalArgumentException().isThrownBy(() -> new ObjectIdentityImpl(mockId)); // getId() should return a Serializable object mockId.setId(new MockIdDomainObject()); assertThatIllegalArgumentException().isThrownBy(() -> new ObjectIdentityImpl(mockId)); // getId() should return a Serializable object mockId.setId(100L); assertThatNoException().isThrownBy(() -> new ObjectIdentityImpl(mockId)); } @Test public void constructorRejectsInvalidTypeParameter() { assertThatIllegalArgumentException().isThrownBy(() -> new ObjectIdentityImpl("", 1L)); } @Test public void testEquals() { ObjectIdentity obj = new ObjectIdentityImpl(DOMAIN_CLASS, 1L); MockIdDomainObject mockObj = new MockIdDomainObject(); mockObj.setId(1L); String string = "SOME_STRING"; assertThat(string).isNotSameAs(obj); assertThat(obj).isNotNull(); assertThat(obj).isNotEqualTo("DIFFERENT_OBJECT_TYPE"); assertThat(obj).isNotEqualTo(new ObjectIdentityImpl(DOMAIN_CLASS, 2L)); assertThat(obj).isNotEqualTo(new ObjectIdentityImpl( "org.springframework.security.acls.domain.ObjectIdentityImplTests$MockOtherIdDomainObject", 1L)); assertThat(new ObjectIdentityImpl(DOMAIN_CLASS, 1L)).isEqualTo(obj); assertThat(new ObjectIdentityImpl(mockObj)).isEqualTo(obj); } @Test public void hashcodeIsDifferentForDifferentJavaTypes() { ObjectIdentity obj = new ObjectIdentityImpl(Object.class, 1L); ObjectIdentity obj2 = new ObjectIdentityImpl(String.class, 1L); assertThat(obj.hashCode()).isNotEqualTo(obj2.hashCode()); } @Test public void longAndIntegerIdsWithSameValueAreEqualAndHaveSameHashcode() { ObjectIdentity obj = new ObjectIdentityImpl(Object.class, 5L); ObjectIdentity obj2 = new ObjectIdentityImpl(Object.class, 5); assertThat(obj2).isEqualTo(obj); assertThat(obj2.hashCode()).isEqualTo(obj.hashCode()); } @Test public void equalStringIdsAreEqualAndHaveSameHashcode() { ObjectIdentity obj = new ObjectIdentityImpl(Object.class, "1000"); ObjectIdentity obj2 = new ObjectIdentityImpl(Object.class, "1000"); assertThat(obj2).isEqualTo(obj); assertThat(obj2.hashCode()).isEqualTo(obj.hashCode()); } @Test public void stringAndNumericIdsAreNotEqual() { ObjectIdentity obj = new ObjectIdentityImpl(Object.class, "1000"); ObjectIdentity obj2 = new ObjectIdentityImpl(Object.class, 1000L); assertThat(obj).isNotEqualTo(obj2); } private class MockIdDomainObject { private Object id; public Object getId() { return this.id; } public void setId(Object id) { this.id = id; } } private class MockOtherIdDomainObject { private Object id; public Object getId() { return this.id; } public void setId(Object id) { this.id = id; } } } ================================================ FILE: acl/src/test/java/org/springframework/security/acls/domain/ObjectIdentityRetrievalStrategyImplTests.java ================================================ /* * 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 org.springframework.security.acls.domain; import org.junit.jupiter.api.Test; import org.springframework.security.acls.model.ObjectIdentity; import org.springframework.security.acls.model.ObjectIdentityRetrievalStrategy; import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link ObjectIdentityRetrievalStrategyImpl} * * @author Andrei Stefan */ public class ObjectIdentityRetrievalStrategyImplTests { @Test public void testObjectIdentityCreation() { MockIdDomainObject domain = new MockIdDomainObject(); domain.setId(1); ObjectIdentityRetrievalStrategy retStrategy = new ObjectIdentityRetrievalStrategyImpl(); ObjectIdentity identity = retStrategy.getObjectIdentity(domain); assertThat(identity).isNotNull(); assertThat(new ObjectIdentityImpl(domain)).isEqualTo(identity); } @SuppressWarnings("unused") private class MockIdDomainObject { private Object id; public Object getId() { return this.id; } public void setId(Object id) { this.id = id; } } } ================================================ FILE: acl/src/test/java/org/springframework/security/acls/domain/PermissionTests.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.acls.domain; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.security.acls.model.Permission; import static org.assertj.core.api.Assertions.assertThat; /** * Tests classes associated with Permission. * * @author Ben Alex */ public class PermissionTests { private DefaultPermissionFactory permissionFactory; @BeforeEach public void createPermissionfactory() { this.permissionFactory = new DefaultPermissionFactory(); } @Test public void basePermissionTest() { Permission p = this.permissionFactory.buildFromName("WRITE"); assertThat(p).isNotNull(); } @Test public void expectedIntegerValues() { assertThat(BasePermission.READ.getMask()).isEqualTo(1); assertThat(BasePermission.ADMINISTRATION.getMask()).isEqualTo(16); assertThat(new CumulativePermission().set(BasePermission.READ) .set(BasePermission.WRITE) .set(BasePermission.CREATE) .getMask()).isEqualTo(7); assertThat(new CumulativePermission().set(BasePermission.READ).set(BasePermission.ADMINISTRATION).getMask()) .isEqualTo(17); } @Test public void fromInteger() { Permission permission = this.permissionFactory.buildFromMask(7); permission = this.permissionFactory.buildFromMask(4); } @Test public void stringConversion() { this.permissionFactory.registerPublicPermissions(SpecialPermission.class); assertThat(BasePermission.READ.toString()).isEqualTo("BasePermission[...............................R=1]"); assertThat(BasePermission.ADMINISTRATION.toString()) .isEqualTo("BasePermission[...........................A....=16]"); assertThat(new CumulativePermission().set(BasePermission.READ).toString()) .isEqualTo("CumulativePermission[...............................R=1]"); assertThat( new CumulativePermission().set(SpecialPermission.ENTER).set(BasePermission.ADMINISTRATION).toString()) .isEqualTo("CumulativePermission[..........................EA....=48]"); assertThat(new CumulativePermission().set(BasePermission.ADMINISTRATION).set(BasePermission.READ).toString()) .isEqualTo("CumulativePermission[...........................A...R=17]"); assertThat(new CumulativePermission().set(BasePermission.ADMINISTRATION) .set(BasePermission.READ) .clear(BasePermission.ADMINISTRATION) .toString()).isEqualTo("CumulativePermission[...............................R=1]"); assertThat(new CumulativePermission().set(BasePermission.ADMINISTRATION) .set(BasePermission.READ) .clear(BasePermission.ADMINISTRATION) .clear(BasePermission.READ) .toString()).isEqualTo("CumulativePermission[................................=0]"); } } ================================================ FILE: acl/src/test/java/org/springframework/security/acls/domain/SpecialPermission.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.acls.domain; import org.springframework.security.acls.model.Permission; /** * A test permission. * * @author Ben Alex */ public class SpecialPermission extends BasePermission { public static final Permission ENTER = new SpecialPermission(1 << 5, 'E'); // 32 public static final Permission LEAVE = new SpecialPermission(1 << 6, 'L'); protected SpecialPermission(int mask, char code) { super(mask, code); } } ================================================ FILE: acl/src/test/java/org/springframework/security/acls/jdbc/AbstractBasicLookupStrategyTests.java ================================================ /* * 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 org.springframework.security.acls.jdbc; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.UUID; import javax.sql.DataSource; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.cache.Cache; import org.springframework.cache.CacheManager; import org.springframework.cache.concurrent.ConcurrentMapCache; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.security.acls.TargetObject; import org.springframework.security.acls.TargetObjectWithUUID; import org.springframework.security.acls.domain.AclAuthorizationStrategy; import org.springframework.security.acls.domain.AclAuthorizationStrategyImpl; import org.springframework.security.acls.domain.BasePermission; import org.springframework.security.acls.domain.ConsoleAuditLogger; import org.springframework.security.acls.domain.DefaultPermissionFactory; import org.springframework.security.acls.domain.DefaultPermissionGrantingStrategy; import org.springframework.security.acls.domain.GrantedAuthoritySid; import org.springframework.security.acls.domain.ObjectIdentityImpl; import org.springframework.security.acls.domain.PrincipalSid; import org.springframework.security.acls.domain.SpringCacheBasedAclCache; import org.springframework.security.acls.model.Acl; import org.springframework.security.acls.model.AuditableAccessControlEntry; import org.springframework.security.acls.model.MutableAcl; import org.springframework.security.acls.model.ObjectIdentity; import org.springframework.security.acls.model.Permission; import org.springframework.security.acls.model.Sid; import org.springframework.security.core.authority.SimpleGrantedAuthority; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; /** * Tests {@link BasicLookupStrategy} * * @author Andrei Stefan */ public abstract class AbstractBasicLookupStrategyTests { protected static final Sid BEN_SID = new PrincipalSid("ben"); protected static final String TARGET_CLASS = TargetObject.class.getName(); protected static final String TARGET_CLASS_WITH_UUID = TargetObjectWithUUID.class.getName(); protected static final UUID OBJECT_IDENTITY_UUID = UUID.randomUUID(); protected static final Long OBJECT_IDENTITY_LONG_AS_UUID = 110L; private BasicLookupStrategy strategy; private static CacheManagerMock cacheManager; public abstract JdbcTemplate getJdbcTemplate(); public abstract DataSource getDataSource(); @BeforeAll public static void initCacheManaer() { cacheManager = new CacheManagerMock(); cacheManager.addCache("basiclookuptestcache"); } @AfterAll public static void shutdownCacheManager() { cacheManager.clear(); } @BeforeEach public void populateDatabase() { String query = "INSERT INTO acl_sid(ID,PRINCIPAL,SID) VALUES (1,1,'ben');" + "INSERT INTO acl_class(ID,CLASS) VALUES (2,'" + TARGET_CLASS + "');" + "INSERT INTO acl_object_identity(ID,OBJECT_ID_CLASS,OBJECT_ID_IDENTITY,PARENT_OBJECT,OWNER_SID,ENTRIES_INHERITING) VALUES (1,2,100,null,1,1);" + "INSERT INTO acl_object_identity(ID,OBJECT_ID_CLASS,OBJECT_ID_IDENTITY,PARENT_OBJECT,OWNER_SID,ENTRIES_INHERITING) VALUES (2,2,101,1,1,1);" + "INSERT INTO acl_object_identity(ID,OBJECT_ID_CLASS,OBJECT_ID_IDENTITY,PARENT_OBJECT,OWNER_SID,ENTRIES_INHERITING) VALUES (3,2,102,2,1,1);" + "INSERT INTO acl_entry(ID,ACL_OBJECT_IDENTITY,ACE_ORDER,SID,MASK,GRANTING,AUDIT_SUCCESS,AUDIT_FAILURE) VALUES (1,1,0,1,1,1,0,0);" + "INSERT INTO acl_entry(ID,ACL_OBJECT_IDENTITY,ACE_ORDER,SID,MASK,GRANTING,AUDIT_SUCCESS,AUDIT_FAILURE) VALUES (2,1,1,1,2,0,0,0);" + "INSERT INTO acl_entry(ID,ACL_OBJECT_IDENTITY,ACE_ORDER,SID,MASK,GRANTING,AUDIT_SUCCESS,AUDIT_FAILURE) VALUES (3,2,0,1,8,1,0,0);" + "INSERT INTO acl_entry(ID,ACL_OBJECT_IDENTITY,ACE_ORDER,SID,MASK,GRANTING,AUDIT_SUCCESS,AUDIT_FAILURE) VALUES (4,3,0,1,8,0,0,0);"; getJdbcTemplate().execute(query); } @BeforeEach public void initializeBeans() { this.strategy = new BasicLookupStrategy(getDataSource(), aclCache(), aclAuthStrategy(), new DefaultPermissionGrantingStrategy(new ConsoleAuditLogger())); this.strategy.setPermissionFactory(new DefaultPermissionFactory()); } protected AclAuthorizationStrategy aclAuthStrategy() { return new AclAuthorizationStrategyImpl(new SimpleGrantedAuthority("ROLE_ADMINISTRATOR")); } protected SpringCacheBasedAclCache aclCache() { return new SpringCacheBasedAclCache(getCache(), new DefaultPermissionGrantingStrategy(new ConsoleAuditLogger()), new AclAuthorizationStrategyImpl(new SimpleGrantedAuthority("ROLE_USER"))); } protected Cache getCache() { Cache cache = cacheManager.getCacheManager().getCache("basiclookuptestcache"); cache.clear(); return cache; } @AfterEach public void emptyDatabase() { String query = "DELETE FROM acl_entry;" + "DELETE FROM acl_object_identity WHERE ID = 9;" + "DELETE FROM acl_object_identity WHERE ID = 8;" + "DELETE FROM acl_object_identity WHERE ID = 7;" + "DELETE FROM acl_object_identity WHERE ID = 6;" + "DELETE FROM acl_object_identity WHERE ID = 5;" + "DELETE FROM acl_object_identity WHERE ID = 4;" + "DELETE FROM acl_object_identity WHERE ID = 3;" + "DELETE FROM acl_object_identity WHERE ID = 2;" + "DELETE FROM acl_object_identity WHERE ID = 1;" + "DELETE FROM acl_class;" + "DELETE FROM acl_sid;"; getJdbcTemplate().execute(query); } @Test public void testAclsRetrievalWithDefaultBatchSize() throws Exception { ObjectIdentity topParentOid = new ObjectIdentityImpl(TARGET_CLASS, 100L); ObjectIdentity middleParentOid = new ObjectIdentityImpl(TARGET_CLASS, 101L); // Deliberately use an integer for the child, to reproduce bug report in SEC-819 ObjectIdentity childOid = new ObjectIdentityImpl(TARGET_CLASS, 102); Map map = this.strategy .readAclsById(Arrays.asList(topParentOid, middleParentOid, childOid), null); checkEntries(topParentOid, middleParentOid, childOid, map); } @Test public void testAclsRetrievalFromCacheOnly() throws Exception { ObjectIdentity topParentOid = new ObjectIdentityImpl(TARGET_CLASS, 100); ObjectIdentity middleParentOid = new ObjectIdentityImpl(TARGET_CLASS, 101L); ObjectIdentity childOid = new ObjectIdentityImpl(TARGET_CLASS, 102L); // Objects were put in cache this.strategy.readAclsById(Arrays.asList(topParentOid, middleParentOid, childOid), null); // Let's empty the database to force acls retrieval from cache emptyDatabase(); Map map = this.strategy .readAclsById(Arrays.asList(topParentOid, middleParentOid, childOid), null); checkEntries(topParentOid, middleParentOid, childOid, map); } @Test public void testAclsRetrievalWithCustomBatchSize() throws Exception { ObjectIdentity topParentOid = new ObjectIdentityImpl(TARGET_CLASS, 100L); ObjectIdentity middleParentOid = new ObjectIdentityImpl(TARGET_CLASS, 101); ObjectIdentity childOid = new ObjectIdentityImpl(TARGET_CLASS, 102L); // Set a batch size to allow multiple database queries in order to retrieve all // acls this.strategy.setBatchSize(1); Map map = this.strategy .readAclsById(Arrays.asList(topParentOid, middleParentOid, childOid), null); checkEntries(topParentOid, middleParentOid, childOid, map); } private void checkEntries(ObjectIdentity topParentOid, ObjectIdentity middleParentOid, ObjectIdentity childOid, Map map) { assertThat(map).hasSize(3); MutableAcl topParent = (MutableAcl) map.get(topParentOid); MutableAcl middleParent = (MutableAcl) map.get(middleParentOid); MutableAcl child = (MutableAcl) map.get(childOid); // Check the retrieved versions has IDs assertThat(topParent.getId()).isNotNull(); assertThat(middleParent.getId()).isNotNull(); assertThat(child.getId()).isNotNull(); // Check their parents were correctly retrieved assertThat(topParent.getParentAcl()).isNull(); assertThat(middleParent.getParentAcl().getObjectIdentity()).isEqualTo(topParentOid); assertThat(child.getParentAcl().getObjectIdentity()).isEqualTo(middleParentOid); // Check their ACEs were correctly retrieved assertThat(topParent.getEntries()).hasSize(2); assertThat(middleParent.getEntries()).hasSize(1); assertThat(child.getEntries()).hasSize(1); // Check object identities were correctly retrieved assertThat(topParent.getObjectIdentity()).isEqualTo(topParentOid); assertThat(middleParent.getObjectIdentity()).isEqualTo(middleParentOid); assertThat(child.getObjectIdentity()).isEqualTo(childOid); // Check each entry assertThat(topParent.isEntriesInheriting()).isTrue(); assertThat(Long.valueOf(1)).isEqualTo(topParent.getId()); assertThat(new PrincipalSid("ben")).isEqualTo(topParent.getOwner()); assertThat(Long.valueOf(1)).isEqualTo(topParent.getEntries().get(0).getId()); assertThat(topParent.getEntries().get(0).getPermission()).isEqualTo(BasePermission.READ); assertThat(topParent.getEntries().get(0).getSid()).isEqualTo(new PrincipalSid("ben")); assertThat(((AuditableAccessControlEntry) topParent.getEntries().get(0)).isAuditFailure()).isFalse(); assertThat(((AuditableAccessControlEntry) topParent.getEntries().get(0)).isAuditSuccess()).isFalse(); assertThat((topParent.getEntries().get(0)).isGranting()).isTrue(); assertThat(Long.valueOf(2)).isEqualTo(topParent.getEntries().get(1).getId()); assertThat(topParent.getEntries().get(1).getPermission()).isEqualTo(BasePermission.WRITE); assertThat(topParent.getEntries().get(1).getSid()).isEqualTo(new PrincipalSid("ben")); assertThat(((AuditableAccessControlEntry) topParent.getEntries().get(1)).isAuditFailure()).isFalse(); assertThat(((AuditableAccessControlEntry) topParent.getEntries().get(1)).isAuditSuccess()).isFalse(); assertThat(topParent.getEntries().get(1).isGranting()).isFalse(); assertThat(middleParent.isEntriesInheriting()).isTrue(); assertThat(Long.valueOf(2)).isEqualTo(middleParent.getId()); assertThat(new PrincipalSid("ben")).isEqualTo(middleParent.getOwner()); assertThat(Long.valueOf(3)).isEqualTo(middleParent.getEntries().get(0).getId()); assertThat(middleParent.getEntries().get(0).getPermission()).isEqualTo(BasePermission.DELETE); assertThat(middleParent.getEntries().get(0).getSid()).isEqualTo(new PrincipalSid("ben")); assertThat(((AuditableAccessControlEntry) middleParent.getEntries().get(0)).isAuditFailure()).isFalse(); assertThat(((AuditableAccessControlEntry) middleParent.getEntries().get(0)).isAuditSuccess()).isFalse(); assertThat(middleParent.getEntries().get(0).isGranting()).isTrue(); assertThat(child.isEntriesInheriting()).isTrue(); assertThat(Long.valueOf(3)).isEqualTo(child.getId()); assertThat(new PrincipalSid("ben")).isEqualTo(child.getOwner()); assertThat(Long.valueOf(4)).isEqualTo(child.getEntries().get(0).getId()); assertThat(child.getEntries().get(0).getPermission()).isEqualTo(BasePermission.DELETE); assertThat(new PrincipalSid("ben")).isEqualTo(child.getEntries().get(0).getSid()); assertThat(((AuditableAccessControlEntry) child.getEntries().get(0)).isAuditFailure()).isFalse(); assertThat(((AuditableAccessControlEntry) child.getEntries().get(0)).isAuditSuccess()).isFalse(); assertThat((child.getEntries().get(0)).isGranting()).isFalse(); } @Test public void testAllParentsAreRetrievedWhenChildIsLoaded() { String query = "INSERT INTO acl_object_identity(ID,OBJECT_ID_CLASS,OBJECT_ID_IDENTITY,PARENT_OBJECT,OWNER_SID,ENTRIES_INHERITING) VALUES (6,2,103,1,1,1);"; getJdbcTemplate().execute(query); ObjectIdentity topParentOid = new ObjectIdentityImpl(TARGET_CLASS, 100L); ObjectIdentity middleParentOid = new ObjectIdentityImpl(TARGET_CLASS, 101L); ObjectIdentity childOid = new ObjectIdentityImpl(TARGET_CLASS, 102L); ObjectIdentity middleParent2Oid = new ObjectIdentityImpl(TARGET_CLASS, 103L); // Retrieve the child Map map = this.strategy.readAclsById(Arrays.asList(childOid), null); // Check that the child and all its parents were retrieved assertThat(map.get(childOid)).isNotNull(); assertThat(map.get(childOid).getObjectIdentity()).isEqualTo(childOid); assertThat(map.get(middleParentOid)).isNotNull(); assertThat(map.get(middleParentOid).getObjectIdentity()).isEqualTo(middleParentOid); assertThat(map.get(topParentOid)).isNotNull(); assertThat(map.get(topParentOid).getObjectIdentity()).isEqualTo(topParentOid); // The second parent shouldn't have been retrieved assertThat(map.get(middleParent2Oid)).isNull(); } /** * Test created from SEC-590. */ @Test public void testReadAllObjectIdentitiesWhenLastElementIsAlreadyCached() { String query = "INSERT INTO acl_object_identity(ID,OBJECT_ID_CLASS,OBJECT_ID_IDENTITY,PARENT_OBJECT,OWNER_SID,ENTRIES_INHERITING) VALUES (6,2,105,null,1,1);" + "INSERT INTO acl_object_identity(ID,OBJECT_ID_CLASS,OBJECT_ID_IDENTITY,PARENT_OBJECT,OWNER_SID,ENTRIES_INHERITING) VALUES (7,2,106,6,1,1);" + "INSERT INTO acl_object_identity(ID,OBJECT_ID_CLASS,OBJECT_ID_IDENTITY,PARENT_OBJECT,OWNER_SID,ENTRIES_INHERITING) VALUES (8,2,107,6,1,1);" + "INSERT INTO acl_object_identity(ID,OBJECT_ID_CLASS,OBJECT_ID_IDENTITY,PARENT_OBJECT,OWNER_SID,ENTRIES_INHERITING) VALUES (9,2,108,7,1,1);" + "INSERT INTO acl_entry(ID,ACL_OBJECT_IDENTITY,ACE_ORDER,SID,MASK,GRANTING,AUDIT_SUCCESS,AUDIT_FAILURE) VALUES (7,6,0,1,1,1,0,0)"; getJdbcTemplate().execute(query); ObjectIdentity grandParentOid = new ObjectIdentityImpl(TARGET_CLASS, 104L); ObjectIdentity parent1Oid = new ObjectIdentityImpl(TARGET_CLASS, 105L); ObjectIdentity parent2Oid = new ObjectIdentityImpl(TARGET_CLASS, 106); ObjectIdentity childOid = new ObjectIdentityImpl(TARGET_CLASS, 107); // First lookup only child, thus populating the cache with grandParent, // parent1 // and child List checkPermission = Arrays.asList(BasePermission.READ); List sids = Arrays.asList(BEN_SID); List childOids = Arrays.asList(childOid); this.strategy.setBatchSize(6); Map foundAcls = this.strategy.readAclsById(childOids, sids); Acl foundChildAcl = foundAcls.get(childOid); assertThat(foundChildAcl).isNotNull(); assertThat(foundChildAcl.isGranted(checkPermission, sids, false)).isTrue(); // Search for object identities has to be done in the following order: // last // element have to be one which // is already in cache and the element before it must not be stored in // cache List allOids = Arrays.asList(grandParentOid, parent1Oid, parent2Oid, childOid); foundAcls = this.strategy.readAclsById(allOids, sids); Acl foundParent2Acl = foundAcls.get(parent2Oid); assertThat(foundParent2Acl).isNotNull(); assertThat(foundParent2Acl.isGranted(checkPermission, sids, false)).isTrue(); } @Test public void nullOwnerIsNotSupported() { String query = "INSERT INTO acl_object_identity(ID,OBJECT_ID_CLASS,OBJECT_ID_IDENTITY,PARENT_OBJECT,OWNER_SID,ENTRIES_INHERITING) VALUES (6,2,104,null,null,1);"; getJdbcTemplate().execute(query); ObjectIdentity oid = new ObjectIdentityImpl(TARGET_CLASS, 104L); assertThatIllegalArgumentException() .isThrownBy(() -> this.strategy.readAclsById(Arrays.asList(oid), Arrays.asList(BEN_SID))); } @Test public void testCreatePrincipalSid() { Sid result = this.strategy.createSid(true, "sid"); assertThat(result.getClass()).isEqualTo(PrincipalSid.class); assertThat(((PrincipalSid) result).getPrincipal()).isEqualTo("sid"); } @Test public void testCreateGrantedAuthority() { Sid result = this.strategy.createSid(false, "sid"); assertThat(result.getClass()).isEqualTo(GrantedAuthoritySid.class); assertThat(((GrantedAuthoritySid) result).getGrantedAuthority()).isEqualTo("sid"); } @Test public void setObjectIdentityGeneratorWhenNullThenThrowsIllegalArgumentException() { // @formatter:off assertThatIllegalArgumentException() .isThrownBy(() -> this.strategy.setObjectIdentityGenerator(null)) .withMessage("objectIdentityGenerator cannot be null"); // @formatter:on } private static final class CacheManagerMock { private final List cacheNames; private final CacheManager cacheManager; private CacheManagerMock() { this.cacheNames = new ArrayList<>(); this.cacheManager = mock(CacheManager.class); given(this.cacheManager.getCacheNames()).willReturn(this.cacheNames); } private CacheManager getCacheManager() { return this.cacheManager; } private void addCache(String name) { this.cacheNames.add(name); Cache cache = new ConcurrentMapCache(name); given(this.cacheManager.getCache(name)).willReturn(cache); } private void clear() { this.cacheNames.clear(); } } } ================================================ FILE: acl/src/test/java/org/springframework/security/acls/jdbc/AclClassIdUtilsTests.java ================================================ /* * 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 org.springframework.security.acls.jdbc; import java.io.Serializable; import java.math.BigInteger; import java.sql.ResultSet; import java.sql.SQLException; import java.util.UUID; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.core.convert.ConversionService; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.mockito.BDDMockito.given; /** * Tests for {@link AclClassIdUtils}. * * @author paulwheeler */ @ExtendWith(MockitoExtension.class) public class AclClassIdUtilsTests { private static final Long DEFAULT_IDENTIFIER = 999L; private static final BigInteger BIGINT_IDENTIFIER = new BigInteger("999"); private static final String DEFAULT_IDENTIFIER_AS_STRING = DEFAULT_IDENTIFIER.toString(); @Mock private ResultSet resultSet; @Mock private ConversionService conversionService; private AclClassIdUtils aclClassIdUtils; @BeforeEach public void setUp() { this.aclClassIdUtils = new AclClassIdUtils(); } @Test public void shouldReturnLongIfIdentifierIsLong() throws SQLException { Serializable newIdentifier = this.aclClassIdUtils.identifierFrom(DEFAULT_IDENTIFIER, this.resultSet); assertThat(newIdentifier).isEqualTo(DEFAULT_IDENTIFIER); } @Test public void shouldReturnLongIfIdentifierIsBigInteger() throws SQLException { Serializable newIdentifier = this.aclClassIdUtils.identifierFrom(BIGINT_IDENTIFIER, this.resultSet); assertThat(newIdentifier).isEqualTo(DEFAULT_IDENTIFIER); } @Test public void shouldReturnLongIfClassIdTypeIsNull() throws SQLException { given(this.resultSet.getString("class_id_type")).willReturn(null); Serializable newIdentifier = this.aclClassIdUtils.identifierFrom(DEFAULT_IDENTIFIER_AS_STRING, this.resultSet); assertThat(newIdentifier).isEqualTo(DEFAULT_IDENTIFIER); } @Test public void shouldReturnLongIfNoClassIdTypeColumn() throws SQLException { given(this.resultSet.getString("class_id_type")).willThrow(SQLException.class); Serializable newIdentifier = this.aclClassIdUtils.identifierFrom(DEFAULT_IDENTIFIER_AS_STRING, this.resultSet); assertThat(newIdentifier).isEqualTo(DEFAULT_IDENTIFIER); } @Test public void shouldReturnLongIfTypeClassNotFound() throws SQLException { given(this.resultSet.getString("class_id_type")).willReturn("com.example.UnknownType"); Serializable newIdentifier = this.aclClassIdUtils.identifierFrom(DEFAULT_IDENTIFIER_AS_STRING, this.resultSet); assertThat(newIdentifier).isEqualTo(DEFAULT_IDENTIFIER); } @Test public void shouldReturnLongEvenIfCustomConversionServiceDoesNotSupportLongConversion() throws SQLException { given(this.resultSet.getString("class_id_type")).willReturn("java.lang.Long"); given(this.conversionService.canConvert(String.class, Long.class)).willReturn(false); this.aclClassIdUtils.setConversionService(this.conversionService); Serializable newIdentifier = this.aclClassIdUtils.identifierFrom(DEFAULT_IDENTIFIER_AS_STRING, this.resultSet); assertThat(newIdentifier).isEqualTo(DEFAULT_IDENTIFIER); } @Test public void shouldReturnLongWhenLongClassIdType() throws SQLException { given(this.resultSet.getString("class_id_type")).willReturn("java.lang.Long"); Serializable newIdentifier = this.aclClassIdUtils.identifierFrom(DEFAULT_IDENTIFIER_AS_STRING, this.resultSet); assertThat(newIdentifier).isEqualTo(DEFAULT_IDENTIFIER); } @Test public void shouldReturnUUIDWhenUUIDClassIdType() throws SQLException { UUID identifier = UUID.randomUUID(); given(this.resultSet.getString("class_id_type")).willReturn("java.util.UUID"); Serializable newIdentifier = this.aclClassIdUtils.identifierFrom(identifier.toString(), this.resultSet); assertThat(newIdentifier).isEqualTo(identifier); } @Test public void shouldReturnStringWhenStringClassIdType() throws SQLException { String identifier = "MY_STRING_IDENTIFIER"; given(this.resultSet.getString("class_id_type")).willReturn("java.lang.String"); Serializable newIdentifier = this.aclClassIdUtils.identifierFrom(identifier, this.resultSet); assertThat(newIdentifier).isEqualTo(identifier); } @Test public void shouldNotAcceptNullConversionServiceInConstruction() { assertThatIllegalArgumentException().isThrownBy(() -> new AclClassIdUtils(null)); } @Test public void shouldNotAcceptNullConversionServiceInSetter() { assertThatIllegalArgumentException().isThrownBy(() -> this.aclClassIdUtils.setConversionService(null)); } } ================================================ FILE: acl/src/test/java/org/springframework/security/acls/jdbc/BasicLookupStrategyTests.java ================================================ /* * 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 org.springframework.security.acls.jdbc; import javax.sql.DataSource; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.springframework.jdbc.core.JdbcTemplate; /** * Tests {@link BasicLookupStrategy} with Acl Class type id not specified. * * @author Andrei Stefan * @author Paul Wheeler */ public class BasicLookupStrategyTests extends AbstractBasicLookupStrategyTests { private static final BasicLookupStrategyTestsDbHelper DATABASE_HELPER = new BasicLookupStrategyTestsDbHelper(); @BeforeAll public static void createDatabase() throws Exception { DATABASE_HELPER.createDatabase(); } @AfterAll public static void dropDatabase() { DATABASE_HELPER.getDataSource().destroy(); } @Override public JdbcTemplate getJdbcTemplate() { return DATABASE_HELPER.getJdbcTemplate(); } @Override public DataSource getDataSource() { return DATABASE_HELPER.getDataSource(); } } ================================================ FILE: acl/src/test/java/org/springframework/security/acls/jdbc/BasicLookupStrategyTestsDbHelper.java ================================================ /* * 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 org.springframework.security.acls.jdbc; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.datasource.SingleConnectionDataSource; import org.springframework.util.FileCopyUtils; /** * Helper class to initialize the database for BasicLookupStrategyTests. * * @author Andrei Stefan * @author Paul Wheeler */ public class BasicLookupStrategyTestsDbHelper { private static final String ACL_SCHEMA_SQL_FILE = "createAclSchema.sql"; private static final String ACL_SCHEMA_SQL_FILE_WITH_ACL_CLASS_ID = "createAclSchemaWithAclClassIdType.sql"; private SingleConnectionDataSource dataSource; private JdbcTemplate jdbcTemplate; private boolean withAclClassIdType; public BasicLookupStrategyTestsDbHelper() { } public BasicLookupStrategyTestsDbHelper(boolean withAclClassIdType) { this.withAclClassIdType = withAclClassIdType; } public void createDatabase() throws Exception { // Use a different connection url so the tests can run in parallel String connectionUrl; String sqlClassPathResource; if (!this.withAclClassIdType) { connectionUrl = "jdbc:hsqldb:mem:lookupstrategytest"; sqlClassPathResource = ACL_SCHEMA_SQL_FILE; } else { connectionUrl = "jdbc:hsqldb:mem:lookupstrategytestWithAclClassIdType"; sqlClassPathResource = ACL_SCHEMA_SQL_FILE_WITH_ACL_CLASS_ID; } this.dataSource = new SingleConnectionDataSource(connectionUrl, "sa", "", true); this.dataSource.setDriverClassName("org.hsqldb.jdbcDriver"); this.jdbcTemplate = new JdbcTemplate(this.dataSource); Resource resource = new ClassPathResource(sqlClassPathResource); String sql = new String(FileCopyUtils.copyToByteArray(resource.getInputStream())); this.jdbcTemplate.execute(sql); } public JdbcTemplate getJdbcTemplate() { return this.jdbcTemplate; } public SingleConnectionDataSource getDataSource() { return this.dataSource; } } ================================================ FILE: acl/src/test/java/org/springframework/security/acls/jdbc/BasicLookupStrategyWithAclClassTypeTests.java ================================================ /* * 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 org.springframework.security.acls.jdbc; import java.util.Arrays; import java.util.Map; import javax.sql.DataSource; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.core.convert.ConversionFailedException; import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.security.acls.domain.ConsoleAuditLogger; import org.springframework.security.acls.domain.DefaultPermissionFactory; import org.springframework.security.acls.domain.DefaultPermissionGrantingStrategy; import org.springframework.security.acls.domain.ObjectIdentityImpl; import org.springframework.security.acls.model.Acl; import org.springframework.security.acls.model.ObjectIdentity; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; /** * Tests {@link BasicLookupStrategy} with Acl Class type id set to UUID. * * @author Paul Wheeler */ public class BasicLookupStrategyWithAclClassTypeTests extends AbstractBasicLookupStrategyTests { private static final BasicLookupStrategyTestsDbHelper DATABASE_HELPER = new BasicLookupStrategyTestsDbHelper(true); private BasicLookupStrategy uuidEnabledStrategy; @Override public JdbcTemplate getJdbcTemplate() { return DATABASE_HELPER.getJdbcTemplate(); } @Override public DataSource getDataSource() { return DATABASE_HELPER.getDataSource(); } @BeforeAll public static void createDatabase() throws Exception { DATABASE_HELPER.createDatabase(); } @AfterAll public static void dropDatabase() { DATABASE_HELPER.getDataSource().destroy(); } @Override @BeforeEach public void initializeBeans() { super.initializeBeans(); this.uuidEnabledStrategy = new BasicLookupStrategy(getDataSource(), aclCache(), aclAuthStrategy(), new DefaultPermissionGrantingStrategy(new ConsoleAuditLogger())); this.uuidEnabledStrategy.setPermissionFactory(new DefaultPermissionFactory()); this.uuidEnabledStrategy.setAclClassIdSupported(true); this.uuidEnabledStrategy.setConversionService(new DefaultConversionService()); } @BeforeEach public void populateDatabaseForAclClassTypeTests() { String query = "INSERT INTO acl_class(ID,CLASS,CLASS_ID_TYPE) VALUES (3,'" + TARGET_CLASS_WITH_UUID + "', 'java.util.UUID');" + "INSERT INTO acl_object_identity(ID,OBJECT_ID_CLASS,OBJECT_ID_IDENTITY,PARENT_OBJECT,OWNER_SID,ENTRIES_INHERITING) VALUES (4,3,'" + OBJECT_IDENTITY_UUID.toString() + "',null,1,1);" + "INSERT INTO acl_object_identity(ID,OBJECT_ID_CLASS,OBJECT_ID_IDENTITY,PARENT_OBJECT,OWNER_SID,ENTRIES_INHERITING) VALUES (5,3,'" + OBJECT_IDENTITY_LONG_AS_UUID + "',null,1,1);" + "INSERT INTO acl_entry(ID,ACL_OBJECT_IDENTITY,ACE_ORDER,SID,MASK,GRANTING,AUDIT_SUCCESS,AUDIT_FAILURE) VALUES (5,4,0,1,8,0,0,0);" + "INSERT INTO acl_entry(ID,ACL_OBJECT_IDENTITY,ACE_ORDER,SID,MASK,GRANTING,AUDIT_SUCCESS,AUDIT_FAILURE) VALUES (6,5,0,1,8,0,0,0);"; DATABASE_HELPER.getJdbcTemplate().execute(query); } @Test public void testReadObjectIdentityUsingUuidType() { ObjectIdentity oid = new ObjectIdentityImpl(TARGET_CLASS_WITH_UUID, OBJECT_IDENTITY_UUID); Map foundAcls = this.uuidEnabledStrategy.readAclsById(Arrays.asList(oid), Arrays.asList(BEN_SID)); assertThat(foundAcls).hasSize(1); assertThat(foundAcls.get(oid)).isNotNull(); } @Test public void testReadObjectIdentityUsingLongTypeWithConversionServiceEnabled() { ObjectIdentity oid = new ObjectIdentityImpl(TARGET_CLASS, 100L); Map foundAcls = this.uuidEnabledStrategy.readAclsById(Arrays.asList(oid), Arrays.asList(BEN_SID)); assertThat(foundAcls).hasSize(1); assertThat(foundAcls.get(oid)).isNotNull(); } @Test public void testReadObjectIdentityUsingNonUuidInDatabase() { ObjectIdentity oid = new ObjectIdentityImpl(TARGET_CLASS_WITH_UUID, OBJECT_IDENTITY_LONG_AS_UUID); assertThatExceptionOfType(ConversionFailedException.class) .isThrownBy(() -> this.uuidEnabledStrategy.readAclsById(Arrays.asList(oid), Arrays.asList(BEN_SID))); } } ================================================ FILE: acl/src/test/java/org/springframework/security/acls/jdbc/DatabaseSeeder.java ================================================ /* * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited * * 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 org.springframework.security.acls.jdbc; import java.io.IOException; import javax.sql.DataSource; import org.springframework.core.io.Resource; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.util.Assert; import org.springframework.util.FileCopyUtils; /** * Seeds the database for {@link JdbcMutableAclServiceTests}. * * @author Ben Alex */ public class DatabaseSeeder { public DatabaseSeeder(DataSource dataSource, Resource resource) throws IOException { Assert.notNull(dataSource, "dataSource required"); Assert.notNull(resource, "resource required"); JdbcTemplate template = new JdbcTemplate(dataSource); String sql = new String(FileCopyUtils.copyToByteArray(resource.getInputStream())); template.execute(sql); } } ================================================ FILE: acl/src/test/java/org/springframework/security/acls/jdbc/JdbcAclServiceTests.java ================================================ /* * 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 org.springframework.security.acls.jdbc; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; import javax.sql.DataSource; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentMatchers; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.jdbc.core.JdbcOperations; import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; import org.springframework.security.acls.domain.ObjectIdentityImpl; import org.springframework.security.acls.domain.PrincipalSid; import org.springframework.security.acls.model.Acl; import org.springframework.security.acls.model.NotFoundException; import org.springframework.security.acls.model.ObjectIdentity; import org.springframework.security.acls.model.Sid; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.mockito.ArgumentMatchers.anyList; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.given; /** * Unit and Integration tests the ACL JdbcAclService using an in-memory database. * * @author Nena Raab */ @ExtendWith(MockitoExtension.class) public class JdbcAclServiceTests { private EmbeddedDatabase embeddedDatabase; @Mock private DataSource dataSource; @Mock private LookupStrategy lookupStrategy; @Mock JdbcOperations jdbcOperations; private JdbcAclService aclServiceIntegration; private JdbcAclService aclService; @BeforeEach public void setUp() { // @formatter:off this.embeddedDatabase = new EmbeddedDatabaseBuilder() .addScript("createAclSchemaWithAclClassIdType.sql") .addScript("db/sql/test_data_hierarchy.sql") .build(); // @formatter:on this.aclService = new JdbcAclService(this.jdbcOperations, this.lookupStrategy); this.aclServiceIntegration = new JdbcAclService(this.embeddedDatabase, this.lookupStrategy); } @AfterEach public void tearDownEmbeddedDatabase() { this.embeddedDatabase.shutdown(); } // SEC-1898 @Test public void readAclByIdMissingAcl() { Map result = new HashMap<>(); given(this.lookupStrategy.readAclsById(anyList(), anyList())).willReturn(result); ObjectIdentity objectIdentity = new ObjectIdentityImpl(Object.class, 1); List sids = Arrays.asList(new PrincipalSid("user")); assertThatExceptionOfType(NotFoundException.class) .isThrownBy(() -> this.aclService.readAclById(objectIdentity, sids)); } @Test public void findOneChildren() { List result = new ArrayList<>(); result.add(new ObjectIdentityImpl(Object.class, "5577")); Object[] args = { "1", "org.springframework.security.acls.jdbc.JdbcAclServiceTests$MockLongIdDomainObject" }; given(this.jdbcOperations.query(anyString(), ArgumentMatchers.>any(), eq(args))) .willReturn(result); ObjectIdentity objectIdentity = new ObjectIdentityImpl(MockLongIdDomainObject.class, 1L); List objectIdentities = this.aclService.findChildren(objectIdentity); assertThat(objectIdentities).hasSize(1); assertThat(objectIdentities.get(0).getIdentifier()).isEqualTo("5577"); } @Test public void findNoChildren() { ObjectIdentity objectIdentity = new ObjectIdentityImpl(MockLongIdDomainObject.class, 1L); List objectIdentities = this.aclService.findChildren(objectIdentity); assertThat(objectIdentities).isNull(); } @Test public void findChildrenWithoutIdType() { ObjectIdentity objectIdentity = new ObjectIdentityImpl(MockLongIdDomainObject.class, 4711L); List objectIdentities = this.aclServiceIntegration.findChildren(objectIdentity); assertThat(objectIdentities).hasSize(1); assertThat(objectIdentities.get(0).getType()).isEqualTo(MockUntypedIdDomainObject.class.getName()); assertThat(objectIdentities.get(0).getIdentifier()).isEqualTo(5000L); } @Test public void findChildrenForUnknownObject() { ObjectIdentity objectIdentity = new ObjectIdentityImpl(Object.class, 33); List objectIdentities = this.aclServiceIntegration.findChildren(objectIdentity); assertThat(objectIdentities).isNull(); } @Test public void findChildrenOfIdTypeLong() { ObjectIdentity objectIdentity = new ObjectIdentityImpl("location", "US-PAL"); List objectIdentities = this.aclServiceIntegration.findChildren(objectIdentity); assertThat(objectIdentities).hasSize(2); assertThat(objectIdentities.get(0).getType()).isEqualTo(MockLongIdDomainObject.class.getName()); assertThat(objectIdentities.get(0).getIdentifier()).isEqualTo(4711L); assertThat(objectIdentities.get(1).getType()).isEqualTo(MockLongIdDomainObject.class.getName()); assertThat(objectIdentities.get(1).getIdentifier()).isEqualTo(4712L); } @Test public void findChildrenOfIdTypeString() { ObjectIdentity objectIdentity = new ObjectIdentityImpl("location", "US"); this.aclServiceIntegration.setAclClassIdSupported(true); List objectIdentities = this.aclServiceIntegration.findChildren(objectIdentity); assertThat(objectIdentities).hasSize(1); assertThat(objectIdentities.get(0).getType()).isEqualTo("location"); assertThat(objectIdentities.get(0).getIdentifier()).isEqualTo("US-PAL"); } @Test public void findChildrenOfIdTypeUUID() { ObjectIdentity objectIdentity = new ObjectIdentityImpl(MockUntypedIdDomainObject.class, 5000L); this.aclServiceIntegration.setAclClassIdSupported(true); List objectIdentities = this.aclServiceIntegration.findChildren(objectIdentity); assertThat(objectIdentities).hasSize(1); assertThat(objectIdentities.get(0).getType()).isEqualTo("costcenter"); assertThat(objectIdentities.get(0).getIdentifier()) .isEqualTo(UUID.fromString("25d93b3f-c3aa-4814-9d5e-c7c96ced7762")); } @Test public void setObjectIdentityGeneratorWhenNullThenThrowsIllegalArgumentException() { assertThatIllegalArgumentException() .isThrownBy(() -> this.aclServiceIntegration.setObjectIdentityGenerator(null)) .withMessage("objectIdentityGenerator cannot be null"); } @Test public void findChildrenWhenObjectIdentityGeneratorSetThenUsed() { this.aclServiceIntegration .setObjectIdentityGenerator((id, type) -> new ObjectIdentityImpl(type, "prefix:" + id)); ObjectIdentity objectIdentity = new ObjectIdentityImpl("location", "US"); this.aclServiceIntegration.setAclClassIdSupported(true); List objectIdentities = this.aclServiceIntegration.findChildren(objectIdentity); assertThat(objectIdentities).hasSize(1); assertThat(objectIdentities.get(0).getType()).isEqualTo("location"); assertThat(objectIdentities.get(0).getIdentifier()).isEqualTo("prefix:US-PAL"); } class MockLongIdDomainObject { private Object id; Object getId() { return this.id; } void setId(Object id) { this.id = id; } } class MockUntypedIdDomainObject { private Object id; Object getId() { return this.id; } void setId(Object id) { this.id = id; } } } ================================================ FILE: acl/src/test/java/org/springframework/security/acls/jdbc/JdbcMutableAclServiceTests.java ================================================ /* * Copyright 2004, 2005, 2006, 2017 Acegi Technology Pty Limited * * 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 org.springframework.security.acls.jdbc; import java.util.Arrays; import java.util.List; import java.util.Map; import javax.sql.DataSource; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.ClassPathResource; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.security.acls.TargetObject; import org.springframework.security.acls.domain.AclImpl; import org.springframework.security.acls.domain.BasePermission; import org.springframework.security.acls.domain.CumulativePermission; import org.springframework.security.acls.domain.GrantedAuthoritySid; import org.springframework.security.acls.domain.ObjectIdentityImpl; import org.springframework.security.acls.domain.PrincipalSid; import org.springframework.security.acls.model.AccessControlEntry; import org.springframework.security.acls.model.Acl; import org.springframework.security.acls.model.AclCache; import org.springframework.security.acls.model.AlreadyExistsException; import org.springframework.security.acls.model.ChildrenExistException; import org.springframework.security.acls.model.MutableAcl; import org.springframework.security.acls.model.NotFoundException; import org.springframework.security.acls.model.ObjectIdentity; import org.springframework.security.acls.model.Permission; import org.springframework.security.acls.model.Sid; import org.springframework.security.acls.sid.CustomSid; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.security.core.context.SecurityContextImpl; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.context.transaction.AfterTransaction; import org.springframework.test.context.transaction.BeforeTransaction; import org.springframework.transaction.annotation.Transactional; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; /** * Integration tests the ACL system using an in-memory database. * * @author Ben Alex * @author Andrei Stefan */ @Transactional @ExtendWith(SpringExtension.class) @ContextConfiguration(locations = { "/jdbcMutableAclServiceTests-context.xml" }) public class JdbcMutableAclServiceTests { private static final String TARGET_CLASS = TargetObject.class.getName(); private final Authentication auth = new TestingAuthenticationToken("ben", "ignored", "ROLE_ADMINISTRATOR"); public static final String SELECT_ALL_CLASSES = "SELECT * FROM acl_class WHERE class = ?"; private final ObjectIdentity topParentOid = new ObjectIdentityImpl(TARGET_CLASS, 100L); private final ObjectIdentity middleParentOid = new ObjectIdentityImpl(TARGET_CLASS, 101L); private final ObjectIdentity childOid = new ObjectIdentityImpl(TARGET_CLASS, 102L); @Autowired private JdbcMutableAclService jdbcMutableAclService; @Autowired private AclCache aclCache; @Autowired private LookupStrategy lookupStrategy; @Autowired private DataSource dataSource; @Autowired private JdbcTemplate jdbcTemplate; protected String getSqlClassPathResource() { return "createAclSchema.sql"; } protected ObjectIdentity getTopParentOid() { return this.topParentOid; } protected ObjectIdentity getMiddleParentOid() { return this.middleParentOid; } protected ObjectIdentity getChildOid() { return this.childOid; } protected String getTargetClass() { return TARGET_CLASS; } @BeforeTransaction public void createTables() throws Exception { try { new DatabaseSeeder(this.dataSource, new ClassPathResource(getSqlClassPathResource())); // new DatabaseSeeder(dataSource, new // ClassPathResource("createAclSchemaPostgres.sql")); } catch (Exception ex) { ex.printStackTrace(); throw ex; } } @AfterTransaction public void clearContextAndData() { SecurityContextHolder.clearContext(); this.jdbcTemplate.execute("drop table acl_entry"); this.jdbcTemplate.execute("drop table acl_object_identity"); this.jdbcTemplate.execute("drop table acl_class"); this.jdbcTemplate.execute("drop table acl_sid"); this.aclCache.clearCache(); } @Test @Transactional public void testLifecycle() { SecurityContextHolder.getContext().setAuthentication(this.auth); MutableAcl topParent = this.jdbcMutableAclService.createAcl(getTopParentOid()); MutableAcl middleParent = this.jdbcMutableAclService.createAcl(getMiddleParentOid()); MutableAcl child = this.jdbcMutableAclService.createAcl(getChildOid()); // Specify the inheritance hierarchy middleParent.setParent(topParent); child.setParent(middleParent); // Now let's add a couple of permissions topParent.insertAce(0, BasePermission.READ, new PrincipalSid(this.auth), true); topParent.insertAce(1, BasePermission.WRITE, new PrincipalSid(this.auth), false); middleParent.insertAce(0, BasePermission.DELETE, new PrincipalSid(this.auth), true); child.insertAce(0, BasePermission.DELETE, new PrincipalSid(this.auth), false); // Explicitly save the changed ACL this.jdbcMutableAclService.updateAcl(topParent); this.jdbcMutableAclService.updateAcl(middleParent); this.jdbcMutableAclService.updateAcl(child); // Let's check if we can read them back correctly Map map = this.jdbcMutableAclService .readAclsById(Arrays.asList(getTopParentOid(), getMiddleParentOid(), getChildOid())); assertThat(map).hasSize(3); // Get the retrieved versions MutableAcl retrievedTopParent = (MutableAcl) map.get(getTopParentOid()); MutableAcl retrievedMiddleParent = (MutableAcl) map.get(getMiddleParentOid()); MutableAcl retrievedChild = (MutableAcl) map.get(getChildOid()); // Check the retrieved versions has IDs assertThat(retrievedTopParent.getId()).isNotNull(); assertThat(retrievedMiddleParent.getId()).isNotNull(); assertThat(retrievedChild.getId()).isNotNull(); // Check their parents were correctly persisted assertThat(retrievedTopParent.getParentAcl()).isNull(); assertThat(retrievedMiddleParent.getParentAcl().getObjectIdentity()).isEqualTo(getTopParentOid()); assertThat(retrievedChild.getParentAcl().getObjectIdentity()).isEqualTo(getMiddleParentOid()); // Check their ACEs were correctly persisted assertThat(retrievedTopParent.getEntries()).hasSize(2); assertThat(retrievedMiddleParent.getEntries()).hasSize(1); assertThat(retrievedChild.getEntries()).hasSize(1); // Check the retrieved rights are correct List read = Arrays.asList(BasePermission.READ); List write = Arrays.asList(BasePermission.WRITE); List delete = Arrays.asList(BasePermission.DELETE); List pSid = Arrays.asList((Sid) new PrincipalSid(this.auth)); assertThat(retrievedTopParent.isGranted(read, pSid, false)).isTrue(); assertThat(retrievedTopParent.isGranted(write, pSid, false)).isFalse(); assertThat(retrievedMiddleParent.isGranted(delete, pSid, false)).isTrue(); assertThat(retrievedChild.isGranted(delete, pSid, false)).isFalse(); assertThatExceptionOfType(NotFoundException.class) .isThrownBy(() -> retrievedChild.isGranted(Arrays.asList(BasePermission.ADMINISTRATION), pSid, false)); // Now check the inherited rights (when not explicitly overridden) also look OK assertThat(retrievedChild.isGranted(read, pSid, false)).isTrue(); assertThat(retrievedChild.isGranted(write, pSid, false)).isFalse(); assertThat(retrievedChild.isGranted(delete, pSid, false)).isFalse(); // Next change the child so it doesn't inherit permissions from above retrievedChild.setEntriesInheriting(false); this.jdbcMutableAclService.updateAcl(retrievedChild); MutableAcl nonInheritingChild = (MutableAcl) this.jdbcMutableAclService.readAclById(getChildOid()); assertThat(nonInheritingChild.isEntriesInheriting()).isFalse(); // Check the child permissions no longer inherit assertThat(nonInheritingChild.isGranted(delete, pSid, true)).isFalse(); assertThatExceptionOfType(NotFoundException.class) .isThrownBy(() -> nonInheritingChild.isGranted(read, pSid, true)); assertThatExceptionOfType(NotFoundException.class) .isThrownBy(() -> nonInheritingChild.isGranted(write, pSid, true)); // Let's add an identical permission to the child, but it'll appear AFTER the // current permission, so has no impact nonInheritingChild.insertAce(1, BasePermission.DELETE, new PrincipalSid(this.auth), true); // Let's also add another permission to the child nonInheritingChild.insertAce(2, BasePermission.CREATE, new PrincipalSid(this.auth), true); // Save the changed child this.jdbcMutableAclService.updateAcl(nonInheritingChild); MutableAcl retrievedNonInheritingChild = (MutableAcl) this.jdbcMutableAclService.readAclById(getChildOid()); assertThat(retrievedNonInheritingChild.getEntries()).hasSize(3); // Output permissions for (int i = 0; i < retrievedNonInheritingChild.getEntries().size(); i++) { System.out.println(retrievedNonInheritingChild.getEntries().get(i)); } // Check the permissions are as they should be assertThat(retrievedNonInheritingChild.isGranted(delete, pSid, true)).isFalse(); // as // earlier // permission // overrode assertThat(retrievedNonInheritingChild.isGranted(Arrays.asList(BasePermission.CREATE), pSid, true)).isTrue(); // Now check the first ACE (index 0) really is DELETE for our Sid and is // non-granting AccessControlEntry entry = retrievedNonInheritingChild.getEntries().get(0); assertThat(entry.getPermission().getMask()).isEqualTo(BasePermission.DELETE.getMask()); assertThat(entry.getSid()).isEqualTo(new PrincipalSid(this.auth)); assertThat(entry.isGranting()).isFalse(); assertThat(entry.getId()).isNotNull(); // Now delete that first ACE retrievedNonInheritingChild.deleteAce(0); // Save and check it worked MutableAcl savedChild = this.jdbcMutableAclService.updateAcl(retrievedNonInheritingChild); assertThat(savedChild.getEntries()).hasSize(2); assertThat(savedChild.isGranted(delete, pSid, false)).isTrue(); SecurityContextHolder.clearContext(); } /** * Test method that demonstrates eviction failure from cache - SEC-676 */ @Test @Transactional public void deleteAclAlsoDeletesChildren() { SecurityContextHolder.getContext().setAuthentication(this.auth); this.jdbcMutableAclService.createAcl(getTopParentOid()); MutableAcl middleParent = this.jdbcMutableAclService.createAcl(getMiddleParentOid()); MutableAcl child = this.jdbcMutableAclService.createAcl(getChildOid()); child.setParent(middleParent); this.jdbcMutableAclService.updateAcl(middleParent); this.jdbcMutableAclService.updateAcl(child); // Check the childOid really is a child of middleParentOid Acl childAcl = this.jdbcMutableAclService.readAclById(getChildOid()); assertThat(childAcl.getParentAcl().getObjectIdentity()).isEqualTo(getMiddleParentOid()); // Delete the mid-parent and test if the child was deleted, as well this.jdbcMutableAclService.deleteAcl(getMiddleParentOid(), true); assertThatExceptionOfType(NotFoundException.class) .isThrownBy(() -> this.jdbcMutableAclService.readAclById(getMiddleParentOid())); assertThatExceptionOfType(NotFoundException.class) .isThrownBy(() -> this.jdbcMutableAclService.readAclById(getChildOid())); Acl acl = this.jdbcMutableAclService.readAclById(getTopParentOid()); assertThat(acl).isNotNull(); assertThat(getTopParentOid()).isEqualTo(acl.getObjectIdentity()); } @Test public void constructorRejectsNullParameters() { assertThatIllegalArgumentException() .isThrownBy(() -> new JdbcMutableAclService(null, this.lookupStrategy, this.aclCache)); assertThatIllegalArgumentException() .isThrownBy(() -> new JdbcMutableAclService(this.dataSource, null, this.aclCache)); assertThatIllegalArgumentException() .isThrownBy(() -> new JdbcMutableAclService(this.dataSource, this.lookupStrategy, null)); } @Test public void createAclRejectsNullParameter() { assertThatIllegalArgumentException().isThrownBy(() -> this.jdbcMutableAclService.createAcl(null)); } @Test @Transactional public void createAclForADuplicateDomainObject() { SecurityContextHolder.getContext().setAuthentication(this.auth); ObjectIdentity duplicateOid = new ObjectIdentityImpl(TARGET_CLASS, 100L); this.jdbcMutableAclService.createAcl(duplicateOid); // Try to add the same object second time assertThatExceptionOfType(AlreadyExistsException.class) .isThrownBy(() -> this.jdbcMutableAclService.createAcl(duplicateOid)); } @Test @Transactional public void deleteAclRejectsNullParameters() { assertThatIllegalArgumentException().isThrownBy(() -> this.jdbcMutableAclService.deleteAcl(null, true)); } @Test @Transactional public void deleteAclWithChildrenThrowsException() { SecurityContextHolder.getContext().setAuthentication(this.auth); MutableAcl parent = this.jdbcMutableAclService.createAcl(getTopParentOid()); MutableAcl child = this.jdbcMutableAclService.createAcl(getMiddleParentOid()); // Specify the inheritance hierarchy child.setParent(parent); this.jdbcMutableAclService.updateAcl(child); // switch on FK this.jdbcMutableAclService.setForeignKeysInDatabase(false); try { // checking in the class, not database assertThatExceptionOfType(ChildrenExistException.class) .isThrownBy(() -> this.jdbcMutableAclService.deleteAcl(getTopParentOid(), false)); } finally { // restore to the default this.jdbcMutableAclService.setForeignKeysInDatabase(true); } } @Test @Transactional public void deleteAclRemovesRowsFromDatabase() { SecurityContextHolder.getContext().setAuthentication(this.auth); MutableAcl child = this.jdbcMutableAclService.createAcl(getChildOid()); child.insertAce(0, BasePermission.DELETE, new PrincipalSid(this.auth), false); this.jdbcMutableAclService.updateAcl(child); // Remove the child and check all related database rows were removed accordingly this.jdbcMutableAclService.deleteAcl(getChildOid(), false); assertThat(this.jdbcTemplate.queryForList(SELECT_ALL_CLASSES, new Object[] { getTargetClass() })).hasSize(1); assertThat(this.jdbcTemplate.queryForList("select * from acl_object_identity")).isEmpty(); assertThat(this.jdbcTemplate.queryForList("select * from acl_entry")).isEmpty(); // Check the cache assertThat(this.aclCache.getFromCache(getChildOid())).isNull(); assertThat(this.aclCache.getFromCache(102L)).isNull(); } /** SEC-1107 */ @Test @Transactional public void identityWithIntegerIdIsSupportedByCreateAcl() { SecurityContextHolder.getContext().setAuthentication(this.auth); ObjectIdentity oid = new ObjectIdentityImpl(TARGET_CLASS, 101); this.jdbcMutableAclService.createAcl(oid); assertThat(this.jdbcMutableAclService.readAclById(new ObjectIdentityImpl(TARGET_CLASS, 101L))).isNotNull(); } @Test @Transactional public void createAclWhenCustomSecurityContextHolderStrategyThenUses() { SecurityContextHolderStrategy securityContextHolderStrategy = mock(SecurityContextHolderStrategy.class); SecurityContext context = new SecurityContextImpl(this.auth); given(securityContextHolderStrategy.getContext()).willReturn(context); JdbcMutableAclService service = new JdbcMutableAclService(this.dataSource, this.lookupStrategy, this.aclCache); service.setSecurityContextHolderStrategy(securityContextHolderStrategy); ObjectIdentity oid = new ObjectIdentityImpl(TARGET_CLASS, 101); service.createAcl(oid); verify(securityContextHolderStrategy).getContext(); } /** * SEC-655 */ @Test @Transactional public void childrenAreClearedFromCacheWhenParentIsUpdated() { Authentication auth = new TestingAuthenticationToken("ben", "ignored", "ROLE_ADMINISTRATOR"); auth.setAuthenticated(true); SecurityContextHolder.getContext().setAuthentication(auth); ObjectIdentity parentOid = new ObjectIdentityImpl(TARGET_CLASS, 104L); ObjectIdentity childOid = new ObjectIdentityImpl(TARGET_CLASS, 105L); MutableAcl parent = this.jdbcMutableAclService.createAcl(parentOid); MutableAcl child = this.jdbcMutableAclService.createAcl(childOid); child.setParent(parent); this.jdbcMutableAclService.updateAcl(child); parent = (AclImpl) this.jdbcMutableAclService.readAclById(parentOid); parent.insertAce(0, BasePermission.READ, new PrincipalSid("ben"), true); this.jdbcMutableAclService.updateAcl(parent); parent = (AclImpl) this.jdbcMutableAclService.readAclById(parentOid); parent.insertAce(1, BasePermission.READ, new PrincipalSid("scott"), true); this.jdbcMutableAclService.updateAcl(parent); child = (MutableAcl) this.jdbcMutableAclService.readAclById(childOid); parent = (MutableAcl) child.getParentAcl(); assertThat(parent.getEntries()).hasSize(2) .withFailMessage("Fails because child has a stale reference to its parent"); assertThat(parent.getEntries().get(0).getPermission().getMask()).isEqualTo(1); assertThat(parent.getEntries().get(0).getSid()).isEqualTo(new PrincipalSid("ben")); assertThat(parent.getEntries().get(1).getPermission().getMask()).isEqualTo(1); assertThat(parent.getEntries().get(1).getSid()).isEqualTo(new PrincipalSid("scott")); } /** * SEC-655 */ @Test @Transactional public void childrenAreClearedFromCacheWhenParentisUpdated2() { Authentication auth = new TestingAuthenticationToken("system", "secret", "ROLE_IGNORED"); SecurityContextHolder.getContext().setAuthentication(auth); ObjectIdentityImpl rootObject = new ObjectIdentityImpl(TARGET_CLASS, 1L); MutableAcl parent = this.jdbcMutableAclService.createAcl(rootObject); MutableAcl child = this.jdbcMutableAclService.createAcl(new ObjectIdentityImpl(TARGET_CLASS, 2L)); child.setParent(parent); this.jdbcMutableAclService.updateAcl(child); parent.insertAce(0, BasePermission.ADMINISTRATION, new GrantedAuthoritySid("ROLE_ADMINISTRATOR"), true); this.jdbcMutableAclService.updateAcl(parent); parent.insertAce(1, BasePermission.DELETE, new PrincipalSid("terry"), true); this.jdbcMutableAclService.updateAcl(parent); child = (MutableAcl) this.jdbcMutableAclService.readAclById(new ObjectIdentityImpl(TARGET_CLASS, 2L)); parent = (MutableAcl) child.getParentAcl(); assertThat(parent.getEntries()).hasSize(2); assertThat(parent.getEntries().get(0).getPermission().getMask()).isEqualTo(16); assertThat(parent.getEntries().get(0).getSid()).isEqualTo(new GrantedAuthoritySid("ROLE_ADMINISTRATOR")); assertThat(parent.getEntries().get(1).getPermission().getMask()).isEqualTo(8); assertThat(parent.getEntries().get(1).getSid()).isEqualTo(new PrincipalSid("terry")); } @Test @Transactional public void cumulativePermissions() { Authentication auth = new TestingAuthenticationToken("ben", "ignored", "ROLE_ADMINISTRATOR"); auth.setAuthenticated(true); SecurityContextHolder.getContext().setAuthentication(auth); ObjectIdentity topParentOid = new ObjectIdentityImpl(TARGET_CLASS, 110L); MutableAcl topParent = this.jdbcMutableAclService.createAcl(topParentOid); // Add an ACE permission entry Permission cm = new CumulativePermission().set(BasePermission.READ).set(BasePermission.ADMINISTRATION); assertThat(cm.getMask()).isEqualTo(17); Sid benSid = new PrincipalSid(auth); topParent.insertAce(0, cm, benSid, true); assertThat(topParent.getEntries()).hasSize(1); // Explicitly save the changed ACL topParent = this.jdbcMutableAclService.updateAcl(topParent); // Check the mask was retrieved correctly assertThat(topParent.getEntries().get(0).getPermission().getMask()).isEqualTo(17); assertThat(topParent.isGranted(Arrays.asList(cm), Arrays.asList(benSid), true)).isTrue(); SecurityContextHolder.clearContext(); } @Test public void testProcessingCustomSid() { CustomJdbcMutableAclService customJdbcMutableAclService = spy( new CustomJdbcMutableAclService(this.dataSource, this.lookupStrategy, this.aclCache)); CustomSid customSid = new CustomSid("Custom sid"); given(customJdbcMutableAclService.createOrRetrieveSidPrimaryKey("Custom sid", false, false)).willReturn(1L); Long result = customJdbcMutableAclService.createOrRetrieveSidPrimaryKey(customSid, false); assertThat(Long.valueOf(1L)).isEqualTo(result); } protected Authentication getAuth() { return this.auth; } protected JdbcMutableAclService getJdbcMutableAclService() { return this.jdbcMutableAclService; } /** * This class needed to show how to extend {@link JdbcMutableAclService} for * processing custom {@link Sid} implementations */ private class CustomJdbcMutableAclService extends JdbcMutableAclService { CustomJdbcMutableAclService(DataSource dataSource, LookupStrategy lookupStrategy, AclCache aclCache) { super(dataSource, lookupStrategy, aclCache); } @Override protected Long createOrRetrieveSidPrimaryKey(Sid sid, boolean allowCreate) { String sidName; boolean isPrincipal = false; if (sid instanceof CustomSid) { sidName = ((CustomSid) sid).getSid(); } else if (sid instanceof GrantedAuthoritySid) { sidName = ((GrantedAuthoritySid) sid).getGrantedAuthority(); } else { sidName = ((PrincipalSid) sid).getPrincipal(); isPrincipal = true; } return createOrRetrieveSidPrimaryKey(sidName, isPrincipal, allowCreate); } } } ================================================ FILE: acl/src/test/java/org/springframework/security/acls/jdbc/JdbcMutableAclServiceTestsWithAclClassId.java ================================================ /* * 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 org.springframework.security.acls.jdbc; import java.util.UUID; import org.junit.jupiter.api.Test; import org.springframework.security.acls.TargetObjectWithUUID; import org.springframework.security.acls.domain.ObjectIdentityImpl; import org.springframework.security.acls.model.ObjectIdentity; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.test.context.ContextConfiguration; import org.springframework.transaction.annotation.Transactional; import static org.assertj.core.api.Assertions.assertThat; /** * Integration tests the ACL system using ACL class id type of UUID and using an in-memory * database. * * @author Paul Wheeler */ @ContextConfiguration(locations = { "/jdbcMutableAclServiceTestsWithAclClass-context.xml" }) public class JdbcMutableAclServiceTestsWithAclClassId extends JdbcMutableAclServiceTests { private static final String TARGET_CLASS_WITH_UUID = TargetObjectWithUUID.class.getName(); private final ObjectIdentity topParentOid = new ObjectIdentityImpl(TARGET_CLASS_WITH_UUID, UUID.randomUUID()); private final ObjectIdentity middleParentOid = new ObjectIdentityImpl(TARGET_CLASS_WITH_UUID, UUID.randomUUID()); private final ObjectIdentity childOid = new ObjectIdentityImpl(TARGET_CLASS_WITH_UUID, UUID.randomUUID()); @Override protected String getSqlClassPathResource() { return "createAclSchemaWithAclClassIdType.sql"; } @Override protected ObjectIdentity getTopParentOid() { return this.topParentOid; } @Override protected ObjectIdentity getMiddleParentOid() { return this.middleParentOid; } @Override protected ObjectIdentity getChildOid() { return this.childOid; } @Override protected String getTargetClass() { return TARGET_CLASS_WITH_UUID; } @Test @Transactional public void identityWithUuidIdIsSupportedByCreateAcl() { SecurityContextHolder.getContext().setAuthentication(getAuth()); UUID id = UUID.randomUUID(); ObjectIdentity oid = new ObjectIdentityImpl(TARGET_CLASS_WITH_UUID, id); getJdbcMutableAclService().createAcl(oid); assertThat(getJdbcMutableAclService().readAclById(new ObjectIdentityImpl(TARGET_CLASS_WITH_UUID, id))) .isNotNull(); } } ================================================ FILE: acl/src/test/java/org/springframework/security/acls/jdbc/SpringCacheBasedAclCacheTests.java ================================================ /* * 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 org.springframework.security.acls.jdbc; import java.util.Map; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.springframework.cache.Cache; import org.springframework.cache.CacheManager; import org.springframework.cache.concurrent.ConcurrentMapCacheManager; import org.springframework.security.acls.domain.AclAuthorizationStrategy; import org.springframework.security.acls.domain.AclAuthorizationStrategyImpl; import org.springframework.security.acls.domain.AclImpl; import org.springframework.security.acls.domain.AuditLogger; import org.springframework.security.acls.domain.ConsoleAuditLogger; import org.springframework.security.acls.domain.DefaultPermissionGrantingStrategy; import org.springframework.security.acls.domain.ObjectIdentityImpl; import org.springframework.security.acls.domain.SpringCacheBasedAclCache; import org.springframework.security.acls.model.MutableAcl; import org.springframework.security.acls.model.ObjectIdentity; import org.springframework.security.acls.model.PermissionGrantingStrategy; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.util.FieldUtils; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; /** * Tests {@link org.springframework.security.acls.domain.SpringCacheBasedAclCache} * * @author Marten Deinum */ public class SpringCacheBasedAclCacheTests { private static final String TARGET_CLASS = "org.springframework.security.acls.TargetObject"; private static CacheManager cacheManager; @BeforeAll public static void initCacheManaer() { cacheManager = new ConcurrentMapCacheManager(); // Use disk caching immediately (to test for serialization issue reported in // SEC-527) cacheManager.getCache("springcasebasedacltests"); } @AfterEach public void clearContext() { SecurityContextHolder.clearContext(); } private Cache getCache() { Cache cache = cacheManager.getCache("springcasebasedacltests"); cache.clear(); return cache; } @Test public void constructorRejectsNullParameters() { assertThatIllegalArgumentException().isThrownBy(() -> new SpringCacheBasedAclCache(null, null, null)); } @Test public void cacheOperationsAclWithoutParent() { Cache cache = getCache(); Map realCache = (Map) cache.getNativeCache(); ObjectIdentity identity = new ObjectIdentityImpl(TARGET_CLASS, 100L); AclAuthorizationStrategy aclAuthorizationStrategy = new AclAuthorizationStrategyImpl( new SimpleGrantedAuthority("ROLE_OWNERSHIP"), new SimpleGrantedAuthority("ROLE_AUDITING"), new SimpleGrantedAuthority("ROLE_GENERAL")); AuditLogger auditLogger = new ConsoleAuditLogger(); PermissionGrantingStrategy permissionGrantingStrategy = new DefaultPermissionGrantingStrategy(auditLogger); SpringCacheBasedAclCache myCache = new SpringCacheBasedAclCache(cache, permissionGrantingStrategy, aclAuthorizationStrategy); MutableAcl acl = new AclImpl(identity, 1L, aclAuthorizationStrategy, auditLogger); assertThat(realCache).isEmpty(); myCache.putInCache(acl); // Check we can get from cache the same objects we put in assertThat(acl).isEqualTo(myCache.getFromCache(1L)); assertThat(acl).isEqualTo(myCache.getFromCache(identity)); // Put another object in cache ObjectIdentity identity2 = new ObjectIdentityImpl(TARGET_CLASS, 101L); MutableAcl acl2 = new AclImpl(identity2, 2L, aclAuthorizationStrategy, new ConsoleAuditLogger()); myCache.putInCache(acl2); // Try to evict an entry that doesn't exist myCache.evictFromCache(3L); myCache.evictFromCache(new ObjectIdentityImpl(TARGET_CLASS, 102L)); assertThat(realCache).hasSize(4); myCache.evictFromCache(1L); assertThat(realCache).hasSize(2); // Check the second object inserted assertThat(acl2).isEqualTo(myCache.getFromCache(2L)); assertThat(acl2).isEqualTo(myCache.getFromCache(identity2)); myCache.evictFromCache(identity2); assertThat(realCache).isEmpty(); } @Test public void cacheOperationsAclWithParent() throws Exception { Cache cache = getCache(); Map realCache = (Map) cache.getNativeCache(); Authentication auth = new TestingAuthenticationToken("user", "password", "ROLE_GENERAL"); auth.setAuthenticated(true); SecurityContextHolder.getContext().setAuthentication(auth); ObjectIdentity identity = new ObjectIdentityImpl(TARGET_CLASS, 1L); ObjectIdentity identityParent = new ObjectIdentityImpl(TARGET_CLASS, 2L); AclAuthorizationStrategy aclAuthorizationStrategy = new AclAuthorizationStrategyImpl( new SimpleGrantedAuthority("ROLE_OWNERSHIP"), new SimpleGrantedAuthority("ROLE_AUDITING"), new SimpleGrantedAuthority("ROLE_GENERAL")); AuditLogger auditLogger = new ConsoleAuditLogger(); PermissionGrantingStrategy permissionGrantingStrategy = new DefaultPermissionGrantingStrategy(auditLogger); SpringCacheBasedAclCache myCache = new SpringCacheBasedAclCache(cache, permissionGrantingStrategy, aclAuthorizationStrategy); MutableAcl acl = new AclImpl(identity, 1L, aclAuthorizationStrategy, auditLogger); MutableAcl parentAcl = new AclImpl(identityParent, 2L, aclAuthorizationStrategy, auditLogger); acl.setParent(parentAcl); assertThat(realCache).isEmpty(); myCache.putInCache(acl); assertThat(4).isEqualTo(realCache.size()); // Check we can get from cache the same objects we put in AclImpl aclFromCache = (AclImpl) myCache.getFromCache(1L); assertThat(aclFromCache).isEqualTo(acl); // SEC-951 check transient fields are set on parent assertThat(FieldUtils.getFieldValue(aclFromCache.getParentAcl(), "aclAuthorizationStrategy")).isNotNull(); assertThat(FieldUtils.getFieldValue(aclFromCache.getParentAcl(), "permissionGrantingStrategy")).isNotNull(); assertThat(myCache.getFromCache(identity)).isEqualTo(acl); assertThat(FieldUtils.getFieldValue(aclFromCache, "aclAuthorizationStrategy")).isNotNull(); AclImpl parentAclFromCache = (AclImpl) myCache.getFromCache(2L); assertThat(parentAclFromCache).isEqualTo(parentAcl); assertThat(FieldUtils.getFieldValue(parentAclFromCache, "aclAuthorizationStrategy")).isNotNull(); assertThat(myCache.getFromCache(identityParent)).isEqualTo(parentAcl); } } ================================================ FILE: acl/src/test/java/org/springframework/security/acls/sid/CustomSid.java ================================================ /* * 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 org.springframework.security.acls.sid; import org.springframework.security.acls.model.Sid; /** * This class is example of custom {@link Sid} implementation * * @author Mikhail Stryzhonok */ public class CustomSid implements Sid { private String sid; public CustomSid(String sid) { this.sid = sid; } public String getSid() { return this.sid; } public void setSid(String sid) { this.sid = sid; } } ================================================ FILE: acl/src/test/java/org/springframework/security/acls/sid/SidRetrievalStrategyTests.java ================================================ /* * 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 org.springframework.security.acls.sid; import java.util.List; import org.junit.jupiter.api.Test; import org.springframework.security.access.hierarchicalroles.RoleHierarchy; import org.springframework.security.acls.domain.GrantedAuthoritySid; import org.springframework.security.acls.domain.PrincipalSid; import org.springframework.security.acls.domain.SidRetrievalStrategyImpl; import org.springframework.security.acls.model.Sid; import org.springframework.security.acls.model.SidRetrievalStrategy; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.authority.AuthorityUtils; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.anyCollection; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; /** * Tests for {@link SidRetrievalStrategyImpl} * * @author Andrei Stefan * @author Luke Taylor */ @SuppressWarnings("unchecked") public class SidRetrievalStrategyTests { Authentication authentication = new TestingAuthenticationToken("scott", "password", "A", "B", "C"); @Test public void correctSidsAreRetrieved() { SidRetrievalStrategy retrStrategy = new SidRetrievalStrategyImpl(); List sids = retrStrategy.getSids(this.authentication); assertThat(sids).isNotNull(); assertThat(sids).hasSize(4); assertThat(sids.get(0)).isNotNull(); assertThat(sids.get(0) instanceof PrincipalSid).isTrue(); for (int i = 1; i < sids.size(); i++) { assertThat(sids.get(i) instanceof GrantedAuthoritySid).isTrue(); } assertThat(((PrincipalSid) sids.get(0)).getPrincipal()).isEqualTo("scott"); assertThat(((GrantedAuthoritySid) sids.get(1)).getGrantedAuthority()).isEqualTo("A"); assertThat(((GrantedAuthoritySid) sids.get(2)).getGrantedAuthority()).isEqualTo("B"); assertThat(((GrantedAuthoritySid) sids.get(3)).getGrantedAuthority()).isEqualTo("C"); } @Test public void roleHierarchyIsUsedWhenSet() { RoleHierarchy rh = mock(RoleHierarchy.class); List rhAuthorities = AuthorityUtils.createAuthorityList("D"); given(rh.getReachableGrantedAuthorities(anyCollection())).willReturn(rhAuthorities); SidRetrievalStrategy strat = new SidRetrievalStrategyImpl(rh); List sids = strat.getSids(this.authentication); assertThat(sids).hasSize(2); assertThat(sids.get(0)).isNotNull(); assertThat(sids.get(0) instanceof PrincipalSid).isTrue(); assertThat(((GrantedAuthoritySid) sids.get(1)).getGrantedAuthority()).isEqualTo("D"); } } ================================================ FILE: acl/src/test/java/org/springframework/security/acls/sid/SidTests.java ================================================ /* * 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 org.springframework.security.acls.sid; import java.util.Collection; import java.util.Collections; import org.junit.jupiter.api.Test; import org.springframework.security.acls.domain.GrantedAuthoritySid; import org.springframework.security.acls.domain.PrincipalSid; import org.springframework.security.acls.model.Sid; import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.User; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.assertj.core.api.Assertions.assertThatNoException; public class SidTests { @Test public void testPrincipalSidConstructorsRequiredFields() { // Check one String-argument constructor assertThatIllegalArgumentException().isThrownBy(() -> new PrincipalSid((String) null)); assertThatIllegalArgumentException().isThrownBy(() -> new PrincipalSid("")); assertThatNoException().isThrownBy(() -> new PrincipalSid("johndoe")); // Check one Authentication-argument constructor assertThatIllegalArgumentException().isThrownBy(() -> new PrincipalSid((Authentication) null)); assertThatIllegalArgumentException() .isThrownBy(() -> new PrincipalSid(new TestingAuthenticationToken(null, "password"))); assertThatNoException() .isThrownBy(() -> new PrincipalSid(new TestingAuthenticationToken("johndoe", "password"))); } @Test public void testGrantedAuthoritySidConstructorsRequiredFields() { // Check one String-argument constructor assertThatIllegalArgumentException().isThrownBy(() -> new GrantedAuthoritySid((String) null)); assertThatIllegalArgumentException().isThrownBy(() -> new GrantedAuthoritySid("")); assertThatNoException().isThrownBy(() -> new GrantedAuthoritySid("ROLE_TEST")); // Check one GrantedAuthority-argument constructor assertThatIllegalArgumentException().isThrownBy(() -> new GrantedAuthoritySid((GrantedAuthority) null)); assertThatIllegalArgumentException() .isThrownBy(() -> new GrantedAuthoritySid(new SimpleGrantedAuthority(null))); assertThatNoException().isThrownBy(() -> new GrantedAuthoritySid(new SimpleGrantedAuthority("ROLE_TEST"))); } @Test public void testPrincipalSidEquals() { Authentication authentication = new TestingAuthenticationToken("johndoe", "password"); Sid principalSid = new PrincipalSid(authentication); assertThat(principalSid.equals(null)).isFalse(); assertThat(principalSid.equals("DIFFERENT_TYPE_OBJECT")).isFalse(); assertThat(principalSid.equals(principalSid)).isTrue(); assertThat(principalSid.equals(new PrincipalSid(authentication))).isTrue(); assertThat(principalSid.equals(new PrincipalSid(new TestingAuthenticationToken("johndoe", null)))).isTrue(); assertThat(principalSid.equals(new PrincipalSid(new TestingAuthenticationToken("scott", null)))).isFalse(); assertThat(principalSid.equals(new PrincipalSid("johndoe"))).isTrue(); assertThat(principalSid.equals(new PrincipalSid("scott"))).isFalse(); } @Test public void testGrantedAuthoritySidEquals() { GrantedAuthority ga = new SimpleGrantedAuthority("ROLE_TEST"); Sid gaSid = new GrantedAuthoritySid(ga); assertThat(gaSid.equals(null)).isFalse(); assertThat(gaSid.equals("DIFFERENT_TYPE_OBJECT")).isFalse(); assertThat(gaSid.equals(gaSid)).isTrue(); assertThat(gaSid.equals(new GrantedAuthoritySid(ga))).isTrue(); assertThat(gaSid.equals(new GrantedAuthoritySid(new SimpleGrantedAuthority("ROLE_TEST")))).isTrue(); assertThat(gaSid.equals(new GrantedAuthoritySid(new SimpleGrantedAuthority("ROLE_NOT_EQUAL")))).isFalse(); assertThat(gaSid.equals(new GrantedAuthoritySid("ROLE_TEST"))).isTrue(); assertThat(gaSid.equals(new GrantedAuthoritySid("ROLE_NOT_EQUAL"))).isFalse(); } @Test public void testPrincipalSidHashCode() { Authentication authentication = new TestingAuthenticationToken("johndoe", "password"); Sid principalSid = new PrincipalSid(authentication); assertThat(principalSid.hashCode()).isEqualTo("johndoe".hashCode()); assertThat(principalSid.hashCode()).isEqualTo(new PrincipalSid("johndoe").hashCode()); assertThat(principalSid.hashCode()).isNotEqualTo(new PrincipalSid("scott").hashCode()); assertThat(principalSid.hashCode()) .isNotEqualTo(new PrincipalSid(new TestingAuthenticationToken("scott", "password")).hashCode()); } @Test public void testGrantedAuthoritySidHashCode() { GrantedAuthority ga = new SimpleGrantedAuthority("ROLE_TEST"); Sid gaSid = new GrantedAuthoritySid(ga); assertThat(gaSid.hashCode()).isEqualTo("ROLE_TEST".hashCode()); assertThat(gaSid.hashCode()).isEqualTo(new GrantedAuthoritySid("ROLE_TEST").hashCode()); assertThat(gaSid.hashCode()).isNotEqualTo(new GrantedAuthoritySid("ROLE_TEST_2").hashCode()); assertThat(gaSid.hashCode()) .isNotEqualTo(new GrantedAuthoritySid(new SimpleGrantedAuthority("ROLE_TEST_2")).hashCode()); } @Test public void testGetters() { Authentication authentication = new TestingAuthenticationToken("johndoe", "password"); PrincipalSid principalSid = new PrincipalSid(authentication); GrantedAuthority ga = new SimpleGrantedAuthority("ROLE_TEST"); GrantedAuthoritySid gaSid = new GrantedAuthoritySid(ga); assertThat("johndoe").isEqualTo(principalSid.getPrincipal()); assertThat("scott".equals(principalSid.getPrincipal())).isFalse(); assertThat("ROLE_TEST").isEqualTo(gaSid.getGrantedAuthority()); assertThat("ROLE_TEST2".equals(gaSid.getGrantedAuthority())).isFalse(); } @Test public void getPrincipalWhenPrincipalInstanceOfUserDetailsThenReturnsUsername() { User user = new User("user", "password", Collections.singletonList(new SimpleGrantedAuthority("ROLE_TEST"))); Authentication authentication = new TestingAuthenticationToken(user, "password"); PrincipalSid principalSid = new PrincipalSid(authentication); assertThat("user").isEqualTo(principalSid.getPrincipal()); } @Test public void getPrincipalWhenPrincipalNotInstanceOfUserDetailsThenReturnsPrincipalName() { Authentication authentication = new TestingAuthenticationToken("token", "password"); PrincipalSid principalSid = new PrincipalSid(authentication); assertThat("token").isEqualTo(principalSid.getPrincipal()); } @Test public void getPrincipalWhenCustomAuthenticationPrincipalThenReturnsPrincipalName() { Authentication authentication = new CustomAuthenticationToken(new CustomToken("token"), null); PrincipalSid principalSid = new PrincipalSid(authentication); assertThat("token").isEqualTo(principalSid.getPrincipal()); } static class CustomAuthenticationToken extends AbstractAuthenticationToken { private CustomToken principal; CustomAuthenticationToken(CustomToken principal, Collection authorities) { super(authorities); this.principal = principal; } @Override public Object getCredentials() { return null; } @Override public CustomToken getPrincipal() { return this.principal; } @Override public String getName() { return this.principal.getName(); } } static class CustomToken { private String name; CustomToken(String name) { this.name = name; } String getName() { return this.name; } } } ================================================ FILE: acl/src/test/resources/db/sql/test_data_hierarchy.sql ================================================ --- insert ACL data INSERT INTO ACL_SID (ID, PRINCIPAL, SID) VALUES (10, true, 'user'); INSERT INTO acl_class (id, class, class_id_type) VALUES (20,'location','java.lang.String'), (21,'org.springframework.security.acls.jdbc.JdbcAclServiceTests$MockLongIdDomainObject','java.lang.Long'), (22,'org.springframework.security.acls.jdbc.JdbcAclServiceTests$MockUntypedIdDomainObject',''), (23,'costcenter','java.util.UUID'); INSERT INTO acl_object_identity (id, object_id_class, object_id_identity, parent_object, owner_sid, entries_inheriting) VALUES (1,20,'US',NULL,10,false), (2,20,'US-PAL',1,10,true), (3,21,'4711',2,10,true), (4,21,'4712',2,10,true), (5,22,'5000',3,10,true), (6,23,'25d93b3f-c3aa-4814-9d5e-c7c96ced7762',5,10,true); ================================================ FILE: acl/src/test/resources/jdbcMutableAclServiceTests-context.xml ================================================ ================================================ FILE: acl/src/test/resources/jdbcMutableAclServiceTestsWithAclClass-context.xml ================================================ ================================================ FILE: acl/src/test/resources/logback-test.xml ================================================ %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n ================================================ FILE: aspects/spring-security-aspects.gradle ================================================ apply plugin: 'io.spring.convention.spring-module' apply plugin: 'io.freefair.aspectj' apply plugin: 'javadoc-warnings-error' apply plugin: 'compile-warnings-error' compileAspectj { sourceCompatibility = "17" targetCompatibility = "17" ajcOptions.compilerArgs += ['-Xlint:ignore'] } compileTestAspectj { sourceCompatibility = "17" targetCompatibility = "17" ajcOptions.compilerArgs += ['-Xlint:ignore'] } dependencies { management platform(project(":spring-security-dependencies")) api "org.aspectj:aspectjrt" api project(':spring-security-core') api 'org.springframework:spring-beans' api 'org.springframework:spring-context' api 'org.springframework:spring-core' optional project(':spring-security-access') testImplementation 'org.springframework:spring-aop' testImplementation "org.assertj:assertj-core" testImplementation "org.junit.jupiter:junit-jupiter-api" testImplementation "org.junit.jupiter:junit-jupiter-params" testImplementation "org.junit.jupiter:junit-jupiter-engine" testImplementation "org.mockito:mockito-core" testImplementation "org.mockito:mockito-junit-jupiter" testImplementation "org.springframework:spring-test" testAspect sourceSets.main.output testRuntimeOnly 'org.junit.platform:junit-platform-launcher' } compileAspectj.ajcOptions.outxmlfile = "META-INF/aop.xml" ================================================ FILE: aspects/src/main/java/org/springframework/security/access/intercept/aspectj/aspect/AnnotationSecurityAspect.aj ================================================ /* * 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 org.springframework.security.access.intercept.aspectj.aspect; import org.springframework.beans.factory.InitializingBean; import org.springframework.security.access.annotation.Secured; import org.springframework.security.access.intercept.aspectj.AspectJCallback; import org.springframework.security.access.intercept.aspectj.AspectJMethodSecurityInterceptor; import org.springframework.security.access.prepost.PostAuthorize; import org.springframework.security.access.prepost.PostFilter; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreFilter; /** * Concrete AspectJ aspect using Spring Security @Secured annotation * for JDK 1.5+. * *

* When using this aspect, you must annotate the implementation class * (and/or methods within that class), not the interface (if any) that * the class implements. AspectJ follows Java's rule that annotations on * interfaces are not inherited. This will vary from Spring AOP. * * @author Mike Wiesner * @author Luke Taylor * @since 3.1 * @deprecated Use aspects in {@link org.springframework.security.authorization.method.aspectj} instead */ @Deprecated public aspect AnnotationSecurityAspect implements InitializingBean { /** * Matches the execution of any public method in a type with the Secured * annotation, or any subtype of a type with the Secured annotation. */ private pointcut executionOfAnyPublicMethodInAtSecuredType() : execution(public * ((@Secured *)+).*(..)) && @this(Secured); /** * Matches the execution of any method with the Secured annotation. */ private pointcut executionOfSecuredMethod() : execution(* *(..)) && @annotation(Secured); /** * Matches the execution of any method with Pre/Post annotations. */ private pointcut executionOfPrePostAnnotatedMethod() : execution(* *(..)) && (@annotation(PreAuthorize) || @annotation(PreFilter) || @annotation(PostAuthorize) || @annotation(PostFilter)); private pointcut securedMethodExecution() : executionOfAnyPublicMethodInAtSecuredType() || executionOfSecuredMethod() || executionOfPrePostAnnotatedMethod(); private AspectJMethodSecurityInterceptor securityInterceptor; Object around(): securedMethodExecution() { if (this.securityInterceptor == null) { return proceed(); } AspectJCallback callback = () -> proceed(); return this.securityInterceptor.invoke(thisJoinPoint, callback); } public void setSecurityInterceptor(AspectJMethodSecurityInterceptor securityInterceptor) { this.securityInterceptor = securityInterceptor; } public void afterPropertiesSet() { if (this.securityInterceptor == null) { throw new IllegalArgumentException("securityInterceptor required"); } } } ================================================ FILE: aspects/src/main/java/org/springframework/security/authorization/method/aspectj/AbstractMethodInterceptorAspect.aj ================================================ /* * 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 org.springframework.security.authorization.method.aspectj; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; import org.springframework.beans.factory.InitializingBean; import org.springframework.security.access.prepost.PostAuthorize; /** * Abstract AspectJ aspect for adapting a {@link MethodInvocation} * * @author Josh Cummings * @since 5.8 */ abstract aspect AbstractMethodInterceptorAspect { protected abstract pointcut executionOfAnnotatedMethod(); private MethodInterceptor securityInterceptor; Object around(): executionOfAnnotatedMethod() { if (this.securityInterceptor == null) { return proceed(); } MethodInvocation invocation = new JoinPointMethodInvocation(thisJoinPoint, () -> proceed()); try { return this.securityInterceptor.invoke(invocation); } catch (Throwable t) { throwUnchecked(t); throw new IllegalStateException("Code unexpectedly reached", t); } } public void setSecurityInterceptor(MethodInterceptor securityInterceptor) { this.securityInterceptor = securityInterceptor; } private static void throwUnchecked(Throwable ex) { AbstractMethodInterceptorAspect.throwAny(ex); } @SuppressWarnings("unchecked") private static void throwAny(Throwable ex) throws E { throw (E) ex; } } ================================================ FILE: aspects/src/main/java/org/springframework/security/authorization/method/aspectj/JoinPointMethodInvocation.aj ================================================ /* * 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 org.springframework.security.authorization.method.aspectj; import java.lang.reflect.AccessibleObject; import java.lang.reflect.Method; import java.util.function.Supplier; import org.aopalliance.intercept.MethodInvocation; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.reflect.CodeSignature; import org.springframework.util.Assert; class JoinPointMethodInvocation implements MethodInvocation { private final JoinPoint jp; private final Method method; private final Object target; private final Supplier proceed; JoinPointMethodInvocation(JoinPoint jp, Supplier proceed) { this.jp = jp; if (jp.getTarget() != null) { this.target = jp.getTarget(); } else { // SEC-1295: target may be null if an ITD is in use this.target = jp.getSignature().getDeclaringType(); } String targetMethodName = jp.getStaticPart().getSignature().getName(); Class[] types = ((CodeSignature) jp.getStaticPart().getSignature()).getParameterTypes(); Class declaringType = jp.getStaticPart().getSignature().getDeclaringType(); this.method = findMethod(targetMethodName, declaringType, types); Assert.notNull(this.method, () -> "Could not obtain target method from JoinPoint: '" + jp + "'"); this.proceed = proceed; } private Method findMethod(String name, Class declaringType, Class[] params) { Method method = null; try { method = declaringType.getMethod(name, params); } catch (NoSuchMethodException ignored) { } if (method == null) { try { method = declaringType.getDeclaredMethod(name, params); } catch (NoSuchMethodException ignored) { } } return method; } @Override public Method getMethod() { return this.method; } @Override public Object[] getArguments() { return this.jp.getArgs(); } @Override public AccessibleObject getStaticPart() { return this.method; } @Override public Object getThis() { return this.target; } @Override public Object proceed() throws Throwable { return this.proceed.get(); } } ================================================ FILE: aspects/src/main/java/org/springframework/security/authorization/method/aspectj/PostAuthorizeAspect.aj ================================================ /* * 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 org.springframework.security.authorization.method.aspectj; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; import org.springframework.beans.factory.InitializingBean; import org.springframework.security.access.prepost.PostAuthorize; /** * Concrete AspectJ aspect using Spring Security @PostAuthorize annotation. * *

* When using this aspect, you must annotate the implementation class * (and/or methods within that class), not the interface (if any) that * the class implements. AspectJ follows Java's rule that annotations on * interfaces are not inherited. This will vary from Spring AOP. * * @author Mike Wiesner * @author Luke Taylor * @author Josh Cummings * @since 5.8 */ public aspect PostAuthorizeAspect extends AbstractMethodInterceptorAspect { /** * Matches the execution of any method with a PostAuthorize annotation. */ protected pointcut executionOfAnnotatedMethod() : execution(* *(..)) && @annotation(PostAuthorize); } ================================================ FILE: aspects/src/main/java/org/springframework/security/authorization/method/aspectj/PostFilterAspect.aj ================================================ /* * 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 org.springframework.security.authorization.method.aspectj; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; import org.springframework.beans.factory.InitializingBean; import org.springframework.security.access.prepost.PostFilter; /** * Concrete AspectJ aspect using Spring Security @PostFilter annotation. * *

* When using this aspect, you must annotate the implementation class * (and/or methods within that class), not the interface (if any) that * the class implements. AspectJ follows Java's rule that annotations on * interfaces are not inherited. This will vary from Spring AOP. * * @author Mike Wiesner * @author Luke Taylor * @author Josh Cummings * @since 5.8 */ public aspect PostFilterAspect extends AbstractMethodInterceptorAspect { /** * Matches the execution of any method with a PostFilter annotation. */ protected pointcut executionOfAnnotatedMethod() : execution(* *(..)) && @annotation(PostFilter); } ================================================ FILE: aspects/src/main/java/org/springframework/security/authorization/method/aspectj/PreAuthorizeAspect.aj ================================================ /* * 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 org.springframework.security.authorization.method.aspectj; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; import org.springframework.beans.factory.InitializingBean; import org.springframework.security.access.prepost.PreAuthorize; /** * Concrete AspectJ aspect using Spring Security @PreAuthorize annotation. * *

* When using this aspect, you must annotate the implementation class * (and/or methods within that class), not the interface (if any) that * the class implements. AspectJ follows Java's rule that annotations on * interfaces are not inherited. This will vary from Spring AOP. * * @author Mike Wiesner * @author Luke Taylor * @author Josh Cummings * @since 5.8 */ public aspect PreAuthorizeAspect extends AbstractMethodInterceptorAspect { /** * Matches the execution of any method with a PreAuthorize annotation. */ protected pointcut executionOfAnnotatedMethod() : execution(* *(..)) && @annotation(PreAuthorize); } ================================================ FILE: aspects/src/main/java/org/springframework/security/authorization/method/aspectj/PreFilterAspect.aj ================================================ /* * 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 org.springframework.security.authorization.method.aspectj; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; import org.springframework.beans.factory.InitializingBean; import org.springframework.security.access.prepost.PreFilter; /** * Concrete AspectJ aspect using Spring Security @PreFilter annotation. * *

* When using this aspect, you must annotate the implementation class * (and/or methods within that class), not the interface (if any) that * the class implements. AspectJ follows Java's rule that annotations on * interfaces are not inherited. This will vary from Spring AOP. * * @author Mike Wiesner * @author Luke Taylor * @author Josh Cummings * @since 5.8 */ public aspect PreFilterAspect extends AbstractMethodInterceptorAspect { /** * Matches the execution of any method with a PreFilter annotation. */ protected pointcut executionOfAnnotatedMethod() : execution(* *(..)) && @annotation(PreFilter); } ================================================ FILE: aspects/src/main/java/org/springframework/security/authorization/method/aspectj/SecuredAspect.aj ================================================ /* * 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 org.springframework.security.authorization.method.aspectj; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; import org.springframework.beans.factory.InitializingBean; import org.springframework.security.access.annotation.Secured; /** * Concrete AspectJ aspect using Spring Security @Secured annotation. * *

* When using this aspect, you must annotate the implementation class * (and/or methods within that class), not the interface (if any) that * the class implements. AspectJ follows Java's rule that annotations on * interfaces are not inherited. This will vary from Spring AOP. * * @author Mike Wiesner * @author Luke Taylor * @author Josh Cummings * @since 5.8 */ public aspect SecuredAspect extends AbstractMethodInterceptorAspect { /** * Matches the execution of any public method in a type with the Secured * annotation, or any subtype of a type with the Secured annotation. */ private pointcut executionOfAnyPublicMethodInAtSecuredType() : execution(public * ((@Secured *)+).*(..)) && @this(Secured); /** * Matches the execution of any method with the Secured annotation. */ private pointcut executionOfSecuredMethod() : execution(* *(..)) && @annotation(Secured); protected pointcut executionOfAnnotatedMethod() : executionOfAnyPublicMethodInAtSecuredType() || executionOfSecuredMethod(); } ================================================ FILE: aspects/src/test/java/org/springframework/security/access/intercept/aspectj/aspect/AnnotationSecurityAspectTests.java ================================================ /* * 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 org.springframework.security.access.intercept.aspectj.aspect; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.springframework.security.access.AccessDecisionVoter; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.annotation.Secured; import org.springframework.security.access.annotation.SecuredAnnotationSecurityMetadataSource; import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler; import org.springframework.security.access.expression.method.ExpressionBasedAnnotationAttributeFactory; import org.springframework.security.access.expression.method.ExpressionBasedPostInvocationAdvice; import org.springframework.security.access.expression.method.ExpressionBasedPreInvocationAdvice; import org.springframework.security.access.intercept.AfterInvocationProviderManager; import org.springframework.security.access.intercept.aspectj.AspectJMethodSecurityInterceptor; import org.springframework.security.access.prepost.PostFilter; import org.springframework.security.access.prepost.PostInvocationAdviceProvider; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreInvocationAuthorizationAdviceVoter; import org.springframework.security.access.prepost.PrePostAnnotationSecurityMetadataSource; import org.springframework.security.access.vote.AffirmativeBased; import org.springframework.security.access.vote.RoleVoter; import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; /** * @author Luke Taylor * @since 3.0.3 */ public class AnnotationSecurityAspectTests { private AffirmativeBased adm; @Mock private AuthenticationManager authman; private TestingAuthenticationToken anne = new TestingAuthenticationToken("anne", "", "ROLE_A"); // private TestingAuthenticationToken bob = new TestingAuthenticationToken("bob", "", // "ROLE_B"); private AspectJMethodSecurityInterceptor interceptor; private SecuredImpl secured = new SecuredImpl(); private SecuredImplSubclass securedSub = new SecuredImplSubclass(); private PrePostSecured prePostSecured = new PrePostSecured(); @BeforeEach public final void setUp() { MockitoAnnotations.initMocks(this); this.interceptor = new AspectJMethodSecurityInterceptor(); AccessDecisionVoter[] voters = new AccessDecisionVoter[] { new RoleVoter(), new PreInvocationAuthorizationAdviceVoter(new ExpressionBasedPreInvocationAdvice()) }; this.adm = new AffirmativeBased(Arrays.>asList(voters)); this.interceptor.setAccessDecisionManager(this.adm); this.interceptor.setAuthenticationManager(this.authman); this.interceptor.setSecurityMetadataSource(new SecuredAnnotationSecurityMetadataSource()); AnnotationSecurityAspect secAspect = AnnotationSecurityAspect.aspectOf(); secAspect.setSecurityInterceptor(this.interceptor); } @AfterEach public void clearContext() { SecurityContextHolder.clearContext(); } @Test public void securedInterfaceMethodAllowsAllAccess() { this.secured.securedMethod(); } @Test public void securedClassMethodDeniesUnauthenticatedAccess() { assertThatExceptionOfType(AuthenticationCredentialsNotFoundException.class) .isThrownBy(() -> this.secured.securedClassMethod()); } @Test public void securedClassMethodAllowsAccessToRoleA() { SecurityContextHolder.getContext().setAuthentication(this.anne); this.secured.securedClassMethod(); } @Test public void internalPrivateCallIsIntercepted() { SecurityContextHolder.getContext().setAuthentication(this.anne); assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(() -> this.secured.publicCallsPrivate()); assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(() -> this.securedSub.publicCallsPrivate()); } @Test public void protectedMethodIsIntercepted() { SecurityContextHolder.getContext().setAuthentication(this.anne); assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(() -> this.secured.protectedMethod()); } @Test public void overriddenProtectedMethodIsNotIntercepted() { // AspectJ doesn't inherit annotations this.securedSub.protectedMethod(); } // SEC-1262 @Test public void denyAllPreAuthorizeDeniesAccess() { configureForElAnnotations(); SecurityContextHolder.getContext().setAuthentication(this.anne); assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.prePostSecured::denyAllMethod); } @Test public void postFilterIsApplied() { configureForElAnnotations(); SecurityContextHolder.getContext().setAuthentication(this.anne); List objects = this.prePostSecured.postFilterMethod(); assertThat(objects).hasSize(2); assertThat(objects).contains("apple"); assertThat(objects).contains("aubergine"); } private void configureForElAnnotations() { DefaultMethodSecurityExpressionHandler eh = new DefaultMethodSecurityExpressionHandler(); this.interceptor.setSecurityMetadataSource( new PrePostAnnotationSecurityMetadataSource(new ExpressionBasedAnnotationAttributeFactory(eh))); this.interceptor.setAccessDecisionManager(this.adm); AfterInvocationProviderManager aim = new AfterInvocationProviderManager(); aim.setProviders(Arrays.asList(new PostInvocationAdviceProvider(new ExpressionBasedPostInvocationAdvice(eh)))); this.interceptor.setAfterInvocationManager(aim); } interface SecuredInterface { @Secured("ROLE_X") void securedMethod(); } static class SecuredImpl implements SecuredInterface { // Not really secured because AspectJ doesn't inherit annotations from interfaces @Override public void securedMethod() { } @Secured("ROLE_A") public void securedClassMethod() { } @Secured("ROLE_X") private void privateMethod() { } @Secured("ROLE_X") protected void protectedMethod() { } @Secured("ROLE_X") public void publicCallsPrivate() { privateMethod(); } } static class SecuredImplSubclass extends SecuredImpl { @Override protected void protectedMethod() { } @Override public void publicCallsPrivate() { super.publicCallsPrivate(); } } static class PrePostSecured { @PreAuthorize("denyAll") public void denyAllMethod() { } @PostFilter("filterObject.startsWith('a')") public List postFilterMethod() { ArrayList objects = new ArrayList<>(); objects.addAll(Arrays.asList(new String[] { "apple", "banana", "aubergine", "orange" })); return objects; } } } ================================================ FILE: aspects/src/test/java/org/springframework/security/authorization/method/aspectj/PostAuthorizeAspectTests.java ================================================ /* * 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 org.springframework.security.authorization.method.aspectj; import org.aopalliance.intercept.MethodInterceptor; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.MockitoAnnotations; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.prepost.PostAuthorize; import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.authorization.method.AuthorizationManagerAfterMethodInterceptor; import org.springframework.security.core.context.SecurityContextHolder; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; /** * @author Luke Taylor * @since 3.0.3 */ public class PostAuthorizeAspectTests { private TestingAuthenticationToken anne = new TestingAuthenticationToken("anne", "", "ROLE_A"); private MethodInterceptor interceptor; private SecuredImpl secured = new SecuredImpl(); private SecuredImplSubclass securedSub = new SecuredImplSubclass(); private PrePostSecured prePostSecured = new PrePostSecured(); @BeforeEach public final void setUp() { MockitoAnnotations.initMocks(this); this.interceptor = AuthorizationManagerAfterMethodInterceptor.postAuthorize(); PostAuthorizeAspect secAspect = PostAuthorizeAspect.aspectOf(); secAspect.setSecurityInterceptor(this.interceptor); } @AfterEach public void clearContext() { SecurityContextHolder.clearContext(); } @Test public void securedInterfaceMethodAllowsAllAccess() { this.secured.securedMethod(); } @Test public void securedClassMethodDeniesUnauthenticatedAccess() { assertThatExceptionOfType(AuthenticationCredentialsNotFoundException.class) .isThrownBy(() -> this.secured.securedClassMethod()); } @Test public void securedClassMethodAllowsAccessToRoleA() { SecurityContextHolder.getContext().setAuthentication(this.anne); this.secured.securedClassMethod(); } @Test public void internalPrivateCallIsIntercepted() { SecurityContextHolder.getContext().setAuthentication(this.anne); assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(() -> this.secured.publicCallsPrivate()); assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(() -> this.securedSub.publicCallsPrivate()); } @Test public void protectedMethodIsIntercepted() { SecurityContextHolder.getContext().setAuthentication(this.anne); assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(() -> this.secured.protectedMethod()); } @Test public void overriddenProtectedMethodIsNotIntercepted() { // AspectJ doesn't inherit annotations this.securedSub.protectedMethod(); } // SEC-1262 @Test public void denyAllPreAuthorizeDeniesAccess() { SecurityContextHolder.getContext().setAuthentication(this.anne); assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.prePostSecured::denyAllMethod); } @Test public void nestedDenyAllPostAuthorizeDeniesAccess() { SecurityContextHolder.getContext().setAuthentication(this.anne); assertThatExceptionOfType(AccessDeniedException.class) .isThrownBy(() -> this.secured.myObject().denyAllMethod()); } interface SecuredInterface { @PostAuthorize("hasRole('X')") void securedMethod(); } static class SecuredImpl implements SecuredInterface { // Not really secured because AspectJ doesn't inherit annotations from interfaces @Override public void securedMethod() { } @PostAuthorize("hasRole('A')") void securedClassMethod() { } @PostAuthorize("hasRole('X')") private void privateMethod() { } @PostAuthorize("hasRole('X')") protected void protectedMethod() { } @PostAuthorize("hasRole('X')") void publicCallsPrivate() { privateMethod(); } NestedObject myObject() { return new NestedObject(); } } static class SecuredImplSubclass extends SecuredImpl { @Override protected void protectedMethod() { } @Override public void publicCallsPrivate() { super.publicCallsPrivate(); } } static class PrePostSecured { @PostAuthorize("denyAll") void denyAllMethod() { } } static class NestedObject { @PostAuthorize("denyAll") void denyAllMethod() { } } } ================================================ FILE: aspects/src/test/java/org/springframework/security/authorization/method/aspectj/PostFilterAspectTests.java ================================================ /* * 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 org.springframework.security.authorization.method.aspectj; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import org.aopalliance.intercept.MethodInterceptor; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.MockitoAnnotations; import org.springframework.security.access.prepost.PostFilter; import org.springframework.security.authorization.method.PostFilterAuthorizationMethodInterceptor; import static org.assertj.core.api.Assertions.assertThat; /** * @author Luke Taylor * @since 3.0.3 */ public class PostFilterAspectTests { private MethodInterceptor interceptor; private PrePostSecured prePostSecured = new PrePostSecured(); @BeforeEach public final void setUp() { MockitoAnnotations.initMocks(this); this.interceptor = new PostFilterAuthorizationMethodInterceptor(); PostFilterAspect secAspect = PostFilterAspect.aspectOf(); secAspect.setSecurityInterceptor(this.interceptor); } @Test public void preFilterMethodWhenListThenFilters() { List objects = new ArrayList<>(Arrays.asList("apple", "banana", "aubergine", "orange")); assertThat(this.prePostSecured.postFilterMethod(objects)).containsExactly("apple", "aubergine"); } @Test public void nestedDenyAllPostFilterDeniesAccess() { assertThat(this.prePostSecured.myObject().denyAllMethod()).isEmpty(); } static class PrePostSecured { @PostFilter("filterObject.startsWith('a')") List postFilterMethod(List objects) { return objects; } NestedObject myObject() { return new NestedObject(); } } static class NestedObject { @PostFilter("filterObject == null") List denyAllMethod() { return new ArrayList<>(List.of("deny")); } } } ================================================ FILE: aspects/src/test/java/org/springframework/security/authorization/method/aspectj/PreAuthorizeAspectTests.java ================================================ /* * 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 org.springframework.security.authorization.method.aspectj; import org.aopalliance.intercept.MethodInterceptor; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.MockitoAnnotations; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor; import org.springframework.security.core.context.SecurityContextHolder; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; /** * @author Luke Taylor * @since 3.0.3 */ public class PreAuthorizeAspectTests { private TestingAuthenticationToken anne = new TestingAuthenticationToken("anne", "", "ROLE_A"); private MethodInterceptor interceptor; private SecuredImpl secured = new SecuredImpl(); private SecuredImplSubclass securedSub = new SecuredImplSubclass(); private PrePostSecured prePostSecured = new PrePostSecured(); private MultipleInterfaces multiple = new MultipleInterfaces(); @BeforeEach public final void setUp() { MockitoAnnotations.initMocks(this); this.interceptor = AuthorizationManagerBeforeMethodInterceptor.preAuthorize(); PreAuthorizeAspect secAspect = PreAuthorizeAspect.aspectOf(); secAspect.setSecurityInterceptor(this.interceptor); } @AfterEach public void clearContext() { SecurityContextHolder.clearContext(); } @Test public void securedInterfaceMethodAllowsAllAccess() { this.secured.securedMethod(); } @Test public void securedClassMethodDeniesUnauthenticatedAccess() { assertThatExceptionOfType(AuthenticationCredentialsNotFoundException.class) .isThrownBy(() -> this.secured.securedClassMethod()); } @Test public void securedClassMethodAllowsAccessToRoleA() { SecurityContextHolder.getContext().setAuthentication(this.anne); this.secured.securedClassMethod(); } @Test public void internalPrivateCallIsIntercepted() { SecurityContextHolder.getContext().setAuthentication(this.anne); assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(() -> this.secured.publicCallsPrivate()); assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(() -> this.securedSub.publicCallsPrivate()); } @Test public void protectedMethodIsIntercepted() { SecurityContextHolder.getContext().setAuthentication(this.anne); assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(() -> this.secured.protectedMethod()); } @Test public void overriddenProtectedMethodIsNotIntercepted() { // AspectJ doesn't inherit annotations this.securedSub.protectedMethod(); } // SEC-1262 @Test public void denyAllPreAuthorizeDeniesAccess() { SecurityContextHolder.getContext().setAuthentication(this.anne); assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.prePostSecured::denyAllMethod); } @Test public void nestedDenyAllPreAuthorizeDeniesAccess() { SecurityContextHolder.getContext().setAuthentication(this.anne); assertThatExceptionOfType(AccessDeniedException.class) .isThrownBy(() -> this.secured.myObject().denyAllMethod()); } @Test public void multipleInterfacesPreAuthorizeAllows() { // aspectj doesn't inherit annotations this.multiple.securedMethod(); } interface SecuredInterface { @PreAuthorize("hasRole('X')") void securedMethod(); } static class SecuredImpl implements SecuredInterface { // Not really secured because AspectJ doesn't inherit annotations from interfaces @Override public void securedMethod() { } @PreAuthorize("hasRole('A')") void securedClassMethod() { } @PreAuthorize("hasRole('X')") private void privateMethod() { } @PreAuthorize("hasRole('X')") protected void protectedMethod() { } @PreAuthorize("hasRole('A')") void publicCallsPrivate() { privateMethod(); } NestedObject myObject() { return new NestedObject(); } } static class SecuredImplSubclass extends SecuredImpl { @Override protected void protectedMethod() { } @Override public void publicCallsPrivate() { super.publicCallsPrivate(); } } static class PrePostSecured { @PreAuthorize("denyAll") void denyAllMethod() { } } static class NestedObject { @PreAuthorize("denyAll") void denyAllMethod() { } } interface AnotherSecuredInterface { @PreAuthorize("hasRole('Y')") void securedMethod(); } static class MultipleInterfaces implements SecuredInterface, AnotherSecuredInterface { @Override public void securedMethod() { } } } ================================================ FILE: aspects/src/test/java/org/springframework/security/authorization/method/aspectj/PreFilterAspectTests.java ================================================ /* * 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 org.springframework.security.authorization.method.aspectj; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import org.aopalliance.intercept.MethodInterceptor; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.MockitoAnnotations; import org.springframework.security.access.prepost.PreFilter; import org.springframework.security.authorization.method.PreFilterAuthorizationMethodInterceptor; import static org.assertj.core.api.Assertions.assertThat; /** * @author Luke Taylor * @since 3.0.3 */ public class PreFilterAspectTests { private MethodInterceptor interceptor; private PrePostSecured prePostSecured = new PrePostSecured(); @BeforeEach public final void setUp() { MockitoAnnotations.initMocks(this); this.interceptor = new PreFilterAuthorizationMethodInterceptor(); PreFilterAspect secAspect = PreFilterAspect.aspectOf(); secAspect.setSecurityInterceptor(this.interceptor); } @Test public void preFilterMethodWhenListThenFilters() { List objects = new ArrayList<>(Arrays.asList("apple", "banana", "aubergine", "orange")); assertThat(this.prePostSecured.preFilterMethod(objects)).containsExactly("apple", "aubergine"); } @Test public void nestedDenyAllPreFilterDeniesAccess() { assertThat(this.prePostSecured.myObject().denyAllMethod(new ArrayList<>(List.of("deny")))).isEmpty(); } static class PrePostSecured { @PreFilter("filterObject.startsWith('a')") List preFilterMethod(List objects) { return objects; } NestedObject myObject() { return new NestedObject(); } } static class NestedObject { @PreFilter("filterObject == null") List denyAllMethod(List list) { return list; } } } ================================================ FILE: aspects/src/test/java/org/springframework/security/authorization/method/aspectj/SecuredAspectTests.java ================================================ /* * 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 org.springframework.security.authorization.method.aspectj; import org.aopalliance.intercept.MethodInterceptor; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.MockitoAnnotations; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.annotation.Secured; import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor; import org.springframework.security.core.context.SecurityContextHolder; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; /** * @author Luke Taylor * @since 3.0.3 */ public class SecuredAspectTests { private TestingAuthenticationToken anne = new TestingAuthenticationToken("anne", "", "ROLE_A"); private MethodInterceptor interceptor; private SecuredImpl secured = new SecuredImpl(); private SecuredImplSubclass securedSub = new SecuredImplSubclass(); @BeforeEach public final void setUp() { MockitoAnnotations.initMocks(this); this.interceptor = AuthorizationManagerBeforeMethodInterceptor.secured(); SecuredAspect secAspect = SecuredAspect.aspectOf(); secAspect.setSecurityInterceptor(this.interceptor); } @AfterEach public void clearContext() { SecurityContextHolder.clearContext(); } @Test public void securedInterfaceMethodAllowsAllAccess() { this.secured.securedMethod(); } @Test public void securedClassMethodDeniesUnauthenticatedAccess() { assertThatExceptionOfType(AuthenticationCredentialsNotFoundException.class) .isThrownBy(() -> this.secured.securedClassMethod()); } @Test public void securedClassMethodAllowsAccessToRoleA() { SecurityContextHolder.getContext().setAuthentication(this.anne); this.secured.securedClassMethod(); } @Test public void internalPrivateCallIsIntercepted() { SecurityContextHolder.getContext().setAuthentication(this.anne); assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(() -> this.secured.publicCallsPrivate()); assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(() -> this.securedSub.publicCallsPrivate()); } @Test public void protectedMethodIsIntercepted() { SecurityContextHolder.getContext().setAuthentication(this.anne); assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(() -> this.secured.protectedMethod()); } @Test public void overriddenProtectedMethodIsNotIntercepted() { // AspectJ doesn't inherit annotations this.securedSub.protectedMethod(); } interface SecuredInterface { @Secured("ROLE_X") void securedMethod(); } static class SecuredImpl implements SecuredInterface { // Not really secured because AspectJ doesn't inherit annotations from interfaces @Override public void securedMethod() { } @Secured("ROLE_A") void securedClassMethod() { } @Secured("ROLE_X") private void privateMethod() { } @Secured("ROLE_X") protected void protectedMethod() { } @Secured("ROLE_X") void publicCallsPrivate() { privateMethod(); } } static class SecuredImplSubclass extends SecuredImpl { @Override protected void protectedMethod() { } @Override public void publicCallsPrivate() { super.publicCallsPrivate(); } } } ================================================ FILE: aspects/src/test/resources/logback-test.xml ================================================ %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n ================================================ FILE: bom/spring-security-bom.gradle ================================================ import io.spring.gradle.convention.SpringModulePlugin apply plugin: 'io.spring.convention.bom' apply plugin: 'compile-warnings-error' dependencies { constraints { project.rootProject.allprojects { p -> p.plugins.withType(SpringModulePlugin) { api p } } } } ================================================ FILE: build.gradle ================================================ import io.spring.gradle.IncludeRepoTask import org.jetbrains.kotlin.gradle.dsl.JvmTarget import trang.RncToXsd import org.jetbrains.kotlin.gradle.tasks.KotlinCompile buildscript { dependencies { classpath libs.io.spring.javaformat.spring.javaformat.gradle.plugin classpath libs.io.spring.nohttp.nohttp.gradle classpath libs.io.freefair.gradle.aspectj.plugin classpath libs.org.jetbrains.kotlin.kotlin.gradle.plugin classpath libs.com.netflix.nebula.nebula.project.plugin } repositories { maven { url='https://plugins.gradle.org/m2/' } } } plugins { alias(libs.plugins.org.gradle.wrapper.upgrade) } apply plugin: 'io.spring.nohttp' apply plugin: 'locks' apply plugin: 'io.spring.convention.root' apply plugin: 'org.jetbrains.kotlin.jvm' apply plugin: 'org.springframework.security.versions.verify-dependencies-versions' apply plugin: 'org.springframework.security.check-expected-branch-version' apply plugin: 'io.spring.security.release' group = 'org.springframework.security' description = 'Spring Security' ext.snapshotBuild = version.contains("SNAPSHOT") ext.releaseBuild = version.contains("SNAPSHOT") ext.milestoneBuild = !(snapshotBuild || releaseBuild) repositories { mavenCentral() maven { url = "https://repo.spring.io/milestone" } } springRelease { weekOfMonth = 3 dayOfWeek = 1 referenceDocUrl = "https://docs.spring.io/spring-security/reference/{version}/index.html" apiDocUrl = "https://docs.spring.io/spring-security/reference/{version}/api/java/index.html" replaceSnapshotVersionInReferenceDocUrl = true } allprojects { if (!['spring-security-bom', 'spring-security-docs'].contains(project.name)) { apply plugin: 'io.spring.javaformat' apply plugin: 'checkstyle' pluginManager.withPlugin("io.spring.convention.checkstyle") { dependencies { checkstyle libs.io.spring.javaformat.spring.javaformat.checkstyle } checkstyle { toolVersion = '8.34' } } if (project.name.contains('sample')) { tasks.whenTaskAdded { task -> if (task.name.contains('format') || task.name.contains('checkFormat') || task.name.contains("checkstyle")) { task.enabled = false } } } } } develocity { buildScan { termsOfUseUrl = 'https://gradle.com/help/legal-terms-of-use' termsOfUseAgree = 'yes' } } nohttp { source.exclude "buildSrc/build/**", "**/build/**", "**/target/**", "javascript/.gradle/**", "javascript/package-lock.json", "javascript/node_modules/**", "javascript/build/**", "javascript/dist/**" source.builtBy(project(':spring-security-config').tasks.withType(RncToXsd)) } tasks.named('checkstyleNohttp') { maxHeapSize = '1g' } tasks.register('cloneRepository', IncludeRepoTask) { repository = project.getProperties().get("repositoryName") ref = project.getProperties().get("ref") var defaultDirectory = project.file("build/tmp/clone") outputDirectory = project.hasProperty("cloneOutputDirectory") ? project.file("$cloneOutputDirectory") : defaultDirectory } wrapperUpgrade { gradle { 'spring-security' { repo = 'spring-projects/spring-security' baseBranch = '6.3.x' // runs only on 6.3.x and the update is merged forward to main } } } ================================================ FILE: buildSrc/.idea/compiler.xml ================================================ ================================================ FILE: buildSrc/.idea/gradle.xml ================================================ ================================================ FILE: buildSrc/.idea/jarRepositories.xml ================================================ ================================================ FILE: buildSrc/.idea/misc.xml ================================================ ================================================ FILE: buildSrc/.idea/uiDesigner.xml ================================================ ================================================ FILE: buildSrc/.idea/workspace.xml ================================================

""".formatted(token.getToken())); }); // @formatter:on } @Test public void loginWhenNoCredentialsThenRedirectedToLoginPageWithError() throws Exception { this.spring.register(DefaultLoginPageConfig.class).autowire(); this.mvc.perform(post("/login").with(csrf())).andExpect(redirectedUrl("/login?error")); } @Test public void loginPageWhenErrorThenDefaultLoginPageWithError() throws Exception { this.spring.register(DefaultLoginPageConfig.class).autowire(); CsrfToken csrfToken = new DefaultCsrfToken("X-CSRF-TOKEN", "_csrf", "BaseSpringSpec_CSRFTOKEN"); String csrfAttributeName = HttpSessionCsrfTokenRepository.class.getName().concat(".CSRF_TOKEN"); MvcResult mvcResult = this.mvc.perform(post("/login").with(csrf())).andReturn(); // @formatter:off this.mvc.perform(get("/login?error").session((MockHttpSession) mvcResult.getRequest().getSession()) .sessionAttr(csrfAttributeName, csrfToken)) .andExpect((result) -> { String defaultErrorMessage = "Invalid credentials"; CsrfToken token = (CsrfToken) result.getRequest().getAttribute(CsrfToken.class.getName()); assertThat(result.getResponse().getContentAsString()).isEqualTo(""" Please sign in
""".formatted(defaultErrorMessage, token.getToken())); }); // @formatter:on } @Test public void loginWhenValidCredentialsThenRedirectsToDefaultSuccessPage() throws Exception { this.spring.register(DefaultLoginPageConfig.class).autowire(); // @formatter:off MockHttpServletRequestBuilder loginRequest = post("/login") .with(csrf()) .param("username", "user") .param("password", "password"); // @formatter:on this.mvc.perform(loginRequest).andExpect(redirectedUrl("/")); } @Test public void loginPageWhenLoggedOutThenDefaultLoginPageWithLogoutMessage() throws Exception { this.spring.register(DefaultLoginPageConfig.class).autowire(); CsrfToken csrfToken = new DefaultCsrfToken("X-CSRF-TOKEN", "_csrf", "BaseSpringSpec_CSRFTOKEN"); String csrfAttributeName = HttpSessionCsrfTokenRepository.class.getName().concat(".CSRF_TOKEN"); // @formatter:off this.mvc.perform(get("/login?logout").sessionAttr(csrfAttributeName, csrfToken)) .andExpect((result) -> { CsrfToken token = (CsrfToken) result.getRequest().getAttribute(CsrfToken.class.getName()); assertThat(result.getResponse().getContentAsString()).isEqualTo(""" Please sign in
""".formatted(token.getToken())); }); } @Test public void cssWhenFormLoginConfiguredThenServesCss() throws Exception { this.spring.register(DefaultLoginPageConfig.class).autowire(); this.mvc.perform(get("/default-ui.css")) .andExpect(status().isOk()) .andExpect(header().string("content-type", "text/css;charset=UTF-8")) .andExpect(content().string(containsString("body {"))); } @Test public void loginPageWhenLoggedOutAndCustomLogoutSuccessHandlerThenDoesNotRenderLoginPage() throws Exception { this.spring.register(DefaultLoginPageCustomLogoutSuccessHandlerConfig.class).autowire(); this.mvc.perform(get("/login?logout")).andExpect(content().string("")); } @Test public void loginPageWhenLoggedOutAndCustomLogoutSuccessUrlThenDoesNotRenderLoginPage() throws Exception { this.spring.register(DefaultLoginPageCustomLogoutSuccessUrlConfig.class).autowire(); this.mvc.perform(get("/login?logout")).andExpect(content().string("")); } @Test public void loginPageWhenRememberConfigureThenDefaultLoginPageWithRememberMeCheckbox() throws Exception { this.spring.register(DefaultLoginPageWithRememberMeConfig.class).autowire(); CsrfToken csrfToken = new DefaultCsrfToken("X-CSRF-TOKEN", "_csrf", "BaseSpringSpec_CSRFTOKEN"); String csrfAttributeName = HttpSessionCsrfTokenRepository.class.getName().concat(".CSRF_TOKEN"); // @formatter:off this.mvc.perform(get("/login").sessionAttr(csrfAttributeName, csrfToken)) .andExpect((result) -> { CsrfToken token = (CsrfToken) result.getRequest().getAttribute(CsrfToken.class.getName()); assertThat(result.getResponse().getContentAsString()).isEqualTo(""" Please sign in
""".formatted(token.getToken())); }); // @formatter:on } @Test public void configureWhenRegisteringObjectPostProcessorThenInvokedOnDefaultLoginPageGeneratingFilter() { ObjectPostProcessorConfig.objectPostProcessor = spy(ReflectingObjectPostProcessor.class); this.spring.register(ObjectPostProcessorConfig.class).autowire(); verify(ObjectPostProcessorConfig.objectPostProcessor).postProcess(any(DefaultLoginPageGeneratingFilter.class)); } @Test public void configureWhenRegisteringObjectPostProcessorThenInvokedOnUsernamePasswordAuthenticationFilter() { ObjectPostProcessorConfig.objectPostProcessor = spy(ReflectingObjectPostProcessor.class); this.spring.register(ObjectPostProcessorConfig.class).autowire(); verify(ObjectPostProcessorConfig.objectPostProcessor) .postProcess(any(UsernamePasswordAuthenticationFilter.class)); } @Test public void configureWhenRegisteringObjectPostProcessorThenInvokedOnLoginUrlAuthenticationEntryPoint() { ObjectPostProcessorConfig.objectPostProcessor = spy(ReflectingObjectPostProcessor.class); this.spring.register(ObjectPostProcessorConfig.class).autowire(); verify(ObjectPostProcessorConfig.objectPostProcessor).postProcess(any(LoginUrlAuthenticationEntryPoint.class)); } @Test public void configureWhenRegisteringObjectPostProcessorThenInvokedOnExceptionTranslationFilter() { ObjectPostProcessorConfig.objectPostProcessor = spy(ReflectingObjectPostProcessor.class); this.spring.register(ObjectPostProcessorConfig.class).autowire(); verify(ObjectPostProcessorConfig.objectPostProcessor).postProcess(any(ExceptionTranslationFilter.class)); } @Test public void configureWhenAuthenticationEntryPointThenNoDefaultLoginPageGeneratingFilter() { this.spring.register(DefaultLoginWithCustomAuthenticationEntryPointConfig.class).autowire(); FilterChainProxy filterChain = this.spring.getContext().getBean(FilterChainProxy.class); assertThat(filterChain.getFilterChains() .get(0) .getFilters() .stream() .filter((filter) -> filter.getClass().isAssignableFrom(DefaultLoginPageGeneratingFilter.class)) .count()).isZero(); } @Test public void configureWhenAuthenticationEntryPointThenDoesNotServeCss() throws Exception { this.spring.register(DefaultLoginWithCustomAuthenticationEntryPointConfig.class).autowire(); FilterChainProxy filterChain = this.spring.getContext().getBean(FilterChainProxy.class); assertThat(filterChain.getFilterChains() .get(0) .getFilters() .stream() .filter((filter) -> filter.getClass().isAssignableFrom(DefaultResourcesFilter.class)) .count()).isZero(); //@formatter:off this.mvc.perform(get("/default-ui.css")) .andExpect(status().is3xxRedirection()); //@formatter:on } @Test public void formLoginWhenLogoutEnabledThenCreatesDefaultLogoutPage() throws Exception { this.spring.register(DefaultLogoutPageConfig.class).autowire(); this.mvc.perform(get("/logout").with(user("user"))).andExpect(status().isOk()); } @Test public void formLoginWhenLogoutDisabledThenDefaultLogoutPageDoesNotExist() throws Exception { this.spring.register(LogoutDisabledConfig.class).autowire(); this.mvc.perform(get("/logout").with(user("user"))).andExpect(status().isNotFound()); } @Configuration @EnableWebSecurity static class DefaultLoginPageConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((requests) -> requests .anyRequest().hasRole("USER")) .formLogin(withDefaults()); // @formatter:on return http.build(); } @Bean UserDetailsService userDetailsService() { return new InMemoryUserDetailsManager(PasswordEncodedUser.user()); } } @Configuration @EnableWebSecurity static class DefaultLoginPageCustomLogoutSuccessHandlerConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((requests) -> requests .anyRequest().hasRole("USER")) .logout((logout) -> logout .logoutSuccessHandler(new SimpleUrlLogoutSuccessHandler())) .formLogin(withDefaults()); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class DefaultLoginPageCustomLogoutSuccessUrlConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((requests) -> requests .anyRequest().hasRole("USER")) .logout((logout) -> logout .logoutSuccessUrl("/login?logout")) .formLogin(withDefaults()); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class DefaultLoginPageWithRememberMeConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((requests) -> requests .anyRequest().hasRole("USER")) .formLogin(withDefaults()) .rememberMe(withDefaults()); return http.build(); // @formatter:on } @Bean UserDetailsService userDetailsService() { return new InMemoryUserDetailsManager(PasswordEncodedUser.user()); } } @Configuration @EnableWebSecurity static class DefaultLoginWithCustomAuthenticationEntryPointConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .exceptionHandling((handling) -> handling .authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login"))) .authorizeHttpRequests((requests) -> requests .anyRequest().hasRole("USER")) .formLogin(withDefaults()); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class ObjectPostProcessorConfig { static ObjectPostProcessor objectPostProcessor; @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .exceptionHandling(withDefaults()) .formLogin(withDefaults()); return http.build(); // @formatter:on } @Bean static ObjectPostProcessor objectPostProcessor() { return objectPostProcessor; } } @Configuration @EnableWebSecurity static class DefaultLogoutPageConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((authorize) -> authorize .anyRequest().authenticated() ) .formLogin(withDefaults()); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class LogoutDisabledConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((authorize) -> authorize .anyRequest().authenticated() ) .formLogin(withDefaults()) .logout((logout) -> logout .disable() ); return http.build(); // @formatter:on } } static class ReflectingObjectPostProcessor implements ObjectPostProcessor { @Override public O postProcess(O object) { return object; } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/annotation/web/configurers/ExceptionHandlingConfigurerAccessDeniedHandlerTests.java ================================================ /* * 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 org.springframework.security.config.annotation.web.configurers; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpStatus; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners; import org.springframework.security.test.context.support.WithMockUser; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.access.AccessDeniedHandler; import org.springframework.security.web.access.AccessDeniedHandlerImpl; import org.springframework.security.web.util.matcher.AnyRequestMatcher; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.servlet.MockMvc; import static org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher.pathPattern; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * @author Josh Cummings */ @ExtendWith({ SpringExtension.class, SpringTestContextExtension.class }) @SecurityTestExecutionListeners public class ExceptionHandlingConfigurerAccessDeniedHandlerTests { @Autowired MockMvc mvc; public final SpringTestContext spring = new SpringTestContext(this); @Test @WithMockUser(roles = "ANYTHING") public void getWhenAccessDeniedOverriddenThenCustomizesResponseByRequest() throws Exception { this.spring.register(RequestMatcherBasedAccessDeniedHandlerConfig.class).autowire(); this.mvc.perform(get("/hello")).andExpect(status().isIAmATeapot()); this.mvc.perform(get("/goodbye")).andExpect(status().isForbidden()); } @Test @WithMockUser(roles = "ANYTHING") public void getWhenAccessDeniedOverriddenInLambdaThenCustomizesResponseByRequest() throws Exception { this.spring.register(RequestMatcherBasedAccessDeniedHandlerInLambdaConfig.class).autowire(); this.mvc.perform(get("/hello")).andExpect(status().isIAmATeapot()); this.mvc.perform(get("/goodbye")).andExpect(status().isForbidden()); } @Test @WithMockUser(roles = "ANYTHING") public void getWhenAccessDeniedOverriddenByOnlyOneHandlerThenAllRequestsUseThatHandler() throws Exception { this.spring.register(SingleRequestMatcherAccessDeniedHandlerConfig.class).autowire(); this.mvc.perform(get("/hello")).andExpect(status().isIAmATeapot()); this.mvc.perform(get("/goodbye")).andExpect(status().isIAmATeapot()); } @Configuration @EnableWebSecurity static class RequestMatcherBasedAccessDeniedHandlerConfig { AccessDeniedHandler teapotDeniedHandler = (request, response, exception) -> response .setStatus(HttpStatus.I_AM_A_TEAPOT.value()); @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((requests) -> requests .anyRequest().denyAll()) .exceptionHandling((handling) -> handling .defaultAccessDeniedHandlerFor( this.teapotDeniedHandler, pathPattern("/hello/**")) .defaultAccessDeniedHandlerFor( new AccessDeniedHandlerImpl(), AnyRequestMatcher.INSTANCE)); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class RequestMatcherBasedAccessDeniedHandlerInLambdaConfig { AccessDeniedHandler teapotDeniedHandler = (request, response, exception) -> response .setStatus(HttpStatus.I_AM_A_TEAPOT.value()); @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((authorize) -> authorize .anyRequest().denyAll() ) .exceptionHandling((exceptionHandling) -> exceptionHandling .defaultAccessDeniedHandlerFor( this.teapotDeniedHandler, pathPattern("/hello/**") ) .defaultAccessDeniedHandlerFor( new AccessDeniedHandlerImpl(), AnyRequestMatcher.INSTANCE ) ); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class SingleRequestMatcherAccessDeniedHandlerConfig { AccessDeniedHandler teapotDeniedHandler = (request, response, exception) -> response .setStatus(HttpStatus.I_AM_A_TEAPOT.value()); @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((requests) -> requests .anyRequest().denyAll()) .exceptionHandling((handling) -> handling .defaultAccessDeniedHandlerFor( this.teapotDeniedHandler, pathPattern("/hello/**"))); return http.build(); // @formatter:on } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/annotation/web/configurers/ExceptionHandlingConfigurerTests.java ================================================ /* * 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 org.springframework.security.config.annotation.web.configurers; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.security.authentication.AnonymousAuthenticationToken; import org.springframework.security.config.ObjectPostProcessor; import org.springframework.security.config.annotation.SecurityContextChangedListenerConfig; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.context.SecurityContextChangedListener; import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.security.core.userdetails.PasswordEncodedUser; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.access.ExceptionTranslationFilter; import org.springframework.test.web.servlet.MockMvc; import org.springframework.web.accept.ContentNegotiationStrategy; import org.springframework.web.context.request.NativeWebRequest; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.springframework.security.config.Customizer.withDefaults; import static org.springframework.security.config.annotation.SecurityContextChangedListenerArgumentMatchers.setAuthentication; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Tests for {@link ExceptionHandlingConfigurer} * * @author Rob Winch * @author Josh Cummings */ @ExtendWith(SpringTestContextExtension.class) public class ExceptionHandlingConfigurerTests { public final SpringTestContext spring = new SpringTestContext(this); @Autowired MockMvc mvc; @Test public void configureWhenRegisteringObjectPostProcessorThenInvokedOnExceptionTranslationFilter() { this.spring.register(ObjectPostProcessorConfig.class, DefaultSecurityConfig.class).autowire(); verify(ObjectPostProcessorConfig.objectPostProcessor).postProcess(any(ExceptionTranslationFilter.class)); } // SEC-2199 @Test public void getWhenAcceptHeaderIsApplicationXhtmlXmlThenRespondsWith302() throws Exception { this.spring.register(HttpBasicAndFormLoginEntryPointsConfig.class).autowire(); this.mvc.perform(get("/").header(HttpHeaders.ACCEPT, MediaType.APPLICATION_XHTML_XML)) .andExpect(status().isFound()); } // SEC-2199 @Test public void getWhenAcceptHeaderIsImageGifThenRespondsWith302() throws Exception { this.spring.register(HttpBasicAndFormLoginEntryPointsConfig.class).autowire(); this.mvc.perform(get("/").header(HttpHeaders.ACCEPT, MediaType.IMAGE_GIF)).andExpect(status().isFound()); } // SEC-2199 @Test public void getWhenAcceptHeaderIsImageJpgThenRespondsWith302() throws Exception { this.spring.register(HttpBasicAndFormLoginEntryPointsConfig.class).autowire(); this.mvc.perform(get("/").header(HttpHeaders.ACCEPT, MediaType.IMAGE_JPEG)).andExpect(status().isFound()); } // SEC-2199 @Test public void getWhenAcceptHeaderIsImagePngThenRespondsWith302() throws Exception { this.spring.register(HttpBasicAndFormLoginEntryPointsConfig.class).autowire(); this.mvc.perform(get("/").header(HttpHeaders.ACCEPT, MediaType.IMAGE_PNG)).andExpect(status().isFound()); } // SEC-2199 @Test public void getWhenAcceptHeaderIsTextHtmlThenRespondsWith302() throws Exception { this.spring.register(HttpBasicAndFormLoginEntryPointsConfig.class).autowire(); this.mvc.perform(get("/").header(HttpHeaders.ACCEPT, MediaType.TEXT_HTML)).andExpect(status().isFound()); } // SEC-2199 @Test public void getWhenAcceptHeaderIsTextPlainThenRespondsWith302() throws Exception { this.spring.register(HttpBasicAndFormLoginEntryPointsConfig.class).autowire(); this.mvc.perform(get("/").header(HttpHeaders.ACCEPT, MediaType.TEXT_PLAIN)).andExpect(status().isFound()); } // SEC-2199 @Test public void getWhenAcceptHeaderIsApplicationAtomXmlThenRespondsWith401() throws Exception { this.spring.register(HttpBasicAndFormLoginEntryPointsConfig.class).autowire(); this.mvc.perform(get("/").header(HttpHeaders.ACCEPT, MediaType.APPLICATION_ATOM_XML)) .andExpect(status().isUnauthorized()); } // SEC-2199 @Test public void getWhenAcceptHeaderIsApplicationFormUrlEncodedThenRespondsWith401() throws Exception { this.spring.register(HttpBasicAndFormLoginEntryPointsConfig.class).autowire(); this.mvc.perform(get("/").header(HttpHeaders.ACCEPT, MediaType.APPLICATION_FORM_URLENCODED)) .andExpect(status().isUnauthorized()); } // SEC-2199 @Test public void getWhenAcceptHeaderIsApplicationJsonThenRespondsWith401() throws Exception { this.spring.register(HttpBasicAndFormLoginEntryPointsConfig.class).autowire(); this.mvc.perform(get("/").header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON)) .andExpect(status().isUnauthorized()); } // SEC-2199 @Test public void getWhenAcceptHeaderIsApplicationOctetStreamThenRespondsWith401() throws Exception { this.spring.register(HttpBasicAndFormLoginEntryPointsConfig.class).autowire(); this.mvc.perform(get("/").header(HttpHeaders.ACCEPT, MediaType.APPLICATION_OCTET_STREAM)) .andExpect(status().isUnauthorized()); } // SEC-2199 @Test public void getWhenAcceptHeaderIsMultipartFormDataThenRespondsWith401() throws Exception { this.spring.register(HttpBasicAndFormLoginEntryPointsConfig.class).autowire(); this.mvc.perform(get("/").header(HttpHeaders.ACCEPT, MediaType.MULTIPART_FORM_DATA)) .andExpect(status().isUnauthorized()); } // SEC-2199 @Test public void getWhenAcceptHeaderIsTextXmlThenRespondsWith401() throws Exception { this.spring.register(HttpBasicAndFormLoginEntryPointsConfig.class).autowire(); this.mvc.perform(get("/").header(HttpHeaders.ACCEPT, MediaType.TEXT_XML)).andExpect(status().isUnauthorized()); } // gh-4831 @Test public void getWhenAcceptIsAnyThenRespondsWith401() throws Exception { this.spring.register(DefaultSecurityConfig.class).autowire(); this.mvc.perform(get("/").header(HttpHeaders.ACCEPT, MediaType.ALL)).andExpect(status().isUnauthorized()); } @Test public void getWhenAcceptIsChromeThenRespondsWith302() throws Exception { this.spring.register(DefaultSecurityConfig.class).autowire(); this.mvc .perform(get("/").header(HttpHeaders.ACCEPT, "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8")) .andExpect(status().isFound()); } @Test public void getWhenAcceptIsTextPlainAndXRequestedWithIsXHRThenRespondsWith401() throws Exception { this.spring.register(HttpBasicAndFormLoginEntryPointsConfig.class).autowire(); this.mvc.perform(get("/").header("Accept", MediaType.TEXT_PLAIN).header("X-Requested-With", "XMLHttpRequest")) .andExpect(status().isUnauthorized()); } @Test public void getWhenCustomContentNegotiationStrategyThenStrategyIsUsed() throws Exception { this.spring.register(OverrideContentNegotiationStrategySharedObjectConfig.class, DefaultSecurityConfig.class) .autowire(); this.mvc.perform(get("/")); verify(OverrideContentNegotiationStrategySharedObjectConfig.CNS, atLeastOnce()) .resolveMediaTypes(any(NativeWebRequest.class)); } @Test public void getWhenCustomSecurityContextHolderStrategyThenUsed() throws Exception { this.spring.register(SecurityContextChangedListenerConfig.class, DefaultSecurityConfig.class).autowire(); this.mvc.perform(get("/")); SecurityContextHolderStrategy strategy = this.spring.getContext().getBean(SecurityContextHolderStrategy.class); verify(strategy, atLeastOnce()).getContext(); SecurityContextChangedListener listener = this.spring.getContext() .getBean(SecurityContextChangedListener.class); verify(listener).securityContextChanged(setAuthentication(AnonymousAuthenticationToken.class)); } @Test public void getWhenUsingDefaultsAndUnauthenticatedThenRedirectsToLogin() throws Exception { this.spring.register(DefaultHttpConfig.class).autowire(); this.mvc.perform(get("/").header(HttpHeaders.ACCEPT, "bogus/type")).andExpect(redirectedUrl("/login")); } @Test public void getWhenDeclaringHttpBasicBeforeFormLoginThenRespondsWith401() throws Exception { this.spring.register(BasicAuthenticationEntryPointBeforeFormLoginConfig.class).autowire(); this.mvc.perform(get("/").header(HttpHeaders.ACCEPT, "bogus/type")).andExpect(status().isUnauthorized()); } @Test public void getWhenInvokingExceptionHandlingTwiceThenOriginalEntryPointUsed() throws Exception { this.spring.register(InvokeTwiceDoesNotOverrideConfig.class).autowire(); this.mvc.perform(get("/")); verify(InvokeTwiceDoesNotOverrideConfig.AEP).commence(any(HttpServletRequest.class), any(HttpServletResponse.class), any(AuthenticationException.class)); } @Configuration @EnableWebSecurity static class ObjectPostProcessorConfig { static ObjectPostProcessor objectPostProcessor = spy(ReflectingObjectPostProcessor.class); @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .exceptionHandling(withDefaults()); return http.build(); // @formatter:on } @Bean static ObjectPostProcessor objectPostProcessor() { return objectPostProcessor; } } static class ReflectingObjectPostProcessor implements ObjectPostProcessor { @Override public O postProcess(O object) { return object; } } @Configuration @EnableWebSecurity static class DefaultSecurityConfig { @Bean InMemoryUserDetailsManager userDetailsManager() { // @formatter:off return new InMemoryUserDetailsManager(User.withDefaultPasswordEncoder() .username("user") .password("password") .roles("USER") .build() ); // @formatter:off } } @Configuration @EnableWebSecurity static class HttpBasicAndFormLoginEntryPointsConfig { @Bean UserDetailsService userDetailsService() { return new InMemoryUserDetailsManager(PasswordEncodedUser.user()); } @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((requests) -> requests .anyRequest().authenticated()) .httpBasic(withDefaults()) .formLogin(withDefaults()); // @formatter:on return http.build(); } } @Configuration @EnableWebSecurity static class OverrideContentNegotiationStrategySharedObjectConfig { static ContentNegotiationStrategy CNS = mock(ContentNegotiationStrategy.class); @Bean static ContentNegotiationStrategy cns() { return CNS; } } @Configuration @EnableWebSecurity static class DefaultHttpConfig { } @Configuration @EnableWebSecurity static class BasicAuthenticationEntryPointBeforeFormLoginConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((requests) -> requests .anyRequest().authenticated()) .httpBasic(withDefaults()) .formLogin(withDefaults()); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class InvokeTwiceDoesNotOverrideConfig { static AuthenticationEntryPoint AEP = mock(AuthenticationEntryPoint.class); @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((requests) -> requests .anyRequest().authenticated()) .exceptionHandling((handling) -> handling .authenticationEntryPoint(AEP)) .exceptionHandling(withDefaults()); return http.build(); // @formatter:on } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/annotation/web/configurers/FormLoginConfigurerTests.java ================================================ /* * 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 org.springframework.security.config.annotation.web.configurers; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.authorization.AllAuthoritiesAuthorizationManager; import org.springframework.security.authorization.AuthenticatedAuthorizationManager; import org.springframework.security.authorization.AuthorityAuthorizationManager; import org.springframework.security.authorization.AuthorizationDecision; import org.springframework.security.authorization.AuthorizationManager; import org.springframework.security.authorization.AuthorizationManagers; import org.springframework.security.config.Customizer; import org.springframework.security.config.ObjectPostProcessor; import org.springframework.security.config.annotation.SecurityContextChangedListenerConfig; import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.config.users.AuthenticationTestConfiguration; import org.springframework.security.core.authority.FactorGrantedAuthority; import org.springframework.security.core.context.SecurityContextChangedListener; import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.security.core.userdetails.PasswordEncodedUser; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders; import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors; import org.springframework.security.web.PortMapper; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.access.ExceptionTranslationFilter; import org.springframework.security.web.access.intercept.RequestAuthorizationContext; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.authentication.ott.OneTimeTokenGenerationSuccessHandler; import org.springframework.security.web.authentication.ott.RedirectOneTimeTokenGenerationSuccessHandler; import org.springframework.security.web.savedrequest.RequestCache; import org.springframework.test.web.servlet.MockMvc; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.springframework.security.config.Customizer.withDefaults; import static org.springframework.security.config.annotation.SecurityContextChangedListenerArgumentMatchers.setAuthentication; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.logout; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user; import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.forwardedUrl; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * @author Rob Winch * @author Eleftheria Stein * @since 5.1 */ @ExtendWith(SpringTestContextExtension.class) public class FormLoginConfigurerTests { public final SpringTestContext spring = new SpringTestContext(this); @Autowired private MockMvc mockMvc; @Test public void requestCache() throws Exception { this.spring.register(RequestCacheConfig.class, AuthenticationTestConfiguration.class).autowire(); RequestCacheConfig config = this.spring.getContext().getBean(RequestCacheConfig.class); this.mockMvc.perform(formLogin()).andExpect(authenticated()); verify(config.requestCache).getRequest(any(), any()); } @Test public void requestCacheAsBean() throws Exception { this.spring.register(RequestCacheBeanConfig.class, AuthenticationTestConfiguration.class).autowire(); RequestCache requestCache = this.spring.getContext().getBean(RequestCache.class); this.mockMvc.perform(formLogin()).andExpect(authenticated()); verify(requestCache).getRequest(any(), any()); } @Test public void loginWhenFormLoginConfiguredThenHasDefaultUsernameAndPasswordParameterNames() throws Exception { this.spring.register(FormLoginConfig.class).autowire(); // @formatter:off SecurityMockMvcRequestBuilders.FormLoginRequestBuilder loginRequest = formLogin() .user("username", "user") .password("password", "password"); this.mockMvc.perform(loginRequest) .andExpect(status().isFound()) .andExpect(redirectedUrl("/")); // @formatter:on } @Test public void formLoginWhenSecurityContextHolderStrategyThenUses() throws Exception { this.spring.register(FormLoginConfig.class, SecurityContextChangedListenerConfig.class).autowire(); // @formatter:off SecurityMockMvcRequestBuilders.FormLoginRequestBuilder loginRequest = formLogin() .user("username", "user") .password("password", "password"); this.mockMvc.perform(loginRequest) .andExpect(status().isFound()) .andExpect(redirectedUrl("/")); // @formatter:on SecurityContextHolderStrategy strategy = this.spring.getContext().getBean(SecurityContextHolderStrategy.class); verify(strategy, atLeastOnce()).getContext(); SecurityContextChangedListener listener = this.spring.getContext() .getBean(SecurityContextChangedListener.class); verify(listener).securityContextChanged(setAuthentication(UsernamePasswordAuthenticationToken.class)); } @Test public void loginWhenFormLoginConfiguredThenHasDefaultFailureUrl() throws Exception { this.spring.register(FormLoginConfig.class).autowire(); // @formatter:off this.mockMvc.perform(formLogin().user("invalid")) .andExpect(status().isFound()) .andExpect(redirectedUrl("/login?error")); // @formatter:on } @Test public void loginWhenFormLoginConfiguredThenHasDefaultSuccessUrl() throws Exception { this.spring.register(FormLoginConfig.class).autowire(); // @formatter:off this.mockMvc.perform(formLogin()) .andExpect(status().isFound()) .andExpect(redirectedUrl("/")); // @formatter:on } @Test public void getLoginPageWhenFormLoginConfiguredThenNotSecured() throws Exception { this.spring.register(FormLoginConfig.class).autowire(); this.mockMvc.perform(get("/login")).andExpect(status().isFound()); } @Test public void loginWhenFormLoginConfiguredThenSecured() throws Exception { this.spring.register(FormLoginConfig.class).autowire(); this.mockMvc.perform(post("/login")).andExpect(status().isForbidden()); } @Test public void requestProtectedWhenFormLoginConfiguredThenRedirectsToLogin() throws Exception { this.spring.register(FormLoginConfig.class).autowire(); // @formatter:off this.mockMvc.perform(get("/private")) .andExpect(status().isFound()) .andExpect(redirectedUrl("/login")); // @formatter:on } @Test public void loginWhenFormLoginDefaultsInLambdaThenHasDefaultUsernameAndPasswordParameterNames() throws Exception { this.spring.register(FormLoginInLambdaConfig.class).autowire(); // @formatter:off SecurityMockMvcRequestBuilders.FormLoginRequestBuilder loginRequest = formLogin() .user("username", "user") .password("password", "password"); this.mockMvc.perform(loginRequest) .andExpect(status().isFound()) .andExpect(redirectedUrl("/")); // @formatter:on } @Test public void loginWhenFormLoginDefaultsInLambdaThenHasDefaultFailureUrl() throws Exception { this.spring.register(FormLoginInLambdaConfig.class).autowire(); // @formatter:off this.mockMvc.perform(formLogin().user("invalid")) .andExpect(status().isFound()) .andExpect(redirectedUrl("/login?error")); // @formatter:on } @Test public void loginWhenFormLoginDefaultsInLambdaThenHasDefaultSuccessUrl() throws Exception { this.spring.register(FormLoginInLambdaConfig.class).autowire(); // @formatter:off this.mockMvc.perform(formLogin()) .andExpect(status().isFound()) .andExpect(redirectedUrl("/")); // @formatter:on } @Test public void getLoginPageWhenFormLoginDefaultsInLambdaThenNotSecured() throws Exception { this.spring.register(FormLoginInLambdaConfig.class).autowire(); this.mockMvc.perform(get("/login")).andExpect(status().isOk()); } @Test public void loginWhenFormLoginDefaultsInLambdaThenSecured() throws Exception { this.spring.register(FormLoginInLambdaConfig.class).autowire(); this.mockMvc.perform(post("/login")).andExpect(status().isForbidden()); } @Test public void requestProtectedWhenFormLoginDefaultsInLambdaThenRedirectsToLogin() throws Exception { this.spring.register(FormLoginInLambdaConfig.class).autowire(); // @formatter:off this.mockMvc.perform(get("/private")) .andExpect(status().isFound()) .andExpect(redirectedUrl("/login")); // @formatter:on } @Test public void getLoginPageWhenFormLoginPermitAllThenPermittedAndNoRedirect() throws Exception { this.spring.register(FormLoginConfigPermitAll.class).autowire(); // @formatter:off this.mockMvc.perform(get("/login")) .andExpect(status().isOk()) .andExpect(redirectedUrl(null)); // @formatter:on } @Test public void getLoginPageWithErrorQueryWhenFormLoginPermitAllThenPermittedAndNoRedirect() throws Exception { this.spring.register(FormLoginConfigPermitAll.class).autowire(); // @formatter:off this.mockMvc.perform(get("/login?error")) .andExpect(status().isOk()) .andExpect(redirectedUrl(null)); // @formatter:on } @Test public void loginWhenFormLoginPermitAllAndInvalidUserThenRedirectsToLoginPageWithError() throws Exception { this.spring.register(FormLoginConfigPermitAll.class).autowire(); // @formatter:off this.mockMvc.perform(formLogin().user("invalid")) .andExpect(status().isFound()) .andExpect(redirectedUrl("/login?error")); // @formatter:on } @Test public void getLoginPageWhenCustomLoginPageThenPermittedAndNoRedirect() throws Exception { this.spring.register(FormLoginDefaultsConfig.class).autowire(); this.mockMvc.perform(get("/authenticate")).andExpect(redirectedUrl(null)); } @Test public void getLoginPageWithErrorQueryWhenCustomLoginPageThenPermittedAndNoRedirect() throws Exception { this.spring.register(FormLoginDefaultsConfig.class).autowire(); this.mockMvc.perform(get("/authenticate?error")).andExpect(redirectedUrl(null)); } @Test public void loginWhenCustomLoginPageAndInvalidUserThenRedirectsToCustomLoginPageWithError() throws Exception { this.spring.register(FormLoginDefaultsConfig.class).autowire(); SecurityMockMvcRequestBuilders.FormLoginRequestBuilder request = formLogin("/authenticate").user("invalid"); // @formatter:off this.mockMvc.perform(request) .andExpect(status().isFound()) .andExpect(redirectedUrl("/authenticate?error")); // @formatter:on } @Test public void logoutWhenCustomLoginPageThenRedirectsToCustomLoginPage() throws Exception { this.spring.register(FormLoginDefaultsConfig.class).autowire(); this.mockMvc.perform(logout()).andExpect(redirectedUrl("/authenticate?logout")); } @Test public void getLoginPageWithLogoutQueryWhenCustomLoginPageThenPermittedAndNoRedirect() throws Exception { this.spring.register(FormLoginDefaultsConfig.class).autowire(); this.mockMvc.perform(get("/authenticate?logout")).andExpect(redirectedUrl(null)); } @Test public void getLoginPageWhenCustomLoginPageInLambdaThenPermittedAndNoRedirect() throws Exception { this.spring.register(FormLoginDefaultsInLambdaConfig.class).autowire(); this.mockMvc.perform(get("/authenticate")).andExpect(redirectedUrl(null)); } @Test public void loginWhenCustomLoginProcessingUrlThenRedirectsToHome() throws Exception { this.spring.register(FormLoginLoginProcessingUrlConfig.class).autowire(); // @formatter:off this.mockMvc.perform(formLogin("/loginCheck")) .andExpect(status().isFound()) .andExpect(redirectedUrl("/")); // @formatter:on } @Test public void loginWhenCustomLoginProcessingUrlInLambdaThenRedirectsToHome() throws Exception { this.spring.register(FormLoginLoginProcessingUrlInLambdaConfig.class).autowire(); // @formatter:off this.mockMvc.perform(formLogin("/loginCheck")) .andExpect(status().isFound()) .andExpect(redirectedUrl("/")); // @formatter:on } @Test public void requestWhenCustomPortMapperThenPortMapperUsed() throws Exception { FormLoginUsesPortMapperConfig.PORT_MAPPER = mock(PortMapper.class); given(FormLoginUsesPortMapperConfig.PORT_MAPPER.lookupHttpsPort(any())).willReturn(9443); this.spring.register(FormLoginUsesPortMapperConfig.class).autowire(); // @formatter:off this.mockMvc.perform(get("http://localhost:9090")) .andExpect(status().isFound()) .andExpect(redirectedUrl("https://localhost:9443/login")); // @formatter:on verify(FormLoginUsesPortMapperConfig.PORT_MAPPER).lookupHttpsPort(any()); } @Test public void failureUrlWhenPermitAllAndFailureHandlerThenSecured() throws Exception { this.spring.register(PermitAllIgnoresFailureHandlerConfig.class).autowire(); // @formatter:off this.mockMvc.perform(get("/login?error")) .andExpect(status().isFound()) .andExpect(redirectedUrl("/login")); // @formatter:on } @Test public void formLoginWhenInvokedTwiceThenUsesOriginalUsernameParameter() throws Exception { this.spring.register(DuplicateInvocationsDoesNotOverrideConfig.class).autowire(); SecurityMockMvcRequestBuilders.FormLoginRequestBuilder loginRequest = formLogin().user("custom-username", "user"); this.mockMvc.perform(loginRequest).andExpect(authenticated()); } @Test public void loginWhenInvalidLoginAndFailureForwardUrlThenForwardsToFailureForwardUrl() throws Exception { this.spring.register(FormLoginUserForwardAuthenticationSuccessAndFailureConfig.class).autowire(); SecurityMockMvcRequestBuilders.FormLoginRequestBuilder loginRequest = formLogin().user("invalid"); this.mockMvc.perform(loginRequest).andExpect(forwardedUrl("/failure_forward_url")); } @Test public void loginWhenSuccessForwardUrlThenForwardsToSuccessForwardUrl() throws Exception { this.spring.register(FormLoginUserForwardAuthenticationSuccessAndFailureConfig.class).autowire(); this.mockMvc.perform(formLogin()).andExpect(forwardedUrl("/success_forward_url")); } @Test public void configureWhenRegisteringObjectPostProcessorThenInvokedOnUsernamePasswordAuthenticationFilter() { ObjectPostProcessorConfig.objectPostProcessor = spy(ReflectingObjectPostProcessor.class); this.spring.register(ObjectPostProcessorConfig.class).autowire(); verify(ObjectPostProcessorConfig.objectPostProcessor) .postProcess(any(UsernamePasswordAuthenticationFilter.class)); } @Test public void configureWhenRegisteringObjectPostProcessorThenInvokedOnLoginUrlAuthenticationEntryPoint() { ObjectPostProcessorConfig.objectPostProcessor = spy(ReflectingObjectPostProcessor.class); this.spring.register(ObjectPostProcessorConfig.class).autowire(); verify(ObjectPostProcessorConfig.objectPostProcessor).postProcess(any(LoginUrlAuthenticationEntryPoint.class)); } @Test public void configureWhenRegisteringObjectPostProcessorThenInvokedOnExceptionTranslationFilter() { ObjectPostProcessorConfig.objectPostProcessor = spy(ReflectingObjectPostProcessor.class); this.spring.register(ObjectPostProcessorConfig.class).autowire(); verify(ObjectPostProcessorConfig.objectPostProcessor).postProcess(any(ExceptionTranslationFilter.class)); } @Test void requestWhenUnauthenticatedThenRequiresTwoSteps() throws Exception { this.spring.register(MfaDslConfig.class, UserConfig.class).autowire(); UserDetails user = PasswordEncodedUser.user(); this.mockMvc.perform(get("/profile").with(user(user))) .andExpect(status().is3xxRedirection()) .andExpect(redirectedUrl( "/login?factor.type=password&factor.type=ott&factor.reason=missing&factor.reason=missing")); this.mockMvc .perform(post("/ott/generate").param("username", "rod") .with(user(user)) .with(SecurityMockMvcRequestPostProcessors.csrf())) .andExpect(status().is3xxRedirection()) .andExpect(redirectedUrl("/ott/sent")); this.mockMvc .perform(post("/login").param("username", "rod") .param("password", "password") .with(SecurityMockMvcRequestPostProcessors.csrf())) .andExpect(status().is3xxRedirection()) .andExpect(redirectedUrl("/")); user = PasswordEncodedUser.withUserDetails(user) .authorities("profile:read", FactorGrantedAuthority.OTT_AUTHORITY) .build(); this.mockMvc.perform(get("/profile").with(user(user))) .andExpect(status().is3xxRedirection()) .andExpect(redirectedUrl("/login?factor.type=password&factor.reason=missing")); user = PasswordEncodedUser.withUserDetails(user) .authorities("profile:read", FactorGrantedAuthority.PASSWORD_AUTHORITY) .build(); this.mockMvc.perform(get("/profile").with(user(user))) .andExpect(status().is3xxRedirection()) .andExpect(redirectedUrl("/login?factor.type=ott&factor.reason=missing")); user = PasswordEncodedUser.withUserDetails(user) .authorities("profile:read", FactorGrantedAuthority.PASSWORD_AUTHORITY, FactorGrantedAuthority.OTT_AUTHORITY) .build(); this.mockMvc.perform(get("/profile").with(user(user))).andExpect(status().isNotFound()); } @Test void requestWhenUnauthenticatedX509ThenRequiresTwoSteps() throws Exception { this.spring.register(MfaDslX509Config.class, UserConfig.class, BasicMfaController.class).autowire(); this.mockMvc.perform(get("/profile")).andExpect(status().is3xxRedirection()); this.mockMvc.perform(get("/profile").with(user(User.withUsername("rod").authorities("profile:read").build()))) .andExpect(status().isForbidden()); this.mockMvc.perform(get("/login")).andExpect(status().isOk()); this.mockMvc.perform(get("/profile").with(SecurityMockMvcRequestPostProcessors.x509("rod.cer"))) .andExpect(status().is3xxRedirection()) .andExpect(redirectedUrl("/login?factor.type=password&factor.reason=missing")); this.mockMvc .perform(post("/login").param("username", "rod") .param("password", "password") .with(SecurityMockMvcRequestPostProcessors.x509("rod.cer")) .with(SecurityMockMvcRequestPostProcessors.csrf())) .andExpect(status().is3xxRedirection()) .andExpect(redirectedUrl("/")); UserDetails authorized = PasswordEncodedUser.withUsername("rod") .authorities("profile:read", FactorGrantedAuthority.X509_AUTHORITY, FactorGrantedAuthority.PASSWORD_AUTHORITY) .build(); this.mockMvc.perform(get("/profile").with(user(authorized))).andExpect(status().isOk()); } @Configuration @EnableWebSecurity static class RequestCacheConfig { private RequestCache requestCache = mock(RequestCache.class); @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .formLogin(withDefaults()) .requestCache((cache) -> cache .requestCache(this.requestCache)); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class RequestCacheBeanConfig { @Bean RequestCache requestCache() { return mock(RequestCache.class); } } @Configuration @EnableWebSecurity @EnableWebMvc static class FormLoginConfig { @Bean WebSecurityCustomizer webSecurityCustomizer() { return (web) -> web.ignoring().requestMatchers("/resources/**"); } @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((requests) -> requests .anyRequest().hasRole("USER")) .formLogin((login) -> login .loginPage("/login")); // @formatter:on return http.build(); } @Bean UserDetailsService userDetailsService() { return new InMemoryUserDetailsManager(PasswordEncodedUser.user()); } } @Configuration @EnableWebSecurity static class FormLoginInLambdaConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((authorize) -> authorize .anyRequest().hasRole("USER") ) .formLogin(withDefaults()); // @formatter:on return http.build(); } @Bean UserDetailsService userDetailsService() { return new InMemoryUserDetailsManager(PasswordEncodedUser.user()); } } @Configuration @EnableWebSecurity static class FormLoginConfigPermitAll { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((requests) -> requests .anyRequest().hasRole("USER")) .formLogin((login) -> login .permitAll()); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class FormLoginDefaultsConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((requests) -> requests .anyRequest().hasRole("USER")) .formLogin((login) -> login .loginPage("/authenticate") .permitAll()) .logout((logout) -> logout .permitAll()); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class FormLoginDefaultsInLambdaConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((authorize) -> authorize .anyRequest().hasRole("USER") ) .formLogin((formLogin) -> formLogin .loginPage("/authenticate") .permitAll() ) .logout(LogoutConfigurer::permitAll); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class FormLoginLoginProcessingUrlConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((requests) -> requests .anyRequest().authenticated()) .formLogin((login) -> login .loginProcessingUrl("/loginCheck") .loginPage("/login") .defaultSuccessUrl("/", true) .passwordParameter("password") .usernameParameter("username") .permitAll()) .logout((logout) -> logout .logoutSuccessUrl("/login") .logoutUrl("/logout") .deleteCookies("JSESSIONID")); // @formatter:on return http.build(); } @Bean UserDetailsService userDetailsService() { return new InMemoryUserDetailsManager(PasswordEncodedUser.user()); } } @Configuration @EnableWebSecurity static class FormLoginLoginProcessingUrlInLambdaConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((authorize) -> authorize .anyRequest().authenticated() ) .formLogin((formLogin) -> formLogin .loginProcessingUrl("/loginCheck") .loginPage("/login") .defaultSuccessUrl("/", true) .permitAll() ) .logout((logout) -> logout .logoutSuccessUrl("/login") .logoutUrl("/logout") .deleteCookies("JSESSIONID") ); // @formatter:on return http.build(); } @Bean UserDetailsService userDetailsService() { return new InMemoryUserDetailsManager(PasswordEncodedUser.user()); } } @Configuration @EnableWebSecurity static class FormLoginUsesPortMapperConfig { static PortMapper PORT_MAPPER; @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((requests) -> requests .anyRequest().hasRole("USER")) .formLogin((login) -> login .permitAll()) .portMapper((mapper) -> mapper .portMapper(PORT_MAPPER)); // @formatter:on LoginUrlAuthenticationEntryPoint authenticationEntryPoint = (LoginUrlAuthenticationEntryPoint) http .getConfigurer(FormLoginConfigurer.class) .getAuthenticationEntryPoint(); authenticationEntryPoint.setForceHttps(true); return http.build(); } } @Configuration @EnableWebSecurity static class PermitAllIgnoresFailureHandlerConfig { static AuthenticationFailureHandler FAILURE_HANDLER = mock(AuthenticationFailureHandler.class); @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((requests) -> requests .anyRequest().hasRole("USER")) .formLogin((login) -> login .failureHandler(FAILURE_HANDLER) .permitAll()); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class DuplicateInvocationsDoesNotOverrideConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .formLogin((login) -> login .usernameParameter("custom-username")) .formLogin(withDefaults()); // @formatter:on return http.build(); } @Bean UserDetailsService userDetailsService() { return new InMemoryUserDetailsManager(PasswordEncodedUser.user()); } } @Configuration @EnableWebSecurity static class FormLoginUserForwardAuthenticationSuccessAndFailureConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .csrf((csrf) -> csrf .disable()) .authorizeHttpRequests((requests) -> requests .anyRequest().authenticated()) .formLogin((login) -> login .failureForwardUrl("/failure_forward_url") .successForwardUrl("/success_forward_url") .permitAll()); // @formatter:on return http.build(); } @Bean UserDetailsService userDetailsService() { return new InMemoryUserDetailsManager(PasswordEncodedUser.user()); } } @Configuration @EnableWebSecurity static class ObjectPostProcessorConfig { static ObjectPostProcessor objectPostProcessor; @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .exceptionHandling(withDefaults()) .formLogin(withDefaults()); return http.build(); // @formatter:on } @Bean static ObjectPostProcessor objectPostProcessor() { return objectPostProcessor; } } static class ReflectingObjectPostProcessor implements ObjectPostProcessor { @Override public O postProcess(O object) { return object; } } @Configuration @EnableWebSecurity static class MfaDslConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http, AuthorizationManagerFactory authz) throws Exception { // @formatter:off http .formLogin(Customizer.withDefaults()) .oneTimeTokenLogin(Customizer.withDefaults()) .authorizeHttpRequests((authorize) -> authorize .requestMatchers("/profile").access(authz.hasAuthority("profile:read")) .anyRequest().access(authz.authenticated()) ); return http.build(); // @formatter:on } @Bean OneTimeTokenGenerationSuccessHandler tokenGenerationSuccessHandler() { return new RedirectOneTimeTokenGenerationSuccessHandler("/ott/sent"); } @Bean AuthorizationManagerFactory authz() { return new AuthorizationManagerFactory<>(FactorGrantedAuthority.PASSWORD_AUTHORITY, FactorGrantedAuthority.OTT_AUTHORITY); } } @Configuration @EnableWebSecurity @EnableMethodSecurity static class MfaDslX509Config { @Bean SecurityFilterChain filterChain(HttpSecurity http, AuthorizationManagerFactory authz) throws Exception { // @formatter:off http .x509(Customizer.withDefaults()) .formLogin(Customizer.withDefaults()) .authorizeHttpRequests((authorize) -> authorize .anyRequest().access(authz.authenticated()) ); return http.build(); // @formatter:on } @Bean AuthorizationManagerFactory authz() { return new AuthorizationManagerFactory<>(FactorGrantedAuthority.X509_AUTHORITY, FactorGrantedAuthority.PASSWORD_AUTHORITY); } } @Configuration static class UserConfig { @Bean UserDetails rod() { return PasswordEncodedUser.withUsername("rod").password("password").build(); } @Bean UserDetailsService users(UserDetails user) { return new InMemoryUserDetailsManager(user); } } @RestController static class BasicMfaController { @GetMapping("/profile") @PreAuthorize("@authz.hasAuthority('profile:read')") String profile() { return "profile"; } } public static class AuthorizationManagerFactory { private final AuthorizationManager authorities; AuthorizationManagerFactory(String... authorities) { this.authorities = AllAuthoritiesAuthorizationManager.hasAllAuthorities(authorities); } public AuthorizationManager authenticated() { AuthenticatedAuthorizationManager authenticated = AuthenticatedAuthorizationManager.authenticated(); return AuthorizationManagers.allOf(new AuthorizationDecision(false), this.authorities, authenticated); } public AuthorizationManager hasAuthority(String authority) { AuthorityAuthorizationManager authorized = AuthorityAuthorizationManager.hasAuthority(authority); return AuthorizationManagers.allOf(new AuthorizationDecision(false), this.authorities, authorized); } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/annotation/web/configurers/HeadersConfigurerEagerHeadersTests.java ================================================ /* * 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 org.springframework.security.config.annotation.web.configurers; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpHeaders; import org.springframework.security.config.ObjectPostProcessor; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.header.HeaderWriterFilter; import org.springframework.test.web.servlet.MockMvc; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; /** * Tests for {@link HeadersConfigurer}. * * @author Ankur Pathak */ @ExtendWith(SpringTestContextExtension.class) public class HeadersConfigurerEagerHeadersTests { public final SpringTestContext spring = new SpringTestContext(this); @Autowired MockMvc mvc; @Test public void requestWhenHeadersEagerlyConfiguredThenHeadersAreWritten() throws Exception { this.spring.register(HeadersAtTheBeginningOfRequestConfig.class, HomeController.class).autowire(); this.mvc.perform(get("/").secure(true)) .andExpect(header().string("X-Content-Type-Options", "nosniff")) .andExpect(header().string("X-Frame-Options", "DENY")) .andExpect(header().string("Strict-Transport-Security", "max-age=31536000 ; includeSubDomains")) .andExpect(header().string(HttpHeaders.CACHE_CONTROL, "no-cache, no-store, max-age=0, must-revalidate")) .andExpect(header().string(HttpHeaders.EXPIRES, "0")) .andExpect(header().string(HttpHeaders.PRAGMA, "no-cache")) .andExpect(header().string("X-XSS-Protection", "0")); } @Configuration @EnableWebSecurity public static class HeadersAtTheBeginningOfRequestConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .headers((headers) -> headers .addObjectPostProcessor(new ObjectPostProcessor() { @Override public HeaderWriterFilter postProcess(HeaderWriterFilter filter) { filter.setShouldWriteHeadersEagerly(true); return filter; } })); return http.build(); // @formatter:on } } @RestController private static class HomeController { @GetMapping("/") String ok() { return "ok"; } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/annotation/web/configurers/HeadersConfigurerTests.java ================================================ /* * 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 org.springframework.security.config.annotation.web.configurers; import java.net.URI; import java.util.LinkedHashMap; import java.util.Map; import com.google.common.net.HttpHeaders; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.header.writers.CrossOriginEmbedderPolicyHeaderWriter; import org.springframework.security.web.header.writers.CrossOriginOpenerPolicyHeaderWriter; import org.springframework.security.web.header.writers.CrossOriginResourcePolicyHeaderWriter; import org.springframework.security.web.header.writers.ReferrerPolicyHeaderWriter.ReferrerPolicy; import org.springframework.security.web.header.writers.XXssProtectionHeaderWriter; import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter.XFrameOptionsMode; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.ResultMatcher; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.springframework.security.config.Customizer.withDefaults; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; /** * Tests for {@link HeadersConfigurer}. * * @author Rob Winch * @author Tim Ysewyn * @author Joe Grandja * @author Eddú Meléndez * @author Vedran Pavic * @author Eleftheria Stein * @author Marcus Da Coregio * @author Daniel Garnier-Moiroux */ @ExtendWith(SpringTestContextExtension.class) public class HeadersConfigurerTests { public final SpringTestContext spring = new SpringTestContext(this); @Autowired MockMvc mvc; @Test public void getWhenHeadersConfiguredThenDefaultHeadersInResponse() throws Exception { this.spring.register(HeadersConfig.class).autowire(); MvcResult mvcResult = this.mvc.perform(get("/").secure(true)) .andExpect(header().string(HttpHeaders.X_CONTENT_TYPE_OPTIONS, "nosniff")) .andExpect(header().string(HttpHeaders.X_FRAME_OPTIONS, XFrameOptionsMode.DENY.name())) .andExpect(header().string(HttpHeaders.STRICT_TRANSPORT_SECURITY, "max-age=31536000 ; includeSubDomains")) .andExpect(header().string(HttpHeaders.CACHE_CONTROL, "no-cache, no-store, max-age=0, must-revalidate")) .andExpect(header().string(HttpHeaders.EXPIRES, "0")) .andExpect(header().string(HttpHeaders.PRAGMA, "no-cache")) .andExpect(header().string(HttpHeaders.X_XSS_PROTECTION, "0")) .andReturn(); assertThat(mvcResult.getResponse().getHeaderNames()).containsExactlyInAnyOrder( HttpHeaders.X_CONTENT_TYPE_OPTIONS, HttpHeaders.X_FRAME_OPTIONS, HttpHeaders.STRICT_TRANSPORT_SECURITY, HttpHeaders.CACHE_CONTROL, HttpHeaders.EXPIRES, HttpHeaders.PRAGMA, HttpHeaders.X_XSS_PROTECTION); } @Test public void getWhenHeadersConfiguredInLambdaThenDefaultHeadersInResponse() throws Exception { this.spring.register(HeadersInLambdaConfig.class).autowire(); MvcResult mvcResult = this.mvc.perform(get("/").secure(true)) .andExpect(header().string(HttpHeaders.X_CONTENT_TYPE_OPTIONS, "nosniff")) .andExpect(header().string(HttpHeaders.X_FRAME_OPTIONS, XFrameOptionsMode.DENY.name())) .andExpect(header().string(HttpHeaders.STRICT_TRANSPORT_SECURITY, "max-age=31536000 ; includeSubDomains")) .andExpect(header().string(HttpHeaders.CACHE_CONTROL, "no-cache, no-store, max-age=0, must-revalidate")) .andExpect(header().string(HttpHeaders.EXPIRES, "0")) .andExpect(header().string(HttpHeaders.PRAGMA, "no-cache")) .andExpect(header().string(HttpHeaders.X_XSS_PROTECTION, "0")) .andReturn(); assertThat(mvcResult.getResponse().getHeaderNames()).containsExactlyInAnyOrder( HttpHeaders.X_CONTENT_TYPE_OPTIONS, HttpHeaders.X_FRAME_OPTIONS, HttpHeaders.STRICT_TRANSPORT_SECURITY, HttpHeaders.CACHE_CONTROL, HttpHeaders.EXPIRES, HttpHeaders.PRAGMA, HttpHeaders.X_XSS_PROTECTION); } @Test public void getWhenHeaderDefaultsDisabledAndContentTypeConfiguredThenOnlyContentTypeHeaderInResponse() throws Exception { this.spring.register(ContentTypeOptionsConfig.class).autowire(); MvcResult mvcResult = this.mvc.perform(get("/")) .andExpect(header().string(HttpHeaders.X_CONTENT_TYPE_OPTIONS, "nosniff")) .andReturn(); assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly(HttpHeaders.X_CONTENT_TYPE_OPTIONS); } @Test public void getWhenOnlyContentTypeConfiguredInLambdaThenOnlyContentTypeHeaderInResponse() throws Exception { this.spring.register(ContentTypeOptionsInLambdaConfig.class).autowire(); MvcResult mvcResult = this.mvc.perform(get("/")) .andExpect(header().string(HttpHeaders.X_CONTENT_TYPE_OPTIONS, "nosniff")) .andReturn(); assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly(HttpHeaders.X_CONTENT_TYPE_OPTIONS); } @Test public void getWhenHeaderDefaultsDisabledAndFrameOptionsConfiguredThenOnlyFrameOptionsHeaderInResponse() throws Exception { this.spring.register(FrameOptionsConfig.class).autowire(); MvcResult mvcResult = this.mvc.perform(get("/")) .andExpect(header().string(HttpHeaders.X_FRAME_OPTIONS, XFrameOptionsMode.DENY.name())) .andReturn(); assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly(HttpHeaders.X_FRAME_OPTIONS); } @Test public void getWhenHeaderDefaultsDisabledAndHstsConfiguredThenOnlyStrictTransportSecurityHeaderInResponse() throws Exception { this.spring.register(HstsConfig.class).autowire(); MvcResult mvcResult = this.mvc.perform(get("/").secure(true)) .andExpect(header().string(HttpHeaders.STRICT_TRANSPORT_SECURITY, "max-age=31536000 ; includeSubDomains")) .andReturn(); assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly(HttpHeaders.STRICT_TRANSPORT_SECURITY); } @Test public void getWhenHeaderDefaultsDisabledAndCacheControlConfiguredThenCacheControlAndExpiresAndPragmaHeadersInResponse() throws Exception { this.spring.register(CacheControlConfig.class).autowire(); MvcResult mvcResult = this.mvc.perform(get("/").secure(true)) .andExpect(header().string(HttpHeaders.CACHE_CONTROL, "no-cache, no-store, max-age=0, must-revalidate")) .andExpect(header().string(HttpHeaders.EXPIRES, "0")) .andExpect(header().string(HttpHeaders.PRAGMA, "no-cache")) .andReturn(); assertThat(mvcResult.getResponse().getHeaderNames()).containsExactlyInAnyOrder(HttpHeaders.CACHE_CONTROL, HttpHeaders.EXPIRES, HttpHeaders.PRAGMA); } @Test public void getWhenOnlyCacheControlConfiguredInLambdaThenCacheControlAndExpiresAndPragmaHeadersInResponse() throws Exception { this.spring.register(CacheControlInLambdaConfig.class).autowire(); MvcResult mvcResult = this.mvc.perform(get("/").secure(true)) .andExpect(header().string(HttpHeaders.CACHE_CONTROL, "no-cache, no-store, max-age=0, must-revalidate")) .andExpect(header().string(HttpHeaders.EXPIRES, "0")) .andExpect(header().string(HttpHeaders.PRAGMA, "no-cache")) .andReturn(); assertThat(mvcResult.getResponse().getHeaderNames()).containsExactlyInAnyOrder(HttpHeaders.CACHE_CONTROL, HttpHeaders.EXPIRES, HttpHeaders.PRAGMA); } @Test public void getWhenHeaderDefaultsDisabledAndXssProtectionConfiguredThenOnlyXssProtectionHeaderInResponse() throws Exception { this.spring.register(XssProtectionConfig.class).autowire(); MvcResult mvcResult = this.mvc.perform(get("/").secure(true)) .andExpect(header().string(HttpHeaders.X_XSS_PROTECTION, "0")) .andReturn(); assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly(HttpHeaders.X_XSS_PROTECTION); } @Test public void getWhenHeaderDefaultsDisabledAndXssProtectionConfiguredEnabledModeBlockThenOnlyXssProtectionHeaderInResponse() throws Exception { this.spring.register(XssProtectionValueEnabledModeBlockConfig.class).autowire(); MvcResult mvcResult = this.mvc.perform(get("/").secure(true)) .andExpect(header().string(HttpHeaders.X_XSS_PROTECTION, "1; mode=block")) .andReturn(); assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly(HttpHeaders.X_XSS_PROTECTION); } @Test public void getWhenOnlyXssProtectionConfiguredInLambdaThenOnlyXssProtectionHeaderInResponse() throws Exception { this.spring.register(XssProtectionInLambdaConfig.class).autowire(); MvcResult mvcResult = this.mvc.perform(get("/").secure(true)) .andExpect(header().string(HttpHeaders.X_XSS_PROTECTION, "0")) .andReturn(); assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly(HttpHeaders.X_XSS_PROTECTION); } @Test public void getWhenHeaderDefaultsDisabledAndXssProtectionConfiguredValueEnabledModeBlockInLambdaThenOnlyXssProtectionHeaderInResponse() throws Exception { this.spring.register(XssProtectionValueEnabledModeBlockInLambdaConfig.class).autowire(); MvcResult mvcResult = this.mvc.perform(get("/").secure(true)) .andExpect(header().string(HttpHeaders.X_XSS_PROTECTION, "1; mode=block")) .andReturn(); assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly(HttpHeaders.X_XSS_PROTECTION); } @Test public void getWhenFrameOptionsSameOriginConfiguredThenFrameOptionsHeaderHasValueSameOrigin() throws Exception { this.spring.register(HeadersCustomSameOriginConfig.class).autowire(); this.mvc.perform(get("/").secure(true)) .andExpect(header().string(HttpHeaders.X_FRAME_OPTIONS, XFrameOptionsMode.SAMEORIGIN.name())) .andReturn(); } @Test public void getWhenFrameOptionsSameOriginConfiguredInLambdaThenFrameOptionsHeaderHasValueSameOrigin() throws Exception { this.spring.register(HeadersCustomSameOriginInLambdaConfig.class).autowire(); this.mvc.perform(get("/").secure(true)) .andExpect(header().string(HttpHeaders.X_FRAME_OPTIONS, XFrameOptionsMode.SAMEORIGIN.name())) .andReturn(); } @Test public void getWhenHeaderDefaultsDisabledAndPublicHpkpWithNoPinThenNoHeadersInResponse() throws Exception { this.spring.register(HpkpConfigNoPins.class).autowire(); MvcResult mvcResult = this.mvc.perform(get("/")).andReturn(); assertThat(mvcResult.getResponse().getHeaderNames()).isEmpty(); } @Test public void getWhenSecureRequestAndHpkpWithPinThenPublicKeyPinsReportOnlyHeaderInResponse() throws Exception { this.spring.register(HpkpConfig.class).autowire(); ResultMatcher pinsReportOnly = header().string(HttpHeaders.PUBLIC_KEY_PINS_REPORT_ONLY, "max-age=5184000 ; pin-sha256=\"d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=\""); // @formatter:off MvcResult mvcResult = this.mvc.perform(get("/").secure(true)) .andExpect(pinsReportOnly) .andReturn(); // @formatter:on assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly(HttpHeaders.PUBLIC_KEY_PINS_REPORT_ONLY); } @Test public void getWhenInsecureRequestHeaderDefaultsDisabledAndHpkpWithPinThenNoHeadersInResponse() throws Exception { this.spring.register(HpkpConfig.class).autowire(); MvcResult mvcResult = this.mvc.perform(get("/")).andReturn(); assertThat(mvcResult.getResponse().getHeaderNames()).isEmpty(); } @Test public void getWhenHpkpWithMultiplePinsThenPublicKeyPinsReportOnlyHeaderWithMultiplePinsInResponse() throws Exception { this.spring.register(HpkpConfigWithPins.class).autowire(); ResultMatcher pinsReportOnly = header().string(HttpHeaders.PUBLIC_KEY_PINS_REPORT_ONLY, "max-age=5184000 ; pin-sha256=\"d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=\" ; pin-sha256=\"E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=\""); // @formatter:off MvcResult mvcResult = this.mvc.perform(get("/").secure(true)) .andExpect(pinsReportOnly) .andReturn(); // @formatter:on assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly(HttpHeaders.PUBLIC_KEY_PINS_REPORT_ONLY); } @Test public void getWhenHpkpWithCustomAgeThenPublicKeyPinsReportOnlyHeaderWithCustomAgeInResponse() throws Exception { this.spring.register(HpkpConfigCustomAge.class).autowire(); ResultMatcher pinsReportOnly = header().string(HttpHeaders.PUBLIC_KEY_PINS_REPORT_ONLY, "max-age=604800 ; pin-sha256=\"d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=\""); // @formatter:off MvcResult mvcResult = this.mvc.perform(get("/").secure(true)) .andExpect(pinsReportOnly) .andReturn(); // @formatter:on assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly(HttpHeaders.PUBLIC_KEY_PINS_REPORT_ONLY); } @Test public void getWhenHpkpWithReportOnlyFalseThenPublicKeyPinsHeaderInResponse() throws Exception { this.spring.register(HpkpConfigTerminateConnection.class).autowire(); ResultMatcher pins = header().string(HttpHeaders.PUBLIC_KEY_PINS, "max-age=5184000 ; pin-sha256=\"d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=\""); // @formatter:off MvcResult mvcResult = this.mvc.perform(get("/").secure(true)) .andExpect(pins) .andReturn(); // @formatter:on assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly(HttpHeaders.PUBLIC_KEY_PINS); } @Test public void getWhenHpkpIncludeSubdomainThenPublicKeyPinsReportOnlyHeaderWithIncludeSubDomainsInResponse() throws Exception { this.spring.register(HpkpConfigIncludeSubDomains.class).autowire(); ResultMatcher pinsReportOnly = header().string(HttpHeaders.PUBLIC_KEY_PINS_REPORT_ONLY, "max-age=5184000 ; pin-sha256=\"d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=\" ; includeSubDomains"); // @formatter:off MvcResult mvcResult = this.mvc.perform(get("/").secure(true)) .andExpect(pinsReportOnly) .andReturn(); // @formatter:on assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly(HttpHeaders.PUBLIC_KEY_PINS_REPORT_ONLY); } @Test public void getWhenHpkpWithReportUriThenPublicKeyPinsReportOnlyHeaderWithReportUriInResponse() throws Exception { this.spring.register(HpkpConfigWithReportURI.class).autowire(); ResultMatcher pinsReportOnly = header().string(HttpHeaders.PUBLIC_KEY_PINS_REPORT_ONLY, "max-age=5184000 ; pin-sha256=\"d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=\" ; report-uri=\"https://example.net/pkp-report\""); // @formatter:off MvcResult mvcResult = this.mvc.perform(get("/").secure(true)) .andExpect(pinsReportOnly) .andReturn(); // @formatter:on assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly(HttpHeaders.PUBLIC_KEY_PINS_REPORT_ONLY); } @Test public void getWhenHpkpWithReportUriAsStringThenPublicKeyPinsReportOnlyHeaderWithReportUriInResponse() throws Exception { this.spring.register(HpkpConfigWithReportURIAsString.class).autowire(); ResultMatcher pinsReportOnly = header().string(HttpHeaders.PUBLIC_KEY_PINS_REPORT_ONLY, "max-age=5184000 ; pin-sha256=\"d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=\" ; report-uri=\"https://example.net/pkp-report\""); // @formatter:off MvcResult mvcResult = this.mvc.perform(get("/").secure(true)) .andExpect(pinsReportOnly) .andReturn(); // @formatter:on assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly(HttpHeaders.PUBLIC_KEY_PINS_REPORT_ONLY); } @Test public void getWhenHpkpWithReportUriInLambdaThenPublicKeyPinsReportOnlyHeaderWithReportUriInResponse() throws Exception { this.spring.register(HpkpWithReportUriInLambdaConfig.class).autowire(); ResultMatcher pinsReportOnly = header().string(HttpHeaders.PUBLIC_KEY_PINS_REPORT_ONLY, "max-age=5184000 ; pin-sha256=\"d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=\" ; report-uri=\"https://example.net/pkp-report\""); // @formatter:off MvcResult mvcResult = this.mvc.perform(get("/").secure(true)) .andExpect(pinsReportOnly) .andReturn(); // @formatter:on assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly(HttpHeaders.PUBLIC_KEY_PINS_REPORT_ONLY); } @Test public void getWhenContentSecurityPolicyConfiguredThenContentSecurityPolicyHeaderInResponse() throws Exception { this.spring.register(ContentSecurityPolicyDefaultConfig.class).autowire(); ResultMatcher csp = header().string(HttpHeaders.CONTENT_SECURITY_POLICY, "default-src 'self'"); // @formatter:off MvcResult mvcResult = this.mvc.perform(get("/").secure(true)) .andExpect(csp) .andReturn(); // @formatter:on assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly(HttpHeaders.CONTENT_SECURITY_POLICY); } @Test public void getWhenContentSecurityPolicyWithReportOnlyThenContentSecurityPolicyReportOnlyHeaderInResponse() throws Exception { this.spring.register(ContentSecurityPolicyReportOnlyConfig.class).autowire(); ResultMatcher cspReportOnly = header().string(HttpHeaders.CONTENT_SECURITY_POLICY_REPORT_ONLY, "default-src 'self'; script-src trustedscripts.example.com"); // @formatter:off MvcResult mvcResult = this.mvc.perform(get("/").secure(true)) .andExpect(cspReportOnly) .andReturn(); // @formatter:on assertThat(mvcResult.getResponse().getHeaderNames()) .containsExactly(HttpHeaders.CONTENT_SECURITY_POLICY_REPORT_ONLY); } @Test public void getWhenContentSecurityPolicyWithReportOnlyInLambdaThenContentSecurityPolicyReportOnlyHeaderInResponse() throws Exception { this.spring.register(ContentSecurityPolicyReportOnlyInLambdaConfig.class).autowire(); ResultMatcher csp = header().string(HttpHeaders.CONTENT_SECURITY_POLICY_REPORT_ONLY, "default-src 'self'; script-src trustedscripts.example.com"); // @formatter:off MvcResult mvcResult = this.mvc.perform(get("/").secure(true)) .andExpect(csp) .andReturn(); // @formatter:on assertThat(mvcResult.getResponse().getHeaderNames()) .containsExactly(HttpHeaders.CONTENT_SECURITY_POLICY_REPORT_ONLY); } @Test public void configureWhenContentSecurityPolicyEmptyThenException() { assertThatExceptionOfType(BeanCreationException.class) .isThrownBy(() -> this.spring.register(ContentSecurityPolicyInvalidConfig.class).autowire()) .withRootCauseInstanceOf(IllegalArgumentException.class); } @Test public void configureWhenContentSecurityPolicyEmptyInLambdaThenException() { assertThatExceptionOfType(BeanCreationException.class) .isThrownBy(() -> this.spring.register(ContentSecurityPolicyInvalidInLambdaConfig.class).autowire()) .withRootCauseInstanceOf(IllegalArgumentException.class); } @Test public void configureWhenContentSecurityPolicyNoPolicyDirectivesInLambdaThenDefaultHeaderValue() throws Exception { this.spring.register(ContentSecurityPolicyNoDirectivesInLambdaConfig.class).autowire(); ResultMatcher csp = header().string(HttpHeaders.CONTENT_SECURITY_POLICY, "default-src 'self'"); // @formatter:off MvcResult mvcResult = this.mvc.perform(get("/").secure(true)) .andExpect(csp) .andReturn(); // @formatter:on assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly(HttpHeaders.CONTENT_SECURITY_POLICY); } @Test public void getWhenReferrerPolicyConfiguredThenReferrerPolicyHeaderInResponse() throws Exception { this.spring.register(ReferrerPolicyDefaultConfig.class).autowire(); ResultMatcher referrerPolicy = header().string("Referrer-Policy", ReferrerPolicy.NO_REFERRER.getPolicy()); // @formatter:off MvcResult mvcResult = this.mvc.perform(get("/").secure(true)) .andExpect(referrerPolicy) .andReturn(); // @formatter:on assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly("Referrer-Policy"); } @Test public void getWhenReferrerPolicyInLambdaThenReferrerPolicyHeaderInResponse() throws Exception { this.spring.register(ReferrerPolicyDefaultInLambdaConfig.class).autowire(); ResultMatcher referrerPolicy = header().string("Referrer-Policy", ReferrerPolicy.NO_REFERRER.getPolicy()); // @formatter:off MvcResult mvcResult = this.mvc.perform(get("/").secure(true)) .andExpect(referrerPolicy) .andReturn(); // @formatter:on assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly("Referrer-Policy"); } @Test public void getWhenReferrerPolicyConfiguredWithCustomValueThenReferrerPolicyHeaderWithCustomValueInResponse() throws Exception { this.spring.register(ReferrerPolicyCustomConfig.class).autowire(); ResultMatcher referrerPolicy = header().string("Referrer-Policy", ReferrerPolicy.SAME_ORIGIN.getPolicy()); // @formatter:off MvcResult mvcResult = this.mvc.perform(get("/").secure(true)) .andExpect(referrerPolicy) .andReturn(); // @formatter:on assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly("Referrer-Policy"); } @Test public void getWhenReferrerPolicyConfiguredWithCustomValueInLambdaThenCustomValueInResponse() throws Exception { this.spring.register(ReferrerPolicyCustomInLambdaConfig.class).autowire(); ResultMatcher referrerPolicy = header().string("Referrer-Policy", ReferrerPolicy.SAME_ORIGIN.getPolicy()); // @formatter:off MvcResult mvcResult = this.mvc.perform(get("/").secure(true)) .andExpect(referrerPolicy) .andReturn(); // @formatter:on assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly("Referrer-Policy"); } @Test public void getWhenFeaturePolicyConfiguredThenFeaturePolicyHeaderInResponse() throws Exception { this.spring.register(FeaturePolicyConfig.class).autowire(); ResultMatcher featurePolicy = header().string("Feature-Policy", "geolocation 'self'"); // @formatter:off MvcResult mvcResult = this.mvc.perform(get("/").secure(true)) .andExpect(featurePolicy) .andReturn(); // @formatter:on assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly("Feature-Policy"); } @Test public void configureWhenFeaturePolicyEmptyThenException() { assertThatExceptionOfType(BeanCreationException.class) .isThrownBy(() -> this.spring.register(FeaturePolicyInvalidConfig.class).autowire()) .withRootCauseInstanceOf(IllegalArgumentException.class); } @Test public void getWhenPermissionsPolicyConfiguredThenPermissionsPolicyHeaderInResponse() throws Exception { this.spring.register(PermissionsPolicyConfig.class).autowire(); ResultMatcher permissionsPolicy = header().string("Permissions-Policy", "geolocation=(self)"); // @formatter:off MvcResult mvcResult = this.mvc.perform(get("/").secure(true)) .andExpect(permissionsPolicy) .andReturn(); // @formatter:on assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly("Permissions-Policy"); } @Test public void getWhenPermissionsPolicyConfiguredWithStringThenPermissionsPolicyHeaderInResponse() throws Exception { this.spring.register(PermissionsPolicyStringConfig.class).autowire(); ResultMatcher permissionsPolicy = header().string("Permissions-Policy", "geolocation=(self)"); // @formatter:off MvcResult mvcResult = this.mvc.perform(get("/").secure(true)) .andExpect(permissionsPolicy) .andReturn(); // @formatter:on assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly("Permissions-Policy"); } @Test public void configureWhenPermissionsPolicyEmptyThenException() { assertThatExceptionOfType(BeanCreationException.class) .isThrownBy(() -> this.spring.register(PermissionsPolicyInvalidConfig.class).autowire()) .withRootCauseInstanceOf(IllegalArgumentException.class); } @Test public void configureWhenPermissionsPolicyStringEmptyThenException() { assertThatExceptionOfType(BeanCreationException.class) .isThrownBy(() -> this.spring.register(PermissionsPolicyInvalidStringConfig.class).autowire()) .withRootCauseInstanceOf(IllegalArgumentException.class); } @Test public void getWhenHstsConfiguredWithPreloadThenStrictTransportSecurityHeaderWithPreloadInResponse() throws Exception { this.spring.register(HstsWithPreloadConfig.class).autowire(); ResultMatcher hsts = header().string(HttpHeaders.STRICT_TRANSPORT_SECURITY, "max-age=31536000 ; includeSubDomains ; preload"); // @formatter:off MvcResult mvcResult = this.mvc.perform(get("/").secure(true)) .andExpect(hsts) .andReturn(); // @formatter:on assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly(HttpHeaders.STRICT_TRANSPORT_SECURITY); } @Test public void getWhenHstsConfiguredWithPreloadInLambdaThenStrictTransportSecurityHeaderWithPreloadInResponse() throws Exception { this.spring.register(HstsWithPreloadInLambdaConfig.class).autowire(); ResultMatcher hsts = header().string(HttpHeaders.STRICT_TRANSPORT_SECURITY, "max-age=31536000 ; includeSubDomains ; preload"); // @formatter:off MvcResult mvcResult = this.mvc.perform(get("/").secure(true)) .andExpect(hsts) .andReturn(); // @formatter:on assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly(HttpHeaders.STRICT_TRANSPORT_SECURITY); } @Test public void getWhenCustomCrossOriginPoliciesInLambdaThenCrossOriginPolicyHeadersWithCustomValuesInResponse() throws Exception { this.spring.register(CrossOriginCustomPoliciesInLambdaConfig.class).autowire(); MvcResult mvcResult = this.mvc.perform(get("/")) .andExpect(header().string(HttpHeaders.CROSS_ORIGIN_OPENER_POLICY, "same-origin")) .andExpect(header().string(HttpHeaders.CROSS_ORIGIN_EMBEDDER_POLICY, "require-corp")) .andExpect(header().string(HttpHeaders.CROSS_ORIGIN_RESOURCE_POLICY, "same-origin")) .andReturn(); assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly(HttpHeaders.CROSS_ORIGIN_OPENER_POLICY, HttpHeaders.CROSS_ORIGIN_EMBEDDER_POLICY, HttpHeaders.CROSS_ORIGIN_RESOURCE_POLICY); } @Test public void getWhenCustomCrossOriginPoliciesThenCrossOriginPolicyHeadersWithCustomValuesInResponse() throws Exception { this.spring.register(CrossOriginCustomPoliciesConfig.class).autowire(); MvcResult mvcResult = this.mvc.perform(get("/")) .andExpect(header().string(HttpHeaders.CROSS_ORIGIN_OPENER_POLICY, "same-origin")) .andExpect(header().string(HttpHeaders.CROSS_ORIGIN_EMBEDDER_POLICY, "require-corp")) .andExpect(header().string(HttpHeaders.CROSS_ORIGIN_RESOURCE_POLICY, "same-origin")) .andReturn(); assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly(HttpHeaders.CROSS_ORIGIN_OPENER_POLICY, HttpHeaders.CROSS_ORIGIN_EMBEDDER_POLICY, HttpHeaders.CROSS_ORIGIN_RESOURCE_POLICY); } @Configuration @EnableWebSecurity static class HeadersConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .headers(withDefaults()); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class HeadersInLambdaConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .headers(withDefaults()); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class ContentTypeOptionsConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .headers((headers) -> headers .defaultsDisabled() .contentTypeOptions(Customizer.withDefaults())); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class ContentTypeOptionsInLambdaConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .headers((headers) -> headers .defaultsDisabled() .contentTypeOptions(withDefaults()) ); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class FrameOptionsConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .headers((headers) -> headers .defaultsDisabled() .frameOptions(Customizer.withDefaults())); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class HstsConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .headers((headers) -> headers .defaultsDisabled() .httpStrictTransportSecurity(Customizer.withDefaults())); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class CacheControlConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .headers((headers) -> headers .defaultsDisabled() .cacheControl(Customizer.withDefaults())); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class CacheControlInLambdaConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .headers((headers) -> headers .defaultsDisabled() .cacheControl(withDefaults()) ); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class XssProtectionConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .headers((headers) -> headers .defaultsDisabled() .xssProtection(Customizer.withDefaults())); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class XssProtectionValueEnabledModeBlockConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .headers((headers) -> headers .defaultsDisabled() .xssProtection((xss) -> xss .headerValue(XXssProtectionHeaderWriter.HeaderValue.ENABLED_MODE_BLOCK))); // @formatter:on return http.build(); } } @EnableWebSecurity static class XssProtectionInLambdaConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .headers((headers) -> headers .defaultsDisabled() .xssProtection(withDefaults()) ); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class XssProtectionValueEnabledModeBlockInLambdaConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .headers((headers) -> headers .defaultsDisabled() .xssProtection((xXssConfig) -> xXssConfig.headerValue(XXssProtectionHeaderWriter.HeaderValue.ENABLED_MODE_BLOCK) ) ); // @formatter:on return http.build(); } } @EnableWebSecurity static class HeadersCustomSameOriginConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .headers((headers) -> headers .frameOptions((frameOptions) -> frameOptions.sameOrigin())); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class HeadersCustomSameOriginInLambdaConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .headers((headers) -> headers .frameOptions((frameOptionsConfig) -> frameOptionsConfig.sameOrigin()) ); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class HpkpConfigNoPins { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .headers((headers) -> headers .defaultsDisabled() .httpPublicKeyPinning(Customizer.withDefaults())); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class HpkpConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .headers((headers) -> headers .defaultsDisabled() .httpPublicKeyPinning((hpkp) -> hpkp .addSha256Pins("d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM="))); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class HpkpConfigWithPins { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { Map pins = new LinkedHashMap<>(); pins.put("d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=", "sha256"); pins.put("E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=", "sha256"); // @formatter:off http .headers((headers) -> headers .defaultsDisabled() .httpPublicKeyPinning((hpkp) -> hpkp.withPins(pins))); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class HpkpConfigCustomAge { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .headers((headers) -> headers .defaultsDisabled() .httpPublicKeyPinning((hpkp) -> hpkp .addSha256Pins("d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=") .maxAgeInSeconds(604800))); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class HpkpConfigTerminateConnection { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .headers((headers) -> headers .defaultsDisabled() .httpPublicKeyPinning((hpkp) -> hpkp .addSha256Pins("d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=") .reportOnly(false))); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class HpkpConfigIncludeSubDomains { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .headers((headers) -> headers .defaultsDisabled() .httpPublicKeyPinning((hpkp) -> hpkp .addSha256Pins("d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=") .includeSubDomains(true))); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class HpkpConfigWithReportURI { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .headers((headers) -> headers .defaultsDisabled() .httpPublicKeyPinning((hpkp) -> hpkp .addSha256Pins("d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=") .reportUri(URI.create("https://example.net/pkp-report")))); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class HpkpConfigWithReportURIAsString { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .headers((headers) -> headers .defaultsDisabled() .httpPublicKeyPinning((hpkp) -> hpkp .addSha256Pins("d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=") .reportUri("https://example.net/pkp-report"))); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class HpkpWithReportUriInLambdaConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .headers((headers) -> headers .defaultsDisabled() .httpPublicKeyPinning((hpkp) -> hpkp .addSha256Pins("d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=") .reportUri("https://example.net/pkp-report") ) ); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class ContentSecurityPolicyDefaultConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .headers((headers) -> headers .defaultsDisabled() .contentSecurityPolicy((csp) -> csp.policyDirectives("default-src 'self'"))); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class ContentSecurityPolicyReportOnlyConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .headers((headers) -> headers .defaultsDisabled() .contentSecurityPolicy((csp) -> csp .policyDirectives("default-src 'self'; script-src trustedscripts.example.com") .reportOnly())); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class ContentSecurityPolicyReportOnlyInLambdaConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .headers((headers) -> headers .defaultsDisabled() .contentSecurityPolicy((csp) -> csp .policyDirectives("default-src 'self'; script-src trustedscripts.example.com") .reportOnly() ) ); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class ContentSecurityPolicyInvalidConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .headers((headers) -> headers .defaultsDisabled() .contentSecurityPolicy((csp) -> csp.policyDirectives(""))); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class ContentSecurityPolicyInvalidInLambdaConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .headers((headers) -> headers .defaultsDisabled() .contentSecurityPolicy((csp) -> csp.policyDirectives("") ) ); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class ContentSecurityPolicyNoDirectivesInLambdaConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .headers((headers) -> headers .defaultsDisabled() .contentSecurityPolicy(withDefaults()) ); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class ReferrerPolicyDefaultConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .headers((headers) -> headers .defaultsDisabled() .referrerPolicy(Customizer.withDefaults())); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class ReferrerPolicyDefaultInLambdaConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .headers((headers) -> headers .defaultsDisabled() .referrerPolicy(Customizer.withDefaults()) ); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class ReferrerPolicyCustomConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .headers((headers) -> headers .defaultsDisabled() .referrerPolicy((referrer) -> referrer.policy(ReferrerPolicy.SAME_ORIGIN))); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class ReferrerPolicyCustomInLambdaConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .headers((headers) -> headers .defaultsDisabled() .referrerPolicy((referrerPolicy) -> referrerPolicy.policy(ReferrerPolicy.SAME_ORIGIN) ) ); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class FeaturePolicyConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .headers((headers) -> headers .defaultsDisabled() .featurePolicy("geolocation 'self'")); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class FeaturePolicyInvalidConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .headers((headers) -> headers .defaultsDisabled() .featurePolicy("")); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity @SuppressWarnings("removal") static class PermissionsPolicyConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .headers((headers) -> headers .defaultsDisabled() .permissionsPolicy((permissionsPolicy) -> permissionsPolicy.policy("geolocation=(self)"))); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class PermissionsPolicyStringConfig { @Bean @SuppressWarnings("removal") SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .headers((headers) -> headers .defaultsDisabled() .permissionsPolicy((permissions) -> permissions.policy("geolocation=(self)"))); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity @SuppressWarnings("removal") static class PermissionsPolicyInvalidConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .headers((headers) -> headers .defaultsDisabled() .permissionsPolicy((permissionsPolicy) -> permissionsPolicy.policy(null))); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity @SuppressWarnings("removal") static class PermissionsPolicyInvalidStringConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .headers((headers) -> headers .defaultsDisabled() .permissionsPolicy((permissions) -> permissions.policy(""))); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class HstsWithPreloadConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .headers((headers) -> headers .defaultsDisabled() .httpStrictTransportSecurity((hsts) -> hsts.preload(true))); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class HstsWithPreloadInLambdaConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .headers((headers) -> headers .defaultsDisabled() .httpStrictTransportSecurity((hstsConfig) -> hstsConfig.preload(true)) ); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class CrossOriginCustomPoliciesInLambdaConfig { @Bean SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { // @formatter:off http.headers((headers) -> headers .defaultsDisabled() .crossOriginOpenerPolicy((policy) -> policy .policy(CrossOriginOpenerPolicyHeaderWriter.CrossOriginOpenerPolicy.SAME_ORIGIN) ) .crossOriginEmbedderPolicy((policy) -> policy .policy(CrossOriginEmbedderPolicyHeaderWriter.CrossOriginEmbedderPolicy.REQUIRE_CORP) ) .crossOriginResourcePolicy((policy) -> policy .policy(CrossOriginResourcePolicyHeaderWriter.CrossOriginResourcePolicy.SAME_ORIGIN) ) ); // @formatter:on return http.build(); } } @Configuration @EnableWebSecurity static class CrossOriginCustomPoliciesConfig { @Bean SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { // @formatter:off http.headers((headers) -> headers .defaultsDisabled() .crossOriginOpenerPolicy((opener) -> opener .policy(CrossOriginOpenerPolicyHeaderWriter.CrossOriginOpenerPolicy.SAME_ORIGIN)) .crossOriginEmbedderPolicy((embedder) -> embedder .policy(CrossOriginEmbedderPolicyHeaderWriter.CrossOriginEmbedderPolicy.REQUIRE_CORP)) .crossOriginResourcePolicy((resource) -> resource .policy(CrossOriginResourcePolicyHeaderWriter.CrossOriginResourcePolicy.SAME_ORIGIN))); // @formatter:on return http.build(); } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/annotation/web/configurers/HttpBasicConfigurerTests.java ================================================ /* * 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 org.springframework.security.config.annotation.web.configurers; import io.micrometer.observation.Observation; import io.micrometer.observation.ObservationHandler; import io.micrometer.observation.ObservationRegistry; import io.micrometer.observation.ObservationTextPublisher; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; import org.springframework.beans.BeansException; import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationObservationContext; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.config.Customizer; import org.springframework.security.config.ObjectPostProcessor; import org.springframework.security.config.annotation.SecurityContextChangedListenerConfig; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.observation.SecurityObservationSettings; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextChangedListener; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; import org.springframework.security.web.context.SecurityContextRepository; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.springframework.security.config.Customizer.withDefaults; import static org.springframework.security.config.annotation.SecurityContextChangedListenerArgumentMatchers.setAuthentication; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.cookie; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Tests for {@link HttpBasicConfigurer} * * @author Rob Winch * @author Eleftheria Stein * @author Evgeniy Cheban */ @ExtendWith(SpringTestContextExtension.class) public class HttpBasicConfigurerTests { public final SpringTestContext spring = new SpringTestContext(this); @Autowired MockMvc mvc; @Test public void configureWhenRegisteringObjectPostProcessorThenInvokedOnBasicAuthenticationFilter() { this.spring.register(ObjectPostProcessorConfig.class).autowire(); verify(ObjectPostProcessorConfig.objectPostProcessor).postProcess(any(BasicAuthenticationFilter.class)); } @Test public void httpBasicWhenUsingDefaultsInLambdaThenResponseIncludesBasicChallenge() throws Exception { this.spring.register(DefaultsLambdaEntryPointConfig.class).autowire(); // @formatter:off this.mvc.perform(get("/")) .andExpect(status().isUnauthorized()) .andExpect(header().string("WWW-Authenticate", "Basic realm=\"Realm\", charset=\"UTF-8\"")); // @formatter:on } // SEC-2198 @Test public void httpBasicWhenUsingDefaultsThenResponseIncludesBasicChallenge() throws Exception { this.spring.register(DefaultsEntryPointConfig.class).autowire(); // @formatter:off this.mvc.perform(get("/")) .andExpect(status().isUnauthorized()) .andExpect(header().string("WWW-Authenticate", "Basic realm=\"Realm\", charset=\"UTF-8\"")); // @formatter:on } @Test public void httpBasicWhenUsingCustomAuthenticationEntryPointThenResponseIncludesBasicChallenge() throws Exception { CustomAuthenticationEntryPointConfig.ENTRY_POINT = mock(AuthenticationEntryPoint.class); this.spring.register(CustomAuthenticationEntryPointConfig.class).autowire(); this.mvc.perform(get("/")); verify(CustomAuthenticationEntryPointConfig.ENTRY_POINT).commence(any(HttpServletRequest.class), any(HttpServletResponse.class), any(AuthenticationException.class)); } @Test public void httpBasicWhenInvokedTwiceThenUsesOriginalEntryPoint() throws Exception { this.spring.register(DuplicateDoesNotOverrideConfig.class).autowire(); this.mvc.perform(get("/")); verify(DuplicateDoesNotOverrideConfig.ENTRY_POINT).commence(any(HttpServletRequest.class), any(HttpServletResponse.class), any(AuthenticationException.class)); } // SEC-3019 @Test public void httpBasicWhenRememberMeConfiguredThenSetsRememberMeCookie() throws Exception { this.spring.register(BasicUsesRememberMeConfig.class, Home.class).autowire(); MockHttpServletRequestBuilder rememberMeRequest = get("/").with(httpBasic("user", "password")) .param("remember-me", "true"); this.mvc.perform(rememberMeRequest).andExpect(cookie().exists("remember-me")); } @Test public void httpBasicWhenDefaultsThenAcceptsBasicCredentials() throws Exception { this.spring.register(HttpBasic.class, Users.class, Home.class).autowire(); this.mvc.perform(get("/").with(httpBasic("user", "password"))) .andExpect(status().isOk()) .andExpect(content().string("user")); } @Test public void httpBasicWhenCustomSecurityContextHolderStrategyThenUses() throws Exception { this.spring.register(HttpBasic.class, Users.class, Home.class, SecurityContextChangedListenerConfig.class) .autowire(); this.mvc.perform(get("/").with(httpBasic("user", "password"))) .andExpect(status().isOk()) .andExpect(content().string("user")); SecurityContextChangedListener listener = this.spring.getContext() .getBean(SecurityContextChangedListener.class); verify(listener).securityContextChanged(setAuthentication(UsernamePasswordAuthenticationToken.class)); } @Test public void httpBasicWhenUsingCustomSecurityContextRepositoryThenUses() throws Exception { this.spring.register(CustomSecurityContextRepositoryConfig.class, Users.class, Home.class).autowire(); this.mvc.perform(get("/").with(httpBasic("user", "password"))) .andExpect(status().isOk()) .andExpect(content().string("user")); verify(CustomSecurityContextRepositoryConfig.SECURITY_CONTEXT_REPOSITORY) .saveContext(any(SecurityContext.class), any(HttpServletRequest.class), any(HttpServletResponse.class)); } @Test public void httpBasicWhenObservationRegistryThenObserves() throws Exception { this.spring.register(HttpBasic.class, Users.class, Home.class, ObservationRegistryConfig.class).autowire(); ObservationHandler handler = this.spring.getContext().getBean(ObservationHandler.class); this.mvc.perform(get("/").with(httpBasic("user", "password"))) .andExpect(status().isOk()) .andExpect(content().string("user")); ArgumentCaptor context = ArgumentCaptor.forClass(Observation.Context.class); verify(handler, atLeastOnce()).onStart(context.capture()); assertThat(context.getAllValues()).anyMatch((c) -> c instanceof AuthenticationObservationContext); verify(handler, atLeastOnce()).onStop(context.capture()); assertThat(context.getAllValues()).anyMatch((c) -> c instanceof AuthenticationObservationContext); this.mvc.perform(get("/").with(httpBasic("user", "wrong"))).andExpect(status().isUnauthorized()); verify(handler).onError(context.capture()); assertThat(context.getValue()).isInstanceOf(AuthenticationObservationContext.class); } @Test public void httpBasicWhenExcludeAuthenticationObservationsThenUnobserved() throws Exception { this.spring .register(HttpBasic.class, Users.class, Home.class, ObservationRegistryConfig.class, SelectableObservationsConfig.class) .autowire(); ObservationHandler handler = this.spring.getContext().getBean(ObservationHandler.class); this.mvc.perform(get("/").with(httpBasic("user", "password"))) .andExpect(status().isOk()) .andExpect(content().string("user")); ArgumentCaptor context = ArgumentCaptor.forClass(Observation.Context.class); verify(handler, atLeastOnce()).onStart(context.capture()); assertThat(context.getAllValues()).noneMatch((c) -> c instanceof AuthenticationObservationContext); context = ArgumentCaptor.forClass(Observation.Context.class); verify(handler, atLeastOnce()).onStop(context.capture()); assertThat(context.getAllValues()).noneMatch((c) -> c instanceof AuthenticationObservationContext); this.mvc.perform(get("/").with(httpBasic("user", "wrong"))).andExpect(status().isUnauthorized()); verify(handler, never()).onError(any()); } @Configuration @EnableWebSecurity static class ObjectPostProcessorConfig { static ObjectPostProcessor objectPostProcessor = spy(ReflectingObjectPostProcessor.class); @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .httpBasic(withDefaults()); return http.build(); // @formatter:on } @Bean static ObjectPostProcessor objectPostProcessor() { return objectPostProcessor; } } static class ReflectingObjectPostProcessor implements ObjectPostProcessor { @Override public O postProcess(O object) { return object; } } @Configuration @EnableWebSecurity static class DefaultsLambdaEntryPointConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((authorize) -> authorize .anyRequest().authenticated() ) .httpBasic(withDefaults()); // @formatter:on return http.build(); } @Bean UserDetailsService userDetailsService() { return new InMemoryUserDetailsManager(); } } @Configuration @EnableWebSecurity static class DefaultsEntryPointConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((requests) -> requests .anyRequest().authenticated()) .httpBasic(withDefaults()); // @formatter:on return http.build(); } @Bean UserDetailsService userDetailsService() { return new InMemoryUserDetailsManager(); } } @Configuration @EnableWebSecurity static class CustomAuthenticationEntryPointConfig { static AuthenticationEntryPoint ENTRY_POINT; @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((requests) -> requests .anyRequest().authenticated()) .httpBasic((basic) -> basic .authenticationEntryPoint(ENTRY_POINT)); // @formatter:on return http.build(); } @Bean UserDetailsService userDetailsService() { return new InMemoryUserDetailsManager(); } } @Configuration @EnableWebSecurity static class DuplicateDoesNotOverrideConfig { static AuthenticationEntryPoint ENTRY_POINT = mock(AuthenticationEntryPoint.class); @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((requests) -> requests .anyRequest().authenticated()) .httpBasic((basic) -> basic .authenticationEntryPoint(ENTRY_POINT)) .httpBasic(withDefaults()); // @formatter:on return http.build(); } @Bean UserDetailsService userDetailsService() { return new InMemoryUserDetailsManager(); } } @EnableWebSecurity @Configuration static class BasicUsesRememberMeConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .httpBasic(withDefaults()) .rememberMe(withDefaults()); return http.build(); // @formatter:on } @Bean UserDetailsService userDetailsService() { return new InMemoryUserDetailsManager( // @formatter:off org.springframework.security.core.userdetails.User.withDefaultPasswordEncoder() .username("user") .password("password") .roles("USER") .build() // @formatter:on ); } } @Configuration @EnableWebSecurity static class HttpBasic { @Bean SecurityFilterChain web(HttpSecurity http) throws Exception { http.authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated()) .httpBasic(Customizer.withDefaults()); return http.build(); } } @Configuration @EnableWebSecurity static class CustomSecurityContextRepositoryConfig { static final SecurityContextRepository SECURITY_CONTEXT_REPOSITORY = mock(SecurityContextRepository.class); @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .httpBasic((basic) -> basic .securityContextRepository(SECURITY_CONTEXT_REPOSITORY)); // @formatter:on return http.build(); } } @Configuration static class Users { @Bean UserDetailsService userDetailsService() { return new InMemoryUserDetailsManager( // @formatter:off User.withDefaultPasswordEncoder() .username("user") .password("password") .roles("USER") .build() // @formatter:on ); } } @EnableWebMvc @RestController static class Home { @GetMapping("/") String home(@AuthenticationPrincipal UserDetails user) { return user.getUsername(); } } @Configuration static class ObservationRegistryConfig { private final ObservationRegistry registry = ObservationRegistry.create(); private final ObservationHandler handler = spy(new ObservationTextPublisher()); @Bean ObservationRegistry observationRegistry() { return this.registry; } @Bean ObservationHandler observationHandler() { return this.handler; } @Bean ObservationRegistryPostProcessor observationRegistryPostProcessor( ObjectProvider> handler) { return new ObservationRegistryPostProcessor(handler); } } static class ObservationRegistryPostProcessor implements BeanPostProcessor { private final ObjectProvider> handler; ObservationRegistryPostProcessor(ObjectProvider> handler) { this.handler = handler; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { if (bean instanceof ObservationRegistry registry) { registry.observationConfig().observationHandler(this.handler.getObject()); } return bean; } } @Configuration static class SelectableObservationsConfig { @Bean SecurityObservationSettings observabilityDefaults() { return SecurityObservationSettings.withDefaults().shouldObserveAuthentications(false).build(); } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/annotation/web/configurers/HttpSecurityLogoutTests.java ================================================ /* * 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 org.springframework.security.config.annotation.web.configurers; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.mock.web.MockFilterChain; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.web.FilterChainProxy; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.context.HttpSessionSecurityContextRepository; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.security.web.servlet.TestMockHttpServletRequests.post; /** * @author Rob Winch * */ public class HttpSecurityLogoutTests { AnnotationConfigWebApplicationContext context; MockHttpServletResponse response; MockFilterChain chain; @Autowired FilterChainProxy springSecurityFilterChain; @BeforeEach public void setup() { this.response = new MockHttpServletResponse(); this.chain = new MockFilterChain(); } @AfterEach public void cleanup() { if (this.context != null) { this.context.close(); } } // SEC-2848 @Test public void clearAuthenticationFalse() throws Exception { loadConfig(ClearAuthenticationFalseConfig.class); SecurityContext currentContext = SecurityContextHolder.createEmptyContext(); currentContext.setAuthentication(new TestingAuthenticationToken("user", "password", "ROLE_USER")); MockHttpServletRequest request = post("/logout").build(); request.getSession() .setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, currentContext); this.springSecurityFilterChain.doFilter(request, this.response, this.chain); assertThat(currentContext.getAuthentication()).isNotNull(); } public void loadConfig(Class... configs) { this.context = new AnnotationConfigWebApplicationContext(); this.context.register(configs); this.context.refresh(); this.context.getAutowireCapableBeanFactory().autowireBean(this); } @EnableWebSecurity @Configuration static class ClearAuthenticationFalseConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .csrf((csrf) -> csrf.disable()) .logout((logout) -> logout .clearAuthentication(false)); // @formatter:on return http.build(); } @Bean UserDetailsService userDetailsService() { return new InMemoryUserDetailsManager(); } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/annotation/web/configurers/HttpSecurityObservationTests.java ================================================ /* * 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 org.springframework.security.config.annotation.web.configurers; import java.util.Iterator; import io.micrometer.observation.Observation; import io.micrometer.observation.ObservationHandler; import io.micrometer.observation.ObservationRegistry; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.web.SecurityFilterChain; import org.springframework.test.web.servlet.MockMvc; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.springframework.security.config.Customizer.withDefaults; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * @author Josh Cummings * */ @ExtendWith(SpringTestContextExtension.class) public class HttpSecurityObservationTests { @Autowired MockMvc mvc; public final SpringTestContext spring = new SpringTestContext(this); @Test public void getWhenUsingObservationRegistryThenObservesRequest() throws Exception { this.spring.register(ObservationRegistryConfig.class).autowire(); // @formatter:off this.mvc.perform(get("/").with(httpBasic("user", "password"))) .andExpect(status().isNotFound()); // @formatter:on ObservationHandler handler = this.spring.getContext().getBean(ObservationHandler.class); ArgumentCaptor captor = ArgumentCaptor.forClass(Observation.Context.class); verify(handler, times(5)).onStart(captor.capture()); Iterator contexts = captor.getAllValues().iterator(); assertThat(contexts.next().getContextualName()).isEqualTo("security filterchain before"); assertThat(contexts.next().getName()).isEqualTo("spring.security.authentications"); assertThat(contexts.next().getName()).isEqualTo("spring.security.authorizations"); assertThat(contexts.next().getName()).isEqualTo("spring.security.http.secured.requests"); assertThat(contexts.next().getContextualName()).isEqualTo("security filterchain after"); } @EnableWebSecurity @Configuration static class ObservationRegistryConfig { private ObservationHandler handler = mock(ObservationHandler.class); @Bean SecurityFilterChain app(HttpSecurity http) throws Exception { http.httpBasic(withDefaults()).authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated()); return http.build(); } @Bean UserDetailsService userDetailsService() { return new InMemoryUserDetailsManager( User.withDefaultPasswordEncoder().username("user").password("password").authorities("app").build()); } @Bean ObservationHandler observationHandler() { return this.handler; } @Bean ObservationRegistry observationRegistry() { given(this.handler.supportsContext(any())).willReturn(true); ObservationRegistry registry = ObservationRegistry.create(); registry.observationConfig().observationHandler(this.handler); return registry; } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/annotation/web/configurers/HttpSecurityRequestMatchersTests.java ================================================ /* * 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 org.springframework.security.config.annotation.web.configurers; import jakarta.servlet.http.HttpServletResponse; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.mock.web.MockFilterChain; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.mock.web.MockServletContext; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.web.PathPatternRequestMatcherBuilderFactoryBean; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.web.FilterChainProxy; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.security.config.Customizer.withDefaults; import static org.springframework.security.web.servlet.TestMockHttpServletRequests.get; /** * @author Rob Winch * */ public class HttpSecurityRequestMatchersTests { AnnotationConfigWebApplicationContext context; MockHttpServletResponse response; MockFilterChain chain; @Autowired FilterChainProxy springSecurityFilterChain; @BeforeEach public void setup() { this.response = new MockHttpServletResponse(); this.chain = new MockFilterChain(); } @AfterEach public void cleanup() { if (this.context != null) { this.context.close(); } } @Test public void mvcMatcherGetFiltersNoUnsupportedMethodExceptionFromDummyRequest() { loadConfig(MvcMatcherConfig.class); assertThat(this.springSecurityFilterChain.getFilters("/path")).isNotEmpty(); } @Test public void requestMatchersMvcMatcherServletPath() throws Exception { loadConfig(RequestMatchersMvcMatcherServeltPathConfig.class); MockHttpServletRequest request = get().requestUri(null, "/spring", "/path").build(); this.springSecurityFilterChain.doFilter(request, this.response, this.chain); assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); setup(); request = get().requestUri(null, "", "/path").build(); this.springSecurityFilterChain.doFilter(request, this.response, this.chain); assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); setup(); request = get().requestUri(null, "/other", "/path").build(); this.springSecurityFilterChain.doFilter(request, this.response, this.chain); assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); } @Test public void requestMatcherWhensMvcMatcherServletPathInLambdaThenPathIsSecured() throws Exception { loadConfig(RequestMatchersMvcMatcherServletPathInLambdaConfig.class); MockHttpServletRequest request = get().requestUri(null, "/spring", "/path").build(); this.springSecurityFilterChain.doFilter(request, this.response, this.chain); assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); setup(); request = get().requestUri(null, "", "/path").build(); this.springSecurityFilterChain.doFilter(request, this.response, this.chain); assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); setup(); request = get().requestUri(null, "/other", "/path").build(); this.springSecurityFilterChain.doFilter(request, this.response, this.chain); assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); } @Test public void requestMatcherWhenMultiMvcMatcherInLambdaThenAllPathsAreDenied() throws Exception { loadConfig(MultiMvcMatcherInLambdaConfig.class); MockHttpServletRequest request = get("/test-1").build(); this.springSecurityFilterChain.doFilter(request, this.response, this.chain); assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); setup(); request = get("/test-2").build(); this.springSecurityFilterChain.doFilter(request, this.response, this.chain); assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); setup(); request = get("/test-3").build(); this.springSecurityFilterChain.doFilter(request, this.response, this.chain); assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); } @Test public void requestMatcherWhenMultiMvcMatcherThenAllPathsAreDenied() throws Exception { loadConfig(MultiMvcMatcherConfig.class); MockHttpServletRequest request = get("/test-1").build(); this.springSecurityFilterChain.doFilter(request, this.response, this.chain); assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); setup(); request = get("/test-2").build(); this.springSecurityFilterChain.doFilter(request, this.response, this.chain); assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); setup(); request = get("/test-3").build(); this.springSecurityFilterChain.doFilter(request, this.response, this.chain); assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); } public void loadConfig(Class... configs) { this.context = new AnnotationConfigWebApplicationContext(); this.context.register(configs); this.context.setServletContext(new MockServletContext()); this.context.refresh(); this.context.getAutowireCapableBeanFactory().autowireBean(this); } @EnableWebSecurity @Configuration @EnableWebMvc static class MultiMvcMatcherInLambdaConfig { @Bean PathPatternRequestMatcherBuilderFactoryBean requestMatcherBuilder() { return new PathPatternRequestMatcherBuilderFactoryBean(); } @Bean @Order(Ordered.HIGHEST_PRECEDENCE) SecurityFilterChain first(HttpSecurity http, PathPatternRequestMatcher.Builder builder) throws Exception { // @formatter:off http .securityMatchers((requests) -> requests .requestMatchers(builder.matcher("/test-1")) .requestMatchers(builder.matcher("/test-2")) .requestMatchers(builder.matcher("/test-3")) ) .authorizeHttpRequests((authorize) -> authorize.anyRequest().denyAll()) .httpBasic(withDefaults()); // @formatter:on return http.build(); } @Bean SecurityFilterChain second(HttpSecurity http, PathPatternRequestMatcher.Builder builder) throws Exception { // @formatter:off http .securityMatchers((requests) -> requests .requestMatchers(builder.matcher("/test-1")) ) .authorizeHttpRequests((authorize) -> authorize .anyRequest().permitAll() ); // @formatter:on return http.build(); } @RestController static class PathController { @RequestMapping({ "/test-1", "/test-2", "/test-3" }) String path() { return "path"; } } } @EnableWebSecurity @Configuration @EnableWebMvc static class MultiMvcMatcherConfig { @Bean PathPatternRequestMatcherBuilderFactoryBean requestMatcherBuilder() { return new PathPatternRequestMatcherBuilderFactoryBean(); } @Bean @Order(Ordered.HIGHEST_PRECEDENCE) SecurityFilterChain first(HttpSecurity http, PathPatternRequestMatcher.Builder builder) throws Exception { // @formatter:off http .securityMatchers((security) -> security .requestMatchers(builder.matcher("/test-1")) .requestMatchers(builder.matcher("/test-2")) .requestMatchers(builder.matcher("/test-3"))) .authorizeHttpRequests((requests) -> requests .anyRequest().denyAll()) .httpBasic(withDefaults()); // @formatter:on return http.build(); } @Bean SecurityFilterChain second(HttpSecurity http, PathPatternRequestMatcher.Builder builder) throws Exception { // @formatter:off http .securityMatchers((security) -> security .requestMatchers(builder.matcher("/test-1"))) .authorizeHttpRequests((requests) -> requests .anyRequest().permitAll()); // @formatter:on return http.build(); } @RestController static class PathController { @RequestMapping({ "/test-1", "/test-2", "/test-3" }) String path() { return "path"; } } } @EnableWebSecurity @Configuration @EnableWebMvc static class MvcMatcherConfig { @Bean PathPatternRequestMatcherBuilderFactoryBean requestMatcherBuilder() { return new PathPatternRequestMatcherBuilderFactoryBean(); } @Bean SecurityFilterChain filterChain(HttpSecurity http, PathPatternRequestMatcher.Builder builder) throws Exception { // @formatter:off http .securityMatcher(builder.matcher("/path")) .httpBasic(withDefaults()) .authorizeHttpRequests((requests) -> requests .anyRequest().denyAll()); // @formatter:on return http.build(); } @Bean UserDetailsService userDetailsService() { return new InMemoryUserDetailsManager(); } @RestController static class PathController { @RequestMapping("/path") String path() { return "path"; } } } @EnableWebSecurity @Configuration @EnableWebMvc static class RequestMatchersMvcMatcherConfig { @Bean PathPatternRequestMatcherBuilderFactoryBean requestMatcherBuilder() { return new PathPatternRequestMatcherBuilderFactoryBean(); } @Bean SecurityFilterChain filterChain(HttpSecurity http, PathPatternRequestMatcher.Builder builder) throws Exception { // @formatter:off http .securityMatchers((security) -> security .requestMatchers(builder.matcher("/path"))) .httpBasic(withDefaults()) .authorizeHttpRequests((requests) -> requests .anyRequest().denyAll()); // @formatter:on return http.build(); } @Bean UserDetailsService userDetailsService() { return new InMemoryUserDetailsManager(); } @RestController static class PathController { @RequestMapping("/path") String path() { return "path"; } } } @EnableWebSecurity @Configuration @EnableWebMvc static class RequestMatchersMvcMatcherInLambdaConfig { @Bean PathPatternRequestMatcherBuilderFactoryBean requestMatcherBuilder() { return new PathPatternRequestMatcherBuilderFactoryBean(); } @Bean SecurityFilterChain filterChain(HttpSecurity http, PathPatternRequestMatcher.Builder builder) throws Exception { // @formatter:off http .securityMatchers((secure) -> secure .requestMatchers(builder.matcher("/path")) ) .httpBasic(withDefaults()) .authorizeHttpRequests((authorize) -> authorize .anyRequest().denyAll() ); return http.build(); // @formatter:on } @RestController static class PathController { @RequestMapping("/path") String path() { return "path"; } } } @EnableWebSecurity @Configuration @EnableWebMvc static class RequestMatchersMvcMatcherServeltPathConfig { @Bean PathPatternRequestMatcherBuilderFactoryBean requestMatcherBuilder() { return new PathPatternRequestMatcherBuilderFactoryBean(); } @Bean SecurityFilterChain filterChain(HttpSecurity http, PathPatternRequestMatcher.Builder builder) throws Exception { // @formatter:off http .securityMatchers((security) -> security .requestMatchers(builder.basePath("/spring").matcher("/path")) .requestMatchers("/never-match")) .httpBasic(withDefaults()) .authorizeHttpRequests((requests) -> requests .anyRequest().denyAll()); // @formatter:on return http.build(); } @Bean UserDetailsService userDetailsService() { return new InMemoryUserDetailsManager(); } @RestController static class PathController { @RequestMapping("/path") String path() { return "path"; } } } @EnableWebSecurity @Configuration @EnableWebMvc static class RequestMatchersMvcMatcherServletPathInLambdaConfig { @Bean PathPatternRequestMatcherBuilderFactoryBean requestMatcherBuilder() { return new PathPatternRequestMatcherBuilderFactoryBean(); } @Bean SecurityFilterChain filterChain(HttpSecurity http, PathPatternRequestMatcher.Builder builder) throws Exception { // @formatter:off http .securityMatchers((secure) -> secure .requestMatchers(builder.basePath("/spring").matcher("/path")) .requestMatchers("/never-match") ) .httpBasic(withDefaults()) .authorizeHttpRequests((authorize) -> authorize .anyRequest().denyAll() ); return http.build(); // @formatter:on } @RestController static class PathController { @RequestMapping("/path") String path() { return "path"; } } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/annotation/web/configurers/HttpSecuritySecurityMatchersNoMvcTests.java ================================================ /* * 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 org.springframework.security.config.annotation.web.configurers; import java.util.List; import jakarta.servlet.http.HttpServletResponse; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.mock.web.MockFilterChain; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.mock.web.MockServletContext; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.test.support.ClassPathExclusions; import org.springframework.security.web.DefaultSecurityFilterChain; import org.springframework.security.web.FilterChainProxy; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher; import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.test.util.ReflectionTestUtils; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.security.config.Customizer.withDefaults; /** * @author Marcus Da Coregio * */ @ClassPathExclusions("spring-webmvc-*.jar") public class HttpSecuritySecurityMatchersNoMvcTests { AnnotationConfigWebApplicationContext context; MockHttpServletRequest request; MockHttpServletResponse response; MockFilterChain chain; @Autowired FilterChainProxy springSecurityFilterChain; @BeforeEach public void setup() throws Exception { this.request = new MockHttpServletRequest(); this.request.setMethod("GET"); this.response = new MockHttpServletResponse(); this.chain = new MockFilterChain(); } @AfterEach public void cleanup() { if (this.context != null) { this.context.close(); } } @Test public void securityMatcherWhenNoMvcThenAntMatcher() throws Exception { loadConfig(SecurityMatcherNoMvcConfig.class); this.request.setRequestURI("/path"); this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); setup(); this.request.setRequestURI("/path.html"); this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); setup(); this.request.setRequestURI("/path/"); this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); List requestMatchers = this.springSecurityFilterChain.getFilterChains() .stream() .map((chain) -> ((DefaultSecurityFilterChain) chain).getRequestMatcher()) .map((matcher) -> ReflectionTestUtils.getField(matcher, "requestMatchers")) .map((matchers) -> (List) matchers) .findFirst() .get(); assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); assertThat(requestMatchers).hasOnlyElementsOfType(PathPatternRequestMatcher.class); } public void loadConfig(Class... configs) { this.context = new AnnotationConfigWebApplicationContext(); this.context.register(configs); this.context.setServletContext(new MockServletContext()); this.context.refresh(); this.context.getAutowireCapableBeanFactory().autowireBean(this); } @EnableWebSecurity @Configuration @Import(HttpSecuritySecurityMatchersTests.UsersConfig.class) static class SecurityMatcherNoMvcConfig { @Bean SecurityFilterChain appSecurity(HttpSecurity http) throws Exception { // @formatter:off http .securityMatcher("/path") .httpBasic(withDefaults()) .authorizeHttpRequests((authorize) -> authorize .anyRequest().denyAll()); // @formatter:on return http.build(); } @RestController static class PathController { @RequestMapping("/path") String path() { return "path"; } } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/annotation/web/configurers/HttpSecuritySecurityMatchersTests.java ================================================ /* * 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 org.springframework.security.config.annotation.web.configurers; import jakarta.servlet.http.HttpServletResponse; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.mock.web.MockFilterChain; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.web.PathPatternRequestMatcherBuilderFactoryBean; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.web.FilterChainProxy; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.servlet.MockServletContext; import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.security.config.Customizer.withDefaults; /** * @author Rob Winch * */ public class HttpSecuritySecurityMatchersTests { AnnotationConfigWebApplicationContext context; MockHttpServletRequest request; MockHttpServletResponse response; MockFilterChain chain; @Autowired FilterChainProxy springSecurityFilterChain; @BeforeEach public void setup() throws Exception { this.request = new MockHttpServletRequest(MockServletContext.mvc(), "GET", ""); this.request.setMethod("GET"); this.response = new MockHttpServletResponse(); this.chain = new MockFilterChain(); } @AfterEach public void cleanup() { if (this.context != null) { this.context.close(); } } @Test public void securityMatcherWhenMvcMatcherAndGetFiltersNoUnsupportedMethodExceptionFromDummyRequest() { loadConfig(SecurityMatcherMvcConfig.class); assertThat(this.springSecurityFilterChain.getFilters("/path")).isNotEmpty(); } @Test public void securityMatchersMvcMatcherServletPath() throws Exception { loadConfig(SecurityMatchersMvcMatcherServletPathConfig.class); this.request.setServletPath("/spring"); this.request.setRequestURI("/spring/path"); this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); setup(); this.request.setServletPath(""); this.request.setRequestURI("/path"); this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); setup(); this.request.setServletPath("/other"); this.request.setRequestURI("/other/path"); this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); } @Test public void securityMatchersWhensMvcMatcherServletPathInLambdaThenPathIsSecured() throws Exception { loadConfig(SecurityMatchersMvcMatcherServletPathInLambdaConfig.class); this.request.setServletPath("/spring"); this.request.setRequestURI("/spring/path"); this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); setup(); this.request.setServletPath(""); this.request.setRequestURI("/path"); this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); setup(); this.request.setServletPath("/other"); this.request.setRequestURI("/other/path"); this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); } @Test public void securityMatchersWhenMultiMvcMatcherInLambdaThenAllPathsAreDenied() throws Exception { loadConfig(MultiMvcMatcherInLambdaConfig.class); this.request.setRequestURI("/test-1"); this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); setup(); this.request.setRequestURI("/test-2"); this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); setup(); this.request.setRequestURI("/test-3"); this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); } @Test public void securityMatchersWhenMultiMvcMatcherThenAllPathsAreDenied() throws Exception { loadConfig(MultiMvcMatcherConfig.class); this.request.setRequestURI("/test-1"); this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); setup(); this.request.setRequestURI("/test-2"); this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); setup(); this.request.setRequestURI("/test-3"); this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); } public void loadConfig(Class... configs) { this.context = new AnnotationConfigWebApplicationContext(); this.context.register(configs); this.context.setServletContext(MockServletContext.mvc()); this.context.refresh(); this.context.getAutowireCapableBeanFactory().autowireBean(this); } @EnableWebSecurity @Configuration @EnableWebMvc static class MultiMvcMatcherInLambdaConfig { @Bean @Order(Ordered.HIGHEST_PRECEDENCE) SecurityFilterChain first(HttpSecurity http) throws Exception { // @formatter:off http .securityMatchers((requests) -> requests .requestMatchers("/test-1") .requestMatchers("/test-2") .requestMatchers("/test-3") ) .authorizeHttpRequests((authorize) -> authorize.anyRequest().denyAll()) .httpBasic(withDefaults()); // @formatter:on return http.build(); } @Bean SecurityFilterChain second(HttpSecurity http) throws Exception { // @formatter:off http .securityMatchers((requests) -> requests .requestMatchers("/test-1") ) .authorizeHttpRequests((authorize) -> authorize .anyRequest().permitAll() ); // @formatter:on return http.build(); } @RestController static class PathController { @RequestMapping({ "/test-1", "/test-2", "/test-3" }) String path() { return "path"; } } } @EnableWebSecurity @Configuration @EnableWebMvc static class MultiMvcMatcherConfig { @Bean @Order(Ordered.HIGHEST_PRECEDENCE) SecurityFilterChain first(HttpSecurity http) throws Exception { // @formatter:off http .securityMatchers((security) -> security .requestMatchers("/test-1") .requestMatchers("/test-2") .requestMatchers("/test-3")) .authorizeHttpRequests((authorize) -> authorize .anyRequest().denyAll()) .httpBasic(withDefaults()); // @formatter:on return http.build(); } @Bean SecurityFilterChain second(HttpSecurity http) throws Exception { // @formatter:off http .securityMatchers((security) -> security .requestMatchers("/test-1")) .authorizeHttpRequests((authorize) -> authorize .anyRequest().permitAll()); // @formatter:on return http.build(); } @RestController static class PathController { @RequestMapping({ "/test-1", "/test-2", "/test-3" }) String path() { return "path"; } } } @EnableWebSecurity @EnableWebMvc @Configuration @Import(UsersConfig.class) static class SecurityMatcherMvcConfig { @Bean SecurityFilterChain appSecurity(HttpSecurity http) throws Exception { // @formatter:off http .securityMatcher("/path") .httpBasic(withDefaults()) .authorizeHttpRequests((authorize) -> authorize .anyRequest().denyAll()); // @formatter:on return http.build(); } @RestController static class PathController { @RequestMapping("/path") String path() { return "path"; } } } @EnableWebSecurity @Configuration @EnableWebMvc @Import(UsersConfig.class) static class SecurityMatchersMvcMatcherConfig { @Bean SecurityFilterChain appSecurity(HttpSecurity http) throws Exception { // @formatter:off http .securityMatcher("/path") .httpBasic(withDefaults()) .authorizeHttpRequests((authorize) -> authorize .anyRequest().denyAll()); // @formatter:on return http.build(); } @RestController static class PathController { @RequestMapping("/path") String path() { return "path"; } } } @EnableWebSecurity @Configuration @EnableWebMvc static class SecurityMatchersMvcMatcherInLambdaConfig { @Bean SecurityFilterChain appSecurity(HttpSecurity http) throws Exception { // @formatter:off http .securityMatchers((matchers) -> matchers .requestMatchers("/path") ) .httpBasic(withDefaults()) .authorizeHttpRequests((authorize) -> authorize .anyRequest().denyAll() ); // @formatter:on return http.build(); } @RestController static class PathController { @RequestMapping("/path") String path() { return "path"; } } } @EnableWebSecurity @Configuration @EnableWebMvc @Import(UsersConfig.class) static class SecurityMatchersMvcMatcherServletPathConfig { @Bean PathPatternRequestMatcherBuilderFactoryBean requestMatcherBuilder() { PathPatternRequestMatcherBuilderFactoryBean bean = new PathPatternRequestMatcherBuilderFactoryBean(); bean.setBasePath("/spring"); return bean; } @Bean SecurityFilterChain appSecurity(HttpSecurity http, PathPatternRequestMatcher.Builder builder) throws Exception { // @formatter:off http .securityMatchers((security) -> security .requestMatchers(builder.matcher("/path")) .requestMatchers(builder.matcher("/never-match")) ) .httpBasic(withDefaults()) .authorizeHttpRequests((authorize) -> authorize .anyRequest().denyAll()); // @formatter:on return http.build(); } @RestController static class PathController { @RequestMapping("/path") String path() { return "path"; } } } @EnableWebSecurity @Configuration @EnableWebMvc @Import(UsersConfig.class) static class SecurityMatchersMvcMatcherServletPathInLambdaConfig { @Bean PathPatternRequestMatcherBuilderFactoryBean requestMatcherBuilder() { PathPatternRequestMatcherBuilderFactoryBean bean = new PathPatternRequestMatcherBuilderFactoryBean(); bean.setBasePath("/spring"); return bean; } @Bean SecurityFilterChain appSecurity(HttpSecurity http, PathPatternRequestMatcher.Builder builder) throws Exception { // @formatter:off http .securityMatchers((matchers) -> matchers .requestMatchers(builder.matcher("/path")) .requestMatchers(builder.matcher("/never-match")) ) .httpBasic(withDefaults()) .authorizeHttpRequests((authorize) -> authorize .anyRequest().denyAll() ); // @formatter:on return http.build(); } @RestController static class PathController { @RequestMapping("/path") String path() { return "path"; } } } @Configuration static class UsersConfig { @Bean UserDetailsService userDetailsService() { UserDetails user = User.withDefaultPasswordEncoder() .username("user") .password("password") .roles("USER") .build(); return new InMemoryUserDetailsManager(user); } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/annotation/web/configurers/HttpsRedirectConfigurerTests.java ================================================ /* * 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 org.springframework.security.config.annotation.web.configurers; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.config.web.PathPatternRequestMatcherBuilderFactoryBean; import org.springframework.security.web.PortMapper; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher; import org.springframework.test.web.servlet.MockMvc; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; import static org.springframework.security.config.Customizer.withDefaults; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Tests for {@link HttpsRedirectConfigurerTests} * * @author Josh Cummings */ @ExtendWith(SpringTestContextExtension.class) public class HttpsRedirectConfigurerTests { public final SpringTestContext spring = new SpringTestContext(this); @Autowired MockMvc mvc; @Test public void getWhenSecureThenDoesNotRedirect() throws Exception { this.spring.register(RedirectToHttpConfig.class).autowire(); // @formatter:off this.mvc.perform(get("https://localhost")) .andExpect(status().isNotFound()); // @formatter:on } @Test public void getWhenInsecureThenRespondsWithRedirectToSecure() throws Exception { this.spring.register(RedirectToHttpConfig.class).autowire(); // @formatter:off this.mvc.perform(get("http://localhost")) .andExpect(status().isFound()) .andExpect(redirectedUrl("https://localhost")); // @formatter:on } @Test public void getWhenInsecureAndPathRequiresTransportSecurityThenRedirects() throws Exception { this.spring.register(SometimesRedirectToHttpsConfig.class, UsePathPatternConfig.class).autowire(); // @formatter:off this.mvc.perform(get("http://localhost:8080")) .andExpect(status().isNotFound()); this.mvc.perform(get("http://localhost:8080/secure")) .andExpect(status().isFound()) .andExpect(redirectedUrl("https://localhost:8443/secure")); // @formatter:on } @Test public void getWhenInsecureAndUsingCustomPortMapperThenRespondsWithRedirectToSecurePort() throws Exception { this.spring.register(RedirectToHttpsViaCustomPortsConfig.class).autowire(); PortMapper portMapper = this.spring.getContext().getBean(PortMapper.class); given(portMapper.lookupHttpsPort(4080)).willReturn(4443); // @formatter:off this.mvc.perform(get("http://localhost:4080")) .andExpect(status().isFound()) .andExpect(redirectedUrl("https://localhost:4443")); // @formatter:on } @Configuration @EnableWebMvc @EnableWebSecurity static class RedirectToHttpConfig { @Bean SecurityFilterChain springSecurity(HttpSecurity http) throws Exception { // @formatter:off http .redirectToHttps(withDefaults()); // @formatter:on return http.build(); } } @Configuration @EnableWebMvc @EnableWebSecurity static class SometimesRedirectToHttpsConfig { @Bean SecurityFilterChain springSecurity(HttpSecurity http, PathPatternRequestMatcher.Builder path) throws Exception { // @formatter:off http .redirectToHttps((https) -> https.requestMatchers(path.matcher("/secure"))); // @formatter:on return http.build(); } } @Configuration @EnableWebMvc @EnableWebSecurity static class RedirectToHttpsViaCustomPortsConfig { @Bean SecurityFilterChain springSecurity(HttpSecurity http) throws Exception { // @formatter:off http .portMapper((p) -> p.portMapper(portMapper())) .redirectToHttps(withDefaults()); // @formatter:on return http.build(); } @Bean PortMapper portMapper() { return mock(PortMapper.class); } } @Configuration static class UsePathPatternConfig { @Bean PathPatternRequestMatcherBuilderFactoryBean requestMatcherBuilder() { return new PathPatternRequestMatcherBuilderFactoryBean(); } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/annotation/web/configurers/JeeConfigurerTests.java ================================================ /* * 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 org.springframework.security.config.annotation.web.configurers; import java.security.Principal; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.security.config.ObjectPostProcessor; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.userdetails.AuthenticationUserDetailsService; import org.springframework.security.core.userdetails.User; import org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken; import org.springframework.security.web.authentication.preauth.j2ee.J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource; import org.springframework.security.web.authentication.preauth.j2ee.J2eePreAuthenticatedProcessingFilter; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.springframework.security.config.Customizer.withDefaults; import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; /** * Tests for {@link JeeConfigurer} * * @author Rob Winch * @author Eleftheria Stein */ @ExtendWith(SpringTestContextExtension.class) public class JeeConfigurerTests { public final SpringTestContext spring = new SpringTestContext(this); @Autowired MockMvc mvc; @Test public void configureWhenRegisteringObjectPostProcessorThenInvokedOnJ2eePreAuthenticatedProcessingFilter() { this.spring.register(ObjectPostProcessorConfig.class).autowire(); ObjectPostProcessor objectPostProcessor = this.spring.getContext().getBean(ObjectPostProcessor.class); verify(objectPostProcessor).postProcess(any(J2eePreAuthenticatedProcessingFilter.class)); } @Test public void configureWhenRegisteringObjectPostProcessorThenInvokedOnJ2eeBasedPreAuthenticatedWebAuthenticationDetailsSource() { this.spring.register(ObjectPostProcessorConfig.class).autowire(); ObjectPostProcessor objectPostProcessor = this.spring.getContext().getBean(ObjectPostProcessor.class); verify(objectPostProcessor).postProcess(any(J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource.class)); } @Test public void jeeWhenInvokedTwiceThenUsesOriginalMappableRoles() throws Exception { this.spring.register(InvokeTwiceDoesNotOverride.class).autowire(); Principal user = mock(Principal.class); given(user.getName()).willReturn("user"); // @formatter:off MockHttpServletRequestBuilder authRequest = get("/") .principal(user) .with((request) -> { request.addUserRole("ROLE_ADMIN"); request.addUserRole("ROLE_USER"); return request; }); // @formatter:on this.mvc.perform(authRequest).andExpect(authenticated().withRoles("USER")); } @Test public void requestWhenJeeMappableRolesInLambdaThenAuthenticatedWithMappableRoles() throws Exception { this.spring.register(JeeMappableRolesConfig.class).autowire(); Principal user = mock(Principal.class); given(user.getName()).willReturn("user"); // @formatter:off MockHttpServletRequestBuilder authRequest = get("/") .principal(user) .with((request) -> { request.addUserRole("ROLE_ADMIN"); request.addUserRole("ROLE_USER"); return request; }); // @formatter:on this.mvc.perform(authRequest).andExpect(authenticated().withRoles("USER")); } @Test public void requestWhenJeeMappableAuthoritiesInLambdaThenAuthenticatedWithMappableAuthorities() throws Exception { this.spring.register(JeeMappableAuthoritiesConfig.class).autowire(); Principal user = mock(Principal.class); given(user.getName()).willReturn("user"); // @formatter:off MockHttpServletRequestBuilder authRequest = get("/") .principal(user) .with((request) -> { request.addUserRole("ROLE_ADMIN"); request.addUserRole("ROLE_USER"); return request; }); // @formatter:on SecurityMockMvcResultMatchers.AuthenticatedMatcher authenticatedAsUser = authenticated() .withAuthorities(AuthorityUtils.createAuthorityList("ROLE_USER")); this.mvc.perform(authRequest).andExpect(authenticatedAsUser); } @Test public void requestWhenCustomAuthenticatedUserDetailsServiceInLambdaThenCustomAuthenticatedUserDetailsServiceUsed() throws Exception { this.spring.register(JeeCustomAuthenticatedUserDetailsServiceConfig.class).autowire(); AuthenticationUserDetailsService userDetailsService = this.spring .getContext() .getBean(AuthenticationUserDetailsService.class); Principal user = mock(Principal.class); User userDetails = new User("user", "N/A", true, true, true, true, AuthorityUtils.createAuthorityList("ROLE_USER")); given(user.getName()).willReturn("user"); given(userDetailsService.loadUserDetails(any())).willReturn(userDetails); // @formatter:off MockHttpServletRequestBuilder authRequest = get("/") .principal(user) .with((request) -> { request.addUserRole("ROLE_ADMIN"); request.addUserRole("ROLE_USER"); return request; }); // @formatter:on this.mvc.perform(authRequest).andExpect(authenticated().withRoles("USER")); } @Configuration @EnableWebSecurity static class ObjectPostProcessorConfig { ObjectPostProcessor objectPostProcessor = spy(ReflectingObjectPostProcessor.class); @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .jee(withDefaults()); return http.build(); // @formatter:on } @Bean @Primary ObjectPostProcessor objectPostProcessor() { return this.objectPostProcessor; } } static class ReflectingObjectPostProcessor implements ObjectPostProcessor { @Override public O postProcess(O object) { return object; } } @Configuration @EnableWebSecurity static class InvokeTwiceDoesNotOverride { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .jee((jee) -> jee .mappableRoles("USER")) .jee(withDefaults()); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity public static class JeeMappableRolesConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((authorize) -> authorize .anyRequest().hasRole("USER") ) .jee((jee) -> jee .mappableRoles("USER") ); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity public static class JeeMappableAuthoritiesConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((authorize) -> authorize .anyRequest().hasRole("USER") ) .jee((jee) -> jee .mappableAuthorities("ROLE_USER") ); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity public static class JeeCustomAuthenticatedUserDetailsServiceConfig { private AuthenticationUserDetailsService authenticationUserDetailsService = mock( AuthenticationUserDetailsService.class); @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((authorize) -> authorize .anyRequest().hasRole("USER") ) .jee((jee) -> jee .authenticatedUserDetailsService(this.authenticationUserDetailsService) ); return http.build(); // @formatter:on } @Bean AuthenticationUserDetailsService authenticationUserDetailsService() { return this.authenticationUserDetailsService; } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/annotation/web/configurers/LogoutConfigurerClearSiteDataTests.java ================================================ /* * 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 org.springframework.security.config.annotation.web.configurers; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners; import org.springframework.security.test.context.support.WithMockUser; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.logout.HeaderWriterLogoutHandler; import org.springframework.security.web.header.writers.ClearSiteDataHeaderWriter; import org.springframework.security.web.header.writers.ClearSiteDataHeaderWriter.Directive; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; /** * * Tests for {@link HeaderWriterLogoutHandler} that passing * {@link ClearSiteDataHeaderWriter} implementation. * * @author Rafiullah Hamedy * */ @ExtendWith({ SpringExtension.class, SpringTestContextExtension.class }) @SecurityTestExecutionListeners public class LogoutConfigurerClearSiteDataTests { private static final String CLEAR_SITE_DATA_HEADER = "Clear-Site-Data"; private static final Directive[] SOURCE = { Directive.CACHE, Directive.COOKIES, Directive.STORAGE, Directive.EXECUTION_CONTEXTS }; private static final String HEADER_VALUE = "\"cache\", \"cookies\", \"storage\", \"executionContexts\""; public final SpringTestContext spring = new SpringTestContext(this); @Autowired MockMvc mvc; @Test @WithMockUser public void logoutWhenRequestTypeGetThenHeaderNotPresentt() throws Exception { this.spring.register(HttpLogoutConfig.class).autowire(); MockHttpServletRequestBuilder logoutRequest = get("/logout").secure(true).with(csrf()); this.mvc.perform(logoutRequest).andExpect(header().doesNotExist(CLEAR_SITE_DATA_HEADER)); } @Test @WithMockUser public void logoutWhenRequestTypePostAndNotSecureThenHeaderNotPresent() throws Exception { this.spring.register(HttpLogoutConfig.class).autowire(); MockHttpServletRequestBuilder logoutRequest = post("/logout").with(csrf()); this.mvc.perform(logoutRequest).andExpect(header().doesNotExist(CLEAR_SITE_DATA_HEADER)); } @Test @WithMockUser public void logoutWhenRequestTypePostAndSecureThenHeaderIsPresent() throws Exception { this.spring.register(HttpLogoutConfig.class).autowire(); MockHttpServletRequestBuilder logoutRequest = post("/logout").secure(true).with(csrf()); this.mvc.perform(logoutRequest).andExpect(header().stringValues(CLEAR_SITE_DATA_HEADER, HEADER_VALUE)); } @Configuration @EnableWebSecurity static class HttpLogoutConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .logout((logout) -> logout .addLogoutHandler(new HeaderWriterLogoutHandler(new ClearSiteDataHeaderWriter(SOURCE)))); return http.build(); // @formatter:on } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/annotation/web/configurers/LogoutConfigurerTests.java ================================================ /* * 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 org.springframework.security.config.annotation.web.configurers; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.apache.http.HttpHeaders; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.MediaType; import org.springframework.mock.web.MockHttpSession; import org.springframework.security.config.Customizer; import org.springframework.security.config.ObjectPostProcessor; import org.springframework.security.config.annotation.SecurityContextChangedListenerConfig; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.security.core.userdetails.PasswordEncodedUser; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.RememberMeServices; import org.springframework.security.web.authentication.logout.LogoutFilter; import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; import org.springframework.security.web.context.HttpSessionSecurityContextRepository; import org.springframework.security.web.context.SecurityContextRepository; import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.springframework.security.config.Customizer.withDefaults; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Tests for {@link LogoutConfigurer} * * @author Rob Winch * @author Eleftheria Stein */ @ExtendWith(SpringTestContextExtension.class) public class LogoutConfigurerTests { public final SpringTestContext spring = new SpringTestContext(this); @Autowired MockMvc mvc; @Test public void configureWhenDefaultLogoutSuccessHandlerForHasNullLogoutHandlerThenException() { assertThatExceptionOfType(BeanCreationException.class) .isThrownBy(() -> this.spring.register(NullLogoutSuccessHandlerConfig.class).autowire()) .withRootCauseInstanceOf(IllegalArgumentException.class); } @Test public void configureWhenDefaultLogoutSuccessHandlerForHasNullLogoutHandlerInLambdaThenException() { assertThatExceptionOfType(BeanCreationException.class) .isThrownBy(() -> this.spring.register(NullLogoutSuccessHandlerInLambdaConfig.class).autowire()) .withRootCauseInstanceOf(IllegalArgumentException.class); } @Test public void configureWhenDefaultLogoutSuccessHandlerForHasNullMatcherThenException() { assertThatExceptionOfType(BeanCreationException.class) .isThrownBy(() -> this.spring.register(NullMatcherConfig.class).autowire()) .withRootCauseInstanceOf(IllegalArgumentException.class); } @Test public void configureWhenDefaultLogoutSuccessHandlerForHasNullMatcherInLambdaThenException() { assertThatExceptionOfType(BeanCreationException.class) .isThrownBy(() -> this.spring.register(NullMatcherInLambdaConfig.class).autowire()) .withRootCauseInstanceOf(IllegalArgumentException.class); } @Test public void configureWhenRegisteringObjectPostProcessorThenInvokedOnLogoutFilter() { this.spring.register(ObjectPostProcessorConfig.class).autowire(); ObjectPostProcessor objectPostProcessor = this.spring.getContext() .getBean(ObjectPostProcessorConfig.class).objectPostProcessor; verify(objectPostProcessor).postProcess(any(LogoutFilter.class)); } @Test public void logoutWhenInvokedTwiceThenUsesOriginalLogoutUrl() throws Exception { this.spring.register(DuplicateDoesNotOverrideConfig.class).autowire(); MockHttpServletRequestBuilder logoutRequest = post("/custom/logout").with(csrf()); // @formatter:off this.mvc.perform(logoutRequest) .andExpect(status().isFound()) .andExpect(redirectedUrl("/login?logout")); // @formatter:on } // SEC-2311 @Test public void logoutWhenGetRequestAndCsrfDisabledThenRedirectsToLogin() throws Exception { this.spring.register(CsrfDisabledConfig.class).autowire(); // @formatter:off this.mvc.perform(get("/logout")) .andExpect(status().isFound()) .andExpect(redirectedUrl("/login?logout")); // @formatter:on } @Test public void logoutWhenPostRequestAndCsrfDisabledThenRedirectsToLogin() throws Exception { this.spring.register(CsrfDisabledConfig.class).autowire(); // @formatter:off this.mvc.perform(post("/logout")) .andExpect(status().isFound()) .andExpect(redirectedUrl("/login?logout")); // @formatter:on } @Test public void logoutWhenPutRequestAndCsrfDisabledThenRedirectsToLogin() throws Exception { this.spring.register(CsrfDisabledConfig.class).autowire(); // @formatter:off this.mvc.perform(put("/logout")) .andExpect(status().isFound()) .andExpect(redirectedUrl("/login?logout")); // @formatter:on } @Test public void logoutWhenDeleteRequestAndCsrfDisabledThenRedirectsToLogin() throws Exception { this.spring.register(CsrfDisabledConfig.class).autowire(); // @formatter:off this.mvc.perform(delete("/logout")) .andExpect(status().isFound()) .andExpect(redirectedUrl("/login?logout")); // @formatter:on } @Test public void logoutWhenGetRequestAndCsrfDisabledAndCustomLogoutUrlThenRedirectsToLogin() throws Exception { this.spring.register(CsrfDisabledAndCustomLogoutConfig.class).autowire(); // @formatter:off this.mvc.perform(get("/custom/logout")) .andExpect(status().isFound()) .andExpect(redirectedUrl("/login?logout")); // @formatter:on } @Test public void logoutWhenPostRequestAndCsrfDisabledAndCustomLogoutUrlThenRedirectsToLogin() throws Exception { this.spring.register(CsrfDisabledAndCustomLogoutConfig.class).autowire(); // @formatter:off this.mvc.perform(post("/custom/logout")) .andExpect(status().isFound()) .andExpect(redirectedUrl("/login?logout")); // @formatter:on } @Test public void logoutWhenPutRequestAndCsrfDisabledAndCustomLogoutUrlThenRedirectsToLogin() throws Exception { this.spring.register(CsrfDisabledAndCustomLogoutConfig.class).autowire(); // @formatter:off this.mvc.perform(put("/custom/logout")) .andExpect(status().isFound()) .andExpect(redirectedUrl("/login?logout")); // @formatter:on } @Test public void logoutWhenDeleteRequestAndCsrfDisabledAndCustomLogoutUrlThenRedirectsToLogin() throws Exception { this.spring.register(CsrfDisabledAndCustomLogoutConfig.class).autowire(); // @formatter:off this.mvc.perform(delete("/custom/logout")) .andExpect(status().isFound()) .andExpect(redirectedUrl("/login?logout")); // @formatter:on } @Test public void logoutWhenCustomLogoutUrlInLambdaThenRedirectsToLogin() throws Exception { this.spring.register(CsrfDisabledAndCustomLogoutInLambdaConfig.class).autowire(); // @formatter:off this.mvc.perform(get("/custom/logout")) .andExpect(status().isFound()) .andExpect(redirectedUrl("/login?logout")); // @formatter:on } // SEC-3170 @Test public void configureWhenLogoutHandlerNullThenException() { assertThatExceptionOfType(BeanCreationException.class) .isThrownBy(() -> this.spring.register(NullLogoutHandlerConfig.class).autowire()) .withRootCauseInstanceOf(IllegalArgumentException.class); } @Test public void configureWhenLogoutHandlerNullInLambdaThenException() { assertThatExceptionOfType(BeanCreationException.class) .isThrownBy(() -> this.spring.register(NullLogoutHandlerInLambdaConfig.class).autowire()) .withRootCauseInstanceOf(IllegalArgumentException.class); } // SEC-3170 @Test public void rememberMeWhenRememberMeServicesNotLogoutHandlerThenRedirectsToLogin() throws Exception { this.spring.register(RememberMeNoLogoutHandler.class).autowire(); this.mvc.perform(post("/logout").with(csrf())) .andExpect(status().isFound()) .andExpect(redirectedUrl("/login?logout")); } @Test public void logoutWhenAcceptTextHtmlThenRedirectsToLogin() throws Exception { this.spring.register(BasicSecurityConfig.class).autowire(); // @formatter:off MockHttpServletRequestBuilder logoutRequest = post("/logout") .with(csrf()) .with(user("user")) .header(HttpHeaders.ACCEPT, MediaType.TEXT_HTML_VALUE); this.mvc.perform(logoutRequest) .andExpect(status().isFound()) .andExpect(redirectedUrl("/login?logout")); // @formatter:on } @Test public void logoutWhenCustomSecurityContextHolderStrategyThenUses() throws Exception { this.spring.register(BasicSecurityConfig.class, SecurityContextChangedListenerConfig.class).autowire(); // @formatter:off MockHttpServletRequestBuilder logoutRequest = post("/logout") .with(csrf()) .with(user("user")) .header(HttpHeaders.ACCEPT, MediaType.TEXT_HTML_VALUE); this.mvc.perform(logoutRequest) .andExpect(status().isFound()) .andExpect(redirectedUrl("/login?logout")); // @formatter:on SecurityContextHolderStrategy strategy = this.spring.getContext().getBean(SecurityContextHolderStrategy.class); verify(strategy, atLeastOnce()).getContext(); } // gh-3282 @Test public void logoutWhenAcceptApplicationJsonThenReturnsStatusNoContent() throws Exception { this.spring.register(BasicSecurityConfig.class).autowire(); // @formatter:off MockHttpServletRequestBuilder request = post("/logout") .with(csrf()) .with(user("user")) .header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE); // @formatter:on this.mvc.perform(request).andExpect(status().isNoContent()); } // gh-4831 @Test public void logoutWhenAcceptAllThenReturnsStatusNoContent() throws Exception { this.spring.register(BasicSecurityConfig.class).autowire(); // @formatter:off MockHttpServletRequestBuilder logoutRequest = post("/logout") .with(csrf()) .with(user("user")) .header(HttpHeaders.ACCEPT, MediaType.ALL_VALUE); // @formatter:on this.mvc.perform(logoutRequest).andExpect(status().isNoContent()); } // gh-3902 @Test public void logoutWhenAcceptFromChromeThenRedirectsToLogin() throws Exception { this.spring.register(BasicSecurityConfig.class).autowire(); // @formatter:off MockHttpServletRequestBuilder request = post("/logout") .with(csrf()) .with(user("user")) .header(HttpHeaders.ACCEPT, "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8"); this.mvc.perform(request) .andExpect(status().isFound()) .andExpect(redirectedUrl("/login?logout")); // @formatter:on } // gh-3997 @Test public void logoutWhenXMLHttpRequestThenReturnsStatusNoContent() throws Exception { this.spring.register(BasicSecurityConfig.class).autowire(); // @formatter:off MockHttpServletRequestBuilder request = post("/logout") .with(csrf()) .with(user("user")) .header(HttpHeaders.ACCEPT, "text/html,application/json") .header("X-Requested-With", "XMLHttpRequest"); // @formatter:on this.mvc.perform(request).andExpect(status().isNoContent()); } @Test public void logoutWhenDisabledThenLogoutUrlNotFound() throws Exception { this.spring.register(LogoutDisabledConfig.class).autowire(); this.mvc.perform(post("/logout").with(csrf())).andExpect(status().isNotFound()); } @Test public void logoutWhenCustomSecurityContextRepositoryThenUses() throws Exception { CustomSecurityContextRepositoryConfig.repository = mock(SecurityContextRepository.class); this.spring.register(CustomSecurityContextRepositoryConfig.class).autowire(); // @formatter:off MockHttpServletRequestBuilder logoutRequest = post("/logout") .with(csrf()) .with(user("user")) .header(HttpHeaders.ACCEPT, MediaType.TEXT_HTML_VALUE); this.mvc.perform(logoutRequest) .andExpect(status().isFound()) .andExpect(redirectedUrl("/login?logout")); // @formatter:on int invocationCount = 2; // 1 from user() post processor and 1 from // SecurityContextLogoutHandler verify(CustomSecurityContextRepositoryConfig.repository, times(invocationCount)).saveContext(any(), any(HttpServletRequest.class), any(HttpServletResponse.class)); } @Test public void logoutWhenNoSecurityContextRepositoryThenHttpSessionSecurityContextRepository() throws Exception { this.spring.register(InvalidateHttpSessionFalseConfig.class).autowire(); MockHttpSession session = mock(MockHttpSession.class); // @formatter:off MockHttpServletRequestBuilder logoutRequest = post("/logout") .with(csrf()) .with(user("user")) .session(session) .header(HttpHeaders.ACCEPT, MediaType.TEXT_HTML_VALUE); this.mvc.perform(logoutRequest) .andExpect(status().isFound()) .andExpect(redirectedUrl("/login?logout")) .andReturn(); // @formatter:on verify(session).removeAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY); } @Configuration @EnableWebSecurity static class InvalidateHttpSessionFalseConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .logout((logout) -> logout.invalidateHttpSession(false)) .securityContext((context) -> context.requireExplicitSave(true)); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class CustomSecurityContextRepositoryConfig { static SecurityContextRepository repository; @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .logout(Customizer.withDefaults()) .securityContext((context) -> context .requireExplicitSave(true) .securityContextRepository(repository) ); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class NullLogoutSuccessHandlerConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .logout((logout) -> logout .defaultLogoutSuccessHandlerFor(null, mock(RequestMatcher.class))); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class NullLogoutSuccessHandlerInLambdaConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .logout((logout) -> logout.defaultLogoutSuccessHandlerFor(null, mock(RequestMatcher.class)) ); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class NullMatcherConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .logout((logout) -> logout .defaultLogoutSuccessHandlerFor(mock(LogoutSuccessHandler.class), null)); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class NullMatcherInLambdaConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .logout((logout) -> logout.defaultLogoutSuccessHandlerFor(mock(LogoutSuccessHandler.class), null) ); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class ObjectPostProcessorConfig { ObjectPostProcessor objectPostProcessor = spy(ReflectingObjectPostProcessor.class); @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .logout(withDefaults()); return http.build(); // @formatter:on } @Bean ObjectPostProcessor objectPostProcessor() { return this.objectPostProcessor; } } static class ReflectingObjectPostProcessor implements ObjectPostProcessor { @Override public O postProcess(O object) { return object; } } @Configuration @EnableWebSecurity static class DuplicateDoesNotOverrideConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .logout((logout) -> logout .logoutUrl("/custom/logout")) .logout(withDefaults()); // @formatter:on return http.build(); } @Bean UserDetailsService userDetailsService() { return new InMemoryUserDetailsManager(PasswordEncodedUser.user()); } } @Configuration @EnableWebSecurity static class CsrfDisabledConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .csrf((csrf) -> csrf .disable()) .logout(withDefaults()); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class CsrfDisabledAndCustomLogoutConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .csrf((csrf) -> csrf .disable()) .logout((logout) -> logout .logoutUrl("/custom/logout")); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class CsrfDisabledAndCustomLogoutInLambdaConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .csrf((csrf) -> csrf .disable()) .logout((logout) -> logout.logoutUrl("/custom/logout")); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class NullLogoutHandlerConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .logout((logout) -> logout .addLogoutHandler(null)); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class NullLogoutHandlerInLambdaConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .logout((logout) -> logout.addLogoutHandler(null)); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class RememberMeNoLogoutHandler { static RememberMeServices REMEMBER_ME = mock(RememberMeServices.class); @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .rememberMe((me) -> me .rememberMeServices(REMEMBER_ME)); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class BasicSecurityConfig { } @Configuration @EnableWebSecurity static class LogoutDisabledConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .logout((logout) -> logout .disable()); return http.build(); // @formatter:on } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceDebugTests.java ================================================ /* * 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 org.springframework.security.config.annotation.web.configurers; import ch.qos.logback.classic.Level; import ch.qos.logback.classic.Logger; import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.core.Appender; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.web.debug.DebugFilter; import org.springframework.test.web.servlet.MockMvc; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; /** * Tests to verify {@code EnableWebSecurity(debug)} functionality * * @author Rob Winch * @author Josh Cummings */ @ExtendWith(SpringTestContextExtension.class) public class NamespaceDebugTests { public final SpringTestContext spring = new SpringTestContext(this); @Autowired MockMvc mvc; @Test public void requestWhenDebugSetToTrueThenLogsDebugInformation() throws Exception { Appender appender = mockAppenderFor("Spring Security Debugger"); this.spring.register(DebugWebSecurity.class).autowire(); this.mvc.perform(get("/")); verify(appender, atLeastOnce()).doAppend(any(ILoggingEvent.class)); } @Test public void requestWhenDebugSetToFalseThenDoesNotLogDebugInformation() throws Exception { Appender appender = mockAppenderFor("Spring Security Debugger"); this.spring.register(NoDebugWebSecurity.class).autowire(); this.mvc.perform(get("/")); assertThat(filterChainClass()).isNotEqualTo(DebugFilter.class); verify(appender, never()).doAppend(any(ILoggingEvent.class)); } private Appender mockAppenderFor(String name) { Appender appender = mock(Appender.class); Logger logger = (Logger) LoggerFactory.getLogger(name); logger.setLevel(Level.DEBUG); logger.addAppender(appender); return appender; } private Class filterChainClass() { return this.spring.getContext().getBean("springSecurityFilterChain").getClass(); } @Configuration @EnableWebSecurity(debug = true) static class DebugWebSecurity { } @Configuration @EnableWebSecurity static class NoDebugWebSecurity { } } ================================================ FILE: config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpAnonymousTests.java ================================================ /* * 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 org.springframework.security.config.annotation.web.configurers; import java.util.Optional; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AnonymousAuthenticationToken; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.PasswordEncodedUser; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.web.SecurityFilterChain; import org.springframework.test.web.servlet.MockMvc; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Tests to verify that all the functionality of <anonymous> attributes is present * * @author Rob Winch * @author Josh Cummings * */ @ExtendWith(SpringTestContextExtension.class) public class NamespaceHttpAnonymousTests { @Autowired MockMvc mvc; public final SpringTestContext spring = new SpringTestContext(this); @Test public void anonymousRequestWhenUsingDefaultAnonymousConfigurationThenUsesAnonymousAuthentication() throws Exception { this.spring.register(AnonymousConfig.class, AnonymousController.class).autowire(); this.mvc.perform(get("/type")).andExpect(content().string(AnonymousAuthenticationToken.class.getSimpleName())); } @Test public void anonymousRequestWhenDisablingAnonymousThenDenies() throws Exception { this.spring.register(AnonymousDisabledConfig.class, AnonymousController.class).autowire(); this.mvc.perform(get("/type")).andExpect(status().isForbidden()); } @Test public void requestWhenAnonymousThenSendsAnonymousConfiguredAuthorities() throws Exception { this.spring.register(AnonymousGrantedAuthorityConfig.class, AnonymousController.class).autowire(); this.mvc.perform(get("/type")).andExpect(content().string(AnonymousAuthenticationToken.class.getSimpleName())); } @Test public void anonymousRequestWhenAnonymousKeyConfiguredThenKeyIsUsed() throws Exception { this.spring.register(AnonymousKeyConfig.class, AnonymousController.class).autowire(); this.mvc.perform(get("/key")).andExpect(content().string(String.valueOf("AnonymousKeyConfig".hashCode()))); } @Test public void anonymousRequestWhenAnonymousUsernameConfiguredThenUsernameIsUsed() throws Exception { this.spring.register(AnonymousUsernameConfig.class, AnonymousController.class).autowire(); this.mvc.perform(get("/principal")).andExpect(content().string("AnonymousUsernameConfig")); } @Configuration @EnableWebSecurity @EnableWebMvc static class AnonymousConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((requests) -> requests .requestMatchers("/type").anonymous() .anyRequest().denyAll()); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class AnonymousDisabledConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((requests) -> requests.anyRequest().anonymous()) .anonymous((anonymous) -> anonymous.disable()); // @formatter:on return http.build(); } @Bean UserDetailsService userDetailsService() { return new InMemoryUserDetailsManager(PasswordEncodedUser.user(), PasswordEncodedUser.admin()); } } @Configuration @EnableWebSecurity @EnableWebMvc static class AnonymousGrantedAuthorityConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((requests) -> requests .requestMatchers("/type").hasRole("ANON") .anyRequest().denyAll()) .anonymous((anonymous) -> anonymous .authorities("ROLE_ANON")); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity @EnableWebMvc static class AnonymousKeyConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((requests) -> requests .requestMatchers("/key").anonymous() .anyRequest().denyAll()) .anonymous((anonymous) -> anonymous.key("AnonymousKeyConfig")); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity @EnableWebMvc static class AnonymousUsernameConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((requests) -> requests .requestMatchers("/principal").anonymous() .anyRequest().denyAll()) .anonymous((anonymous) -> anonymous.principal("AnonymousUsernameConfig")); return http.build(); // @formatter:on } } @RestController static class AnonymousController { @GetMapping("/type") String type() { return anonymousToken().map(AnonymousAuthenticationToken::getClass).map(Class::getSimpleName).orElse(null); } @GetMapping("/key") String key() { return anonymousToken().map(AnonymousAuthenticationToken::getKeyHash).map(String::valueOf).orElse(null); } @GetMapping("/principal") String principal() { return anonymousToken().map(AnonymousAuthenticationToken::getName).orElse(null); } Optional anonymousToken() { return Optional.of(SecurityContextHolder.getContext()) .map(SecurityContext::getAuthentication) .filter((a) -> a instanceof AnonymousAuthenticationToken) .map(AnonymousAuthenticationToken.class::cast); } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpBasicTests.java ================================================ /* * 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 org.springframework.security.config.annotation.web.configurers; import jakarta.servlet.http.HttpServletRequest; import org.apache.http.HttpHeaders; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationDetailsSource; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.security.web.SecurityFilterChain; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.springframework.security.config.Customizer.withDefaults; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Tests to verify that all the functionality of <http-basic> attributes is present * * @author Rob Winch * @author Josh Cummings */ @ExtendWith(SpringTestContextExtension.class) public class NamespaceHttpBasicTests { public final SpringTestContext spring = new SpringTestContext(this); @Autowired MockMvc mvc; /** * http/http-basic equivalent */ @Test public void basicAuthenticationWhenUsingDefaultsThenMatchesNamespace() throws Exception { this.spring.register(HttpBasicConfig.class, UserConfig.class).autowire(); this.mvc.perform(get("/")).andExpect(status().isUnauthorized()); MockHttpServletRequestBuilder requestWithInvalidPassword = get("/").with(httpBasic("user", "invalid")); // @formatter:off this.mvc.perform(requestWithInvalidPassword) .andExpect(status().isUnauthorized()) .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, "Basic realm=\"Realm\", charset=\"UTF-8\"")); // @formatter:on MockHttpServletRequestBuilder requestWithValidPassword = get("/").with(httpBasic("user", "password")); this.mvc.perform(requestWithValidPassword).andExpect(status().isNotFound()); } @Test public void basicAuthenticationWhenUsingDefaultsInLambdaThenMatchesNamespace() throws Exception { this.spring.register(HttpBasicLambdaConfig.class, UserConfig.class).autowire(); this.mvc.perform(get("/")).andExpect(status().isUnauthorized()); MockHttpServletRequestBuilder requestWithInvalidPassword = get("/").with(httpBasic("user", "invalid")); // @formatter:off this.mvc.perform(requestWithInvalidPassword) .andExpect(status().isUnauthorized()) .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, "Basic realm=\"Realm\", charset=\"UTF-8\"")); // @formatter:on MockHttpServletRequestBuilder requestWithValidPassword = get("/").with(httpBasic("user", "password")); this.mvc.perform(requestWithValidPassword).andExpect(status().isNotFound()); } /** * http@realm equivalent */ @Test public void basicAuthenticationWhenUsingCustomRealmThenMatchesNamespace() throws Exception { this.spring.register(CustomHttpBasicConfig.class, UserConfig.class).autowire(); MockHttpServletRequestBuilder requestWithInvalidPassword = get("/").with(httpBasic("user", "invalid")); // @formatter:off this.mvc.perform(requestWithInvalidPassword) .andExpect(status().isUnauthorized()) .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, "Basic realm=\"Custom Realm\", charset=\"UTF-8\"")); // @formatter:on } @Test public void basicAuthenticationWhenUsingCustomRealmInLambdaThenMatchesNamespace() throws Exception { this.spring.register(CustomHttpBasicLambdaConfig.class, UserConfig.class).autowire(); MockHttpServletRequestBuilder requestWithInvalidPassword = get("/").with(httpBasic("user", "invalid")); // @formatter:off this.mvc.perform(requestWithInvalidPassword) .andExpect(status().isUnauthorized()) .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, "Basic realm=\"Custom Realm\", charset=\"UTF-8\"")); // @formatter:on } /** * http/http-basic@authentication-details-source-ref equivalent */ @Test public void basicAuthenticationWhenUsingAuthenticationDetailsSourceRefThenMatchesNamespace() throws Exception { this.spring.register(AuthenticationDetailsSourceHttpBasicConfig.class, UserConfig.class).autowire(); AuthenticationDetailsSource source = this.spring.getContext() .getBean(AuthenticationDetailsSource.class); this.mvc.perform(get("/").with(httpBasic("user", "password"))); verify(source).buildDetails(any(HttpServletRequest.class)); } @Test public void basicAuthenticationWhenUsingAuthenticationDetailsSourceRefInLambdaThenMatchesNamespace() throws Exception { this.spring.register(AuthenticationDetailsSourceHttpBasicLambdaConfig.class, UserConfig.class).autowire(); AuthenticationDetailsSource source = this.spring.getContext() .getBean(AuthenticationDetailsSource.class); this.mvc.perform(get("/").with(httpBasic("user", "password"))); verify(source).buildDetails(any(HttpServletRequest.class)); } /** * http/http-basic@entry-point-ref */ @Test public void basicAuthenticationWhenUsingEntryPointRefThenMatchesNamespace() throws Exception { this.spring.register(EntryPointRefHttpBasicConfig.class, UserConfig.class).autowire(); this.mvc.perform(get("/")).andExpect(status().is(999)); this.mvc.perform(get("/").with(httpBasic("user", "invalid"))).andExpect(status().is(999)); this.mvc.perform(get("/").with(httpBasic("user", "password"))).andExpect(status().isNotFound()); } @Test public void basicAuthenticationWhenUsingEntryPointRefInLambdaThenMatchesNamespace() throws Exception { this.spring.register(EntryPointRefHttpBasicLambdaConfig.class, UserConfig.class).autowire(); this.mvc.perform(get("/")).andExpect(status().is(999)); this.mvc.perform(get("/").with(httpBasic("user", "invalid"))).andExpect(status().is(999)); this.mvc.perform(get("/").with(httpBasic("user", "password"))).andExpect(status().isNotFound()); } @Configuration static class UserConfig { @Bean UserDetailsService userDetailsService() { return new InMemoryUserDetailsManager( // @formatter:off User.withDefaultPasswordEncoder() .username("user") .password("password") .roles("USER") .build() // @formatter:on ); } } @Configuration @EnableWebSecurity static class HttpBasicConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((requests) -> requests .anyRequest().hasRole("USER")) .httpBasic(withDefaults()); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class HttpBasicLambdaConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((authorize) -> authorize .anyRequest().hasRole("USER") ) .httpBasic(withDefaults()); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class CustomHttpBasicConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((requests) -> requests .anyRequest().hasRole("USER")) .httpBasic((basic) -> basic.realmName("Custom Realm")); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class CustomHttpBasicLambdaConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((authorize) -> authorize .anyRequest().hasRole("USER") ) .httpBasic((httpBasicConfig) -> httpBasicConfig.realmName("Custom Realm")); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class AuthenticationDetailsSourceHttpBasicConfig { AuthenticationDetailsSource authenticationDetailsSource = mock( AuthenticationDetailsSource.class); @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .httpBasic((basic) -> basic .authenticationDetailsSource(this.authenticationDetailsSource)); return http.build(); // @formatter:on } @Bean AuthenticationDetailsSource authenticationDetailsSource() { return this.authenticationDetailsSource; } } @Configuration @EnableWebSecurity static class AuthenticationDetailsSourceHttpBasicLambdaConfig { AuthenticationDetailsSource authenticationDetailsSource = mock( AuthenticationDetailsSource.class); @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .httpBasic((httpBasicConfig) -> httpBasicConfig.authenticationDetailsSource(this.authenticationDetailsSource)); return http.build(); // @formatter:on } @Bean AuthenticationDetailsSource authenticationDetailsSource() { return this.authenticationDetailsSource; } } @Configuration @EnableWebSecurity static class EntryPointRefHttpBasicConfig { AuthenticationEntryPoint authenticationEntryPoint = (request, response, ex) -> response.setStatus(999); @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((requests) -> requests .anyRequest().hasRole("USER")) .httpBasic((basic) -> basic .authenticationEntryPoint(this.authenticationEntryPoint)); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class EntryPointRefHttpBasicLambdaConfig { AuthenticationEntryPoint authenticationEntryPoint = (request, response, ex) -> response.setStatus(999); @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((authorize) -> authorize .anyRequest().hasRole("USER") ) .httpBasic((httpBasicConfig) -> httpBasicConfig.authenticationEntryPoint(this.authenticationEntryPoint)); return http.build(); // @formatter:on } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpCustomFilterTests.java ================================================ /* * 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 org.springframework.security.config.annotation.web.configurers; import java.io.IOException; import java.util.List; import java.util.stream.Collectors; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.assertj.core.api.ListAssert; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.TestHttpSecurities; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.web.FilterChainProxy; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.web.filter.OncePerRequestFilter; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.security.config.Customizer.withDefaults; /** * Tests to verify that all the functionality of <custom-filter> attributes is * present * * @author Rob Winch * @author Josh Cummings * */ @ExtendWith(SpringTestContextExtension.class) public class NamespaceHttpCustomFilterTests { public final SpringTestContext spring = new SpringTestContext(this); @Test public void getFiltersWhenFilterAddedBeforeThenBehaviorMatchesNamespace() { this.spring.register(CustomFilterBeforeConfig.class, UserDetailsServiceConfig.class).autowire(); assertThatFilters().containsSubsequence(CustomFilter.class, UsernamePasswordAuthenticationFilter.class); } @Test public void getFiltersWhenFilterAddedAfterThenBehaviorMatchesNamespace() { this.spring.register(CustomFilterAfterConfig.class, UserDetailsServiceConfig.class).autowire(); assertThatFilters().containsSubsequence(UsernamePasswordAuthenticationFilter.class, CustomFilter.class); } @Test public void getFiltersWhenFilterAddedThenBehaviorMatchesNamespace() { this.spring.register(CustomFilterPositionConfig.class, UserDetailsServiceConfig.class).autowire(); assertThatFilters().containsExactly(CustomFilter.class); } @Test public void getFiltersWhenFilterAddedAtPositionThenBehaviorMatchesNamespace() { this.spring.register(CustomFilterPositionAtConfig.class, UserDetailsServiceConfig.class).autowire(); assertThatFilters().containsExactly(OtherCustomFilter.class); } @Test public void getFiltersWhenCustomAuthenticationManagerThenBehaviorMatchesNamespace() { this.spring.register(NoAuthenticationManagerInHttpConfigurationConfig.class).autowire(); assertThatFilters().startsWith(CustomFilter.class); } private ListAssert> assertThatFilters() { FilterChainProxy filterChain = this.spring.getContext().getBean(FilterChainProxy.class); List> filters = filterChain.getFilters("/") .stream() .map(Object::getClass) .collect(Collectors.toList()); return assertThat(filters); } @Configuration @EnableWebSecurity static class CustomFilterBeforeConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .addFilterBefore(new CustomFilter(), UsernamePasswordAuthenticationFilter.class) .formLogin(withDefaults()); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class CustomFilterAfterConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .addFilterAfter(new CustomFilter(), UsernamePasswordAuthenticationFilter.class) .formLogin(withDefaults()); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class CustomFilterPositionConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off TestHttpSecurities.disableDefaults(http); http // this works so long as the CustomFilter extends one of the standard filters // if not, use addFilterBefore or addFilterAfter .addFilter(new CustomFilter()); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class CustomFilterPositionAtConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off TestHttpSecurities.disableDefaults(http); http .addFilterAt(new OtherCustomFilter(), UsernamePasswordAuthenticationFilter.class); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class NoAuthenticationManagerInHttpConfigurationConfig { @Bean AuthenticationManager authenticationManager() { return new CustomAuthenticationManager(); } @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off TestHttpSecurities.disableDefaults(http); http .authorizeHttpRequests((requests) -> requests .anyRequest().hasRole("USER")) .addFilterBefore(new CustomFilter(), UsernamePasswordAuthenticationFilter.class); return http.build(); // @formatter:on } } @Configuration static class UserDetailsServiceConfig { @Bean UserDetailsService userDetailsService() { // @formatter:off UserDetails user = User.withDefaultPasswordEncoder() .username("user") .password("password") .roles("USER") .build(); // @formatter:on return new InMemoryUserDetailsManager(user); } } static class CustomFilter extends UsernamePasswordAuthenticationFilter { } static class OtherCustomFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { filterChain.doFilter(request, response); } } static class CustomAuthenticationManager implements AuthenticationManager { @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { return null; } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpExpressionHandlerTests.java ================================================ /* * 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 org.springframework.security.config.annotation.web.configurers; import java.security.Principal; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.expression.ExpressionParser; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners; import org.springframework.security.test.context.support.WithMockUser; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.access.expression.DefaultHttpSecurityExpressionHandler; import org.springframework.security.web.access.expression.WebExpressionAuthorizationManager; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.servlet.MockMvc; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; /** * Tests to verify that all the functionality of <expression-handler> attributes is * present * * @author Rob Winch * @author Josh Cummings * */ @ExtendWith({ SpringExtension.class, SpringTestContextExtension.class }) @SecurityTestExecutionListeners public class NamespaceHttpExpressionHandlerTests { public final SpringTestContext spring = new SpringTestContext(this); @Autowired MockMvc mvc; @Test @WithMockUser public void getWhenHasCustomExpressionHandlerThenMatchesNamespace() throws Exception { this.spring.register(ExpressionHandlerController.class, ExpressionHandlerConfig.class).autowire(); this.mvc.perform(get("/whoami")).andExpect(content().string("user")); verifyBean("expressionParser", ExpressionParser.class).parseExpression("hasRole('USER')"); } private T verifyBean(String beanName, Class beanClass) { return verify(this.spring.getContext().getBean(beanName, beanClass)); } @Configuration @EnableWebMvc @EnableWebSecurity static class ExpressionHandlerConfig { @Bean UserDetailsService userDetailsService() { UserDetails user = User.withDefaultPasswordEncoder() .username("rod") .password("password") .roles("USER", "ADMIN") .build(); return new InMemoryUserDetailsManager(user); } @Bean SecurityFilterChain filterChain(HttpSecurity http, WebExpressionAuthorizationManager.Builder authz) throws Exception { // @formatter:off http .authorizeHttpRequests((requests) -> requests .anyRequest().access(authz.expression("hasRole('USER')")) ); // @formatter:on return http.build(); } @Bean WebExpressionAuthorizationManager.Builder expressions(DefaultHttpSecurityExpressionHandler expressionHandler) { return WebExpressionAuthorizationManager.withExpressionHandler(expressionHandler); } @Bean DefaultHttpSecurityExpressionHandler expressionHandler(ExpressionParser expressionParser) { DefaultHttpSecurityExpressionHandler expressionHandler = new DefaultHttpSecurityExpressionHandler(); expressionHandler.setExpressionParser(expressionParser); return expressionHandler; } @Bean ExpressionParser expressionParser() { return spy(new SpelExpressionParser()); } } @RestController private static class ExpressionHandlerController { @GetMapping("/whoami") String whoami(Principal user) { return user.getName(); } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpFirewallTests.java ================================================ /* * 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 org.springframework.security.config.annotation.web.configurers; import jakarta.servlet.http.HttpServletRequest; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.web.firewall.DefaultHttpFirewall; import org.springframework.security.web.firewall.FirewalledRequest; import org.springframework.security.web.firewall.HttpFirewall; import org.springframework.security.web.firewall.RequestRejectedException; import org.springframework.test.web.servlet.MockMvc; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Tests to verify that all the functionality of <http-firewall> attributes is * present * * @author Rob Winch * @author Josh Cummings */ @ExtendWith(SpringTestContextExtension.class) public class NamespaceHttpFirewallTests { public final SpringTestContext rule = new SpringTestContext(this); @Autowired MockMvc mvc; @Test @Disabled("MockMvc uses UriComponentsBuilder::fromUriString which was changed in https://github.com/spring-projects/spring-framework/issues/32513") public void requestWhenPathContainsDoubleDotsThenBehaviorMatchesNamespace() throws Exception { this.rule.register(HttpFirewallConfig.class).autowire(); this.mvc.perform(get("/public/../private/")).andExpect(status().isBadRequest()); } @Test public void requestWithCustomFirewallThenBehaviorMatchesNamespace() throws Exception { this.rule.register(CustomHttpFirewallConfig.class).autowire(); this.mvc.perform(get("/").param("deny", "true")).andExpect(status().isBadRequest()); } @Test public void requestWithCustomFirewallBeanThenBehaviorMatchesNamespace() throws Exception { this.rule.register(CustomHttpFirewallBeanConfig.class).autowire(); this.mvc.perform(get("/").param("deny", "true")).andExpect(status().isBadRequest()); } @Configuration @EnableWebSecurity static class HttpFirewallConfig { } @Configuration @EnableWebSecurity static class CustomHttpFirewallConfig { @Bean WebSecurityCustomizer webSecurityCustomizer() { return (web) -> web.httpFirewall(new CustomHttpFirewall()); } } @Configuration @EnableWebSecurity static class CustomHttpFirewallBeanConfig { @Bean HttpFirewall firewall() { return new CustomHttpFirewall(); } } static class CustomHttpFirewall extends DefaultHttpFirewall { @Override public FirewalledRequest getFirewalledRequest(HttpServletRequest request) throws RequestRejectedException { if (request.getParameter("deny") != null) { throw new RequestRejectedException("custom rejection"); } return super.getFirewalledRequest(request); } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpFormLoginTests.java ================================================ /* * 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 org.springframework.security.config.annotation.web.configurers; import jakarta.servlet.http.HttpServletRequest; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler; import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.springframework.security.config.Customizer.withDefaults; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; /** * Tests to verify that all the functionality of <form-login> attributes is present * * @author Rob Winch * @author Josh Cummings * */ @ExtendWith(SpringTestContextExtension.class) public class NamespaceHttpFormLoginTests { public final SpringTestContext spring = new SpringTestContext(this); @Autowired MockMvc mvc; @Test public void formLoginWhenDefaultConfigurationThenMatchesNamespace() throws Exception { this.spring.register(FormLoginConfig.class, UserDetailsServiceConfig.class).autowire(); this.mvc.perform(get("/")).andExpect(redirectedUrl("/login")); this.mvc.perform(post("/login").with(csrf())).andExpect(redirectedUrl("/login?error")); // @formatter:off MockHttpServletRequestBuilder loginRequest = post("/login") .param("username", "user") .param("password", "password") .with(csrf()); // @formatter:on this.mvc.perform(loginRequest).andExpect(redirectedUrl("/")); } @Test public void formLoginWithCustomEndpointsThenBehaviorMatchesNamespace() throws Exception { this.spring.register(FormLoginCustomConfig.class, UserDetailsServiceConfig.class).autowire(); this.mvc.perform(get("/")).andExpect(redirectedUrl("/authentication/login")); this.mvc.perform(post("/authentication/login/process").with(csrf())) .andExpect(redirectedUrl("/authentication/login?failed")); // @formatter:off MockHttpServletRequestBuilder request = post("/authentication/login/process") .param("username", "user") .param("password", "password") .with(csrf()); // @formatter:on this.mvc.perform(request).andExpect(redirectedUrl("/default")); } @Test public void formLoginWithCustomHandlersThenBehaviorMatchesNamespace() throws Exception { this.spring.register(FormLoginCustomRefsConfig.class, UserDetailsServiceConfig.class).autowire(); this.mvc.perform(get("/")).andExpect(redirectedUrl("/login")); this.mvc.perform(post("/login").with(csrf())).andExpect(redirectedUrl("/custom/failure")); verifyBean(WebAuthenticationDetailsSource.class).buildDetails(any(HttpServletRequest.class)); // @formatter:off MockHttpServletRequestBuilder loginRequest = post("/login") .param("username", "user") .param("password", "password") .with(csrf()); // @formatter:on this.mvc.perform(loginRequest).andExpect(redirectedUrl("/custom/targetUrl")); } private T verifyBean(Class beanClass) { return verify(this.spring.getContext().getBean(beanClass)); } @Configuration @EnableWebSecurity @EnableWebMvc static class FormLoginConfig { @Bean WebSecurityCustomizer webSecurityCustomizer() { return (web) -> web.ignoring().requestMatchers("/resources/**"); } @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((requests) -> requests .anyRequest().hasRole("USER")) .formLogin(withDefaults()); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class FormLoginCustomConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { boolean alwaysUseDefaultSuccess = true; // @formatter:off http .authorizeHttpRequests((requests) -> requests .anyRequest().hasRole("USER")) .formLogin((login) -> login .usernameParameter("username") // form-login@username-parameter .passwordParameter("password") // form-login@password-parameter .loginPage("/authentication/login") // form-login@login-page .failureUrl("/authentication/login?failed") // form-login@authentication-failure-url .loginProcessingUrl("/authentication/login/process") // form-login@login-processing-url .defaultSuccessUrl("/default", alwaysUseDefaultSuccess)); return http.build(); // form-login@default-target-url / form-login@always-use-default-target // @formatter:on } } @Configuration @EnableWebSecurity static class FormLoginCustomRefsConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { SavedRequestAwareAuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler(); successHandler.setDefaultTargetUrl("/custom/targetUrl"); // @formatter:off http .authorizeHttpRequests((requests) -> requests .anyRequest().hasRole("USER")) .formLogin((login) -> login .loginPage("/login") .failureHandler(new SimpleUrlAuthenticationFailureHandler("/custom/failure")) // form-login@authentication-failure-handler-ref .successHandler(successHandler) // form-login@authentication-success-handler-ref .authenticationDetailsSource(authenticationDetailsSource())); return http.build(); // @formatter:on } @Bean WebAuthenticationDetailsSource authenticationDetailsSource() { return spy(WebAuthenticationDetailsSource.class); } } @Configuration static class UserDetailsServiceConfig { @Bean UserDetailsService userDetailsService() { return new InMemoryUserDetailsManager( // @formatter:off User.withDefaultPasswordEncoder() .username("user") .password("password") .roles("USER") .build()); // @formatter:on } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpHeadersTests.java ================================================ /* * 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 org.springframework.security.config.annotation.web.configurers; import java.net.URI; import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.header.writers.StaticHeadersWriter; import org.springframework.security.web.header.writers.XXssProtectionHeaderWriter; import org.springframework.security.web.header.writers.frameoptions.StaticAllowFromStrategy; import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter; import org.springframework.security.web.util.matcher.AnyRequestMatcher; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.ResultMatcher; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.security.config.Customizer.withDefaults; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; /** * Tests to verify that all the functionality of <headers> attributes is present * * @author Rob Winch * @author Josh Cummings * */ @ExtendWith(SpringTestContextExtension.class) public class NamespaceHttpHeadersTests { static final Map defaultHeaders = new LinkedHashMap<>(); static { defaultHeaders.put("X-Content-Type-Options", "nosniff"); defaultHeaders.put("X-Frame-Options", "DENY"); defaultHeaders.put("Strict-Transport-Security", "max-age=31536000 ; includeSubDomains"); defaultHeaders.put("Cache-Control", "no-cache, no-store, max-age=0, must-revalidate"); defaultHeaders.put("Expires", "0"); defaultHeaders.put("Pragma", "no-cache"); defaultHeaders.put("X-XSS-Protection", "0"); } public final SpringTestContext spring = new SpringTestContext(this); @Autowired MockMvc mvc; @Test public void secureRequestWhenDefaultConfigThenBehaviorMatchesNamespace() throws Exception { this.spring.register(HeadersDefaultConfig.class).autowire(); this.mvc.perform(get("/").secure(true)).andExpect(includesDefaults()); } @Test public void secureRequestWhenCacheControlOnlyThenBehaviorMatchesNamespace() throws Exception { this.spring.register(HeadersCacheControlConfig.class).autowire(); this.mvc.perform(get("/").secure(true)).andExpect(includes("Cache-Control", "Expires", "Pragma")); } @Test public void secureRequestWhenHstsOnlyThenBehaviorMatchesNamespace() throws Exception { this.spring.register(HstsConfig.class).autowire(); this.mvc.perform(get("/").secure(true)).andExpect(includes("Strict-Transport-Security")); } @Test public void requestWhenHstsCustomThenBehaviorMatchesNamespace() throws Exception { this.spring.register(HstsCustomConfig.class).autowire(); this.mvc.perform(get("/")) .andExpect(includes(Collections.singletonMap("Strict-Transport-Security", "max-age=15768000"))); } @Test public void requestWhenFrameOptionsSameOriginThenBehaviorMatchesNamespace() throws Exception { this.spring.register(FrameOptionsSameOriginConfig.class).autowire(); this.mvc.perform(get("/")).andExpect(includes(Collections.singletonMap("X-Frame-Options", "SAMEORIGIN"))); } @Test public void requestWhenFrameOptionsAllowFromThenBehaviorMatchesNamespace() throws Exception { this.spring.register(FrameOptionsAllowFromConfig.class).autowire(); this.mvc.perform(get("/")) .andExpect(includes(Collections.singletonMap("X-Frame-Options", "ALLOW-FROM https://example.com"))); } @Test public void requestWhenXssOnlyThenBehaviorMatchesNamespace() throws Exception { this.spring.register(XssProtectionConfig.class).autowire(); this.mvc.perform(get("/")).andExpect(includes("X-XSS-Protection")); } @Test public void requestWhenXssCustomThenBehaviorMatchesNamespace() throws Exception { this.spring.register(XssProtectionCustomConfig.class).autowire(); this.mvc.perform(get("/")).andExpect(includes(Collections.singletonMap("X-XSS-Protection", "1; mode=block"))); } @Test public void requestWhenXContentTypeOptionsOnlyThenBehaviorMatchesNamespace() throws Exception { this.spring.register(ContentTypeOptionsConfig.class).autowire(); this.mvc.perform(get("/")).andExpect(includes("X-Content-Type-Options")); } @Test public void requestWhenCustomHeaderOnlyThenBehaviorMatchesNamespace() throws Exception { this.spring.register(HeaderRefConfig.class).autowire(); this.mvc.perform(get("/")) .andExpect(includes(Collections.singletonMap("customHeaderName", "customHeaderValue"))); } private static ResultMatcher includesDefaults() { return includes(defaultHeaders); } private static ResultMatcher includes(String... headerNames) { return includes(defaultHeaders, headerNames); } private static ResultMatcher includes(Map headers) { return includes(headers, headers.keySet().toArray(new String[headers.size()])); } private static ResultMatcher includes(Map headers, String... headerNames) { return (result) -> { assertThat(result.getResponse().getHeaderNames()).hasSameSizeAs(headerNames); for (String headerName : headerNames) { header().string(headerName, headers.get(headerName)).match(result); } }; } @Configuration @EnableWebSecurity static class HeadersDefaultConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .headers(withDefaults()); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class HeadersCacheControlConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .headers((headers) -> headers .defaultsDisabled() .cacheControl(Customizer.withDefaults())); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class HstsConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .headers((headers) -> headers .defaultsDisabled() .httpStrictTransportSecurity(Customizer.withDefaults())); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class HstsCustomConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .headers((headers) -> headers // hsts@request-matcher-ref, hsts@max-age-seconds, hsts@include-subdomains .defaultsDisabled() .httpStrictTransportSecurity((hsts) -> hsts .requestMatcher(AnyRequestMatcher.INSTANCE) .maxAgeInSeconds(15768000) .includeSubDomains(false))); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class FrameOptionsSameOriginConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .headers((headers) -> headers // frame-options@policy=SAMEORIGIN .defaultsDisabled() .frameOptions((frameOptions) -> frameOptions.sameOrigin())); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class FrameOptionsAllowFromConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .headers((headers) -> headers // frame-options@ref .defaultsDisabled() .addHeaderWriter(new XFrameOptionsHeaderWriter( new StaticAllowFromStrategy(URI.create("https://example.com"))))); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class XssProtectionConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .headers((headers) -> headers // xss-protection .defaultsDisabled() .xssProtection(Customizer.withDefaults())); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class XssProtectionCustomConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .headers((headers) -> headers // xss-protection@enabled and xss-protection@block .defaultsDisabled() .xssProtection((xss) -> xss .headerValue(XXssProtectionHeaderWriter.HeaderValue.ENABLED_MODE_BLOCK))); // @formatter:on return http.build(); } } @Configuration @EnableWebSecurity static class ContentTypeOptionsConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .headers((headers) -> headers // content-type-options .defaultsDisabled() .contentTypeOptions(Customizer.withDefaults())); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class HeaderRefConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .headers((headers) -> headers .defaultsDisabled() .addHeaderWriter(new StaticHeadersWriter("customHeaderName", "customHeaderValue"))); return http.build(); // @formatter:on } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpInterceptUrlTests.java ================================================ /* * 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 org.springframework.security.config.annotation.web.configurers; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.core.Authentication; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.userdetails.PasswordEncodedUser; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.web.SecurityFilterChain; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Tests to verify that all the functionality of <intercept-url> attributes is * present * * @author Rob Winch * @author Josh Cummings * */ @ExtendWith(SpringTestContextExtension.class) public class NamespaceHttpInterceptUrlTests { public final SpringTestContext spring = new SpringTestContext(this); @Autowired MockMvc mvc; @Test public void unauthenticatedRequestWhenUrlRequiresAuthenticationThenBehaviorMatchesNamespace() throws Exception { this.spring.register(HttpInterceptUrlConfig.class).autowire(); this.mvc.perform(get("/users")).andExpect(status().isForbidden()); } @Test public void authenticatedRequestWhenUrlRequiresElevatedPrivilegesThenBehaviorMatchesNamespace() throws Exception { this.spring.register(HttpInterceptUrlConfig.class).autowire(); MockHttpServletRequestBuilder requestWithUser = get("/users").with(authentication(user("ROLE_USER"))); this.mvc.perform(requestWithUser).andExpect(status().isForbidden()); } @Test public void authenticatedRequestWhenAuthorizedThenBehaviorMatchesNamespace() throws Exception { this.spring.register(HttpInterceptUrlConfig.class, BaseController.class).autowire(); MockHttpServletRequestBuilder requestWithAdmin = get("/users").with(authentication(user("ROLE_ADMIN"))); this.mvc.perform(requestWithAdmin).andExpect(status().isOk()).andReturn(); } @Test @Disabled // see https://github.com/spring-projects/spring-security/issues/17747 public void requestWhenMappedByPostInterceptUrlThenBehaviorMatchesNamespace() throws Exception { this.spring.register(HttpInterceptUrlConfig.class, BaseController.class).autowire(); MockHttpServletRequestBuilder getWithUser = get("/admin/post").with(authentication(user("ROLE_USER"))); this.mvc.perform(getWithUser).andExpect(status().isOk()); MockHttpServletRequestBuilder postWithUser = post("/admin/post").with(authentication(user("ROLE_USER"))); this.mvc.perform(postWithUser).andExpect(status().isForbidden()); MockHttpServletRequestBuilder requestWithAdmin = post("/admin/post").with(csrf()) .with(authentication(user("ROLE_ADMIN"))); this.mvc.perform(requestWithAdmin).andExpect(status().isOk()); } @Test public void requestWhenRequiresChannelThenBehaviorMatchesNamespace() throws Exception { this.spring.register(HttpInterceptUrlConfig.class).autowire(); this.mvc.perform(get("/login")).andExpect(redirectedUrl("https://localhost/login")); this.mvc.perform(get("/secured/a")).andExpect(redirectedUrl("https://localhost/secured/a")); this.mvc.perform(get("https://localhost/user")).andExpect(redirectedUrl("http://localhost/user")); } private static Authentication user(String role) { return UsernamePasswordAuthenticationToken.authenticated("user", null, AuthorityUtils.createAuthorityList(role)); } @Configuration @EnableWebSecurity @EnableWebMvc static class HttpInterceptUrlConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((requests) -> requests.requestMatchers( // the line below is similar to intercept-url@pattern: // //" access="hasRole('ROLE_ADMIN')"/> "/users**", "/sessions/**").hasRole("ADMIN").requestMatchers( // the line below is similar to intercept-url@method: // //" access="hasRole('ROLE_ADMIN')" method="POST"/> HttpMethod.POST, "/admin/post", "/admin/another-post/**").hasRole("ADMIN") .requestMatchers("/signup").permitAll() .anyRequest().hasRole("USER")) .requiresChannel((channel) -> channel.requestMatchers("/login", "/secured/**") // NOTE: channel security is configured separately of authorization (i.e. intercept-url@access // the line below is similar to intercept-url@requires-channel="https": // //" requires-channel="https"/> .requiresSecure().anyRequest().requiresInsecure()); // @formatter:on return http.build(); } @Bean UserDetailsService userDetailsService() { return new InMemoryUserDetailsManager(PasswordEncodedUser.user(), PasswordEncodedUser.admin()); } } @RestController static class BaseController { @GetMapping("/users") String users() { return "ok"; } @GetMapping("/sessions") String sessions() { return "sessions"; } @RequestMapping("/admin/post") String adminPost() { return "adminPost"; } @GetMapping("/admin/another-post") String adminAnotherPost() { return "adminAnotherPost"; } @GetMapping("/signup") String signup() { return "signup"; } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpJeeTests.java ================================================ /* * 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 org.springframework.security.config.annotation.web.configurers; import java.security.Principal; import java.util.stream.Collectors; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.core.Authentication; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.userdetails.AuthenticationUserDetailsService; import org.springframework.security.core.userdetails.User; import org.springframework.security.web.SecurityFilterChain; import org.springframework.test.web.servlet.MockMvc; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Tests to verify that all the functionality of <jee> attributes is present * * @author Rob Winch * @author Josh Cummings * */ @ExtendWith(SpringTestContextExtension.class) public class NamespaceHttpJeeTests { public final SpringTestContext spring = new SpringTestContext(this); @Autowired MockMvc mvc; @Test public void requestWhenJeeUserThenBehaviorDiffersFromNamespaceForRoleNames() throws Exception { this.spring.register(JeeMappableRolesConfig.class, BaseController.class).autowire(); Principal user = mock(Principal.class); given(user.getName()).willReturn("joe"); this.mvc.perform(get("/roles").principal(user).with((request) -> { request.addUserRole("ROLE_admin"); request.addUserRole("ROLE_user"); request.addUserRole("ROLE_unmapped"); return request; })).andExpect(status().isOk()).andExpect(content().string("ROLE_admin,ROLE_user")); } @Test public void requestWhenCustomAuthenticatedUserDetailsServiceThenBehaviorMatchesNamespace() throws Exception { this.spring.register(JeeUserServiceRefConfig.class, BaseController.class).autowire(); Principal user = mock(Principal.class); given(user.getName()).willReturn("joe"); User result = new User(user.getName(), "N/A", true, true, true, true, AuthorityUtils.createAuthorityList("ROLE_user")); given(bean(AuthenticationUserDetailsService.class).loadUserDetails(any())).willReturn(result); this.mvc.perform(get("/roles").principal(user)) .andExpect(status().isOk()) .andExpect(content().string("ROLE_user")); verifyBean(AuthenticationUserDetailsService.class).loadUserDetails(any()); } private T bean(Class beanClass) { return this.spring.getContext().getBean(beanClass); } private T verifyBean(Class beanClass) { return verify(this.spring.getContext().getBean(beanClass)); } @Configuration @EnableWebSecurity public static class JeeMappableRolesConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((requests) -> requests .anyRequest().hasRole("user")) .jee((jee) -> jee .mappableRoles("user", "admin")); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity public static class JeeUserServiceRefConfig { private final AuthenticationUserDetailsService authenticationUserDetailsService = mock( AuthenticationUserDetailsService.class); @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((requests) -> requests .anyRequest().hasRole("user")) .jee((jee) -> jee .mappableAuthorities("ROLE_user", "ROLE_admin") .authenticatedUserDetailsService(this.authenticationUserDetailsService)); return http.build(); // @formatter:on } @Bean public AuthenticationUserDetailsService authenticationUserDetailsService() { return this.authenticationUserDetailsService; } } @RestController static class BaseController { @GetMapping("/authenticated") String authenticated(Authentication authentication) { return authentication.getName(); } @GetMapping("/roles") String roles(Authentication authentication) { return authentication.getAuthorities().stream().map(Object::toString).collect(Collectors.joining(",")); } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpLogoutTests.java ================================================ /* * 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 org.springframework.security.config.annotation.web.configurers; import java.util.Objects; import java.util.Optional; import java.util.function.Predicate; import jakarta.servlet.http.HttpSession; import org.assertj.core.api.Condition; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners; import org.springframework.security.test.context.support.WithMockUser; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.ResultMatcher; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.cookie; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Tests to verify that all the functionality of <logout> attributes is present * * @author Rob Winch * @author Josh Cummings */ @ExtendWith({ SpringExtension.class, SpringTestContextExtension.class }) @SecurityTestExecutionListeners public class NamespaceHttpLogoutTests { public final SpringTestContext spring = new SpringTestContext(this); @Autowired MockMvc mvc; /** * http/logout equivalent */ @Test @WithMockUser public void logoutWhenUsingDefaultsThenMatchesNamespace() throws Exception { this.spring.register(HttpLogoutConfig.class).autowire(); // @formatter:off this.mvc.perform(post("/logout").with(csrf())) .andExpect(authenticated(false)) .andExpect(redirectedUrl("/login?logout")) .andExpect(noCookies()) .andExpect(session(Objects::isNull)); // @formatter:on } @Test @WithMockUser public void logoutWhenDisabledInLambdaThenRespondsWithNotFound() throws Exception { this.spring.register(HttpLogoutDisabledInLambdaConfig.class).autowire(); MockHttpServletRequestBuilder logoutRequest = post("/logout").with(csrf()).with(user("user")); this.mvc.perform(logoutRequest).andExpect(status().isNotFound()); } /** * http/logout custom */ @Test @WithMockUser public void logoutWhenUsingVariousCustomizationsMatchesNamespace() throws Exception { this.spring.register(CustomHttpLogoutConfig.class).autowire(); // @formatter:off this.mvc.perform(post("/custom-logout").with(csrf())) .andExpect(authenticated(false)) .andExpect(redirectedUrl("/logout-success")) .andExpect((result) -> assertThat(result.getResponse().getCookies()).hasSize(1)) .andExpect(cookie().maxAge("remove", 0)) .andExpect(session(Objects::nonNull)); // @formatter:on } @Test @WithMockUser public void logoutWhenUsingVariousCustomizationsInLambdaThenMatchesNamespace() throws Exception { this.spring.register(CustomHttpLogoutInLambdaConfig.class).autowire(); // @formatter:off this.mvc.perform(post("/custom-logout").with(csrf())) .andExpect(authenticated(false)) .andExpect(redirectedUrl("/logout-success")) .andExpect((result) -> assertThat(result.getResponse().getCookies()).hasSize(1)) .andExpect(cookie().maxAge("remove", 0)) .andExpect(session(Objects::nonNull)); // @formatter:on } /** * http/logout@success-handler-ref */ @Test @WithMockUser public void logoutWhenUsingSuccessHandlerRefThenMatchesNamespace() throws Exception { this.spring.register(SuccessHandlerRefHttpLogoutConfig.class).autowire(); // @formatter:off this.mvc.perform(post("/logout").with(csrf())) .andExpect(authenticated(false)) .andExpect(redirectedUrl("/SuccessHandlerRefHttpLogoutConfig")) .andExpect(noCookies()) .andExpect(session(Objects::isNull)); // @formatter:on } @Test @WithMockUser public void logoutWhenUsingSuccessHandlerRefInLambdaThenMatchesNamespace() throws Exception { this.spring.register(SuccessHandlerRefHttpLogoutInLambdaConfig.class).autowire(); // @formatter:off this.mvc.perform(post("/logout").with(csrf())) .andExpect(authenticated(false)) .andExpect(redirectedUrl("/SuccessHandlerRefHttpLogoutConfig")) .andExpect(noCookies()) .andExpect(session(Objects::isNull)); // @formatter:on } ResultMatcher authenticated(boolean authenticated) { return (result) -> assertThat(Optional.ofNullable(SecurityContextHolder.getContext().getAuthentication()) .map(Authentication::isAuthenticated) .orElse(false)).isEqualTo(authenticated); } ResultMatcher noCookies() { return (result) -> assertThat(result.getResponse().getCookies()).isEmpty(); } ResultMatcher session(Predicate sessionPredicate) { return (result) -> assertThat(result.getRequest().getSession(false)) .is(new Condition<>(sessionPredicate, "sessionPredicate failed")); } @Configuration @EnableWebSecurity static class HttpLogoutConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { return http.build(); } } @Configuration @EnableWebSecurity static class HttpLogoutDisabledInLambdaConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http.logout(AbstractHttpConfigurer::disable); return http.build(); } } @Configuration @EnableWebSecurity static class CustomHttpLogoutConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .logout((logout) -> logout .deleteCookies("remove") // logout@delete-cookies .invalidateHttpSession(false) // logout@invalidate-session=false (default is true) .logoutUrl("/custom-logout") // logout@logout-url (default is /logout) .logoutSuccessUrl("/logout-success")); return http.build(); // logout@success-url (default is /login?logout) // @formatter:on } } @Configuration @EnableWebSecurity static class CustomHttpLogoutInLambdaConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .logout((logout) -> logout.deleteCookies("remove") .invalidateHttpSession(false) .logoutUrl("/custom-logout") .logoutSuccessUrl("/logout-success") ); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class SuccessHandlerRefHttpLogoutConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { SimpleUrlLogoutSuccessHandler logoutSuccessHandler = new SimpleUrlLogoutSuccessHandler(); logoutSuccessHandler.setDefaultTargetUrl("/SuccessHandlerRefHttpLogoutConfig"); // @formatter:off http .logout((logout) -> logout .logoutSuccessHandler(logoutSuccessHandler)); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class SuccessHandlerRefHttpLogoutInLambdaConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { SimpleUrlLogoutSuccessHandler logoutSuccessHandler = new SimpleUrlLogoutSuccessHandler(); logoutSuccessHandler.setDefaultTargetUrl("/SuccessHandlerRefHttpLogoutConfig"); // @formatter:off http .logout((logout) -> logout.logoutSuccessHandler(logoutSuccessHandler)); return http.build(); // @formatter:on } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpPortMappingsTests.java ================================================ /* * 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 org.springframework.security.config.annotation.web.configurers; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.core.userdetails.PasswordEncodedUser; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.web.SecurityFilterChain; import org.springframework.test.web.servlet.MockMvc; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; /** * Tests to verify that all the functionality of <port-mappings> attributes is * present * * @author Rob Winch * @author Josh Cummings * */ @ExtendWith(SpringTestContextExtension.class) public class NamespaceHttpPortMappingsTests { public final SpringTestContext spring = new SpringTestContext(this); @Autowired MockMvc mvc; @Test public void portMappingWhenRequestRequiresChannelThenBehaviorMatchesNamespace() throws Exception { this.spring.register(HttpInterceptUrlWithPortMapperConfig.class).autowire(); this.mvc.perform(get("http://localhost:9080/login")).andExpect(redirectedUrl("https://localhost:9443/login")); this.mvc.perform(get("http://localhost:9080/secured/a")) .andExpect(redirectedUrl("https://localhost:9443/secured/a")); this.mvc.perform(get("https://localhost:9443/user")).andExpect(redirectedUrl("http://localhost:9080/user")); } @Configuration @EnableWebSecurity @EnableWebMvc static class HttpInterceptUrlWithPortMapperConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((requests) -> requests .anyRequest().hasRole("USER")) .portMapper((mapper) -> mapper .http(9080).mapsTo(9443)) .requiresChannel((channel) -> channel .requestMatchers("/login", "/secured/**").requiresSecure() .anyRequest().requiresInsecure()); // @formatter:on return http.build(); } @Bean UserDetailsService userDetailsService() { return new InMemoryUserDetailsManager(PasswordEncodedUser.user(), PasswordEncodedUser.admin()); } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpRequestCacheTests.java ================================================ /* * 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 org.springframework.security.config.annotation.web.configurers; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpSession; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.core.userdetails.PasswordEncodedUser; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.savedrequest.RequestCache; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Tests to verify that all the functionality of <request-cache> attributes is * present * * @author Rob Winch * @author Josh Cummings * */ @ExtendWith(SpringTestContextExtension.class) public class NamespaceHttpRequestCacheTests { public final SpringTestContext spring = new SpringTestContext(this); @Autowired MockMvc mvc; @Test public void requestWhenCustomRequestCacheThenBehaviorMatchesNamespace() throws Exception { this.spring.register(RequestCacheRefConfig.class).autowire(); this.mvc.perform(get("/")).andExpect(status().isForbidden()); verifyBean(RequestCache.class).saveRequest(any(HttpServletRequest.class), any(HttpServletResponse.class)); } @Test public void requestWhenDefaultConfigurationThenUsesHttpSessionRequestCache() throws Exception { this.spring.register(DefaultRequestCacheRefConfig.class).autowire(); MvcResult result = this.mvc.perform(get("/")).andExpect(status().isForbidden()).andReturn(); HttpSession session = result.getRequest().getSession(false); assertThat(session).isNotNull(); assertThat(session.getAttribute("SPRING_SECURITY_SAVED_REQUEST")).isNotNull(); } private T verifyBean(Class beanClass) { return verify(this.spring.getContext().getBean(beanClass)); } @Configuration @EnableWebSecurity static class RequestCacheRefConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((requests) -> requests .anyRequest().authenticated()) .requestCache((cache) -> cache .requestCache(requestCache())); // @formatter:on return http.build(); } @Bean UserDetailsService userDetailsService() { return new InMemoryUserDetailsManager(PasswordEncodedUser.user(), PasswordEncodedUser.admin()); } @Bean RequestCache requestCache() { return mock(RequestCache.class); } } @Configuration @EnableWebSecurity static class DefaultRequestCacheRefConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((requests) -> requests .anyRequest().authenticated()); // @formatter:on return http.build(); } @Bean UserDetailsService userDetailsService() { return new InMemoryUserDetailsManager(PasswordEncodedUser.user(), PasswordEncodedUser.admin()); } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpServerAccessDeniedHandlerTests.java ================================================ /* * 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 org.springframework.security.config.annotation.web.configurers; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.core.Authentication; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.access.AccessDeniedHandler; import org.springframework.test.web.servlet.MockMvc; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.forwardedUrl; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Tests to verify that all the functionality of <access-denied-handler> attributes * is present * * @author Rob Winch * @author Josh Cummings * */ @ExtendWith(SpringTestContextExtension.class) public class NamespaceHttpServerAccessDeniedHandlerTests { public final SpringTestContext spring = new SpringTestContext(this); @Autowired MockMvc mvc; @Test public void requestWhenCustomAccessDeniedPageThenBehaviorMatchesNamespace() throws Exception { this.spring.register(AccessDeniedPageConfig.class).autowire(); // @formatter:off this.mvc.perform(get("/").with(authentication(user()))) .andExpect(status().isForbidden()) .andExpect(forwardedUrl("/AccessDeniedPageConfig")); // @formatter:on } @Test public void requestWhenCustomAccessDeniedPageInLambdaThenForwardedToCustomPage() throws Exception { this.spring.register(AccessDeniedPageInLambdaConfig.class).autowire(); // @formatter:off this.mvc.perform(get("/").with(authentication(user()))) .andExpect(status().isForbidden()) .andExpect(forwardedUrl("/AccessDeniedPageConfig")); // @formatter:on } @Test public void requestWhenCustomAccessDeniedHandlerThenBehaviorMatchesNamespace() throws Exception { this.spring.register(AccessDeniedHandlerRefConfig.class).autowire(); this.mvc.perform(get("/").with(authentication(user()))); verifyBean(AccessDeniedHandler.class).handle(any(HttpServletRequest.class), any(HttpServletResponse.class), any(AccessDeniedException.class)); } @Test public void requestWhenCustomAccessDeniedHandlerInLambdaThenBehaviorMatchesNamespace() throws Exception { this.spring.register(AccessDeniedHandlerRefInLambdaConfig.class).autowire(); this.mvc.perform(get("/").with(authentication(user()))); verify(AccessDeniedHandlerRefInLambdaConfig.accessDeniedHandler).handle(any(HttpServletRequest.class), any(HttpServletResponse.class), any(AccessDeniedException.class)); } private static Authentication user() { return UsernamePasswordAuthenticationToken.authenticated("user", null, AuthorityUtils.NO_AUTHORITIES); } private T verifyBean(Class beanClass) { return verify(this.spring.getContext().getBean(beanClass)); } @Configuration @EnableWebSecurity static class AccessDeniedPageConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((requests) -> requests .anyRequest().denyAll()) .exceptionHandling((handling) -> handling .accessDeniedPage("/AccessDeniedPageConfig")); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class AccessDeniedPageInLambdaConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((authorize) -> authorize .anyRequest().denyAll() ) .exceptionHandling((exceptionHandling) -> exceptionHandling.accessDeniedPage("/AccessDeniedPageConfig") ); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class AccessDeniedHandlerRefConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((requests) -> requests .anyRequest().denyAll()) .exceptionHandling((handling) -> handling .accessDeniedHandler(accessDeniedHandler())); return http.build(); // @formatter:on } @Bean AccessDeniedHandler accessDeniedHandler() { return mock(AccessDeniedHandler.class); } } @Configuration @EnableWebSecurity static class AccessDeniedHandlerRefInLambdaConfig { static AccessDeniedHandler accessDeniedHandler = mock(AccessDeniedHandler.class); @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((authorize) -> authorize .anyRequest().denyAll() ) .exceptionHandling((exceptionHandling) -> exceptionHandling.accessDeniedHandler(accessDeniedHandler()) ); return http.build(); // @formatter:on } @Bean AccessDeniedHandler accessDeniedHandler() { return accessDeniedHandler; } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpX509Tests.java ================================================ /* * 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 org.springframework.security.config.annotation.web.configurers; import java.io.InputStream; import java.security.cert.Certificate; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import javax.security.auth.x500.X500Principal; import jakarta.servlet.http.HttpServletRequest; import org.bouncycastle.asn1.x500.X500Name; import org.bouncycastle.asn1.x500.style.BCStyle; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.ClassPathResource; import org.springframework.security.authentication.AuthenticationDetailsSource; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.preauth.PreAuthenticatedGrantedAuthoritiesWebAuthenticationDetails; import org.springframework.test.web.servlet.MockMvc; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.springframework.security.config.Customizer.withDefaults; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.x509; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; /** * Tests to verify that all the functionality of <x509> attributes is present in * Java config * * @author Rob Winch * @author Josh Cummings * */ @ExtendWith(SpringTestContextExtension.class) public class NamespaceHttpX509Tests { private static final User USER = new User("customuser", "password", AuthorityUtils.createAuthorityList("ROLE_USER")); public final SpringTestContext spring = new SpringTestContext(this); @Autowired MockMvc mvc; @Test public void x509AuthenticationWhenUsingX509DefaultConfigurationThenMatchesNamespace() throws Exception { this.spring.register(X509Config.class, X509Controller.class).autowire(); X509Certificate certificate = loadCert("rod.cer"); this.mvc.perform(get("/whoami").with(x509(certificate))).andExpect(content().string("rod")); } @Test public void x509AuthenticationWhenHasCustomAuthenticationDetailsSourceThenMatchesNamespace() throws Exception { this.spring.register(AuthenticationDetailsSourceRefConfig.class, X509Controller.class).autowire(); X509Certificate certificate = loadCert("rod.cer"); this.mvc.perform(get("/whoami").with(x509(certificate))).andExpect(content().string("rod")); verifyBean(AuthenticationDetailsSource.class).buildDetails(any()); } @Test public void x509AuthenticationWhenHasSubjectPrincipalRegexThenMatchesNamespace() throws Exception { this.spring.register(SubjectPrincipalRegexConfig.class, X509Controller.class).autowire(); X509Certificate certificate = loadCert("rodatexampledotcom.cer"); this.mvc.perform(get("/whoami").with(x509(certificate))).andExpect(content().string("rod")); } @Test public void x509AuthenticationWhenHasCustomPrincipalExtractorThenMatchesNamespace() throws Exception { this.spring.register(CustomPrincipalExtractorConfig.class, X509Controller.class).autowire(); X509Certificate certificate = loadCert("rodatexampledotcom.cer"); this.mvc.perform(get("/whoami").with(x509(certificate))).andExpect(content().string("rod@example.com")); } @Test public void x509AuthenticationWhenHasCustomUserDetailsServiceThenMatchesNamespace() throws Exception { this.spring.register(UserDetailsServiceRefConfig.class, X509Controller.class).autowire(); X509Certificate certificate = loadCert("rodatexampledotcom.cer"); this.mvc.perform(get("/whoami").with(x509(certificate))).andExpect(content().string("customuser")); } @Test public void x509AuthenticationWhenHasCustomAuthenticationUserDetailsServiceThenMatchesNamespace() throws Exception { this.spring.register(AuthenticationUserDetailsServiceConfig.class, X509Controller.class).autowire(); X509Certificate certificate = loadCert("rodatexampledotcom.cer"); this.mvc.perform(get("/whoami").with(x509(certificate))).andExpect(content().string("customuser")); } T loadCert(String location) { try (InputStream is = new ClassPathResource(location).getInputStream()) { CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); return (T) certFactory.generateCertificate(is); } catch (Exception ex) { throw new IllegalArgumentException(ex); } } T verifyBean(Class beanClass) { return verify(this.spring.getContext().getBean(beanClass)); } @Configuration @EnableWebSecurity @EnableWebMvc public static class X509Config { @Bean UserDetailsService userDetailsService() { UserDetails user = User.withDefaultPasswordEncoder() .username("rod") .password("password") .roles("USER", "ADMIN") .build(); return new InMemoryUserDetailsManager(user); } @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((requests) -> requests .anyRequest().hasRole("USER")) .x509(withDefaults()); // @formatter:on return http.build(); } } @Configuration @EnableWebSecurity @EnableWebMvc static class AuthenticationDetailsSourceRefConfig { @Bean UserDetailsService userDetailsService() { UserDetails user = User.withDefaultPasswordEncoder() .username("rod") .password("password") .roles("USER", "ADMIN") .build(); return new InMemoryUserDetailsManager(user); } @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((requests) -> requests .anyRequest().hasRole("USER")) .x509((x509) -> x509 .authenticationDetailsSource(authenticationDetailsSource())); // @formatter:on return http.build(); } @Bean AuthenticationDetailsSource authenticationDetailsSource() { return mock(AuthenticationDetailsSource.class); } } @EnableWebMvc @Configuration @EnableWebSecurity public static class SubjectPrincipalRegexConfig { @Bean UserDetailsService userDetailsService() { UserDetails user = User.withDefaultPasswordEncoder() .username("rod") .password("password") .roles("USER", "ADMIN") .build(); return new InMemoryUserDetailsManager(user); } @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((requests) -> requests .anyRequest().hasRole("USER")) .x509((x509) -> x509 .subjectPrincipalRegex("CN=(.*?)@example.com(?:,|$)")); // @formatter:on return http.build(); } } @EnableWebMvc @Configuration @EnableWebSecurity public static class CustomPrincipalExtractorConfig { @Bean UserDetailsService userDetailsService() { UserDetails user = User.withDefaultPasswordEncoder() .username("rod@example.com") .password("password") .roles("USER", "ADMIN") .build(); return new InMemoryUserDetailsManager(user); } @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((requests) -> requests .anyRequest().hasRole("USER")) .x509((x509) -> x509 .x509PrincipalExtractor(this::extractCommonName)); // @formatter:on return http.build(); } private String extractCommonName(X509Certificate certificate) { X500Principal principal = certificate.getSubjectX500Principal(); return new X500Name(principal.getName()).getRDNs(BCStyle.CN)[0].getFirst().getValue().toString(); } } @EnableWebMvc @Configuration @EnableWebSecurity public static class UserDetailsServiceRefConfig { @Bean UserDetailsService userDetailsService() { UserDetails user = User.withDefaultPasswordEncoder() .username("rod") .password("password") .roles("USER", "ADMIN") .build(); return new InMemoryUserDetailsManager(user); } @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((requests) -> requests .anyRequest().hasRole("USER")) .x509((x509) -> x509 .userDetailsService((username) -> USER)); // @formatter:on return http.build(); } } @EnableWebMvc @Configuration @EnableWebSecurity public static class AuthenticationUserDetailsServiceConfig { @Bean UserDetailsService userDetailsService() { UserDetails user = User.withDefaultPasswordEncoder() .username("rod") .password("password") .roles("USER", "ADMIN") .build(); return new InMemoryUserDetailsManager(user); } @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((requests) -> requests .anyRequest().hasRole("USER")) .x509((x509) -> x509 .authenticationUserDetailsService((authentication) -> USER)); // @formatter:on return http.build(); } } @RestController public static class X509Controller { @GetMapping("/whoami") public String whoami(@AuthenticationPrincipal(expression = "username") String name) { return name; } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceRememberMeTests.java ================================================ /* * 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 org.springframework.security.config.annotation.web.configurers; import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.Order; import org.springframework.mock.web.MockHttpSession; import org.springframework.security.authentication.RememberMeAuthenticationToken; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.core.Authentication; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.security.web.authentication.RememberMeServices; import org.springframework.security.web.authentication.rememberme.AbstractRememberMeServices; import org.springframework.security.web.authentication.rememberme.PersistentRememberMeToken; import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; import org.springframework.test.web.servlet.request.RequestPostProcessor; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.springframework.security.config.Customizer.withDefaults; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Tests to verify that all the functionality of <anonymous> attributes is present * * @author Rob Winch * @author Josh Cummings * */ @ExtendWith(SpringTestContextExtension.class) public class NamespaceRememberMeTests { public final SpringTestContext spring = new SpringTestContext(this); @Autowired MockMvc mvc; @Test public void rememberMeLoginWhenUsingDefaultsThenMatchesNamespace() throws Exception { this.spring.register(RememberMeConfig.class, SecurityController.class).autowire(); MvcResult result = this.mvc.perform(post("/login").with(rememberMeLogin())).andReturn(); MockHttpSession session = (MockHttpSession) result.getRequest().getSession(); Cookie rememberMe = result.getResponse().getCookie("remember-me"); assertThat(rememberMe).isNotNull(); this.mvc.perform(get("/authentication-class").cookie(rememberMe)) .andExpect(content().string(RememberMeAuthenticationToken.class.getName())); // @formatter:off MockHttpServletRequestBuilder logoutRequest = post("/logout") .with(csrf()) .session(session) .cookie(rememberMe); result = this.mvc.perform(logoutRequest) .andExpect(redirectedUrl("/login?logout")) .andReturn(); // @formatter:on rememberMe = result.getResponse().getCookie("remember-me"); assertThat(rememberMe).isNotNull().extracting(Cookie::getMaxAge).isEqualTo(0); // @formatter:off MockHttpServletRequestBuilder authenticationClassRequest = post("/authentication-class") .with(csrf()) .cookie(rememberMe); this.mvc.perform(authenticationClassRequest) .andExpect(redirectedUrl("/login")) .andReturn(); // @formatter:on } // SEC-3170 - RememberMeService implementations should not have to also implement // LogoutHandler @Test public void logoutWhenCustomRememberMeServicesDeclaredThenUses() throws Exception { RememberMeServicesRefConfig.REMEMBER_ME_SERVICES = mock(RememberMeServicesWithoutLogoutHandler.class); this.spring.register(RememberMeServicesRefConfig.class).autowire(); this.mvc.perform(get("/")); verify(RememberMeServicesRefConfig.REMEMBER_ME_SERVICES).autoLogin(any(HttpServletRequest.class), any(HttpServletResponse.class)); this.mvc.perform(post("/login").with(csrf())); verify(RememberMeServicesRefConfig.REMEMBER_ME_SERVICES).loginFail(any(HttpServletRequest.class), any(HttpServletResponse.class)); } @Test public void rememberMeLoginWhenAuthenticationSuccessHandlerDeclaredThenUses() throws Exception { AuthSuccessConfig.SUCCESS_HANDLER = mock(AuthenticationSuccessHandler.class); this.spring.register(AuthSuccessConfig.class).autowire(); MvcResult result = this.mvc.perform(post("/login").with(rememberMeLogin())).andReturn(); verifyNoMoreInteractions(AuthSuccessConfig.SUCCESS_HANDLER); Cookie rememberMe = result.getResponse().getCookie("remember-me"); assertThat(rememberMe).isNotNull(); this.mvc.perform(get("/somewhere").cookie(rememberMe)); verify(AuthSuccessConfig.SUCCESS_HANDLER).onAuthenticationSuccess(any(HttpServletRequest.class), any(HttpServletResponse.class), any(Authentication.class)); } @Test public void rememberMeLoginWhenKeyDeclaredThenMatchesNamespace() throws Exception { this.spring.register(WithoutKeyConfig.class, SecurityController.class).autowire(); MockHttpServletRequestBuilder requestWithRememberme = post("/without-key/login").with(rememberMeLogin()); // @formatter:off Cookie withoutKey = this.mvc.perform(requestWithRememberme) .andReturn() .getResponse() .getCookie("remember-me"); // @formatter:on MockHttpServletRequestBuilder somewhereRequest = get("/somewhere").cookie(withoutKey); // @formatter:off this.mvc.perform(somewhereRequest) .andExpect(status().isFound()) .andExpect(redirectedUrl("/login")); MockHttpServletRequestBuilder loginWithRememberme = post("/login").with(rememberMeLogin()); Cookie withKey = this.mvc.perform(loginWithRememberme) .andReturn() .getResponse() .getCookie("remember-me"); this.mvc.perform(get("/somewhere").cookie(withKey)) .andExpect(status().isNotFound()); // @formatter:on } // http/remember-me@services-alias is not supported use standard aliasing instead // (i.e. @Bean("alias")) // http/remember-me@data-source-ref is not supported directly. Instead use // http/remember-me@token-repository-ref example @Test public void rememberMeLoginWhenDeclaredTokenRepositoryThenMatchesNamespace() throws Exception { TokenRepositoryRefConfig.TOKEN_REPOSITORY = mock(PersistentTokenRepository.class); this.spring.register(TokenRepositoryRefConfig.class).autowire(); this.mvc.perform(post("/login").with(rememberMeLogin())); verify(TokenRepositoryRefConfig.TOKEN_REPOSITORY).createNewToken(any(PersistentRememberMeToken.class)); } @Test public void rememberMeLoginWhenTokenValidityDeclaredThenMatchesNamespace() throws Exception { this.spring.register(TokenValiditySecondsConfig.class).autowire(); // @formatter:off Cookie expiredRememberMe = this.mvc.perform(post("/login").with(rememberMeLogin())) .andReturn() .getResponse() .getCookie("remember-me"); // @formatter:on assertThat(expiredRememberMe).extracting(Cookie::getMaxAge).isEqualTo(314); } @Test public void rememberMeLoginWhenUsingDefaultsThenCookieMaxAgeMatchesNamespace() throws Exception { this.spring.register(RememberMeConfig.class).autowire(); // @formatter:off Cookie expiredRememberMe = this.mvc.perform(post("/login").with(rememberMeLogin())) .andReturn() .getResponse() .getCookie("remember-me"); // @formatter:on assertThat(expiredRememberMe).extracting(Cookie::getMaxAge).isEqualTo(AbstractRememberMeServices.TWO_WEEKS_S); } @Test public void rememberMeLoginWhenUsingSecureCookieThenMatchesNamespace() throws Exception { this.spring.register(UseSecureCookieConfig.class).autowire(); // @formatter:off Cookie secureCookie = this.mvc.perform(post("/login").with(rememberMeLogin())) .andReturn() .getResponse() .getCookie("remember-me"); // @formatter:on assertThat(secureCookie).extracting(Cookie::getSecure).isEqualTo(true); } @Test public void rememberMeLoginWhenUsingDefaultsThenCookieSecurityMatchesNamespace() throws Exception { this.spring.register(RememberMeConfig.class).autowire(); // @formatter:off Cookie secureCookie = this.mvc.perform(post("/login").with(rememberMeLogin()).secure(true)) .andReturn() .getResponse() .getCookie("remember-me"); // @formatter:on assertThat(secureCookie).extracting(Cookie::getSecure).isEqualTo(true); } @Test public void rememberMeLoginWhenParameterSpecifiedThenMatchesNamespace() throws Exception { this.spring.register(RememberMeParameterConfig.class).autowire(); MockHttpServletRequestBuilder loginWithRememberme = post("/login").with(rememberMeLogin("rememberMe", true)); // @formatter:off Cookie rememberMe = this.mvc.perform(loginWithRememberme) .andReturn() .getResponse() .getCookie("remember-me"); // @formatter:on assertThat(rememberMe).isNotNull(); } // SEC-2880 @Test public void rememberMeLoginWhenCookieNameDeclaredThenMatchesNamespace() throws Exception { this.spring.register(RememberMeCookieNameConfig.class).autowire(); // @formatter:off Cookie rememberMe = this.mvc.perform(post("/login").with(rememberMeLogin())) .andReturn() .getResponse() .getCookie("rememberMe"); // @formatter:on assertThat(rememberMe).isNotNull(); } @Test public void rememberMeLoginWhenGlobalUserDetailsServiceDeclaredThenMatchesNamespace() throws Exception { DefaultsUserDetailsServiceWithDaoConfig.USERDETAILS_SERVICE = mock(UserDetailsService.class); this.spring.register(DefaultsUserDetailsServiceWithDaoConfig.class).autowire(); this.mvc.perform(post("/login").with(rememberMeLogin())); verify(DefaultsUserDetailsServiceWithDaoConfig.USERDETAILS_SERVICE).loadUserByUsername("user"); } @Test public void rememberMeLoginWhenUserDetailsServiceDeclaredThenMatchesNamespace() throws Exception { UserServiceRefConfig.USERDETAILS_SERVICE = mock(UserDetailsService.class); this.spring.register(UserServiceRefConfig.class).autowire(); User user = new User("user", "password", AuthorityUtils.createAuthorityList("ROLE_USER")); given(UserServiceRefConfig.USERDETAILS_SERVICE.loadUserByUsername("user")).willReturn(user); this.mvc.perform(post("/login").with(rememberMeLogin())); verify(UserServiceRefConfig.USERDETAILS_SERVICE).loadUserByUsername("user"); } static RequestPostProcessor rememberMeLogin() { return rememberMeLogin("remember-me", true); } static RequestPostProcessor rememberMeLogin(String parameterName, boolean parameterValue) { return (request) -> { csrf().postProcessRequest(request); request.setParameter("username", "user"); request.setParameter("password", "password"); request.setParameter(parameterName, String.valueOf(parameterValue)); return request; }; } @Configuration @EnableWebSecurity static class RememberMeConfig extends UsersConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((requests) -> requests .anyRequest().hasRole("USER")) .formLogin(withDefaults()) .rememberMe(withDefaults()); return http.build(); // @formatter:on } } interface RememberMeServicesWithoutLogoutHandler extends RememberMeServices { } @Configuration @EnableWebSecurity static class RememberMeServicesRefConfig { static RememberMeServices REMEMBER_ME_SERVICES; @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .formLogin(withDefaults()) .rememberMe((me) -> me .rememberMeServices(REMEMBER_ME_SERVICES)); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class AuthSuccessConfig extends UsersConfig { static AuthenticationSuccessHandler SUCCESS_HANDLER; @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .formLogin(withDefaults()) .rememberMe((me) -> me .authenticationSuccessHandler(SUCCESS_HANDLER)); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class WithoutKeyConfig extends UsersConfig { @Bean @Order(0) SecurityFilterChain withoutKeyFilterChain(HttpSecurity http) throws Exception { // @formatter:off http .securityMatcher("/without-key/**") .authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated()) .formLogin((login) -> login .loginProcessingUrl("/without-key/login")) .rememberMe(withDefaults()); return http.build(); // @formatter:on } @Bean @Order(1) SecurityFilterChain keyFilterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((requests) -> requests .anyRequest().authenticated()) .formLogin(withDefaults()) .rememberMe((me) -> me .key("KeyConfig")); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class TokenRepositoryRefConfig extends UsersConfig { static PersistentTokenRepository TOKEN_REPOSITORY; @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl() // tokenRepository.setDataSource(dataSource); // @formatter:off http .formLogin(withDefaults()) .rememberMe((me) -> me .tokenRepository(TOKEN_REPOSITORY)); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class TokenValiditySecondsConfig extends UsersConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((requests) -> requests .anyRequest().authenticated()) .formLogin(withDefaults()) .rememberMe((me) -> me .tokenValiditySeconds(314)); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class UseSecureCookieConfig extends UsersConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .formLogin(withDefaults()) .rememberMe((me) -> me .useSecureCookie(true)); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class RememberMeParameterConfig extends UsersConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .formLogin(withDefaults()) .rememberMe((me) -> me .rememberMeParameter("rememberMe")); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class RememberMeCookieNameConfig extends UsersConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .formLogin(withDefaults()) .rememberMe((me) -> me .rememberMeCookieName("rememberMe")); return http.build(); // @formatter:on } } @EnableWebSecurity @Configuration static class DefaultsUserDetailsServiceWithDaoConfig { static UserDetailsService USERDETAILS_SERVICE; @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .formLogin(withDefaults()) .rememberMe(withDefaults()); // @formatter:on return http.build(); } @Bean UserDetailsService userDetailsService() { return USERDETAILS_SERVICE; } } @Configuration @EnableWebSecurity static class UserServiceRefConfig extends UsersConfig { static UserDetailsService USERDETAILS_SERVICE; @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .formLogin(withDefaults()) .rememberMe((me) -> me .userDetailsService(USERDETAILS_SERVICE)); return http.build(); // @formatter:on } } static class UsersConfig { @Bean UserDetailsService userDetailsService() { return new InMemoryUserDetailsManager( // @formatter:off User.withDefaultPasswordEncoder() .username("user") .password("password") .roles("USER") .build()); // @formatter:on } } @RestController static class SecurityController { @GetMapping("/authentication-class") String authenticationClass(Authentication authentication) { return authentication.getClass().getName(); } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceSessionManagementTests.java ================================================ /* * 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 org.springframework.security.config.annotation.web.configurers; import java.security.Principal; import java.util.ArrayList; import java.util.Date; import java.util.List; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationListener; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpSession; import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.core.Authentication; import org.springframework.security.core.session.SessionInformation; import org.springframework.security.core.session.SessionRegistry; import org.springframework.security.core.session.SessionRegistryImpl; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.session.NullAuthenticatedSessionStrategy; import org.springframework.security.web.authentication.session.SessionAuthenticationException; import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy; import org.springframework.security.web.authentication.session.SessionFixationProtectionEvent; import org.springframework.security.web.session.InvalidSessionStrategy; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.ResultMatcher; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.springframework.security.config.Customizer.withDefaults; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * @author Rob Winch * @author Josh Cummings */ @ExtendWith(SpringTestContextExtension.class) public class NamespaceSessionManagementTests { public final SpringTestContext spring = new SpringTestContext(this); @Autowired MockMvc mvc; @Test public void authenticateWhenDefaultSessionManagementThenMatchesNamespace() throws Exception { this.spring.register(SessionManagementConfig.class, BasicController.class, UserDetailsServiceConfig.class) .autowire(); MockHttpSession session = new MockHttpSession(); String sessionId = session.getId(); MockHttpServletRequestBuilder request = get("/auth").session(session).with(httpBasic("user", "password")); // @formatter:off MvcResult result = this.mvc.perform(request) .andExpect(session()) .andReturn(); // @formatter:on assertThat(result.getRequest().getSession(false).getId()).isNotEqualTo(sessionId); } @Test public void authenticateWhenUsingInvalidSessionUrlThenMatchesNamespace() throws Exception { this.spring.register(CustomSessionManagementConfig.class).autowire(); MockHttpServletRequestBuilder authRequest = get("/auth").with((request) -> { request.setRequestedSessionIdValid(false); request.setRequestedSessionId("id"); return request; }); this.mvc.perform(authRequest).andExpect(redirectedUrl("/invalid-session")); } @Test public void authenticateWhenUsingExpiredUrlThenMatchesNamespace() throws Exception { this.spring.register(CustomSessionManagementConfig.class).autowire(); MockHttpSession session = new MockHttpSession(); SessionInformation sessionInformation = new SessionInformation(new Object(), session.getId(), new Date(0)); sessionInformation.expireNow(); SessionRegistry sessionRegistry = this.spring.getContext().getBean(SessionRegistry.class); given(sessionRegistry.getSessionInformation(session.getId())).willReturn(sessionInformation); this.mvc.perform(get("/auth").session(session)).andExpect(redirectedUrl("/expired-session")); } @Test public void authenticateWhenUsingMaxSessionsThenMatchesNamespace() throws Exception { this.spring.register(CustomSessionManagementConfig.class, BasicController.class, UserDetailsServiceConfig.class) .autowire(); this.mvc.perform(get("/auth").with(httpBasic("user", "password"))).andExpect(status().isOk()); this.mvc.perform(get("/auth").with(httpBasic("user", "password"))) .andExpect(redirectedUrl("/session-auth-error")); } @Test public void authenticateWhenUsingFailureUrlThenMatchesNamespace() throws Exception { this.spring.register(CustomSessionManagementConfig.class, BasicController.class, UserDetailsServiceConfig.class) .autowire(); MockHttpServletRequest mock = spy(MockHttpServletRequest.class); mock.setSession(new MockHttpSession()); given(mock.changeSessionId()).willThrow(SessionAuthenticationException.class); mock.setMethod("GET"); // @formatter:off MockHttpServletRequestBuilder authRequest = get("/auth") .with((request) -> mock) .with(httpBasic("user", "password")); // @formatter:on this.mvc.perform(authRequest).andExpect(redirectedUrl("/session-auth-error")); } @Test public void authenticateWhenUsingSessionRegistryThenMatchesNamespace() throws Exception { this.spring.register(CustomSessionManagementConfig.class, BasicController.class, UserDetailsServiceConfig.class) .autowire(); SessionRegistry sessionRegistry = this.spring.getContext().getBean(SessionRegistry.class); MockHttpServletRequestBuilder request = get("/auth").with(httpBasic("user", "password")); this.mvc.perform(request).andExpect(status().isOk()); verify(sessionRegistry).registerNewSession(any(String.class), any(Object.class)); } // gh-3371 @Test public void authenticateWhenUsingCustomInvalidSessionStrategyThenMatchesNamespace() throws Exception { this.spring.register(InvalidSessionStrategyConfig.class).autowire(); MockHttpServletRequestBuilder authRequest = get("/auth").with((request) -> { request.setRequestedSessionIdValid(false); request.setRequestedSessionId("id"); return request; }); this.mvc.perform(authRequest).andExpect(status().isOk()); verifyBean(InvalidSessionStrategy.class).onInvalidSessionDetected(any(HttpServletRequest.class), any(HttpServletResponse.class)); } @Test public void authenticateWhenUsingCustomSessionAuthenticationStrategyThenMatchesNamespace() throws Exception { this.spring.register(RefsSessionManagementConfig.class, BasicController.class, UserDetailsServiceConfig.class) .autowire(); MockHttpServletRequestBuilder request = get("/auth").with(httpBasic("user", "password")); this.mvc.perform(request).andExpect(status().isOk()); verifyBean(SessionAuthenticationStrategy.class).onAuthentication(any(Authentication.class), any(HttpServletRequest.class), any(HttpServletResponse.class)); } @Test public void authenticateWhenNoSessionFixationProtectionThenMatchesNamespace() throws Exception { this.spring .register(SFPNoneSessionManagementConfig.class, BasicController.class, UserDetailsServiceConfig.class) .autowire(); MockHttpSession givenSession = new MockHttpSession(); String givenSessionId = givenSession.getId(); // @formatter:off MockHttpServletRequestBuilder request = get("/auth") .session(givenSession) .with(httpBasic("user", "password")); MockHttpSession resultingSession = (MockHttpSession) this.mvc.perform(request) .andExpect(status().isOk()) .andReturn() .getRequest() .getSession(false); // @formatter:on assertThat(givenSessionId).isEqualTo(resultingSession.getId()); } @Test public void authenticateWhenMigrateSessionFixationProtectionThenMatchesNamespace() throws Exception { this.spring .register(SFPMigrateSessionManagementConfig.class, BasicController.class, UserDetailsServiceConfig.class) .autowire(); MockHttpSession givenSession = new MockHttpSession(); String givenSessionId = givenSession.getId(); givenSession.setAttribute("name", "value"); // @formatter:off MockHttpSession resultingSession = (MockHttpSession) this.mvc.perform(get("/auth") .session(givenSession) .with(httpBasic("user", "password"))) .andExpect(status().isOk()) .andReturn() .getRequest() .getSession(false); // @formatter:on assertThat(givenSessionId).isNotEqualTo(resultingSession.getId()); assertThat(resultingSession.getAttribute("name")).isEqualTo("value"); } // SEC-2913 @Test public void authenticateWhenUsingSessionFixationProtectionThenUsesNonNullEventPublisher() throws Exception { this.spring.register(SFPPostProcessedConfig.class, UserDetailsServiceConfig.class).autowire(); // @formatter:off MockHttpServletRequestBuilder request = get("/auth") .session(new MockHttpSession()) .with(httpBasic("user", "password")); // @formatter:on this.mvc.perform(request).andExpect(status().isNotFound()); verifyBean(MockEventListener.class).onApplicationEvent(any(SessionFixationProtectionEvent.class)); } @Test public void authenticateWhenNewSessionFixationProtectionThenMatchesNamespace() throws Exception { this.spring.register(SFPNewSessionSessionManagementConfig.class, UserDetailsServiceConfig.class).autowire(); MockHttpSession givenSession = new MockHttpSession(); String givenSessionId = givenSession.getId(); givenSession.setAttribute("name", "value"); MockHttpServletRequestBuilder request = get("/auth").session(givenSession).with(httpBasic("user", "password")); // @formatter:off MockHttpSession resultingSession = (MockHttpSession) this.mvc.perform(request) .andExpect(status().isNotFound()) .andReturn() .getRequest() .getSession(false); // @formatter:on assertThat(givenSessionId).isNotEqualTo(resultingSession.getId()); assertThat(resultingSession.getAttribute("name")).isNull(); } private T verifyBean(Class clazz) { return verify(this.spring.getContext().getBean(clazz)); } private static SessionResultMatcher session() { return new SessionResultMatcher(); } @Configuration @EnableWebSecurity static class SessionManagementConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((authorize) -> authorize .anyRequest().authenticated() ) .sessionManagement((sessions) -> sessions .requireExplicitAuthenticationStrategy(false) ) .httpBasic(Customizer.withDefaults()); // @formatter:on return http.build(); } } @Configuration @EnableWebSecurity static class CustomSessionManagementConfig { SessionRegistry sessionRegistry = spy(SessionRegistryImpl.class); @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((requests) -> requests .anyRequest().authenticated()) .httpBasic(withDefaults()) .sessionManagement((management) -> management .invalidSessionUrl("/invalid-session") // session-management@invalid-session-url .sessionAuthenticationErrorUrl("/session-auth-error") // session-management@session-authentication-error-url .maximumSessions(1) // session-management/concurrency-control@max-sessions .maxSessionsPreventsLogin(true) // session-management/concurrency-control@error-if-maximum-exceeded .expiredUrl("/expired-session") // session-management/concurrency-control@expired-url .sessionRegistry(sessionRegistry())); return http.build(); // session-management/concurrency-control@session-registry-ref // @formatter:on } @Bean SessionRegistry sessionRegistry() { return this.sessionRegistry; } } @Configuration @EnableWebSecurity static class InvalidSessionStrategyConfig { InvalidSessionStrategy invalidSessionStrategy = mock(InvalidSessionStrategy.class); @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .sessionManagement((management) -> management .invalidSessionStrategy(invalidSessionStrategy())); return http.build(); // @formatter:on } @Bean InvalidSessionStrategy invalidSessionStrategy() { return this.invalidSessionStrategy; } } @Configuration @EnableWebSecurity static class RefsSessionManagementConfig { SessionAuthenticationStrategy sessionAuthenticationStrategy = mock(SessionAuthenticationStrategy.class); @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .sessionManagement((management) -> management .sessionAuthenticationStrategy(sessionAuthenticationStrategy())) .httpBasic(withDefaults()); return http.build(); // @formatter:on } @Bean SessionAuthenticationStrategy sessionAuthenticationStrategy() { return this.sessionAuthenticationStrategy; } } @Configuration @EnableWebSecurity static class SFPNoneSessionManagementConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .sessionManagement((management) -> management .sessionAuthenticationStrategy(new NullAuthenticatedSessionStrategy())) .httpBasic(withDefaults()); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class SFPMigrateSessionManagementConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .sessionManagement((management) -> management .requireExplicitAuthenticationStrategy(false)) .httpBasic(withDefaults()); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class SFPPostProcessedConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .sessionManagement((sessions) -> sessions .requireExplicitAuthenticationStrategy(false) ) .httpBasic(withDefaults()); return http.build(); // @formatter:on } @Bean MockEventListener eventListener() { return spy(new MockEventListener()); } } @Configuration @EnableWebSecurity static class SFPNewSessionSessionManagementConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .sessionManagement((sessions) -> sessions .sessionFixation().newSession() .requireExplicitAuthenticationStrategy(false) ) .httpBasic(withDefaults()); return http.build(); // @formatter:on } } static class MockEventListener implements ApplicationListener { List events = new ArrayList<>(); @Override public void onApplicationEvent(SessionFixationProtectionEvent event) { this.events.add(event); } } @Configuration static class UserDetailsServiceConfig { @Bean UserDetailsService userDetailsService() { return new InMemoryUserDetailsManager( // @formatter:off User.withDefaultPasswordEncoder() .username("user") .password("password") .roles("USER") .build()); // @formatter:on } } @RestController static class BasicController { @GetMapping("/") String ok() { return "ok"; } @GetMapping("/auth") String auth(Principal principal) { return principal.getName(); } } private static class SessionResultMatcher implements ResultMatcher { private String id; private Boolean valid; private Boolean exists = true; ResultMatcher exists(boolean exists) { this.exists = exists; return this; } ResultMatcher valid(boolean valid) { this.valid = valid; return this.exists(true); } ResultMatcher id(String id) { this.id = id; return this.exists(true); } @Override public void match(MvcResult result) { if (!this.exists) { assertThat(result.getRequest().getSession(false)).isNull(); return; } assertThat(result.getRequest().getSession(false)).isNotNull(); MockHttpSession session = (MockHttpSession) result.getRequest().getSession(false); if (this.valid != null) { if (this.valid) { assertThat(session.isInvalid()).isFalse(); } else { assertThat(session.isInvalid()).isTrue(); } } if (this.id != null) { assertThat(session.getId()).isEqualTo(this.id); } } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/annotation/web/configurers/PasswordManagementConfigurerTests.java ================================================ /* * 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 org.springframework.security.config.annotation.web.configurers; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.web.SecurityFilterChain; import org.springframework.test.web.servlet.MockMvc; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.springframework.security.config.Customizer.withDefaults; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Tests for {@link PasswordManagementConfigurer}. * * @author Evgeniy Cheban */ @ExtendWith(SpringTestContextExtension.class) public class PasswordManagementConfigurerTests { public final SpringTestContext spring = new SpringTestContext(this); @Autowired MockMvc mvc; @Test public void whenChangePasswordPageNotSetThenDefaultChangePasswordPageUsed() throws Exception { this.spring.register(PasswordManagementWithDefaultChangePasswordPageConfig.class).autowire(); this.mvc.perform(get("/.well-known/change-password")) .andExpect(status().isFound()) .andExpect(redirectedUrl("/change-password")); } @Test public void whenChangePasswordPageSetThenSpecifiedChangePasswordPageUsed() throws Exception { this.spring.register(PasswordManagementWithCustomChangePasswordPageConfig.class).autowire(); this.mvc.perform(get("/.well-known/change-password")) .andExpect(status().isFound()) .andExpect(redirectedUrl("/custom-change-password-page")); } @Test public void whenSettingNullChangePasswordPage() { PasswordManagementConfigurer configurer = new PasswordManagementConfigurer(); assertThatIllegalArgumentException().isThrownBy(() -> configurer.changePasswordPage(null)) .withMessage("changePasswordPage cannot be empty"); } @Test public void whenSettingEmptyChangePasswordPage() { PasswordManagementConfigurer configurer = new PasswordManagementConfigurer(); assertThatIllegalArgumentException().isThrownBy(() -> configurer.changePasswordPage("")) .withMessage("changePasswordPage cannot be empty"); } @Test public void whenSettingBlankChangePasswordPage() { PasswordManagementConfigurer configurer = new PasswordManagementConfigurer(); assertThatIllegalArgumentException().isThrownBy(() -> configurer.changePasswordPage(" ")) .withMessage("changePasswordPage cannot be empty"); } @Configuration @EnableWebSecurity static class PasswordManagementWithDefaultChangePasswordPageConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off return http .passwordManagement(withDefaults()) .build(); // @formatter:on } } @Configuration @EnableWebSecurity static class PasswordManagementWithCustomChangePasswordPageConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off return http .passwordManagement((passwordManagement) -> passwordManagement .changePasswordPage("/custom-change-password-page") ) .build(); // @formatter:on } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/annotation/web/configurers/PermitAllSupportTests.java ================================================ /* * 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 org.springframework.security.config.annotation.web.configurers; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.web.SecurityFilterChain; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * @author Rob Winch * @author Josh Cummings * */ @ExtendWith(SpringTestContextExtension.class) public class PermitAllSupportTests { public final SpringTestContext spring = new SpringTestContext(this); @Autowired private MockMvc mvc; @Test public void performWhenUsingPermitAllExactUrlRequestMatcherThenMatchesExactUrl() throws Exception { this.spring.register(PermitAllConfig.class).autowire(); MockHttpServletRequestBuilder request = get("/app/xyz").contextPath("/app"); this.mvc.perform(request).andExpect(status().isNotFound()); MockHttpServletRequestBuilder getWithQuery = get("/app/xyz?def").contextPath("/app"); this.mvc.perform(getWithQuery).andExpect(status().isFound()); MockHttpServletRequestBuilder postWithQueryAndCsrf = post("/app/abc?def").with(csrf()).contextPath("/app"); this.mvc.perform(postWithQueryAndCsrf).andExpect(status().isNotFound()); MockHttpServletRequestBuilder getWithCsrf = get("/app/abc").with(csrf()).contextPath("/app"); this.mvc.perform(getWithCsrf).andExpect(status().isFound()); } @Test public void performWhenUsingPermitAllExactUrlRequestMatcherThenMatchesExactUrlWithAuthorizeHttp() throws Exception { this.spring.register(PermitAllConfigAuthorizeHttpRequests.class).autowire(); MockHttpServletRequestBuilder request = get("/app/xyz").contextPath("/app"); this.mvc.perform(request).andExpect(status().isNotFound()); MockHttpServletRequestBuilder getWithQuery = get("/app/xyz?def").contextPath("/app"); this.mvc.perform(getWithQuery).andExpect(status().isFound()); MockHttpServletRequestBuilder postWithQueryAndCsrf = post("/app/abc?def").with(csrf()).contextPath("/app"); this.mvc.perform(postWithQueryAndCsrf).andExpect(status().isNotFound()); MockHttpServletRequestBuilder getWithCsrf = get("/app/abc").with(csrf()).contextPath("/app"); this.mvc.perform(getWithCsrf).andExpect(status().isFound()); } @Test public void configureWhenNotAuthorizeRequestsThenException() { assertThatExceptionOfType(BeanCreationException.class) .isThrownBy(() -> this.spring.register(NoAuthorizedUrlsConfig.class).autowire()) .withMessageContaining( "permitAll only works with HttpSecurity.authorizeHttpRequests(). Please define one."); } @Configuration @EnableWebSecurity static class PermitAllConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((requests) -> requests .anyRequest().authenticated()) .formLogin((login) -> login .loginPage("/xyz").permitAll() .loginProcessingUrl("/abc?def").permitAll()); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class PermitAllConfigAuthorizeHttpRequests { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((authorize) -> authorize .anyRequest().authenticated()) .formLogin((login) -> login .loginPage("/xyz").permitAll() .loginProcessingUrl("/abc?def").permitAll()); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class NoAuthorizedUrlsConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .formLogin((login) -> login .permitAll()); return http.build(); // @formatter:on } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/annotation/web/configurers/PortMapperConfigurerTests.java ================================================ /* * 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 org.springframework.security.config.annotation.web.configurers; import java.util.Collections; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.web.PortMapperImpl; import org.springframework.security.web.SecurityFilterChain; import org.springframework.test.web.servlet.MockMvc; import static org.springframework.security.config.Customizer.withDefaults; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; /** * @author Rob Winch * @author Josh Cummings */ @ExtendWith(SpringTestContextExtension.class) public class PortMapperConfigurerTests { public final SpringTestContext spring = new SpringTestContext(this); @Autowired private MockMvc mockMvc; @Test public void requestWhenPortMapperTwiceInvokedThenDoesNotOverride() throws Exception { this.spring.register(InvokeTwiceDoesNotOverride.class).autowire(); this.mockMvc.perform(get("http://localhost:543")).andExpect(redirectedUrl("https://localhost:123")); } @Test public void requestWhenPortMapperHttpMapsToInLambdaThenRedirectsToHttpsPort() throws Exception { this.spring.register(HttpMapsToInLambdaConfig.class).autowire(); this.mockMvc.perform(get("http://localhost:543")).andExpect(redirectedUrl("https://localhost:123")); } @Test public void requestWhenCustomPortMapperInLambdaThenRedirectsToHttpsPort() throws Exception { this.spring.register(CustomPortMapperInLambdaConfig.class).autowire(); this.mockMvc.perform(get("http://localhost:543")).andExpect(redirectedUrl("https://localhost:123")); } @Configuration @EnableWebSecurity static class InvokeTwiceDoesNotOverride { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .requiresChannel((channel) -> channel .anyRequest().requiresSecure()) .portMapper((mapper) -> mapper .http(543).mapsTo(123)) .portMapper(withDefaults()); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class HttpMapsToInLambdaConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .requiresChannel((requiresChannel) -> requiresChannel .anyRequest().requiresSecure() ) .portMapper((portMapper) -> portMapper .http(543).mapsTo(123) ); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class CustomPortMapperInLambdaConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { PortMapperImpl customPortMapper = new PortMapperImpl(); customPortMapper.setPortMappings(Collections.singletonMap("543", "123")); // @formatter:off http .requiresChannel((requiresChannel) -> requiresChannel .anyRequest().requiresSecure() ) .portMapper((portMapper) -> portMapper .portMapper(customPortMapper) ); return http.build(); // @formatter:on } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/annotation/web/configurers/RememberMeConfigurerTests.java ================================================ /* * 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 org.springframework.security.config.annotation.web.configurers; import java.util.Collections; import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpSession; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.UnsatisfiedDependencyException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.mock.web.MockHttpSession; import org.springframework.security.authentication.RememberMeAuthenticationToken; import org.springframework.security.authentication.dao.DaoAuthenticationProvider; import org.springframework.security.config.ObjectPostProcessor; import org.springframework.security.config.annotation.SecurityContextChangedListenerConfig; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.security.core.userdetails.PasswordEncodedUser; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.RememberMeServices; import org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter; import org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices; import org.springframework.security.web.context.HttpRequestResponseHolder; import org.springframework.security.web.context.HttpSessionSecurityContextRepository; import org.springframework.security.web.context.SecurityContextRepository; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.hamcrest.Matchers.startsWith; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.springframework.security.config.Customizer.withDefaults; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic; import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.cookie; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; /** * Tests for {@link RememberMeConfigurer} * * @author Rob Winch * @author Eddú Meléndez * @author Eleftheria Stein */ @ExtendWith(SpringTestContextExtension.class) public class RememberMeConfigurerTests { public final SpringTestContext spring = new SpringTestContext(this); @Autowired MockMvc mvc; @Test public void postWhenNoUserDetailsServiceThenException() { assertThatExceptionOfType(UnsatisfiedDependencyException.class) .isThrownBy(() -> this.spring.register(NullUserDetailsConfig.class).autowire()) .withMessageContaining("userDetailsService cannot be null"); } @Test public void configureWhenRegisteringObjectPostProcessorThenInvokedOnRememberMeAuthenticationFilter() { this.spring.register(ObjectPostProcessorConfig.class).autowire(); verify(this.spring.getContext().getBean(ObjectPostProcessorConfig.class).objectPostProcessor) .postProcess(any(RememberMeAuthenticationFilter.class)); } @Test public void rememberMeWhenInvokedTwiceThenUsesOriginalUserDetailsService() throws Exception { given(DuplicateDoesNotOverrideConfig.userDetailsService.loadUserByUsername(anyString())) .willReturn(new User("user", "password", Collections.emptyList())); this.spring.register(DuplicateDoesNotOverrideConfig.class).autowire(); // @formatter:off MockHttpServletRequestBuilder request = get("/") .with(httpBasic("user", "password")) .param("remember-me", "true"); // @formatter:on this.mvc.perform(request); verify(DuplicateDoesNotOverrideConfig.userDetailsService).loadUserByUsername("user"); } @Test public void rememberMeWhenUserDetailsServiceNotConfiguredThenUsesBean() throws Exception { this.spring.register(UserDetailsServiceBeanConfig.class).autowire(); MvcResult mvcResult = this.mvc .perform(post("/login").with(csrf()) .param("username", "user") .param("password", "password") .param("remember-me", "true")) .andReturn(); Cookie rememberMeCookie = mvcResult.getResponse().getCookie("remember-me"); // @formatter:off MockHttpServletRequestBuilder request = get("/abc").cookie(rememberMeCookie); SecurityMockMvcResultMatchers.AuthenticatedMatcher remembermeAuthentication = authenticated() .withAuthentication((auth) -> assertThat(auth).isInstanceOf(RememberMeAuthenticationToken.class)); // @formatter:on this.mvc.perform(request).andExpect(remembermeAuthentication); } @Test public void rememberMeWhenCustomSecurityContextHolderStrategyThenUses() throws Exception { this.spring.register(UserDetailsServiceBeanConfig.class, SecurityContextChangedListenerConfig.class).autowire(); MvcResult mvcResult = this.mvc .perform(post("/login").with(csrf()) .param("username", "user") .param("password", "password") .param("remember-me", "true")) .andReturn(); Cookie rememberMeCookie = mvcResult.getResponse().getCookie("remember-me"); // @formatter:off MockHttpServletRequestBuilder request = get("/abc").cookie(rememberMeCookie); SecurityMockMvcResultMatchers.AuthenticatedMatcher remembermeAuthentication = authenticated() .withAuthentication((auth) -> assertThat(auth).isInstanceOf(RememberMeAuthenticationToken.class)); // @formatter:on this.mvc.perform(request).andExpect(remembermeAuthentication); verify(this.spring.getContext().getBean(SecurityContextHolderStrategy.class), atLeastOnce()).getContext(); } @Test public void loginWhenRememberMeTrueThenRespondsWithRememberMeCookie() throws Exception { this.spring.register(RememberMeConfig.class).autowire(); // @formatter:off MockHttpServletRequestBuilder request = post("/login") .with(csrf()) .param("username", "user") .param("password", "password") .param("remember-me", "true"); // @formatter:on this.mvc.perform(request).andExpect(cookie().exists("remember-me")); } @Test public void getWhenRememberMeCookieThenAuthenticationIsRememberMeAuthenticationToken() throws Exception { this.spring.register(RememberMeConfig.class).autowire(); MvcResult mvcResult = this.mvc .perform(post("/login").with(csrf()) .param("username", "user") .param("password", "password") .param("remember-me", "true")) .andReturn(); Cookie rememberMeCookie = mvcResult.getResponse().getCookie("remember-me"); // @formatter:off MockHttpServletRequestBuilder request = get("/abc").cookie(rememberMeCookie); SecurityMockMvcResultMatchers.AuthenticatedMatcher remembermeAuthentication = authenticated() .withAuthentication((auth) -> assertThat(auth).isInstanceOf(RememberMeAuthenticationToken.class)); // @formatter:on this.mvc.perform(request).andExpect(remembermeAuthentication); } @Test public void logoutWhenRememberMeCookieThenAuthenticationIsRememberMeCookieExpired() throws Exception { this.spring.register(RememberMeConfig.class).autowire(); // @formatter:off MockHttpServletRequestBuilder loginRequest = post("/login") .with(csrf()) .param("username", "user") .param("password", "password") .param("remember-me", "true"); // @formatter:on MvcResult mvcResult = this.mvc.perform(loginRequest).andReturn(); Cookie rememberMeCookie = mvcResult.getResponse().getCookie("remember-me"); HttpSession session = mvcResult.getRequest().getSession(); // @formatter:off MockHttpServletRequestBuilder logoutRequest = post("/logout") .with(csrf()) .cookie(rememberMeCookie) .session((MockHttpSession) session); this.mvc.perform(logoutRequest) .andExpect(redirectedUrl("/login?logout")) .andExpect(cookie().maxAge("remember-me", 0)); // @formatter:on } @Test public void getWhenRememberMeCookieAndLoggedOutThenRedirectsToLogin() throws Exception { this.spring.register(RememberMeConfig.class).autowire(); // @formatter:off MockHttpServletRequestBuilder loginRequest = post("/login") .with(csrf()) .param("username", "user") .param("password", "password") .param("remember-me", "true"); // @formatter:on MvcResult loginMvcResult = this.mvc.perform(loginRequest).andReturn(); Cookie rememberMeCookie = loginMvcResult.getResponse().getCookie("remember-me"); HttpSession session = loginMvcResult.getRequest().getSession(); // @formatter:off MockHttpServletRequestBuilder logoutRequest = post("/logout") .with(csrf()) .cookie(rememberMeCookie) .session((MockHttpSession) session); // @formatter:on MvcResult logoutMvcResult = this.mvc.perform(logoutRequest).andReturn(); Cookie expiredRememberMeCookie = logoutMvcResult.getResponse().getCookie("remember-me"); // @formatter:off MockHttpServletRequestBuilder expiredRequest = get("/abc") .with(csrf()) .cookie(expiredRememberMeCookie); // @formatter:on this.mvc.perform(expiredRequest).andExpect(redirectedUrl("/login")); } @Test public void loginWhenRememberMeConfiguredInLambdaThenRespondsWithRememberMeCookie() throws Exception { this.spring.register(RememberMeInLambdaConfig.class).autowire(); // @formatter:off MockHttpServletRequestBuilder request = post("/login") .with(csrf()) .param("username", "user") .param("password", "password") .param("remember-me", "true"); // @formatter:on this.mvc.perform(request).andExpect(cookie().exists("remember-me")); } @Test public void loginWhenRememberMeTrueAndCookieDomainThenRememberMeCookieHasDomain() throws Exception { this.spring.register(RememberMeCookieDomainConfig.class).autowire(); // @formatter:off MockHttpServletRequestBuilder request = post("/login") .with(csrf()) .param("username", "user") .param("password", "password") .param("remember-me", "true"); this.mvc.perform(request). andExpect(cookie().exists("remember-me")) .andExpect(cookie().domain("remember-me", "spring.io")); // @formatter:on } @Test public void loginWhenRememberMeTrueAndCookieDomainInLambdaThenRememberMeCookieHasDomain() throws Exception { this.spring.register(RememberMeCookieDomainInLambdaConfig.class).autowire(); // @formatter:off MockHttpServletRequestBuilder loginRequest = post("/login") .with(csrf()) .param("username", "user") .param("password", "password") .param("remember-me", "true"); this.mvc.perform(loginRequest) .andExpect(cookie().exists("remember-me")) .andExpect(cookie().domain("remember-me", "spring.io")); // @formatter:on } @Test public void configureWhenRememberMeCookieNameAndRememberMeServicesThenException() { assertThatExceptionOfType(BeanCreationException.class) .isThrownBy(() -> this.spring.register(RememberMeCookieNameAndRememberMeServicesConfig.class).autowire()) .withRootCauseInstanceOf(IllegalArgumentException.class) .withMessageContaining("Can not set rememberMeCookieName and custom rememberMeServices."); } @Test public void getWhenRememberMeCookieAndNoKeyConfiguredThenKeyFromRememberMeServicesIsUsed() throws Exception { this.spring.register(FallbackRememberMeKeyConfig.class).autowire(); // @formatter:off MockHttpServletRequestBuilder loginRequest = post("/login") .with(csrf()) .param("username", "user") .param("password", "password") .param("remember-me", "true"); // @formatter:on MvcResult mvcResult = this.mvc.perform(loginRequest).andReturn(); Cookie rememberMeCookie = mvcResult.getResponse().getCookie("remember-me"); MockHttpServletRequestBuilder requestWithRememberme = get("/abc").cookie(rememberMeCookie); // @formatter:off SecurityMockMvcResultMatchers.AuthenticatedMatcher remembermeAuthentication = authenticated() .withAuthentication((auth) -> assertThat(auth).isInstanceOf(RememberMeAuthenticationToken.class)); // @formatter:on this.mvc.perform(requestWithRememberme).andExpect(remembermeAuthentication); } // gh-13104 @Test public void getWhenCustomSecurityContextRepositoryThenUses() throws Exception { this.spring.register(SecurityContextRepositoryConfig.class).autowire(); SecurityContextRepository repository = this.spring.getContext().getBean(SecurityContextRepository.class); MvcResult mvcResult = this.mvc .perform(post("/login").with(csrf()) .param("username", "user") .param("password", "password") .param("remember-me", "true")) .andReturn(); Cookie rememberMeCookie = mvcResult.getResponse().getCookie("remember-me"); reset(repository); // @formatter:off MockHttpServletRequestBuilder request = get("/abc").cookie(rememberMeCookie); SecurityMockMvcResultMatchers.AuthenticatedMatcher remembermeAuthentication = authenticated() .withAuthentication((auth) -> assertThat(auth).isInstanceOf(RememberMeAuthenticationToken.class)); // @formatter:on this.mvc.perform(request).andExpect(remembermeAuthentication); verify(repository).saveContext(any(), any(), any()); } @Test public void rememberMeExpiresSessionWhenSessionManagementMaximumSessionsExceeds() throws Exception { this.spring.register(RememberMeMaximumSessionsConfig.class).autowire(); MockHttpServletRequestBuilder loginRequest = post("/login").with(csrf()) .param("username", "user") .param("password", "password") .param("remember-me", "true"); MvcResult mvcResult = this.mvc.perform(loginRequest).andReturn(); Cookie rememberMeCookie = mvcResult.getResponse().getCookie("remember-me"); HttpSession session = mvcResult.getRequest().getSession(); MockHttpServletRequestBuilder exceedsMaximumSessionsRequest = get("/abc").cookie(rememberMeCookie); this.mvc.perform(exceedsMaximumSessionsRequest); MockHttpServletRequestBuilder sessionExpiredRequest = get("/abc").cookie(rememberMeCookie) .session((MockHttpSession) session); this.mvc.perform(sessionExpiredRequest) .andExpect(content().string(startsWith("This session has been expired"))); } @Configuration @EnableWebSecurity static class NullUserDetailsConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((requests) -> requests .anyRequest().hasRole("USER")) .formLogin(withDefaults()) .rememberMe(withDefaults()); // @formatter:on return http.build(); } @Autowired void configure(AuthenticationManagerBuilder auth) { User user = (User) PasswordEncodedUser.user(); DaoAuthenticationProvider provider = new DaoAuthenticationProvider( new InMemoryUserDetailsManager(Collections.singletonList(user))); // @formatter:off auth .authenticationProvider(provider); // @formatter:on } } @Configuration @EnableWebSecurity static class ObjectPostProcessorConfig { ObjectPostProcessor objectPostProcessor = spy(ReflectingObjectPostProcessor.class); @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .rememberMe((me) -> me .userDetailsService(new AuthenticationManagerBuilder(this.objectPostProcessor).getDefaultUserDetailsService())); // @formatter:on return http.build(); } @Bean UserDetailsService userDetailsService() { return new InMemoryUserDetailsManager(); } @Bean ObjectPostProcessor objectPostProcessor() { return this.objectPostProcessor; } } static class ReflectingObjectPostProcessor implements ObjectPostProcessor { @Override public O postProcess(O object) { return object; } } @Configuration @EnableWebSecurity static class DuplicateDoesNotOverrideConfig { static UserDetailsService userDetailsService = mock(UserDetailsService.class); @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .httpBasic(withDefaults()) .rememberMe((me) -> me .userDetailsService(userDetailsService)) .rememberMe(withDefaults()); return http.build(); // @formatter:on } @Bean UserDetailsService userDetailsService() { return new InMemoryUserDetailsManager( // @formatter:off User.withDefaultPasswordEncoder() .username("user") .password("password") .roles("USER") .build() // @formatter:on ); } } @Configuration @EnableWebSecurity static class UserDetailsServiceBeanConfig { @Bean SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { // @formatter:off http .formLogin(withDefaults()) .rememberMe(withDefaults()); // @formatter:on return http.build(); } @Bean UserDetailsService customUserDetailsService() { return new InMemoryUserDetailsManager(PasswordEncodedUser.user()); } } @Configuration @EnableWebSecurity static class RememberMeConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((requests) -> requests .anyRequest().hasRole("USER")) .formLogin(withDefaults()) .rememberMe(withDefaults()); return http.build(); // @formatter:on } @Bean UserDetailsService userDetailsService() { return new InMemoryUserDetailsManager(PasswordEncodedUser.user()); } } @Configuration @EnableWebSecurity static class RememberMeInLambdaConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((authorize) -> authorize .anyRequest().hasRole("USER") ) .formLogin(withDefaults()) .rememberMe(withDefaults()); return http.build(); // @formatter:on } @Bean UserDetailsService userDetailsService() { return new InMemoryUserDetailsManager(PasswordEncodedUser.user()); } } @Configuration @EnableWebSecurity static class RememberMeCookieDomainConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((requests) -> requests .anyRequest().hasRole("USER")) .formLogin(withDefaults()) .rememberMe((me) -> me .rememberMeCookieDomain("spring.io")); return http.build(); // @formatter:on } @Bean UserDetailsService userDetailsService() { return new InMemoryUserDetailsManager(PasswordEncodedUser.user()); } } @Configuration @EnableWebSecurity static class RememberMeCookieDomainInLambdaConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((authorize) -> authorize .anyRequest().hasRole("USER") ) .formLogin(withDefaults()) .rememberMe((rememberMe) -> rememberMe .rememberMeCookieDomain("spring.io") ); return http.build(); // @formatter:on } @Bean UserDetailsService userDetailsService() { return new InMemoryUserDetailsManager(PasswordEncodedUser.user()); } } @Configuration @EnableWebSecurity static class RememberMeCookieNameAndRememberMeServicesConfig { static RememberMeServices REMEMBER_ME = mock(RememberMeServices.class); @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((requests) -> requests .anyRequest().hasRole("USER")) .formLogin(withDefaults()) .rememberMe((me) -> me .rememberMeCookieName("SPRING_COOKIE_DOMAIN") .rememberMeCookieDomain("spring.io") .rememberMeServices(REMEMBER_ME)); return http.build(); // @formatter:on } @Autowired void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { // @formatter:off auth .inMemoryAuthentication() .withUser(PasswordEncodedUser.user()); // @formatter:on } } @Configuration @EnableWebSecurity static class FallbackRememberMeKeyConfig extends RememberMeConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((requests) -> requests .anyRequest().hasRole("USER")) .formLogin(withDefaults()) .rememberMe((me) -> me .rememberMeServices(new TokenBasedRememberMeServices("key", userDetailsService()))); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class RememberMeMaximumSessionsConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((authorize) -> authorize .anyRequest().hasRole("USER") ) .sessionManagement((sessionManagement) -> sessionManagement .maximumSessions(1) ) .formLogin(withDefaults()) .rememberMe(withDefaults()); return http.build(); // @formatter:on } @Bean UserDetailsService userDetailsService() { return new InMemoryUserDetailsManager(PasswordEncodedUser.user()); } } @Configuration @EnableWebSecurity static class SecurityContextRepositoryConfig { private SecurityContextRepository repository = spy(new SpySecurityContextRepository()); @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated()) .securityContext((context) -> context.securityContextRepository(this.repository)) .formLogin(withDefaults()) .rememberMe(withDefaults()); return http.build(); // @formatter:on } @Bean SecurityContextRepository securityContextRepository() { return this.repository; } @Bean UserDetailsService userDetailsService() { return new InMemoryUserDetailsManager(PasswordEncodedUser.user()); } private static class SpySecurityContextRepository implements SecurityContextRepository { SecurityContextRepository delegate = new HttpSessionSecurityContextRepository(); @Override public SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder) { return this.delegate.loadContext(requestResponseHolder); } @Override public void saveContext(SecurityContext context, HttpServletRequest request, HttpServletResponse response) { this.delegate.saveContext(context, request, response); } @Override public boolean containsContext(HttpServletRequest request) { return this.delegate.containsContext(request); } } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/annotation/web/configurers/RequestCacheConfigurerTests.java ================================================ /* * 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 org.springframework.security.config.annotation.web.configurers; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.mock.web.MockHttpSession; import org.springframework.mock.web.MockMultipartFile; import org.springframework.security.config.Customizer; import org.springframework.security.config.ObjectPostProcessor; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.core.userdetails.User; import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.test.web.servlet.RequestCacheResultMatcher; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.savedrequest.NullRequestCache; import org.springframework.security.web.savedrequest.RequestCache; import org.springframework.security.web.savedrequest.RequestCacheAwareFilter; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.RequestBuilder; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; import org.springframework.test.web.servlet.request.MockMultipartHttpServletRequestBuilder; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.springframework.security.config.Customizer.withDefaults; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.multipart; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; /** * Tests for {@link RequestCacheConfigurer} * * @author Rob Winch * @author Josh Cummings */ @ExtendWith(SpringTestContextExtension.class) public class RequestCacheConfigurerTests { public final SpringTestContext spring = new SpringTestContext(this); @Autowired MockMvc mvc; @Test public void configureWhenRegisteringObjectPostProcessorThenInvokedOnExceptionTranslationFilter() { this.spring.register(ObjectPostProcessorConfig.class, DefaultSecurityConfig.class).autowire(); verify(ObjectPostProcessorConfig.objectPostProcessor).postProcess(any(RequestCacheAwareFilter.class)); } @Test public void getWhenInvokingExceptionHandlingTwiceThenOriginalEntryPointUsed() throws Exception { this.spring.register(InvokeTwiceDoesNotOverrideConfig.class).autowire(); this.mvc.perform(get("/")); verify(InvokeTwiceDoesNotOverrideConfig.requestCache).getMatchingRequest(any(HttpServletRequest.class), any(HttpServletResponse.class)); } @Test public void getWhenBookmarkedUrlIsFaviconIcoThenPostAuthenticationRedirectsToRoot() throws Exception { this.spring.register(RequestCacheDefaultsConfig.class, DefaultSecurityConfig.class).autowire(); // @formatter:off MockHttpSession session = (MockHttpSession) this.mvc.perform(get("/favicon.ico")) .andExpect(redirectedUrl("/login")) .andReturn() .getRequest() .getSession(); // @formatter:on // ignores favicon.ico this.mvc.perform(formLogin(session)).andExpect(redirectedUrl("/")); } @Test public void getWhenBookmarkedUrlIsFaviconPngThenPostAuthenticationRedirectsToRoot() throws Exception { this.spring.register(RequestCacheDefaultsConfig.class, DefaultSecurityConfig.class).autowire(); // @formatter:off MockHttpSession session = (MockHttpSession) this.mvc.perform(get("/favicon.png")) .andExpect(redirectedUrl("/login")) .andReturn() .getRequest() .getSession(); // @formatter:on // ignores favicon.png this.mvc.perform(formLogin(session)).andExpect(redirectedUrl("/")); } // SEC-2321 @Test public void getWhenBookmarkedRequestIsApplicationJsonThenPostAuthenticationRedirectsToRoot() throws Exception { this.spring.register(RequestCacheDefaultsConfig.class, DefaultSecurityConfig.class).autowire(); MockHttpServletRequestBuilder request = get("/messages").header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON); // @formatter:off MockHttpSession session = (MockHttpSession) this.mvc.perform(request) .andExpect(redirectedUrl("/login")) .andReturn() .getRequest() .getSession(); // @formatter:on // ignores application/json // This is desirable since JSON requests are typically not invoked directly from // the browser and we don't want the browser to replay them this.mvc.perform(formLogin(session)).andExpect(redirectedUrl("/")); } // SEC-2321 @Test public void getWhenBookmarkedRequestIsXRequestedWithThenPostAuthenticationRedirectsToRoot() throws Exception { this.spring.register(RequestCacheDefaultsConfig.class, DefaultSecurityConfig.class).autowire(); // @formatter:off MockHttpServletRequestBuilder xRequestedWith = get("/messages") .header("X-Requested-With", "XMLHttpRequest"); MockHttpSession session = (MockHttpSession) this.mvc .perform(xRequestedWith) .andExpect(redirectedUrl("/login")) .andReturn() .getRequest() .getSession(); // @formatter:on this.mvc.perform(formLogin(session)).andExpect(redirectedUrl("/")); // This is desirable since XHR requests are typically not invoked directly from // the browser and we don't want the browser to replay them } @Test public void getWhenBookmarkedRequestIsTextEventStreamThenPostAuthenticationRedirectsToRoot() throws Exception { this.spring.register(RequestCacheDefaultsConfig.class, DefaultSecurityConfig.class).autowire(); MockHttpServletRequestBuilder request = get("/messages").header(HttpHeaders.ACCEPT, MediaType.TEXT_EVENT_STREAM); // @formatter:off MockHttpSession session = (MockHttpSession) this.mvc.perform(request) .andExpect(redirectedUrl("/login")) .andReturn() .getRequest() .getSession(); // @formatter:on // ignores text/event-stream // This is desirable since event-stream requests are typically not invoked // directly from the browser and we don't want the browser to replay them this.mvc.perform(formLogin(session)).andExpect(redirectedUrl("/")); } @Test public void getWhenBookmarkedRequestIsWebSocketThenPostAuthenticationRedirectsToRoot() throws Exception { this.spring.register(RequestCacheDefaultsConfig.class, DefaultSecurityConfig.class).autowire(); MockHttpServletRequestBuilder request = get("/messages").header("Upgrade", "websocket"); // @formatter:off MockHttpSession session = (MockHttpSession) this.mvc.perform(request) .andExpect(redirectedUrl("/login")) .andReturn() .getRequest() .getSession(); // @formatter:on // ignores websocket // This is desirable since websocket requests are typically not invoked // directly from the browser and we don't want the browser to replay them this.mvc.perform(formLogin(session)).andExpect(redirectedUrl("/")); } @Test public void getWhenBookmarkedRequestIsAllMediaTypeThenPostAuthenticationRemembers() throws Exception { this.spring.register(RequestCacheDefaultsConfig.class, DefaultSecurityConfig.class).autowire(); MockHttpServletRequestBuilder request = get("/messages").header(HttpHeaders.ACCEPT, MediaType.ALL); // @formatter:off MockHttpSession session = (MockHttpSession) this.mvc.perform(request) .andExpect(redirectedUrl("/login")) .andReturn() .getRequest() .getSession(); // @formatter:on this.mvc.perform(formLogin(session)).andExpect(RequestCacheResultMatcher.redirectToCachedRequest()); } @Test public void getWhenBookmarkedRequestIsTextHtmlThenPostAuthenticationRemembers() throws Exception { this.spring.register(RequestCacheDefaultsConfig.class, DefaultSecurityConfig.class).autowire(); MockHttpServletRequestBuilder request = get("/messages").header(HttpHeaders.ACCEPT, MediaType.TEXT_HTML); // @formatter:off MockHttpSession session = (MockHttpSession) this.mvc.perform(request) .andExpect(redirectedUrl("/login")) .andReturn() .getRequest() .getSession(); // @formatter:on this.mvc.perform(formLogin(session)).andExpect(RequestCacheResultMatcher.redirectToCachedRequest()); } @Test public void getWhenBookmarkedRequestIsChromeThenPostAuthenticationRemembers() throws Exception { this.spring.register(RequestCacheDefaultsConfig.class, DefaultSecurityConfig.class).autowire(); // @formatter:off MockHttpServletRequestBuilder request = get("/messages") .header(HttpHeaders.ACCEPT, "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8"); MockHttpSession session = (MockHttpSession) this.mvc.perform(request) .andExpect(redirectedUrl("/login")) .andReturn() .getRequest() .getSession(); // @formatter:on this.mvc.perform(formLogin(session)).andExpect(RequestCacheResultMatcher.redirectToCachedRequest()); } @Test public void getWhenBookmarkedRequestIsRequestedWithAndroidThenPostAuthenticationRemembers() throws Exception { this.spring.register(RequestCacheDefaultsConfig.class, DefaultSecurityConfig.class).autowire(); // @formatter:off MockHttpServletRequestBuilder request = get("/messages") .header("X-Requested-With", "com.android"); MockHttpSession session = (MockHttpSession) this.mvc.perform(request) .andExpect(redirectedUrl("/login")) .andReturn() .getRequest() .getSession(); // @formatter:on this.mvc.perform(formLogin(session)).andExpect(RequestCacheResultMatcher.redirectToCachedRequest()); } // gh-6102 @Test public void getWhenRequestCacheIsDisabledThenExceptionTranslationFilterDoesNotStoreRequest() throws Exception { this.spring.register(RequestCacheDisabledConfig.class, DefaultSecurityConfig.class).autowire(); // @formatter:off MockHttpSession session = (MockHttpSession) this.mvc.perform(get("/bob")) .andReturn() .getRequest() .getSession(); // @formatter:on this.mvc.perform(formLogin(session)).andExpect(redirectedUrl("/")); } // SEC-7060 @Test public void postWhenRequestIsMultipartThenPostAuthenticationRedirectsToRoot() throws Exception { this.spring.register(RequestCacheDefaultsConfig.class, DefaultSecurityConfig.class).autowire(); MockMultipartFile aFile = new MockMultipartFile("aFile", "A_FILE".getBytes()); MockMultipartHttpServletRequestBuilder request = multipart("/upload").file(aFile); // @formatter:off MockHttpSession session = (MockHttpSession) this.mvc.perform(request) .andReturn() .getRequest() .getSession(); // @formatter:on this.mvc.perform(formLogin(session)).andExpect(redirectedUrl("/")); } @Test public void getWhenRequestCacheIsDisabledInLambdaThenExceptionTranslationFilterDoesNotStoreRequest() throws Exception { this.spring.register(RequestCacheDisabledInLambdaConfig.class, DefaultSecurityConfig.class).autowire(); // @formatter:off MockHttpSession session = (MockHttpSession) this.mvc.perform(get("/bob")) .andReturn() .getRequest() .getSession(); // @formatter:on this.mvc.perform(formLogin(session)).andExpect(redirectedUrl("/")); } @Test public void getWhenRequestCacheInLambdaThenRedirectedToCachedPage() throws Exception { this.spring.register(RequestCacheInLambdaConfig.class, DefaultSecurityConfig.class).autowire(); // @formatter:off MockHttpSession session = (MockHttpSession) this.mvc.perform(get("/bob")) .andReturn() .getRequest() .getSession(); // @formatter:on this.mvc.perform(formLogin(session)).andExpect(RequestCacheResultMatcher.redirectToCachedRequest()); } @Test public void getWhenCustomRequestCacheInLambdaThenCustomRequestCacheUsed() throws Exception { this.spring.register(CustomRequestCacheInLambdaConfig.class, DefaultSecurityConfig.class).autowire(); // @formatter:off MockHttpSession session = (MockHttpSession) this.mvc.perform(get("/bob")) .andReturn() .getRequest() .getSession(); // @formatter:on this.mvc.perform(formLogin(session)).andExpect(redirectedUrl("/")); } @Test public void getWhenPathPatternFactoryBeanThenFaviconIcoRedirectsToRoot() throws Exception { this.spring .register(RequestCacheDefaultsConfig.class, DefaultSecurityConfig.class, PathPatternFactoryBeanConfig.class) .autowire(); // @formatter:off MockHttpSession session = (MockHttpSession) this.mvc.perform(get("/favicon.ico")) .andExpect(redirectedUrl("/login")) .andReturn() .getRequest() .getSession(); // @formatter:on // ignores favicon.ico this.mvc.perform(formLogin(session)).andExpect(redirectedUrl("/")); } private static RequestBuilder formLogin(MockHttpSession session) { // @formatter:off return post("/login") .param("username", "user") .param("password", "password") .session(session) .with(csrf()); // @formatter:on } @Configuration @EnableWebSecurity static class ObjectPostProcessorConfig { static ObjectPostProcessor objectPostProcessor = spy(ReflectingObjectPostProcessor.class); @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .requestCache(withDefaults()); return http.build(); // @formatter:on } @Bean static ObjectPostProcessor objectPostProcessor() { return objectPostProcessor; } } static class ReflectingObjectPostProcessor implements ObjectPostProcessor { @Override public O postProcess(O object) { return object; } } @Configuration @EnableWebSecurity static class InvokeTwiceDoesNotOverrideConfig { static RequestCache requestCache = mock(RequestCache.class); @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .requestCache((cache) -> cache .requestCache(requestCache)) .requestCache(withDefaults()); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class RequestCacheDefaultsConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((requests) -> requests .anyRequest().authenticated()) .formLogin(withDefaults()); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class RequestCacheDisabledConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((authorize) -> authorize .anyRequest().authenticated() ) .formLogin(Customizer.withDefaults()) .requestCache(RequestCacheConfigurer::disable); // @formatter:on return http.build(); } } @Configuration @EnableWebSecurity static class RequestCacheDisabledInLambdaConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((authorize) -> authorize .anyRequest().authenticated() ) .formLogin(withDefaults()) .requestCache(RequestCacheConfigurer::disable); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class RequestCacheInLambdaConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((authorize) -> authorize .anyRequest().authenticated() ) .formLogin(withDefaults()) .requestCache(withDefaults()); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class CustomRequestCacheInLambdaConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((authorize) -> authorize .anyRequest().authenticated() ) .formLogin(withDefaults()) .requestCache((requestCache) -> requestCache .requestCache(new NullRequestCache()) ); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class DefaultSecurityConfig { @Bean InMemoryUserDetailsManager userDetailsManager() { // @formatter:off return new InMemoryUserDetailsManager(User.withDefaultPasswordEncoder() .username("user") .password("password") .roles("USER") .build() ); // @formatter:on } } @Configuration @EnableWebSecurity static class PathPatternFactoryBeanConfig { } } ================================================ FILE: config/src/test/java/org/springframework/security/config/annotation/web/configurers/RequestMatcherConfigurerTests.java ================================================ /* * 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 org.springframework.security.config.annotation.web.configurers; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.web.SecurityFilterChain; import org.springframework.test.web.servlet.MockMvc; import static org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher.pathPattern; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Tests for {@link HttpSecurity.RequestMatcherConfigurer} * * @author Rob Winch * @author Eleftheria Stein */ @ExtendWith(SpringTestContextExtension.class) public class RequestMatcherConfigurerTests { public final SpringTestContext spring = new SpringTestContext(this); @Autowired MockMvc mvc; // SEC-2908 @Test public void authorizeRequestsWhenInvokedMultipleTimesThenChainsPaths() throws Exception { this.spring.register(Sec2908Config.class).autowire(); // @formatter:off this.mvc.perform(get("/oauth/abc")) .andExpect(status().isForbidden()); this.mvc.perform(get("/api/abc")) .andExpect(status().isForbidden()); // @formatter:on } @Test public void authorizeRequestsWhenInvokedMultipleTimesInLambdaThenChainsPaths() throws Exception { this.spring.register(AuthorizeRequestInLambdaConfig.class).autowire(); // @formatter:off this.mvc.perform(get("/oauth/abc")) .andExpect(status().isForbidden()); this.mvc.perform(get("/api/abc")) .andExpect(status().isForbidden()); // @formatter:on } @Configuration @EnableWebSecurity static class Sec2908Config { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .securityMatchers((security) -> security .requestMatchers(pathPattern("/api/**"))) .securityMatchers((security) -> security .requestMatchers(pathPattern("/oauth/**"))) .authorizeHttpRequests((requests) -> requests .anyRequest().denyAll()); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class AuthorizeRequestInLambdaConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .securityMatchers((secure) -> secure .requestMatchers(pathPattern("/api/**")) ) .securityMatchers((securityMatchers) -> securityMatchers .requestMatchers(pathPattern("/oauth/**")) ) .authorizeHttpRequests((authorize) -> authorize .anyRequest().denyAll() ); return http.build(); // @formatter:on } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/annotation/web/configurers/SecurityContextConfigurerTests.java ================================================ /* * 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 org.springframework.security.config.annotation.web.configurers; import java.util.List; import java.util.stream.Collectors; import jakarta.servlet.Filter; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpSession; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.ObjectPostProcessor; import org.springframework.security.config.TestDeferredSecurityContext; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.TestHttpSecurities; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.userdetails.PasswordEncodedUser; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.web.FilterChainProxy; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.context.HttpRequestResponseHolder; import org.springframework.security.web.context.HttpSessionSecurityContextRepository; import org.springframework.security.web.context.NullSecurityContextRepository; import org.springframework.security.web.context.SecurityContextHolderFilter; import org.springframework.security.web.context.SecurityContextPersistenceFilter; import org.springframework.security.web.context.SecurityContextRepository; import org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.springframework.security.config.Customizer.withDefaults; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; /** * Tests for {@link SecurityContextConfigurer} * * @author Rob Winch * @author Eleftheria Stein */ @ExtendWith(SpringTestContextExtension.class) public class SecurityContextConfigurerTests { public final SpringTestContext spring = new SpringTestContext(this); @Autowired MockMvc mvc; @Test public void configureWhenRegisteringObjectPostProcessorThenInvokedOnSecurityContextPersistenceFilter() { this.spring.register(ObjectPostProcessorConfig.class).autowire(); verify(ObjectPostProcessorConfig.objectPostProcessor).postProcess(any(SecurityContextHolderFilter.class)); } @Test public void securityContextWhenInvokedTwiceThenUsesOriginalSecurityContextRepository() throws Exception { this.spring.register(DuplicateDoesNotOverrideConfig.class).autowire(); given(DuplicateDoesNotOverrideConfig.SCR.loadDeferredContext(any(HttpServletRequest.class))) .willReturn(new TestDeferredSecurityContext(mock(SecurityContext.class), false)); this.mvc.perform(get("/")); verify(DuplicateDoesNotOverrideConfig.SCR).loadDeferredContext(any(HttpServletRequest.class)); } // SEC-2932 @Test public void securityContextWhenSecurityContextRepositoryNotConfiguredThenDoesNotThrowException() throws Exception { this.spring.register(SecurityContextRepositoryDefaultsSecurityContextRepositoryConfig.class).autowire(); this.mvc.perform(get("/")); } @Test public void requestWhenSecurityContextWithDefaultsInLambdaThenSessionIsCreated() throws Exception { this.spring.register(SecurityContextWithDefaultsInLambdaConfig.class).autowire(); MvcResult mvcResult = this.mvc.perform(formLogin()).andReturn(); HttpSession session = mvcResult.getRequest().getSession(false); assertThat(session).isNotNull(); } @Test public void requestWhenSecurityContextDisabledInLambdaThenContextNotSavedInSession() throws Exception { this.spring.register(SecurityContextDisabledInLambdaConfig.class).autowire(); MvcResult mvcResult = this.mvc.perform(formLogin()).andReturn(); HttpSession session = mvcResult.getRequest().getSession(false); assertThat(session).isNull(); } @Test public void requestWhenNullSecurityContextRepositoryInLambdaThenContextNotSavedInSession() throws Exception { this.spring.register(NullSecurityContextRepositoryInLambdaConfig.class).autowire(); MvcResult mvcResult = this.mvc.perform(formLogin()).andReturn(); HttpSession session = mvcResult.getRequest().getSession(false); assertThat(session).isNull(); } @Test public void requireExplicitSave() throws Exception { HttpSessionSecurityContextRepository repository = new HttpSessionSecurityContextRepository(); SpringTestContext testContext = this.spring.register(RequireExplicitSaveConfig.class); testContext.autowire(); FilterChainProxy filterChainProxy = testContext.getContext().getBean(FilterChainProxy.class); // @formatter:off List> filterTypes = filterChainProxy.getFilters("/") .stream() .map(Filter::getClass) .collect(Collectors.toList()); assertThat(filterTypes) .contains(SecurityContextHolderFilter.class) .doesNotContain(SecurityContextPersistenceFilter.class); // @formatter:on MvcResult mvcResult = this.mvc.perform(formLogin()).andReturn(); SecurityContext securityContext = repository .loadContext(new HttpRequestResponseHolder(mvcResult.getRequest(), mvcResult.getResponse())); assertThat(securityContext.getAuthentication()).isNotNull(); } @Configuration(proxyBeanMethods = false) @EnableWebSecurity static class ObjectPostProcessorConfig { static ObjectPostProcessor objectPostProcessor = spy(ReflectingObjectPostProcessor.class); @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .securityContext(withDefaults()); return http.build(); // @formatter:on } @Bean static ObjectPostProcessor objectPostProcessor() { return objectPostProcessor; } } static class ReflectingObjectPostProcessor implements ObjectPostProcessor { @Override public O postProcess(O object) { return object; } } @Configuration @EnableWebSecurity static class DuplicateDoesNotOverrideConfig { static SecurityContextRepository SCR = mock(SecurityContextRepository.class); @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .securityContext((context) -> context .securityContextRepository(SCR)) .securityContext(withDefaults()); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class SecurityContextRepositoryDefaultsSecurityContextRepositoryConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { TestHttpSecurities.disableDefaults(http); // @formatter:off http .addFilter(new WebAsyncManagerIntegrationFilter()) .anonymous(withDefaults()) .securityContext(withDefaults()) .authorizeHttpRequests((requests) -> requests .anyRequest().permitAll()) .httpBasic(withDefaults()); // @formatter:on return http.build(); } @Bean UserDetailsService userDetailsService() { return new InMemoryUserDetailsManager(PasswordEncodedUser.user()); } } @Configuration @EnableWebSecurity static class SecurityContextWithDefaultsInLambdaConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .formLogin(withDefaults()) .securityContext(withDefaults()); // @formatter:on return http.build(); } @Bean UserDetailsService userDetailsService() { return new InMemoryUserDetailsManager(PasswordEncodedUser.user()); } } @Configuration @EnableWebSecurity static class SecurityContextDisabledInLambdaConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .formLogin(withDefaults()) .securityContext(AbstractHttpConfigurer::disable); // @formatter:on return http.build(); } @Bean UserDetailsService userDetailsService() { return new InMemoryUserDetailsManager(PasswordEncodedUser.user()); } } @Configuration @EnableWebSecurity static class NullSecurityContextRepositoryInLambdaConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .formLogin(withDefaults()) .securityContext((securityContext) -> securityContext .securityContextRepository(new NullSecurityContextRepository()) ); // @formatter:on return http.build(); } @Bean UserDetailsService userDetailsService() { return new InMemoryUserDetailsManager(PasswordEncodedUser.user()); } } @Configuration @EnableWebSecurity static class RequireExplicitSaveConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .formLogin(withDefaults()) .securityContext((securityContext) -> securityContext .requireExplicitSave(true) ); // @formatter:on return http.build(); } @Bean UserDetailsService userDetailsService() { return new InMemoryUserDetailsManager(PasswordEncodedUser.user()); } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/annotation/web/configurers/ServletApiConfigurerTests.java ================================================ /* * 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 org.springframework.security.config.annotation.web.configurers; import java.util.List; import jakarta.servlet.Filter; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationTrustResolver; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.authentication.dao.DaoAuthenticationProvider; import org.springframework.security.config.Customizer; import org.springframework.security.config.ObjectPostProcessor; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.userdetails.PasswordEncodedUser; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors; import org.springframework.security.util.FieldUtils; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.security.web.FilterChainProxy; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.logout.CompositeLogoutHandler; import org.springframework.security.web.authentication.logout.LogoutFilter; import org.springframework.security.web.authentication.logout.LogoutHandler; import org.springframework.security.web.authentication.logout.LogoutSuccessEventPublishingLogoutHandler; import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.context.ConfigurableWebApplicationContext; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.springframework.security.config.Customizer.withDefaults; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user; import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Tests for {@link ServletApiConfigurer} * * @author Rob Winch * @author Eleftheria Stein * @author Onur Kagan Ozcan */ @ExtendWith(SpringTestContextExtension.class) public class ServletApiConfigurerTests { public final SpringTestContext spring = new SpringTestContext(this); @Autowired MockMvc mvc; @Test public void configureWhenRegisteringObjectPostProcessorThenInvokedOnSecurityContextHolderAwareRequestFilter() { this.spring.register(ObjectPostProcessorConfig.class).autowire(); verify(ObjectPostProcessorConfig.objectPostProcessor) .postProcess(any(SecurityContextHolderAwareRequestFilter.class)); } // SEC-2215 @Test public void configureWhenUsingDefaultsThenAuthenticationManagerIsNotNull() { this.spring.register(ServletApiConfig.class).autowire(); assertThat(this.spring.getContext().getBean("customAuthenticationManager")).isNotNull(); } @Test public void configureWhenUsingDefaultsThenAuthenticationEntryPointIsLogin() throws Exception { this.spring.register(ServletApiConfig.class).autowire(); this.mvc.perform(formLogin()).andExpect(status().isFound()); } // SEC-2926 @Test public void configureWhenUsingDefaultsThenRolePrefixIsSet() throws Exception { this.spring.register(ServletApiConfig.class, AdminController.class).autowire(); TestingAuthenticationToken user = new TestingAuthenticationToken("user", "pass", "ROLE_ADMIN"); MockHttpServletRequestBuilder request = get("/admin").with(authentication(user)); this.mvc.perform(request).andExpect(status().isOk()); } @Test public void requestWhenCustomAuthenticationEntryPointThenEntryPointUsed() throws Exception { this.spring.register(CustomEntryPointConfig.class).autowire(); this.mvc.perform(get("/")); verify(CustomEntryPointConfig.ENTRYPOINT).commence(any(HttpServletRequest.class), any(HttpServletResponse.class), any(AuthenticationException.class)); } @Test public void servletApiWhenInvokedTwiceThenUsesOriginalRole() throws Exception { this.spring.register(DuplicateInvocationsDoesNotOverrideConfig.class, AdminController.class).autowire(); // @formatter:off MockHttpServletRequestBuilder request = get("/admin") .with(user("user").authorities(AuthorityUtils.createAuthorityList("PERMISSION_ADMIN"))); this.mvc.perform(request) .andExpect(status().isOk()); SecurityMockMvcRequestPostProcessors.UserRequestPostProcessor userWithRoleAdmin = user("user") .authorities(AuthorityUtils.createAuthorityList("ROLE_ADMIN")); MockHttpServletRequestBuilder requestWithRoleAdmin = get("/admin") .with(userWithRoleAdmin); this.mvc.perform(requestWithRoleAdmin) .andExpect(status().isForbidden()); // @formatter:on } @Test public void configureWhenSharedObjectTrustResolverThenTrustResolverUsed() throws Exception { this.spring.register(SharedTrustResolverConfig.class).autowire(); this.mvc.perform(get("/")); verify(SharedTrustResolverConfig.TR, atLeastOnce()).isAnonymous(any()); } @Test public void requestWhenServletApiWithDefaultsInLambdaThenUsesDefaultRolePrefix() throws Exception { this.spring.register(ServletApiWithDefaultsInLambdaConfig.class, AdminController.class).autowire(); MockHttpServletRequestBuilder request = get("/admin") .with(user("user").authorities(AuthorityUtils.createAuthorityList("ROLE_ADMIN"))); this.mvc.perform(request).andExpect(status().isOk()); } @Test public void requestWhenRolePrefixInLambdaThenUsesCustomRolePrefix() throws Exception { this.spring.register(RolePrefixInLambdaConfig.class, AdminController.class).autowire(); // @formatter:off MockHttpServletRequestBuilder requestWithAdminPermission = get("/admin") .with(user("user").authorities(AuthorityUtils.createAuthorityList("PERMISSION_ADMIN"))); this.mvc.perform(requestWithAdminPermission) .andExpect(status().isOk()); MockHttpServletRequestBuilder requestWithAdminRole = get("/admin") .with(user("user").authorities(AuthorityUtils.createAuthorityList("ROLE_ADMIN"))); this.mvc.perform(requestWithAdminRole) .andExpect(status().isForbidden()); // @formatter:on } @Test public void checkSecurityContextAwareAndLogoutFilterHasSameSizeAndHasLogoutSuccessEventPublishingLogoutHandler() { this.spring.register(ServletApiWithLogoutConfig.class); SecurityContextHolderAwareRequestFilter scaFilter = getFilter(SecurityContextHolderAwareRequestFilter.class); LogoutFilter logoutFilter = getFilter(LogoutFilter.class); LogoutHandler lfLogoutHandler = getFieldValue(logoutFilter, "handler"); assertThat(lfLogoutHandler).isInstanceOf(CompositeLogoutHandler.class); List scaLogoutHandlers = getFieldValue(scaFilter, "logoutHandlers"); List lfLogoutHandlers = getFieldValue(lfLogoutHandler, "logoutHandlers"); assertThat(scaLogoutHandlers).hasSameSizeAs(lfLogoutHandlers); assertThat(scaLogoutHandlers).hasAtLeastOneElementOfType(LogoutSuccessEventPublishingLogoutHandler.class); assertThat(lfLogoutHandlers).hasAtLeastOneElementOfType(LogoutSuccessEventPublishingLogoutHandler.class); } @Test public void logoutServletApiWhenCsrfDisabled() throws Exception { ConfigurableWebApplicationContext context = this.spring.register(CsrfDisabledConfig.class).getContext(); MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(context).apply(springSecurity()).build(); MvcResult mvcResult = mockMvc.perform(get("/")).andReturn(); assertThat(mvcResult.getRequest().getSession(false)).isNull(); } private T getFilter(Class filterClass) { return (T) getFilters().stream().filter(filterClass::isInstance).findFirst().orElse(null); } private List getFilters() { FilterChainProxy proxy = this.spring.getContext().getBean(FilterChainProxy.class); return proxy.getFilters("/"); } private T getFieldValue(Object target, String fieldName) { try { return (T) FieldUtils.getFieldValue(target, fieldName); } catch (Exception ex) { throw new RuntimeException(ex); } } @Configuration @EnableWebSecurity static class ObjectPostProcessorConfig { static ObjectPostProcessor objectPostProcessor = spy(ReflectingObjectPostProcessor.class); @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .servletApi(withDefaults()); return http.build(); // @formatter:on } @Bean static ObjectPostProcessor objectPostProcessor() { return objectPostProcessor; } } static class ReflectingObjectPostProcessor implements ObjectPostProcessor { @Override public O postProcess(O object) { return object; } } @Configuration @EnableWebSecurity static class ServletApiConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((authorize) -> authorize .anyRequest().authenticated() ) .httpBasic(Customizer.withDefaults()) .formLogin(Customizer.withDefaults()); // @formatter:on return http.build(); } @Bean UserDetailsService userDetailsService() { return new InMemoryUserDetailsManager(PasswordEncodedUser.user()); } @Bean AuthenticationManager customAuthenticationManager(UserDetailsService userDetailsService) { DaoAuthenticationProvider provider = new DaoAuthenticationProvider(userDetailsService); return provider::authenticate; } } @Configuration @EnableWebSecurity static class CustomEntryPointConfig { static AuthenticationEntryPoint ENTRYPOINT = spy(AuthenticationEntryPoint.class); @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((requests) -> requests .anyRequest().authenticated()) .exceptionHandling((handling) -> handling .authenticationEntryPoint(ENTRYPOINT)) .formLogin(withDefaults()); // @formatter:on return http.build(); } @Bean UserDetailsService userDetailsService() { return new InMemoryUserDetailsManager(PasswordEncodedUser.user()); } } @Configuration @EnableWebSecurity static class DuplicateInvocationsDoesNotOverrideConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .servletApi((api) -> api .rolePrefix("PERMISSION_")) .servletApi(withDefaults()); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class SharedTrustResolverConfig { static AuthenticationTrustResolver TR = spy(AuthenticationTrustResolver.class); @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .setSharedObject(AuthenticationTrustResolver.class, TR); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class ServletApiWithDefaultsInLambdaConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .servletApi(withDefaults()); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class RolePrefixInLambdaConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .servletApi((servletApi) -> servletApi .rolePrefix("PERMISSION_") ); return http.build(); // @formatter:on } } @RestController static class AdminController { @GetMapping("/admin") void admin(HttpServletRequest request) { if (!request.isUserInRole("ADMIN")) { throw new AccessDeniedException("This resource is only available to admins"); } } } @Configuration @EnableWebSecurity static class ServletApiWithLogoutConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .servletApi(withDefaults()) .logout(withDefaults()); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class CsrfDisabledConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .csrf((csrf) -> csrf.disable()); return http.build(); // @formatter:on } @RestController static class LogoutController { @GetMapping("/") String logout(HttpServletRequest request) throws ServletException { request.getSession().setAttribute("foo", "bar"); request.logout(); return "logout"; } } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurerServlet31Tests.java ================================================ /* * 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 org.springframework.security.config.annotation.web.configurers; import jakarta.servlet.Filter; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.mock.web.MockFilterChain; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.core.userdetails.PasswordEncodedUser; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.csrf.CsrfToken; import org.springframework.security.web.csrf.CsrfTokenRequestHandler; import org.springframework.security.web.csrf.DeferredCsrfToken; import org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository; import org.springframework.security.web.csrf.XorCsrfTokenRequestAttributeHandler; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.security.config.Customizer.withDefaults; import static org.springframework.security.web.servlet.TestMockHttpServletRequests.post; /** * @author Rob Winch */ public class SessionManagementConfigurerServlet31Tests { MockHttpServletResponse response; MockFilterChain chain; ConfigurableApplicationContext context; Filter springSecurityFilterChain; @BeforeEach public void setup() { this.response = new MockHttpServletResponse(); this.chain = new MockFilterChain(); } @AfterEach public void teardown() { if (this.context != null) { this.context.close(); } } @Test public void changeSessionIdThenPreserveParameters() throws Exception { MockHttpServletRequest request = post("/login").param("username", "user").param("password", "password").build(); String id = request.getSession().getId(); request.getSession(); HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository(); CsrfTokenRequestHandler handler = new XorCsrfTokenRequestAttributeHandler(); DeferredCsrfToken deferredCsrfToken = repository.loadDeferredToken(request, this.response); handler.handle(request, this.response, deferredCsrfToken); CsrfToken token = (CsrfToken) request.getAttribute(CsrfToken.class.getName()); request.setParameter(token.getParameterName(), token.getToken()); request.getSession().setAttribute("attribute1", "value1"); loadConfig(SessionManagementDefaultSessionFixationServlet31Config.class); this.springSecurityFilterChain.doFilter(request, this.response, this.chain); assertThat(request.getSession().getId()).isNotEqualTo(id); assertThat(request.getSession().getAttribute("attribute1")).isEqualTo("value1"); } private void loadConfig(Class... classes) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); context.register(classes); context.refresh(); this.context = context; this.springSecurityFilterChain = this.context.getBean("springSecurityFilterChain", Filter.class); } @Configuration @EnableWebSecurity static class SessionManagementDefaultSessionFixationServlet31Config { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .formLogin(withDefaults()) .sessionManagement(withDefaults()); // @formatter:on return http.build(); } @Bean UserDetailsService userDetailsService() { return new InMemoryUserDetailsManager(PasswordEncodedUser.user()); } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurerSessionAuthenticationStrategyTests.java ================================================ /* * 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 org.springframework.security.config.annotation.web.configurers; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.core.Authentication; import org.springframework.security.core.userdetails.PasswordEncodedUser; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy; import org.springframework.test.web.servlet.MockMvc; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.springframework.security.config.Customizer.withDefaults; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin; /** * @author Joe Grandja */ @ExtendWith(SpringTestContextExtension.class) public class SessionManagementConfigurerSessionAuthenticationStrategyTests { @Autowired private MockMvc mvc; public final SpringTestContext spring = new SpringTestContext(this); // gh-5763 @Test public void requestWhenCustomSessionAuthenticationStrategyProvidedThenCalled() throws Exception { this.spring.register(CustomSessionAuthenticationStrategyConfig.class).autowire(); this.mvc.perform(formLogin().user("user").password("password")); verify(CustomSessionAuthenticationStrategyConfig.customSessionAuthenticationStrategy) .onAuthentication(any(Authentication.class), any(HttpServletRequest.class), any(HttpServletResponse.class)); } @Configuration @EnableWebSecurity static class CustomSessionAuthenticationStrategyConfig { static SessionAuthenticationStrategy customSessionAuthenticationStrategy = mock( SessionAuthenticationStrategy.class); @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .formLogin(withDefaults()) .sessionManagement((management) -> management .sessionAuthenticationStrategy(customSessionAuthenticationStrategy)); // @formatter:on return http.build(); } @Bean UserDetailsService userDetailsService() { return new InMemoryUserDetailsManager(PasswordEncodedUser.user()); } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurerSessionCreationPolicyTests.java ================================================ /* * 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 org.springframework.security.config.annotation.web.configurers; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.web.SecurityFilterChain; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * @author Josh Cummings */ @ExtendWith(SpringTestContextExtension.class) public class SessionManagementConfigurerSessionCreationPolicyTests { @Autowired MockMvc mvc; public final SpringTestContext spring = new SpringTestContext(this); @Test public void getWhenSharedObjectSessionCreationPolicyConfigurationThenOverrides() throws Exception { this.spring.register(StatelessCreateSessionSharedObjectConfig.class).autowire(); MvcResult result = this.mvc.perform(get("/")).andReturn(); assertThat(result.getRequest().getSession(false)).isNull(); } @Test public void getWhenUserSessionCreationPolicyConfigurationThenOverrides() throws Exception { this.spring.register(StatelessCreateSessionUserConfig.class).autowire(); MvcResult result = this.mvc.perform(get("/")).andReturn(); assertThat(result.getRequest().getSession(false)).isNull(); } @Test public void getWhenDefaultsThenLoginChallengeCreatesSession() throws Exception { this.spring.register(DefaultConfig.class, BasicController.class).autowire(); // @formatter:off MvcResult result = this.mvc.perform(get("/")) .andExpect(status().isUnauthorized()) .andReturn(); // @formatter:on assertThat(result.getRequest().getSession(false)).isNotNull(); } @Configuration @EnableWebSecurity static class StatelessCreateSessionSharedObjectConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http.setSharedObject(SessionCreationPolicy.class, SessionCreationPolicy.STATELESS); return http.build(); } } @Configuration @EnableWebSecurity static class StatelessCreateSessionUserConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .sessionManagement((management) -> management.sessionCreationPolicy(SessionCreationPolicy.STATELESS)); // @formatter:on http.setSharedObject(SessionCreationPolicy.class, SessionCreationPolicy.ALWAYS); return http.build(); } } @Configuration @EnableWebSecurity static class DefaultConfig { } @RestController static class BasicController { @GetMapping("/") String root() { return "ok"; } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurerTests.java ================================================ /* * 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 org.springframework.security.config.annotation.web.configurers; import java.io.IOException; import jakarta.servlet.DispatcherType; import jakarta.servlet.Filter; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpSession; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Answers; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.mock.web.MockFilterChain; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpSession; import org.springframework.security.authentication.AuthenticationTrustResolver; import org.springframework.security.config.Customizer; import org.springframework.security.config.ObjectPostProcessor; import org.springframework.security.config.TestDeferredSecurityContext; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.session.SessionRegistry; import org.springframework.security.core.userdetails.PasswordEncodedUser; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.web.DefaultSecurityFilterChain; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.session.ChangeSessionIdAuthenticationStrategy; import org.springframework.security.web.authentication.session.CompositeSessionAuthenticationStrategy; import org.springframework.security.web.authentication.session.ConcurrentSessionControlAuthenticationStrategy; import org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy; import org.springframework.security.web.authentication.session.SessionLimit; import org.springframework.security.web.context.RequestAttributeSecurityContextRepository; import org.springframework.security.web.context.SecurityContextRepository; import org.springframework.security.web.savedrequest.RequestCache; import org.springframework.security.web.session.ConcurrentSessionFilter; import org.springframework.security.web.session.HttpSessionDestroyedEvent; import org.springframework.security.web.session.SessionManagementFilter; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.util.WebUtils; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.withSettings; import static org.springframework.security.config.Customizer.withDefaults; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic; import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Tests for {@link SessionManagementConfigurer} * * @author Rob Winch * @author Eleftheria Stein */ @ExtendWith(SpringTestContextExtension.class) public class SessionManagementConfigurerTests { public final SpringTestContext spring = new SpringTestContext(this); @Autowired MockMvc mvc; @Test public void sessionManagementWhenConfiguredThenDoesNotOverrideRequestCache() throws Exception { SessionManagementRequestCacheConfig.REQUEST_CACHE = mock(RequestCache.class); this.spring.register(SessionManagementRequestCacheConfig.class).autowire(); this.mvc.perform(get("/")); verify(SessionManagementRequestCacheConfig.REQUEST_CACHE).getMatchingRequest(any(HttpServletRequest.class), any(HttpServletResponse.class)); } @Test public void sessionManagementWhenConfiguredThenDoesNotOverrideSecurityContextRepository() throws Exception { SessionManagementSecurityContextRepositoryConfig.SECURITY_CONTEXT_REPO = mock(SecurityContextRepository.class); given(SessionManagementSecurityContextRepositoryConfig.SECURITY_CONTEXT_REPO .loadDeferredContext(any(HttpServletRequest.class))) .willReturn(new TestDeferredSecurityContext(mock(SecurityContext.class), false)); this.spring.register(SessionManagementSecurityContextRepositoryConfig.class).autowire(); this.mvc.perform(get("/")); } @Test public void sessionManagementWhenSecurityContextRepositoryIsConfiguredThenUseIt() throws Exception { SessionManagementSecurityContextRepositoryConfig.SECURITY_CONTEXT_REPO = mock(SecurityContextRepository.class); given(SessionManagementSecurityContextRepositoryConfig.SECURITY_CONTEXT_REPO .loadDeferredContext(any(HttpServletRequest.class))) .willReturn(new TestDeferredSecurityContext(mock(SecurityContext.class), false)); this.spring.register(SessionManagementSecurityContextRepositoryConfig.class).autowire(); this.mvc.perform(get("/")); verify(SessionManagementSecurityContextRepositoryConfig.SECURITY_CONTEXT_REPO) .containsContext(any(HttpServletRequest.class)); } @Test public void sessionManagementWhenInvokedTwiceThenUsesOriginalSessionCreationPolicy() throws Exception { this.spring.register(InvokeTwiceDoesNotOverride.class).autowire(); MvcResult mvcResult = this.mvc.perform(get("/")).andReturn(); HttpSession session = mvcResult.getRequest().getSession(false); assertThat(session).isNull(); } // SEC-2137 @Test public void getWhenSessionFixationDisabledAndConcurrencyControlEnabledThenSessionIsNotInvalidated() throws Exception { this.spring.register(DisableSessionFixationEnableConcurrencyControlConfig.class).autowire(); MockHttpSession session = new MockHttpSession(); String sessionId = session.getId(); // @formatter:off MockHttpServletRequestBuilder request = get("/") .with(httpBasic("user", "password")) .session(session); MvcResult mvcResult = this.mvc.perform(request) .andExpect(status().isNotFound()) .andReturn(); // @formatter:on assertThat(mvcResult.getRequest().getSession().getId()).isEqualTo(sessionId); } @Test public void authenticateWhenNewSessionFixationProtectionInLambdaThenCreatesNewSession() throws Exception { this.spring.register(SFPNewSessionInLambdaConfig.class).autowire(); MockHttpSession givenSession = new MockHttpSession(); String givenSessionId = givenSession.getId(); givenSession.setAttribute("name", "value"); // @formatter:off MockHttpServletRequestBuilder request = get("/auth") .session(givenSession) .with(httpBasic("user", "password")); MockHttpSession resultingSession = (MockHttpSession) this.mvc.perform(request) .andExpect(status().isNotFound()) .andReturn() .getRequest() .getSession(false); // @formatter:on assertThat(givenSessionId).isNotEqualTo(resultingSession.getId()); assertThat(resultingSession.getAttribute("name")).isNull(); } @Test public void loginWhenUserLoggedInAndMaxSessionsIsOneThenLoginPrevented() throws Exception { this.spring.register(ConcurrencyControlConfig.class).autowire(); // @formatter:off MockHttpServletRequestBuilder firstRequest = post("/login") .with(csrf()) .param("username", "user") .param("password", "password"); this.mvc.perform(firstRequest); MockHttpServletRequestBuilder secondRequest = post("/login") .with(csrf()) .param("username", "user") .param("password", "password"); this.mvc.perform(secondRequest) .andExpect(status().isFound()) .andExpect(redirectedUrl("/login?error")); // @formatter:on } @Test public void loginWhenUserSessionExpiredAndMaxSessionsIsOneThenLoggedIn() throws Exception { this.spring.register(ConcurrencyControlConfig.class).autowire(); // @formatter:off MockHttpServletRequestBuilder firstRequest = post("/login") .with(csrf()) .param("username", "user") .param("password", "password"); MvcResult mvcResult = this.mvc.perform(firstRequest) .andReturn(); // @formatter:on HttpSession authenticatedSession = mvcResult.getRequest().getSession(); this.spring.getContext().publishEvent(new HttpSessionDestroyedEvent(authenticatedSession)); // @formatter:off MockHttpServletRequestBuilder secondRequest = post("/login") .with(csrf()) .param("username", "user") .param("password", "password"); this.mvc.perform(secondRequest) .andExpect(status().isFound()) .andExpect(redirectedUrl("/")); // @formatter:on } @Test public void loginWhenUserLoggedInAndMaxSessionsOneInLambdaThenLoginPrevented() throws Exception { this.spring.register(ConcurrencyControlInLambdaConfig.class).autowire(); // @formatter:off MockHttpServletRequestBuilder firstRequest = post("/login") .with(csrf()) .param("username", "user") .param("password", "password"); // @formatter:on this.mvc.perform(firstRequest); // @formatter:off MockHttpServletRequestBuilder secondRequest = post("/login") .with(csrf()) .param("username", "user") .param("password", "password"); this.mvc.perform(secondRequest) .andExpect(status().isFound()) .andExpect(redirectedUrl("/login?error")); // @formatter:on } @Test public void loginWhenAdminUserLoggedInAndSessionLimitIsConfiguredThenLoginSuccessfully() throws Exception { this.spring.register(ConcurrencyControlWithSessionLimitConfig.class).autowire(); // @formatter:off MockHttpServletRequestBuilder requestBuilder = post("/login") .with(csrf()) .param("username", "admin") .param("password", "password"); HttpSession firstSession = this.mvc.perform(requestBuilder) .andExpect(status().is3xxRedirection()) .andExpect(redirectedUrl("/")) .andReturn() .getRequest() .getSession(false); assertThat(firstSession).isNotNull(); HttpSession secondSession = this.mvc.perform(requestBuilder) .andExpect(status().is3xxRedirection()) .andExpect(redirectedUrl("/")) .andReturn() .getRequest() .getSession(false); assertThat(secondSession).isNotNull(); // @formatter:on assertThat(firstSession.getId()).isNotEqualTo(secondSession.getId()); } @Test public void loginWhenAdminUserLoggedInAndSessionLimitIsConfiguredThenLoginPrevented() throws Exception { this.spring.register(ConcurrencyControlWithSessionLimitConfig.class).autowire(); // @formatter:off MockHttpServletRequestBuilder requestBuilder = post("/login") .with(csrf()) .param("username", "admin") .param("password", "password"); HttpSession firstSession = this.mvc.perform(requestBuilder) .andExpect(status().is3xxRedirection()) .andExpect(redirectedUrl("/")) .andReturn() .getRequest() .getSession(false); assertThat(firstSession).isNotNull(); HttpSession secondSession = this.mvc.perform(requestBuilder) .andExpect(status().is3xxRedirection()) .andExpect(redirectedUrl("/")) .andReturn() .getRequest() .getSession(false); assertThat(secondSession).isNotNull(); assertThat(firstSession.getId()).isNotEqualTo(secondSession.getId()); this.mvc.perform(requestBuilder) .andExpect(status().isFound()) .andExpect(redirectedUrl("/login?error")); // @formatter:on } @Test public void loginWhenUserLoggedInAndSessionLimitIsConfiguredThenLoginPrevented() throws Exception { this.spring.register(ConcurrencyControlWithSessionLimitConfig.class).autowire(); // @formatter:off MockHttpServletRequestBuilder requestBuilder = post("/login") .with(csrf()) .param("username", "user") .param("password", "password"); HttpSession firstSession = this.mvc.perform(requestBuilder) .andExpect(status().is3xxRedirection()) .andExpect(redirectedUrl("/")) .andReturn() .getRequest() .getSession(false); assertThat(firstSession).isNotNull(); this.mvc.perform(requestBuilder) .andExpect(status().isFound()) .andExpect(redirectedUrl("/login?error")); // @formatter:on } @Test public void requestWhenSessionCreationPolicyStateLessInLambdaThenNoSessionCreated() throws Exception { this.spring.register(SessionCreationPolicyStateLessInLambdaConfig.class).autowire(); MvcResult mvcResult = this.mvc.perform(get("/")).andReturn(); HttpSession session = mvcResult.getRequest().getSession(false); assertThat(session).isNull(); } @Test public void configureWhenRegisteringObjectPostProcessorThenInvokedOnSessionManagementFilter() { ObjectPostProcessorConfig.objectPostProcessor = spy(ReflectingObjectPostProcessor.class); this.spring.register(ObjectPostProcessorConfig.class).autowire(); verify(ObjectPostProcessorConfig.objectPostProcessor).postProcess(any(SessionManagementFilter.class)); } @Test public void configureWhenRegisteringObjectPostProcessorThenInvokedOnConcurrentSessionFilter() { ObjectPostProcessorConfig.objectPostProcessor = spy(ReflectingObjectPostProcessor.class); this.spring.register(ObjectPostProcessorConfig.class).autowire(); verify(ObjectPostProcessorConfig.objectPostProcessor).postProcess(any(ConcurrentSessionFilter.class)); } @Test public void configureWhenRegisteringObjectPostProcessorThenInvokedOnConcurrentSessionControlAuthenticationStrategy() { ObjectPostProcessorConfig.objectPostProcessor = spy(ReflectingObjectPostProcessor.class); this.spring.register(ObjectPostProcessorConfig.class).autowire(); verify(ObjectPostProcessorConfig.objectPostProcessor) .postProcess(any(ConcurrentSessionControlAuthenticationStrategy.class)); } @Test public void configureWhenRegisteringObjectPostProcessorThenInvokedOnCompositeSessionAuthenticationStrategy() { ObjectPostProcessorConfig.objectPostProcessor = spy(ReflectingObjectPostProcessor.class); this.spring.register(ObjectPostProcessorConfig.class).autowire(); verify(ObjectPostProcessorConfig.objectPostProcessor) .postProcess(any(CompositeSessionAuthenticationStrategy.class)); } @Test public void configureWhenRegisteringObjectPostProcessorThenInvokedOnRegisterSessionAuthenticationStrategy() { ObjectPostProcessorConfig.objectPostProcessor = spy(ReflectingObjectPostProcessor.class); this.spring.register(ObjectPostProcessorConfig.class).autowire(); verify(ObjectPostProcessorConfig.objectPostProcessor) .postProcess(any(RegisterSessionAuthenticationStrategy.class)); } @Test public void configureWhenRegisteringObjectPostProcessorThenInvokedOnChangeSessionIdAuthenticationStrategy() { ObjectPostProcessorConfig.objectPostProcessor = spy(ReflectingObjectPostProcessor.class); this.spring.register(ObjectPostProcessorConfig.class).autowire(); verify(ObjectPostProcessorConfig.objectPostProcessor) .postProcess(any(ChangeSessionIdAuthenticationStrategy.class)); } @Test public void getWhenAnonymousRequestAndTrustResolverSharedObjectReturnsAnonymousFalseThenSessionIsSaved() throws Exception { SharedTrustResolverConfig.TR = mock(AuthenticationTrustResolver.class, withSettings().defaultAnswer(Answers.CALLS_REAL_METHODS)); given(SharedTrustResolverConfig.TR.isAnonymous(any())).willReturn(false); this.spring.register(SharedTrustResolverConfig.class).autowire(); MvcResult mvcResult = this.mvc.perform(get("/")).andReturn(); assertThat(mvcResult.getRequest().getSession(false)).isNotNull(); } @Test public void whenOneSessionRegistryBeanThenUseIt() throws Exception { SessionRegistryOneBeanConfig.SESSION_REGISTRY = mock(SessionRegistry.class); this.spring.register(SessionRegistryOneBeanConfig.class).autowire(); MockHttpSession session = new MockHttpSession(this.spring.getContext().getServletContext()); this.mvc.perform(get("/").session(session)); verify(SessionRegistryOneBeanConfig.SESSION_REGISTRY).getSessionInformation(session.getId()); } @Test public void whenTwoSessionRegistryBeansThenUseNeither() throws Exception { SessionRegistryTwoBeansConfig.SESSION_REGISTRY_ONE = mock(SessionRegistry.class); SessionRegistryTwoBeansConfig.SESSION_REGISTRY_TWO = mock(SessionRegistry.class); this.spring.register(SessionRegistryTwoBeansConfig.class).autowire(); MockHttpSession session = new MockHttpSession(this.spring.getContext().getServletContext()); this.mvc.perform(get("/").session(session)); verifyNoInteractions(SessionRegistryTwoBeansConfig.SESSION_REGISTRY_ONE); verifyNoInteractions(SessionRegistryTwoBeansConfig.SESSION_REGISTRY_TWO); } @Test public void whenEnableSessionUrlRewritingTrueThenEncodeNotInvoked() throws Exception { this.spring.register(EnableUrlRewriteConfig.class).autowire(); // @formatter:off this.mvc = MockMvcBuilders.webAppContextSetup(this.spring.getContext()) .addFilters((request, response, chain) -> { HttpServletResponse responseToSpy = spy((HttpServletResponse) response); chain.doFilter(request, responseToSpy); verify(responseToSpy, atLeastOnce()).encodeRedirectURL(any()); verify(responseToSpy, atLeastOnce()).encodeURL(any()); }) .apply(springSecurity()) .build(); // @formatter:on this.mvc.perform(get("/")).andExpect(content().string("encoded")); } @Test public void whenDefaultThenEncodeNotInvoked() throws Exception { this.spring.register(DefaultUrlRewriteConfig.class).autowire(); // @formatter:off this.mvc = MockMvcBuilders.webAppContextSetup(this.spring.getContext()) .addFilters((request, response, chain) -> { HttpServletResponse responseToSpy = spy((HttpServletResponse) response); chain.doFilter(request, responseToSpy); verify(responseToSpy, never()).encodeRedirectURL(any()); verify(responseToSpy, never()).encodeURL(any()); }) .apply(springSecurity()) .build(); // @formatter:on this.mvc.perform(get("/")).andExpect(content().string("encoded")); } @Test public void loginWhenSessionCreationPolicyStatelessThenSecurityContextIsAvailableInRequestAttributes() throws Exception { this.spring.register(HttpBasicSessionCreationPolicyStatelessConfig.class).autowire(); // @formatter:off MvcResult mvcResult = this.mvc.perform(get("/").with(httpBasic("user", "password"))) .andExpect(status().isOk()) .andReturn(); // @formatter:on HttpSession session = mvcResult.getRequest().getSession(false); assertThat(session).isNull(); SecurityContext securityContext = (SecurityContext) mvcResult.getRequest() .getAttribute(RequestAttributeSecurityContextRepository.DEFAULT_REQUEST_ATTR_NAME); assertThat(securityContext).isNotNull(); } /** * This ensures that if an ErrorDispatch occurs, then the SecurityContextRepository * defaulted by SessionManagementConfigurer is correct (looks at both Session and * Request Attributes). * @throws Exception */ @Test public void gh12070WhenErrorDispatchSecurityContextRepositoryWorks() throws Exception { Filter errorDispatchFilter = new Filter() { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { try { chain.doFilter(request, response); } catch (ServletException ex) { if (request.getDispatcherType() == DispatcherType.ERROR) { throw ex; } MockHttpServletRequest httpRequest = WebUtils.getNativeRequest(request, MockHttpServletRequest.class); httpRequest.setDispatcherType(DispatcherType.ERROR); // necessary to prevent HttpBasicFilter from invoking again httpRequest.setAttribute(WebUtils.ERROR_REQUEST_URI_ATTRIBUTE, "/error"); httpRequest.setRequestURI("/error"); MockFilterChain mockChain = (MockFilterChain) chain; mockChain.reset(); mockChain.doFilter(httpRequest, response); } } }; this.spring.addFilter(errorDispatchFilter).register(Gh12070IssueConfig.class).autowire(); // @formatter:off this.mvc.perform(get("/500").with(httpBasic("user", "password"))) .andExpect(status().isInternalServerError()); // @formatter:on } @Configuration @EnableWebSecurity static class Gh12070IssueConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((authorize) -> authorize .anyRequest().authenticated() ) .httpBasic(Customizer.withDefaults()) .formLogin(Customizer.withDefaults()); return http.build(); // @formatter:on } @Bean UserDetailsService userDetailsService() { return new InMemoryUserDetailsManager(PasswordEncodedUser.user()); } @RestController static class ErrorController { @GetMapping("/500") String error() throws ServletException { throw new ServletException("Error"); } @GetMapping("/error") ResponseEntity errorHandler() { return new ResponseEntity<>("error", HttpStatus.INTERNAL_SERVER_ERROR); } } } @Configuration @EnableWebSecurity static class SessionManagementRequestCacheConfig { static RequestCache REQUEST_CACHE; @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .requestCache((cache) -> cache .requestCache(REQUEST_CACHE)) .sessionManagement((management) -> management .sessionCreationPolicy(SessionCreationPolicy.STATELESS)); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class SessionManagementSecurityContextRepositoryConfig { static SecurityContextRepository SECURITY_CONTEXT_REPO; @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .securityContext((context) -> context .securityContextRepository(SECURITY_CONTEXT_REPO)) .sessionManagement((management) -> management .sessionCreationPolicy(SessionCreationPolicy.STATELESS)); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class InvokeTwiceDoesNotOverride { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .sessionManagement((management) -> management .sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .sessionManagement(withDefaults()); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class DisableSessionFixationEnableConcurrencyControlConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .httpBasic(withDefaults()) .sessionManagement((management) -> management .sessionFixation().none() .maximumSessions(1)); // @formatter:on return http.build(); } @Bean UserDetailsService userDetailsService() { return new InMemoryUserDetailsManager(PasswordEncodedUser.user()); } } @Configuration @EnableWebSecurity static class SFPNewSessionInLambdaConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .sessionManagement((sessionManagement) -> sessionManagement .requireExplicitAuthenticationStrategy(false) .sessionFixation(SessionManagementConfigurer.SessionFixationConfigurer::newSession) ) .httpBasic(withDefaults()); // @formatter:on return http.build(); } @Bean UserDetailsService userDetailsService() { return new InMemoryUserDetailsManager(PasswordEncodedUser.user()); } } @Configuration @EnableWebSecurity static class ConcurrencyControlConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .formLogin(withDefaults()) .sessionManagement((management) -> management .maximumSessions(1) .maxSessionsPreventsLogin(true)); // @formatter:on return http.build(); } @Bean UserDetailsService userDetailsService() { return new InMemoryUserDetailsManager(PasswordEncodedUser.user()); } } @Configuration @EnableWebSecurity static class ConcurrencyControlInLambdaConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .formLogin(withDefaults()) .sessionManagement((sessionManagement) -> sessionManagement .sessionConcurrency((sessionConcurrency) -> sessionConcurrency .maximumSessions(1) .maxSessionsPreventsLogin(true) ) ); // @formatter:on return http.build(); } @Bean UserDetailsService userDetailsService() { return new InMemoryUserDetailsManager(PasswordEncodedUser.user()); } } @Configuration @EnableWebSecurity static class ConcurrencyControlWithSessionLimitConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http, SessionLimit sessionLimit) throws Exception { // @formatter:off http .formLogin(withDefaults()) .sessionManagement((sessionManagement) -> sessionManagement .sessionConcurrency((sessionConcurrency) -> sessionConcurrency .maximumSessions(sessionLimit) .maxSessionsPreventsLogin(true) ) ); // @formatter:on return http.build(); } @Bean UserDetailsService userDetailsService() { return new InMemoryUserDetailsManager(PasswordEncodedUser.admin(), PasswordEncodedUser.user()); } @Bean SessionLimit SessionLimit() { return (authentication) -> { if ("admin".equals(authentication.getName())) { return 2; } return 1; }; } } @Configuration @EnableWebSecurity static class SessionCreationPolicyStateLessInLambdaConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .sessionManagement((sessionManagement) -> sessionManagement .sessionCreationPolicy(SessionCreationPolicy.STATELESS) ); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class ObjectPostProcessorConfig { static ObjectPostProcessor objectPostProcessor; @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .sessionManagement((management) -> management .maximumSessions(1)); return http.build(); // @formatter:on } @Bean static ObjectPostProcessor objectPostProcessor() { return objectPostProcessor; } } static class ReflectingObjectPostProcessor implements ObjectPostProcessor { @Override public O postProcess(O object) { return object; } } @Configuration @EnableWebSecurity static class SharedTrustResolverConfig { static AuthenticationTrustResolver TR; @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .sessionManagement((sessions) -> sessions .requireExplicitAuthenticationStrategy(false) ) .setSharedObject(AuthenticationTrustResolver.class, TR); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class SessionRegistryOneBeanConfig { private static SessionRegistry SESSION_REGISTRY; @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .sessionManagement((management) -> management .maximumSessions(1)); return http.build(); // @formatter:on } @Bean SessionRegistry sessionRegistry() { return SESSION_REGISTRY; } } @Configuration @EnableWebSecurity static class SessionRegistryTwoBeansConfig { private static SessionRegistry SESSION_REGISTRY_ONE; private static SessionRegistry SESSION_REGISTRY_TWO; @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .sessionManagement((management) -> management .maximumSessions(1)); return http.build(); // @formatter:on } @Bean SessionRegistry sessionRegistryOne() { return SESSION_REGISTRY_ONE; } @Bean SessionRegistry sessionRegistryTwo() { return SESSION_REGISTRY_TWO; } } @Configuration @EnableWebSecurity static class DefaultUrlRewriteConfig { @Bean DefaultSecurityFilterChain configure(HttpSecurity http) throws Exception { return http.build(); } @Bean EncodesUrls encodesUrls() { return new EncodesUrls(); } } @Configuration @EnableWebSecurity static class EnableUrlRewriteConfig { @Bean DefaultSecurityFilterChain configure(HttpSecurity http) throws Exception { http.sessionManagement((sessions) -> sessions.enableSessionUrlRewriting(true)); return http.build(); } @Bean EncodesUrls encodesUrls() { return new EncodesUrls(); } } @Configuration @EnableWebSecurity static class HttpBasicSessionCreationPolicyStatelessConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .sessionManagement((sessionManagement) -> sessionManagement .sessionCreationPolicy(SessionCreationPolicy.STATELESS) ) .httpBasic(withDefaults()); // @formatter:on return http.build(); } @Bean UserDetailsService userDetailsService() { return new InMemoryUserDetailsManager(PasswordEncodedUser.user()); } @Bean EncodesUrls encodesUrls() { return new EncodesUrls(); } } @RestController static class EncodesUrls { @RequestMapping("/") String encoded(HttpServletResponse response) { response.encodeURL("/foo"); response.encodeRedirectURL("/foo"); return "encoded"; } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurerTransientAuthenticationTests.java ================================================ /* * 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 org.springframework.security.config.annotation.web.configurers; import java.util.Collection; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.Transient; import org.springframework.security.web.SecurityFilterChain; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; /** * @author Josh Cummings */ @ExtendWith(SpringTestContextExtension.class) public class SessionManagementConfigurerTransientAuthenticationTests { @Autowired MockMvc mvc; public final SpringTestContext spring = new SpringTestContext(this); @Test public void postWhenTransientAuthenticationThenNoSessionCreated() throws Exception { this.spring.register(WithTransientAuthenticationConfig.class).autowire(); MvcResult result = this.mvc.perform(post("/login")).andReturn(); assertThat(result.getRequest().getSession(false)).isNull(); } @Test public void postWhenTransientAuthenticationThenAlwaysSessionOverrides() throws Exception { this.spring.register(AlwaysCreateSessionConfig.class).autowire(); MvcResult result = this.mvc.perform(post("/login")).andReturn(); assertThat(result.getRequest().getSession(false)).isNotNull(); } @Configuration @EnableWebSecurity static class WithTransientAuthenticationConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .csrf((csrf) -> csrf.disable()) .authenticationProvider(new TransientAuthenticationProvider()); // @formatter:on return http.build(); } } @Configuration @EnableWebSecurity static class AlwaysCreateSessionConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .sessionManagement((management) -> management.sessionCreationPolicy(SessionCreationPolicy.ALWAYS)); return http.build(); // @formatter:on } } static class TransientAuthenticationProvider implements AuthenticationProvider { @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { return new SomeTransientAuthentication(); } @Override public boolean supports(Class authentication) { return true; } } @Transient static class SomeTransientAuthentication extends AbstractAuthenticationToken { SomeTransientAuthentication() { super((Collection) null); } @Override public Object getCredentials() { return null; } @Override public Object getPrincipal() { return null; } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/annotation/web/configurers/UrlAuthorizationsTests.java ================================================ /* * 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 org.springframework.security.config.annotation.web.configurers; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners; import org.springframework.security.test.context.support.WithMockUser; import org.springframework.security.web.SecurityFilterChain; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.servlet.MockMvc; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * @author Rob Winch * @author Josh Cummings * */ @ExtendWith({ SpringExtension.class, SpringTestContextExtension.class }) @SecurityTestExecutionListeners public class UrlAuthorizationsTests { public final SpringTestContext spring = new SpringTestContext(this); @Autowired MockMvc mvc; @Test @WithMockUser(authorities = "ROLE_USER") public void hasAnyAuthorityWhenAuthoritySpecifiedThenMatchesAuthority() throws Exception { this.spring.register(RoleConfig.class).autowire(); // @formatter:off this.mvc.perform(get("/role-user-authority")) .andExpect(status().isNotFound()); this.mvc.perform(get("/role-user")) .andExpect(status().isNotFound()); this.mvc.perform(get("/role-admin-authority")) .andExpect(status().isForbidden()); // @formatter:on } @Test @WithMockUser(authorities = "ROLE_ADMIN") public void hasAnyAuthorityWhenAuthoritiesSpecifiedThenMatchesAuthority() throws Exception { this.spring.register(RoleConfig.class).autowire(); this.mvc.perform(get("/role-user-admin-authority")).andExpect(status().isNotFound()); this.mvc.perform(get("/role-user-admin")).andExpect(status().isNotFound()); this.mvc.perform(get("/role-user-authority")).andExpect(status().isForbidden()); } @Test @WithMockUser(roles = "USER") public void hasAnyRoleWhenRoleSpecifiedThenMatchesRole() throws Exception { this.spring.register(RoleConfig.class).autowire(); // @formatter:off this.mvc.perform(get("/role-user")) .andExpect(status().isNotFound()); this.mvc.perform(get("/role-admin")) .andExpect(status().isForbidden()); // @formatter:on } @Test @WithMockUser(roles = "ADMIN") public void hasAnyRoleWhenRolesSpecifiedThenMatchesRole() throws Exception { this.spring.register(RoleConfig.class).autowire(); this.mvc.perform(get("/role-admin-user")).andExpect(status().isForbidden()); this.mvc.perform(get("/role-user")).andExpect(status().isForbidden()); } @Test @WithMockUser(authorities = "USER") public void hasAnyRoleWhenRoleSpecifiedThenDoesNotMatchAuthority() throws Exception { this.spring.register(RoleConfig.class).autowire(); // @formatter:off this.mvc.perform(get("/role-user")) .andExpect(status().isForbidden()); this.mvc.perform(get("/role-admin")) .andExpect(status().isForbidden()); // @formatter:on } @Configuration @EnableWebSecurity @EnableWebMvc static class RoleConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((requests) -> requests .requestMatchers("/role-user-authority").hasAnyAuthority("ROLE_USER") .requestMatchers("/role-admin-authority").hasAnyAuthority("ROLE_ADMIN") .requestMatchers("/role-user-admin-authority").hasAnyAuthority("ROLE_USER", "ROLE_ADMIN") .requestMatchers("/role-user").hasAnyRole("USER") .requestMatchers("/role-admin").hasAnyRole("ADMIN") .requestMatchers("/role-user-admin").hasAnyRole("USER", "ADMIN")); return http.build(); // @formatter:on } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/annotation/web/configurers/WebAuthnConfigurerTests.java ================================================ /* * 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 org.springframework.security.config.annotation.web.configurers; import java.nio.charset.StandardCharsets; import java.util.List; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpOutputMessage; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextImpl; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.web.FilterChainProxy; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.ui.DefaultResourcesFilter; import org.springframework.security.web.webauthn.api.PublicKeyCredentialCreationOptions; import org.springframework.security.web.webauthn.api.TestPublicKeyCredentialCreationOptions; import org.springframework.security.web.webauthn.authentication.WebAuthnAuthenticationFilter; import org.springframework.security.web.webauthn.management.WebAuthnRelyingPartyOperations; import org.springframework.security.web.webauthn.registration.HttpSessionPublicKeyCredentialCreationOptionsRepository; import org.springframework.test.web.servlet.MockMvc; import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.Matchers.containsString; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.willAnswer; import static org.mockito.Mockito.mock; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.request; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * @author Daniel Garnier-Moiroux */ @ExtendWith(SpringTestContextExtension.class) public class WebAuthnConfigurerTests { public final SpringTestContext spring = new SpringTestContext(this); @Autowired MockMvc mvc; @Test public void webauthnWhenConfiguredConfiguredThenServesJavascript() throws Exception { this.spring.register(DefaultWebauthnConfiguration.class).autowire(); this.mvc.perform(get("/login/webauthn.js")) .andExpect(status().isOk()) .andExpect(header().string("content-type", "text/javascript;charset=UTF-8")) .andExpect(content().string(containsString("async function authenticate("))); } @Test public void webauthnWhenConfiguredConfiguredThenServesCss() throws Exception { this.spring.register(DefaultWebauthnConfiguration.class).autowire(); this.mvc.perform(get("/default-ui.css")) .andExpect(status().isOk()) .andExpect(header().string("content-type", "text/css;charset=UTF-8")) .andExpect(content().string(containsString("body {"))); } // gh-18128 @Test public void webAuthnAuthenticationFilterIsPostProcessed() throws Exception { this.spring.register(DefaultWebauthnConfiguration.class, PostProcessorConfiguration.class).autowire(); PostProcessorConfiguration postProcess = this.spring.getContext().getBean(PostProcessorConfiguration.class); assertThat(postProcess.webauthnFilter).isNotNull(); } @Test public void webauthnWhenNoFormLoginAndDefaultRegistrationPageConfiguredThenServesJavascript() throws Exception { this.spring.register(NoFormLoginAndDefaultRegistrationPageConfiguration.class).autowire(); this.mvc.perform(get("/login/webauthn.js")) .andExpect(status().isOk()) .andExpect(header().string("content-type", "text/javascript;charset=UTF-8")) .andExpect(content().string(containsString("async function authenticate("))); } @Test public void webauthnWhenNoFormLoginAndDefaultRegistrationPageConfiguredThenServesCss() throws Exception { this.spring.register(NoFormLoginAndDefaultRegistrationPageConfiguration.class).autowire(); this.mvc.perform(get("/default-ui.css")) .andExpect(status().isOk()) .andExpect(header().string("content-type", "text/css;charset=UTF-8")) .andExpect(content().string(containsString("body {"))); } @Test public void webauthnWhenFormLoginAndDefaultRegistrationPageConfiguredThenNoDuplicateFilters() { this.spring.register(DefaultWebauthnConfiguration.class).autowire(); FilterChainProxy filterChain = this.spring.getContext().getBean(FilterChainProxy.class); List defaultResourcesFilters = filterChain.getFilterChains() .get(0) .getFilters() .stream() .filter(DefaultResourcesFilter.class::isInstance) .map(DefaultResourcesFilter.class::cast) .toList(); assertThat(defaultResourcesFilters).map(DefaultResourcesFilter::toString) .filteredOn((filterDescription) -> filterDescription.contains("login/webauthn.js")) .hasSize(1); assertThat(defaultResourcesFilters).map(DefaultResourcesFilter::toString) .filteredOn((filterDescription) -> filterDescription.contains("default-ui.css")) .hasSize(1); } @Test void webauthnWhenConfiguredDefaultsRpNameToRpId() throws Exception { ObjectMapper mapper = new ObjectMapper(); this.spring.register(DefaultWebauthnConfiguration.class).autowire(); String response = this.mvc .perform(post("/webauthn/register/options").with(csrf()) .with(authentication(new TestingAuthenticationToken("test", "ignored", "ROLE_user")))) .andExpect(status().is2xxSuccessful()) .andReturn() .getResponse() .getContentAsString(); JsonNode parsedResponse = mapper.readTree(response); assertThat(parsedResponse.get("rp").get("id").asText()).isEqualTo("example.com"); assertThat(parsedResponse.get("rp").get("name").asText()).isEqualTo("example.com"); } @Test void webauthnWhenRpNameConfiguredUsesRpName() throws Exception { ObjectMapper mapper = new ObjectMapper(); this.spring.register(CustomRpNameWebauthnConfiguration.class).autowire(); String response = this.mvc .perform(post("/webauthn/register/options").with(csrf()) .with(authentication(new TestingAuthenticationToken("test", "ignored", "ROLE_user")))) .andExpect(status().is2xxSuccessful()) .andReturn() .getResponse() .getContentAsString(); JsonNode parsedResponse = mapper.readTree(response); assertThat(parsedResponse.get("rp").get("id").asText()).isEqualTo("example.com"); assertThat(parsedResponse.get("rp").get("name").asText()).isEqualTo("Test RP Name"); } @Test public void webauthnWhenConfiguredAndFormLoginThenDoesServesJavascript() throws Exception { this.spring.register(FormLoginAndNoDefaultRegistrationPageConfiguration.class).autowire(); this.mvc.perform(get("/login/webauthn.js")) .andExpect(status().isOk()) .andExpect(header().string("content-type", "text/javascript;charset=UTF-8")) .andExpect(content().string(containsString("async function authenticate("))); } @Test public void webauthnWhenConfiguredAndNoDefaultRegistrationPageThenDoesNotServeJavascript() throws Exception { this.spring.register(NoDefaultRegistrationPageConfiguration.class).autowire(); this.mvc.perform(get("/login/webauthn.js")).andExpect(status().isNotFound()); } @Test public void webauthnWhenConfiguredPublicKeyCredentialCreationOptionsRepository() throws Exception { TestingAuthenticationToken user = new TestingAuthenticationToken("user", "password", "ROLE_USER"); SecurityContextHolder.setContext(new SecurityContextImpl(user)); PublicKeyCredentialCreationOptions options = TestPublicKeyCredentialCreationOptions .createPublicKeyCredentialCreationOptions() .build(); WebAuthnRelyingPartyOperations rpOperations = mock(WebAuthnRelyingPartyOperations.class); ConfigCredentialCreationOptionsRepository.rpOperations = rpOperations; given(rpOperations.createPublicKeyCredentialCreationOptions(any())).willReturn(options); String attrName = "attrName"; HttpSessionPublicKeyCredentialCreationOptionsRepository creationOptionsRepository = new HttpSessionPublicKeyCredentialCreationOptionsRepository(); creationOptionsRepository.setAttrName(attrName); ConfigCredentialCreationOptionsRepository.creationOptionsRepository = creationOptionsRepository; this.spring.register(ConfigCredentialCreationOptionsRepository.class).autowire(); this.mvc.perform(post("/webauthn/register/options")) .andExpect(status().isOk()) .andExpect(request().sessionAttribute(attrName, options)); } @Test public void webauthnWhenConfiguredPublicKeyCredentialCreationOptionsRepositoryBeanPresent() throws Exception { TestingAuthenticationToken user = new TestingAuthenticationToken("user", "password", "ROLE_USER"); SecurityContextHolder.setContext(new SecurityContextImpl(user)); PublicKeyCredentialCreationOptions options = TestPublicKeyCredentialCreationOptions .createPublicKeyCredentialCreationOptions() .build(); WebAuthnRelyingPartyOperations rpOperations = mock(WebAuthnRelyingPartyOperations.class); ConfigCredentialCreationOptionsRepositoryFromBean.rpOperations = rpOperations; given(rpOperations.createPublicKeyCredentialCreationOptions(any())).willReturn(options); String attrName = "attrName"; HttpSessionPublicKeyCredentialCreationOptionsRepository creationOptionsRepository = new HttpSessionPublicKeyCredentialCreationOptionsRepository(); creationOptionsRepository.setAttrName(attrName); ConfigCredentialCreationOptionsRepositoryFromBean.creationOptionsRepository = creationOptionsRepository; this.spring.register(ConfigCredentialCreationOptionsRepositoryFromBean.class).autowire(); this.mvc.perform(post("/webauthn/register/options")) .andExpect(status().isOk()) .andExpect(request().sessionAttribute(attrName, options)); } @Test public void webauthnWhenConfiguredMessageConverter() throws Exception { TestingAuthenticationToken user = new TestingAuthenticationToken("user", "password", "ROLE_USER"); SecurityContextHolder.setContext(new SecurityContextImpl(user)); PublicKeyCredentialCreationOptions options = TestPublicKeyCredentialCreationOptions .createPublicKeyCredentialCreationOptions() .build(); WebAuthnRelyingPartyOperations rpOperations = mock(WebAuthnRelyingPartyOperations.class); ConfigMessageConverter.rpOperations = rpOperations; given(rpOperations.createPublicKeyCredentialCreationOptions(any())).willReturn(options); HttpMessageConverter converter = mock(HttpMessageConverter.class); given(converter.canWrite(any(), any())).willReturn(true); String expectedBody = "123"; willAnswer((args) -> { HttpOutputMessage out = (HttpOutputMessage) args.getArguments()[2]; out.getBody().write(expectedBody.getBytes(StandardCharsets.UTF_8)); return null; }).given(converter).write(any(), any(), any()); ConfigMessageConverter.converter = converter; this.spring.register(ConfigMessageConverter.class).autowire(); this.mvc.perform(post("/webauthn/register/options")) .andExpect(status().isOk()) .andExpect(content().string(expectedBody)); } @Configuration @EnableWebSecurity static class ConfigCredentialCreationOptionsRepository { private static HttpSessionPublicKeyCredentialCreationOptionsRepository creationOptionsRepository; private static WebAuthnRelyingPartyOperations rpOperations; @Bean WebAuthnRelyingPartyOperations webAuthnRelyingPartyOperations() { return ConfigCredentialCreationOptionsRepository.rpOperations; } @Bean UserDetailsService userDetailsService() { return new InMemoryUserDetailsManager(); } @Bean SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { return http.csrf(AbstractHttpConfigurer::disable) .webAuthn((c) -> c.creationOptionsRepository(creationOptionsRepository)) .build(); } } @Configuration @EnableWebSecurity static class ConfigCredentialCreationOptionsRepositoryFromBean { private static HttpSessionPublicKeyCredentialCreationOptionsRepository creationOptionsRepository; private static WebAuthnRelyingPartyOperations rpOperations; @Bean WebAuthnRelyingPartyOperations webAuthnRelyingPartyOperations() { return ConfigCredentialCreationOptionsRepositoryFromBean.rpOperations; } @Bean UserDetailsService userDetailsService() { return new InMemoryUserDetailsManager(); } @Bean HttpSessionPublicKeyCredentialCreationOptionsRepository creationOptionsRepository() { return ConfigCredentialCreationOptionsRepositoryFromBean.creationOptionsRepository; } @Bean SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { return http.csrf(AbstractHttpConfigurer::disable).webAuthn(Customizer.withDefaults()).build(); } } @Configuration @EnableWebSecurity static class ConfigMessageConverter { private static HttpMessageConverter converter; private static WebAuthnRelyingPartyOperations rpOperations; @Bean WebAuthnRelyingPartyOperations webAuthnRelyingPartyOperations() { return ConfigMessageConverter.rpOperations; } @Bean UserDetailsService userDetailsService() { return new InMemoryUserDetailsManager(); } @Bean SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { return http.csrf(AbstractHttpConfigurer::disable).webAuthn((c) -> c.messageConverter(converter)).build(); } } @Configuration(proxyBeanMethods = false) static class PostProcessorConfiguration { WebAuthnAuthenticationFilter webauthnFilter; @Bean BeanPostProcessor beanPostProcessor() { return new BeanPostProcessor() { @Override public Object postProcessAfterInitialization(Object bean, String beanName) { if (bean instanceof WebAuthnAuthenticationFilter filter) { PostProcessorConfiguration.this.webauthnFilter = filter; } return bean; } }; } } @Configuration @EnableWebSecurity static class DefaultWebauthnConfiguration { @Bean UserDetailsService userDetailsService() { return new InMemoryUserDetailsManager(); } @Bean SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { // @formatter:off http .formLogin(Customizer.withDefaults()) .webAuthn((authn) -> authn .rpId("example.com") ); // @formatter:on return http.build(); } } @Configuration @EnableWebSecurity static class CustomRpNameWebauthnConfiguration { @Bean UserDetailsService userDetailsService() { return new InMemoryUserDetailsManager(); } @Bean SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { return http.formLogin(Customizer.withDefaults()) .webAuthn((webauthn) -> webauthn.rpId("example.com").rpName("Test RP Name")) .build(); } } @Configuration @EnableWebSecurity static class NoFormLoginAndDefaultRegistrationPageConfiguration { @Bean UserDetailsService userDetailsService() { return new InMemoryUserDetailsManager(); } @Bean SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { // @formatter:off http .webAuthn((authn) -> authn .rpId("spring.io") .rpName("spring") ); // @formatter:on return http.build(); } } @Configuration @EnableWebSecurity static class FormLoginAndNoDefaultRegistrationPageConfiguration { @Bean UserDetailsService userDetailsService() { return new InMemoryUserDetailsManager(); } @Bean SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { // @formatter:off http .formLogin(Customizer.withDefaults()) .webAuthn((authn) -> authn .rpId("spring.io") .rpName("spring") .disableDefaultRegistrationPage(true) ); // @formatter:on return http.build(); } } @Configuration @EnableWebSecurity static class NoDefaultRegistrationPageConfiguration { @Bean UserDetailsService userDetailsService() { return new InMemoryUserDetailsManager(); } @Bean SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { // @formatter:off http .formLogin((login) -> login .loginPage("/custom-login-page") ) .webAuthn((authn) -> authn .rpId("spring.io") .rpName("spring") .disableDefaultRegistrationPage(true) ); // @formatter:on return http.build(); } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/annotation/web/configurers/X509ConfigurerTests.java ================================================ /* * 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 org.springframework.security.config.annotation.web.configurers; import java.io.InputStream; import java.security.cert.Certificate; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.core.io.ClassPathResource; import org.springframework.security.config.ObjectPostProcessor; import org.springframework.security.config.annotation.SecurityContextChangedListenerConfig; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.core.context.SecurityContextChangedListener; import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken; import org.springframework.security.web.authentication.preauth.x509.SubjectX500PrincipalExtractor; import org.springframework.security.web.authentication.preauth.x509.X509AuthenticationFilter; import org.springframework.security.web.authentication.preauth.x509.X509TestUtils; import org.springframework.test.web.servlet.MockMvc; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.springframework.security.config.Customizer.withDefaults; import static org.springframework.security.config.annotation.SecurityContextChangedListenerArgumentMatchers.setAuthentication; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.x509; import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; /** * Tests for {@link X509Configurer} * * @author Rob Winch * @author Eleftheria Stein */ @ExtendWith(SpringTestContextExtension.class) public class X509ConfigurerTests { public final SpringTestContext spring = new SpringTestContext(this); @Autowired MockMvc mvc; @Test public void configureWhenRegisteringObjectPostProcessorThenInvokedOnX509AuthenticationFilter() { this.spring.register(ObjectPostProcessorConfig.class).autowire(); ObjectPostProcessor objectPostProcessor = this.spring.getContext().getBean(ObjectPostProcessor.class); verify(objectPostProcessor).postProcess(any(X509AuthenticationFilter.class)); } @Test public void x509WhenInvokedTwiceThenUsesOriginalSubjectPrincipalRegex() throws Exception { this.spring.register(DuplicateDoesNotOverrideConfig.class).autowire(); X509Certificate certificate = loadCert("rodatexampledotcom.cer"); // @formatter:off this.mvc.perform(get("/").with(x509(certificate))) .andExpect(authenticated().withUsername("rod")); // @formatter:on } @Test public void x509WhenConfiguredInLambdaThenUsesDefaults() throws Exception { this.spring.register(DefaultsInLambdaConfig.class).autowire(); X509Certificate certificate = loadCert("rod.cer"); // @formatter:off this.mvc.perform(get("/").with(x509(certificate))) .andExpect(authenticated().withUsername("rod")); // @formatter:on } @Test public void x509WhenCustomSecurityContextHolderStrategyThenUses() throws Exception { this.spring.register(DefaultsInLambdaConfig.class, SecurityContextChangedListenerConfig.class).autowire(); X509Certificate certificate = loadCert("rod.cer"); // @formatter:off this.mvc.perform(get("/").with(x509(certificate))) .andExpect(authenticated().withUsername("rod")); // @formatter:on SecurityContextHolderStrategy strategy = this.spring.getContext().getBean(SecurityContextHolderStrategy.class); verify(strategy, atLeastOnce()).getContext(); SecurityContextChangedListener listener = this.spring.getContext() .getBean(SecurityContextChangedListener.class); verify(listener).securityContextChanged(setAuthentication(PreAuthenticatedAuthenticationToken.class)); } @Test public void x509WhenSubjectPrincipalRegexInLambdaThenUsesRegexToExtractPrincipal() throws Exception { this.spring.register(SubjectPrincipalRegexInLambdaConfig.class).autowire(); X509Certificate certificate = loadCert("rodatexampledotcom.cer"); // @formatter:off this.mvc.perform(get("/").with(x509(certificate))) .andExpect(authenticated().withUsername("rod")); // @formatter:on } @Test public void x509WhenUserDetailsServiceNotConfiguredThenUsesBean() throws Exception { this.spring.register(UserDetailsServiceBeanConfig.class).autowire(); X509Certificate certificate = loadCert("rod.cer"); // @formatter:off this.mvc.perform(get("/").with(x509(certificate))) .andExpect(authenticated().withUsername("rod")); // @formatter:on } @Test public void x509WhenUserDetailsServiceAndBeanConfiguredThenDoesNotUseBean() throws Exception { this.spring.register(UserDetailsServiceAndBeanConfig.class).autowire(); X509Certificate certificate = loadCert("rod.cer"); // @formatter:off this.mvc.perform(get("/").with(x509(certificate))) .andExpect(authenticated().withUsername("rod")); // @formatter:on } // gh-13008 @Test public void x509WhenStatelessSessionManagementThenDoesNotCreateSession() throws Exception { this.spring.register(StatelessSessionManagementConfig.class).autowire(); X509Certificate certificate = loadCert("rodatexampledotcom.cer"); // @formatter:off this.mvc.perform(get("/").with(x509(certificate))) .andExpect((result) -> assertThat(result.getRequest().getSession(false)).isNull()) .andExpect(authenticated().withUsername("rod")); // @formatter:on } @Test public void x509WhenSubjectX500PrincipalExtractor() throws Exception { this.spring.register(SubjectX500PrincipalExtractorConfig.class).autowire(); X509Certificate certificate = loadCert("rod.cer"); // @formatter:off this.mvc.perform(get("/").with(x509(certificate))) .andExpect((result) -> assertThat(result.getRequest().getSession(false)).isNull()) .andExpect(authenticated().withUsername("rod")); // @formatter:on } @Test public void x509WhenSubjectX500PrincipalExtractorBean() throws Exception { this.spring.register(SubjectX500PrincipalExtractorEmailConfig.class).autowire(); X509Certificate certificate = X509TestUtils.buildTestCertificate(); // @formatter:off this.mvc.perform(get("/").with(x509(certificate))) .andExpect((result) -> assertThat(result.getRequest().getSession(false)).isNull()) .andExpect(authenticated().withUsername("luke@monkeymachine")); // @formatter:on } private T loadCert(String location) { try (InputStream is = new ClassPathResource(location).getInputStream()) { CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); return (T) certFactory.generateCertificate(is); } catch (Exception ex) { throw new IllegalArgumentException(ex); } } @Configuration @EnableWebSecurity static class ObjectPostProcessorConfig { ObjectPostProcessor objectPostProcessor = spy(ReflectingObjectPostProcessor.class); @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .x509(withDefaults()); return http.build(); // @formatter:on } @Bean @Primary ObjectPostProcessor objectPostProcessor() { return this.objectPostProcessor; } } static class ReflectingObjectPostProcessor implements ObjectPostProcessor { @Override public O postProcess(O object) { return object; } } @Configuration @EnableWebSecurity static class DuplicateDoesNotOverrideConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .x509((x509) -> x509 .subjectPrincipalRegex("CN=(.*?)@example.com(?:,|$)")) .x509(withDefaults()); // @formatter:on return http.build(); } @Bean UserDetailsService userDetailsService() { UserDetails user = User.withDefaultPasswordEncoder() .username("rod") .password("password") .roles("USER", "ADMIN") .build(); return new InMemoryUserDetailsManager(user); } } @Configuration @EnableWebSecurity static class DefaultsInLambdaConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .x509(withDefaults()); // @formatter:on return http.build(); } @Bean UserDetailsService userDetailsService() { UserDetails user = User.withDefaultPasswordEncoder() .username("rod") .password("password") .roles("USER", "ADMIN") .build(); return new InMemoryUserDetailsManager(user); } } @Configuration @EnableWebSecurity static class SubjectPrincipalRegexInLambdaConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .x509((x509) -> x509 .subjectPrincipalRegex("CN=(.*?)@example.com(?:,|$)") ); // @formatter:on return http.build(); } @Bean UserDetailsService userDetailsService() { UserDetails user = User.withDefaultPasswordEncoder() .username("rod") .password("password") .roles("USER", "ADMIN") .build(); return new InMemoryUserDetailsManager(user); } } @Configuration @EnableWebSecurity static class UserDetailsServiceBeanConfig { @Bean SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { // @formatter:off http .x509(withDefaults()); // @formatter:on return http.build(); } @Bean UserDetailsService userDetailsService() { // @formatter:off return new InMemoryUserDetailsManager( User.withDefaultPasswordEncoder() .username("rod") .password("password") .roles("USER", "ADMIN") .build() ); // @formatter:on } } @Configuration @EnableWebSecurity static class UserDetailsServiceAndBeanConfig { @Bean SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { // @formatter:off UserDetailsService customUserDetailsService = new InMemoryUserDetailsManager( User.withDefaultPasswordEncoder() .username("rod") .password("password") .roles("USER", "ADMIN") .build()); http .x509((x509) -> x509 .userDetailsService(customUserDetailsService) ); // @formatter:on return http.build(); } @Bean UserDetailsService userDetailsService() { // @formatter:off return mock(UserDetailsService.class); } } @Configuration @EnableWebSecurity static class StatelessSessionManagementConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .sessionManagement((session) -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .x509((x509) -> x509.subjectPrincipalRegex("CN=(.*?)@example.com(?:,|$)")); // @formatter:on return http.build(); } @Bean UserDetailsService userDetailsService() { UserDetails user = User.withDefaultPasswordEncoder() .username("rod") .password("password") .roles("USER", "ADMIN") .build(); return new InMemoryUserDetailsManager(user); } } @Configuration @EnableWebSecurity static class SubjectX500PrincipalExtractorConfig { @Bean SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { // @formatter:off http .x509((x509) -> x509 .x509PrincipalExtractor(new SubjectX500PrincipalExtractor()) ); // @formatter:on return http.build(); } @Bean UserDetailsService userDetailsService() { UserDetails user = User.withDefaultPasswordEncoder() .username("rod") .password("password") .roles("USER", "ADMIN") .build(); return new InMemoryUserDetailsManager(user); } } @Configuration @EnableWebSecurity static class SubjectX500PrincipalExtractorEmailConfig { @Bean SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { SubjectX500PrincipalExtractor principalExtractor = new SubjectX500PrincipalExtractor(); principalExtractor.setExtractPrincipalNameFromEmail(true); // @formatter:off http .x509((x509) -> x509 .x509PrincipalExtractor(principalExtractor) ); // @formatter:on return http.build(); } @Bean UserDetailsService userDetailsService() { UserDetails user = User.withDefaultPasswordEncoder() .username("luke@monkeymachine") .password("password") .roles("USER", "ADMIN") .build(); return new InMemoryUserDetailsManager(user); } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2ClientConfigurerTests.java ================================================ /* * 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 org.springframework.security.config.annotation.web.configurers.oauth2.client; import java.util.HashMap; import java.util.Map; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.mock.web.MockHttpSession; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.oauth2.client.InMemoryOAuth2AuthorizedClientService; import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService; import org.springframework.security.oauth2.client.annotation.RegisteredOAuth2AuthorizedClient; import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient; import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository; import org.springframework.security.oauth2.client.registration.TestClientRegistrations; import org.springframework.security.oauth2.client.web.AuthenticatedPrincipalOAuth2AuthorizedClientRepository; import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository; import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizationRequestResolver; import org.springframework.security.oauth2.client.web.HttpSessionOAuth2AuthorizationRequestRepository; import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver; import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository; import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.core.ClientAuthenticationMethod; import org.springframework.security.oauth2.core.OAuth2AccessToken; import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; import org.springframework.security.web.DefaultRedirectStrategy; import org.springframework.security.web.RedirectStrategy; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.savedrequest.RequestCache; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; import static org.springframework.security.config.Customizer.withDefaults; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Tests for {@link OAuth2ClientConfigurer}. * * @author Joe Grandja * @author Parikshit Dutta */ @ExtendWith(SpringTestContextExtension.class) public class OAuth2ClientConfigurerTests { private static ClientRegistrationRepository clientRegistrationRepository; private static OAuth2AuthorizedClientService authorizedClientService; private static OAuth2AuthorizedClientRepository authorizedClientRepository; private static OAuth2AuthorizationRequestResolver authorizationRequestResolver; private static RedirectStrategy authorizationRedirectStrategy; private static OAuth2AccessTokenResponseClient accessTokenResponseClient; private static RequestCache requestCache; public final SpringTestContext spring = new SpringTestContext(this); @Autowired private MockMvc mockMvc; private ClientRegistration registration1; @BeforeEach public void setup() { // @formatter:off this.registration1 = TestClientRegistrations.clientRegistration() .registrationId("registration-1") .clientId("client-1") .clientSecret("secret") .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) .redirectUri("{baseUrl}/client-1") .scope("user") .authorizationUri("https://provider.com/oauth2/authorize") .tokenUri("https://provider.com/oauth2/token") .userInfoUri("https://provider.com/oauth2/user") .userNameAttributeName("id") .clientName("client-1") .build(); // @formatter:on clientRegistrationRepository = new InMemoryClientRegistrationRepository(this.registration1); authorizedClientService = new InMemoryOAuth2AuthorizedClientService(clientRegistrationRepository); authorizedClientRepository = new AuthenticatedPrincipalOAuth2AuthorizedClientRepository( authorizedClientService); authorizationRequestResolver = new DefaultOAuth2AuthorizationRequestResolver(clientRegistrationRepository, "/oauth2/authorization"); authorizationRedirectStrategy = new DefaultRedirectStrategy(); OAuth2AccessTokenResponse accessTokenResponse = OAuth2AccessTokenResponse.withToken("access-token-1234") .tokenType(OAuth2AccessToken.TokenType.BEARER) .expiresIn(300) .build(); accessTokenResponseClient = mock(OAuth2AccessTokenResponseClient.class); given(accessTokenResponseClient.getTokenResponse(any(OAuth2AuthorizationCodeGrantRequest.class))) .willReturn(accessTokenResponse); requestCache = mock(RequestCache.class); } @Test public void configureWhenAuthorizationCodeRequestThenRedirectForAuthorization() throws Exception { this.spring.register(OAuth2ClientConfig.class).autowire(); // @formatter:off MvcResult mvcResult = this.mockMvc.perform(get("/oauth2/authorization/registration-1")) .andExpect(status().is3xxRedirection()).andReturn(); assertThat(mvcResult.getResponse().getRedirectedUrl()) .matches("https://provider.com/oauth2/authorize\\?" + "response_type=code&client_id=client-1&" + "scope=user&state=.{15,}&" + "redirect_uri=http://localhost/client-1&code_challenge=([a-zA-Z0-9\\-\\.\\_\\~]){43}&code_challenge_method=S256"); // @formatter:on } @Test public void configureWhenOauth2ClientInLambdaThenRedirectForAuthorization() throws Exception { this.spring.register(OAuth2ClientInLambdaConfig.class).autowire(); MvcResult mvcResult = this.mockMvc.perform(get("/oauth2/authorization/registration-1")) .andExpect(status().is3xxRedirection()) .andReturn(); assertThat(mvcResult.getResponse().getRedirectedUrl()).matches("https://provider.com/oauth2/authorize\\?" + "response_type=code&client_id=client-1&" + "scope=user&state=.{15,}&" + "redirect_uri=http://localhost/client-1&code_challenge=([a-zA-Z0-9\\-\\.\\_\\~]){43}&code_challenge_method=S256"); } @Test public void configureWhenAuthorizationCodeResponseSuccessThenAuthorizedClientSaved() throws Exception { this.spring.register(OAuth2ClientConfig.class).autowire(); // Setup the Authorization Request in the session Map attributes = new HashMap<>(); attributes.put(OAuth2ParameterNames.REGISTRATION_ID, this.registration1.getRegistrationId()); // @formatter:off OAuth2AuthorizationRequest authorizationRequest = OAuth2AuthorizationRequest.authorizationCode() .authorizationUri(this.registration1.getProviderDetails().getAuthorizationUri()) .clientId(this.registration1.getClientId()) .redirectUri("http://localhost/client-1") .state("state") .attributes(attributes) .build(); // @formatter:on AuthorizationRequestRepository authorizationRequestRepository = new HttpSessionOAuth2AuthorizationRequestRepository(); MockHttpServletRequest request = new MockHttpServletRequest("GET", ""); MockHttpServletResponse response = new MockHttpServletResponse(); authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest, request, response); MockHttpSession session = (MockHttpSession) request.getSession(); String principalName = "user1"; TestingAuthenticationToken authentication = new TestingAuthenticationToken(principalName, "password"); // @formatter:off MockHttpServletRequestBuilder clientRequest = get("/client-1") .param(OAuth2ParameterNames.CODE, "code") .param(OAuth2ParameterNames.STATE, "state") .with(authentication(authentication)) .session(session); this.mockMvc.perform(clientRequest) .andExpect(status().is3xxRedirection()) .andExpect(redirectedUrl("http://localhost/client-1")); // @formatter:on OAuth2AuthorizedClient authorizedClient = authorizedClientRepository .loadAuthorizedClient(this.registration1.getRegistrationId(), authentication, request); assertThat(authorizedClient).isNotNull(); } @Test public void configureWhenRequestCacheProvidedAndClientAuthorizationRequiredExceptionThrownThenRequestCacheUsed() throws Exception { this.spring.register(OAuth2ClientConfig.class).autowire(); MvcResult mvcResult = this.mockMvc.perform(get("/resource1").with(user("user1"))) .andExpect(status().is3xxRedirection()) .andReturn(); assertThat(mvcResult.getResponse().getRedirectedUrl()).matches("https://provider.com/oauth2/authorize\\?" + "response_type=code&client_id=client-1&" + "scope=user&state=.{15,}&" + "redirect_uri=http://localhost/client-1&code_challenge=([a-zA-Z0-9\\-\\.\\_\\~]){43}&code_challenge_method=S256"); verify(requestCache).saveRequest(any(HttpServletRequest.class), any(HttpServletResponse.class)); } @Test public void configureWhenRequestCacheProvidedAndClientAuthorizationSucceedsThenRequestCacheUsed() throws Exception { this.spring.register(OAuth2ClientConfig.class).autowire(); // Setup the Authorization Request in the session Map attributes = new HashMap<>(); attributes.put(OAuth2ParameterNames.REGISTRATION_ID, this.registration1.getRegistrationId()); // @formatter:off OAuth2AuthorizationRequest authorizationRequest = OAuth2AuthorizationRequest.authorizationCode() .authorizationUri(this.registration1.getProviderDetails().getAuthorizationUri()) .clientId(this.registration1.getClientId()).redirectUri("http://localhost/client-1") .state("state") .attributes(attributes) .build(); // @formatter:on AuthorizationRequestRepository authorizationRequestRepository = new HttpSessionOAuth2AuthorizationRequestRepository(); MockHttpServletRequest request = new MockHttpServletRequest("GET", ""); MockHttpServletResponse response = new MockHttpServletResponse(); authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest, request, response); MockHttpSession session = (MockHttpSession) request.getSession(); String principalName = "user1"; TestingAuthenticationToken authentication = new TestingAuthenticationToken(principalName, "password"); // @formatter:off MockHttpServletRequestBuilder clientRequest = get("/client-1") .param(OAuth2ParameterNames.CODE, "code") .param(OAuth2ParameterNames.STATE, "state") .with(authentication(authentication)) .session(session); this.mockMvc.perform(clientRequest) .andExpect(status().is3xxRedirection()) .andExpect(redirectedUrl("http://localhost/client-1")); // @formatter:on verify(requestCache).getRequest(any(HttpServletRequest.class), any(HttpServletResponse.class)); } // gh-5521 @Test public void configureWhenCustomAuthorizationRequestResolverSetThenAuthorizationRequestIncludesCustomParameters() throws Exception { // Override default resolver OAuth2AuthorizationRequestResolver defaultAuthorizationRequestResolver = authorizationRequestResolver; authorizationRequestResolver = mock(OAuth2AuthorizationRequestResolver.class); given(authorizationRequestResolver.resolve(any())) .willAnswer((invocation) -> defaultAuthorizationRequestResolver.resolve(invocation.getArgument(0))); this.spring.register(OAuth2ClientConfig.class).autowire(); // @formatter:off this.mockMvc.perform(get("/oauth2/authorization/registration-1")) .andExpect(status().is3xxRedirection()) .andReturn(); // @formatter:on verify(authorizationRequestResolver).resolve(any()); } @Test public void configureWhenCustomAuthorizationRedirectStrategySetThenAuthorizationRedirectStrategyUsed() throws Exception { authorizationRedirectStrategy = mock(RedirectStrategy.class); this.spring.register(OAuth2ClientConfig.class).autowire(); // @formatter:off this.mockMvc.perform(get("/oauth2/authorization/registration-1")) .andExpect(status().isOk()) .andReturn(); // @formatter:on verify(authorizationRedirectStrategy).sendRedirect(any(), any(), anyString()); } @Test public void configureWhenCustomAuthorizationRequestResolverBeanPresentThenAuthorizationRequestResolverUsed() throws Exception { OAuth2AuthorizationRequestResolver defaultAuthorizationRequestResolver = authorizationRequestResolver; authorizationRequestResolver = mock(OAuth2AuthorizationRequestResolver.class); given(authorizationRequestResolver.resolve(any())) .willAnswer((invocation) -> defaultAuthorizationRequestResolver.resolve(invocation.getArgument(0))); this.spring.register(OAuth2ClientInLambdaConfig.class, AuthorizationRequestResolverConfig.class).autowire(); // @formatter:off this.mockMvc.perform(get("/oauth2/authorization/registration-1")) .andExpect(status().is3xxRedirection()) .andReturn(); // @formatter:on verify(authorizationRequestResolver).resolve(any()); } @Test public void configureWhenOAuth2LoginBeansConfiguredThenNotShared() throws Exception { this.spring.register(OAuth2ClientConfigWithOAuth2Login.class).autowire(); // Setup the Authorization Request in the session Map attributes = new HashMap<>(); attributes.put(OAuth2ParameterNames.REGISTRATION_ID, this.registration1.getRegistrationId()); // @formatter:off OAuth2AuthorizationRequest authorizationRequest = OAuth2AuthorizationRequest.authorizationCode() .authorizationUri(this.registration1.getProviderDetails().getAuthorizationUri()) .clientId(this.registration1.getClientId()) .redirectUri("http://localhost/client-1") .state("state") .attributes(attributes) .build(); // @formatter:on AuthorizationRequestRepository authorizationRequestRepository = new HttpSessionOAuth2AuthorizationRequestRepository(); MockHttpServletRequest request = new MockHttpServletRequest("GET", ""); MockHttpServletResponse response = new MockHttpServletResponse(); authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest, request, response); MockHttpSession session = (MockHttpSession) request.getSession(); String principalName = "user1"; TestingAuthenticationToken authentication = new TestingAuthenticationToken(principalName, "password"); // @formatter:off MockHttpServletRequestBuilder clientRequest = get("/client-1") .param(OAuth2ParameterNames.CODE, "code") .param(OAuth2ParameterNames.STATE, "state") .with(authentication(authentication)) .session(session); this.mockMvc.perform(clientRequest) .andExpect(status().is3xxRedirection()) .andExpect(redirectedUrl("http://localhost/client-1")); // @formatter:on OAuth2AuthorizedClient authorizedClient = authorizedClientRepository .loadAuthorizedClient(this.registration1.getRegistrationId(), authentication, request); assertThat(authorizedClient).isNotNull(); // Ensure shared objects set for OAuth2 Client are not used ClientRegistrationRepository clientRegistrationRepository = this.spring.getContext() .getBean(ClientRegistrationRepository.class); OAuth2AuthorizedClientRepository authorizedClientRepository = this.spring.getContext() .getBean(OAuth2AuthorizedClientRepository.class); verifyNoInteractions(clientRegistrationRepository, authorizedClientRepository); } @EnableWebSecurity @Configuration @EnableWebMvc static class OAuth2ClientConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((requests) -> requests .anyRequest().authenticated()) .requestCache((cache) -> cache .requestCache(requestCache)) .oauth2Client((client) -> client .authorizationCodeGrant((code) -> code .authorizationRequestResolver(authorizationRequestResolver) .authorizationRedirectStrategy(authorizationRedirectStrategy) .accessTokenResponseClient(accessTokenResponseClient))); return http.build(); // @formatter:on } @Bean ClientRegistrationRepository clientRegistrationRepository() { return clientRegistrationRepository; } @Bean OAuth2AuthorizedClientRepository authorizedClientRepository() { return authorizedClientRepository; } @RestController class ResourceController { @GetMapping("/resource1") String resource1( @RegisteredOAuth2AuthorizedClient("registration-1") OAuth2AuthorizedClient authorizedClient) { return "resource1"; } } } @EnableWebSecurity @Configuration @EnableWebMvc static class OAuth2ClientInLambdaConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((authorize) -> authorize .anyRequest().authenticated() ) .oauth2Client(withDefaults()); return http.build(); // @formatter:on } @Bean ClientRegistrationRepository clientRegistrationRepository() { return clientRegistrationRepository; } @Bean OAuth2AuthorizedClientRepository authorizedClientRepository() { return authorizedClientRepository; } } @Configuration static class AuthorizationRequestResolverConfig { @Bean OAuth2AuthorizationRequestResolver authorizationRequestResolver() { return authorizationRequestResolver; } } @Configuration @EnableWebSecurity @EnableWebMvc static class OAuth2ClientConfigWithOAuth2Login { private final ClientRegistrationRepository clientRegistrationRepository = mock( ClientRegistrationRepository.class); private final OAuth2AuthorizedClientRepository authorizedClientRepository = mock( OAuth2AuthorizedClientRepository.class); @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((authorize) -> authorize .anyRequest().authenticated() ) .oauth2Client((oauth2Client) -> oauth2Client .clientRegistrationRepository(OAuth2ClientConfigurerTests.clientRegistrationRepository) .authorizedClientService(OAuth2ClientConfigurerTests.authorizedClientService) .authorizationCodeGrant((authorizationCode) -> authorizationCode .authorizationRequestResolver(authorizationRequestResolver) .authorizationRedirectStrategy(authorizationRedirectStrategy) .accessTokenResponseClient(accessTokenResponseClient) ) ) .oauth2Login((oauth2Login) -> oauth2Login .clientRegistrationRepository(this.clientRegistrationRepository) .authorizedClientRepository(this.authorizedClientRepository) ); // @formatter:on return http.build(); } @Bean ClientRegistrationRepository clientRegistrationRepository() { return this.clientRegistrationRepository; } @Bean OAuth2AuthorizedClientRepository authorizedClientRepository() { return this.authorizedClientRepository; } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurerTests.java ================================================ /* * 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 org.springframework.security.config.annotation.web.configurers.oauth2.client; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; import org.apache.http.HttpHeaders; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mockito; import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.NoUniqueBeanDefinitionException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationListener; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.event.SmartApplicationListener; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.mock.web.MockFilterChain; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.SecurityAssertions; import org.springframework.security.authentication.event.AuthenticationSuccessEvent; import org.springframework.security.config.Customizer; import org.springframework.security.config.ObjectPostProcessor; import org.springframework.security.config.annotation.SecurityContextChangedListenerConfig; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configurers.oauth2.client.OAuth2LoginConfigurerTests.OAuth2LoginConfigCustomWithPostProcessor.SpyObjectPostProcessor; import org.springframework.security.config.oauth2.client.CommonOAuth2Provider; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.context.DelegatingApplicationListener; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper; import org.springframework.security.core.context.SecurityContextChangedListener; import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.security.core.session.SessionDestroyedEvent; import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient; import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest; import org.springframework.security.oauth2.client.oidc.session.OidcSessionRegistry; import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest; import org.springframework.security.oauth2.client.oidc.web.logout.OidcClientInitiatedLogoutSuccessHandler; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository; import org.springframework.security.oauth2.client.registration.TestClientRegistrations; import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; import org.springframework.security.oauth2.client.userinfo.OAuth2UserService; import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository; import org.springframework.security.oauth2.client.web.HttpSessionOAuth2AuthorizationRequestRepository; import org.springframework.security.oauth2.client.web.HttpSessionOAuth2AuthorizedClientRepository; import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver; import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository; import org.springframework.security.oauth2.core.OAuth2AccessToken; import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; import org.springframework.security.oauth2.core.oidc.IdTokenClaimNames; import org.springframework.security.oauth2.core.oidc.OidcIdToken; import org.springframework.security.oauth2.core.oidc.TestOidcIdTokens; import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames; import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser; import org.springframework.security.oauth2.core.oidc.user.OidcUser; import org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority; import org.springframework.security.oauth2.core.oidc.user.TestOidcUsers; import org.springframework.security.oauth2.core.user.DefaultOAuth2User; import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.security.oauth2.core.user.OAuth2UserAuthority; import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.security.oauth2.jwt.JwtDecoder; import org.springframework.security.oauth2.jwt.JwtDecoderFactory; import org.springframework.security.oauth2.jwt.TestJwts; import org.springframework.security.web.FilterChainProxy; import org.springframework.security.web.RedirectStrategy; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.HttpStatusEntryPoint; import org.springframework.security.web.context.HttpRequestResponseHolder; import org.springframework.security.web.context.HttpSessionSecurityContextRepository; import org.springframework.security.web.context.NullSecurityContextRepository; import org.springframework.security.web.context.SecurityContextRepository; import org.springframework.security.web.servlet.TestMockHttpServletRequests; import org.springframework.security.web.session.HttpSessionDestroyedEvent; import org.springframework.security.web.util.matcher.RequestHeaderRequestMatcher; import org.springframework.test.util.ReflectionTestUtils; import org.springframework.test.web.servlet.MockMvc; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatNoException; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.then; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; import static org.springframework.security.config.Customizer.withDefaults; import static org.springframework.security.config.annotation.SecurityContextChangedListenerArgumentMatchers.setAuthentication; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; import static org.springframework.security.web.servlet.TestMockHttpServletRequests.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; /** * Tests for {@link OAuth2LoginConfigurer}. * * @author Kazuki Shimizu * @author Joe Grandja * @since 5.0.1 */ @ExtendWith(SpringTestContextExtension.class) public class OAuth2LoginConfigurerTests { // @formatter:off private static final ClientRegistration GOOGLE_CLIENT_REGISTRATION = CommonOAuth2Provider.GOOGLE .getBuilder("google") .clientId("clientId") .clientSecret("clientSecret") .build(); // @formatter:on // @formatter:off private static final ClientRegistration GITHUB_CLIENT_REGISTRATION = CommonOAuth2Provider.GITHUB .getBuilder("github") .clientId("clientId") .clientSecret("clientSecret") .build(); // @formatter:on // @formatter:off private static final ClientRegistration CLIENT_CREDENTIALS_REGISTRATION = TestClientRegistrations.clientCredentials() .build(); // @formatter:on private ConfigurableApplicationContext context; @Autowired private FilterChainProxy springSecurityFilterChain; @Autowired(required = false) private AuthorizationRequestRepository authorizationRequestRepository; @Autowired(required = false) SecurityContextRepository securityContextRepository; public final SpringTestContext spring = new SpringTestContext(this); @Autowired(required = false) MockMvc mvc; private MockHttpServletRequest request; private MockHttpServletResponse response; private MockFilterChain filterChain; @BeforeEach public void setup() { this.request = TestMockHttpServletRequests.get("/login/oauth2/code/google").build(); this.response = new MockHttpServletResponse(); this.filterChain = new MockFilterChain(); } @AfterEach public void cleanup() { if (this.context != null) { this.context.close(); } } @Test public void oauth2Login() throws Exception { // setup application context loadConfig(OAuth2LoginConfig.class); // setup authorization request OAuth2AuthorizationRequest authorizationRequest = createOAuth2AuthorizationRequest(); this.authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest, this.request, this.response); // setup authentication parameters this.request.setParameter("code", "code123"); this.request.setParameter("state", authorizationRequest.getState()); // perform test this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain); // assertions Authentication authentication = this.securityContextRepository .loadContext(new HttpRequestResponseHolder(this.request, this.response)) .getAuthentication(); SecurityAssertions.assertThat(authentication) .hasAuthority("OAUTH2_USER") .isInstanceOf(OAuth2UserAuthority.class); } @Test public void requestWhenCustomSecurityContextHolderStrategyThenUses() throws Exception { loadConfig(OAuth2LoginConfig.class, SecurityContextChangedListenerConfig.class); OAuth2AuthorizationRequest authorizationRequest = createOAuth2AuthorizationRequest(); this.authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest, this.request, this.response); this.request.setParameter("code", "code123"); this.request.setParameter("state", authorizationRequest.getState()); this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain); Authentication authentication = this.securityContextRepository .loadContext(new HttpRequestResponseHolder(this.request, this.response)) .getAuthentication(); SecurityAssertions.assertThat(authentication) .hasAuthority("OAUTH2_USER") .isInstanceOf(OAuth2UserAuthority.class); SecurityContextHolderStrategy strategy = this.context.getBean(SecurityContextHolderStrategy.class); verify(strategy, atLeastOnce()).getDeferredContext(); SecurityContextChangedListener listener = this.context.getBean(SecurityContextChangedListener.class); verify(listener).securityContextChanged(setAuthentication(OAuth2AuthenticationToken.class)); } @Test public void requestWhenOauth2LoginInLambdaThenAuthenticationContainsOauth2UserAuthority() throws Exception { loadConfig(OAuth2LoginInLambdaConfig.class); OAuth2AuthorizationRequest authorizationRequest = createOAuth2AuthorizationRequest(); this.authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest, this.request, this.response); this.request.setParameter("code", "code123"); this.request.setParameter("state", authorizationRequest.getState()); this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain); Authentication authentication = this.securityContextRepository .loadContext(new HttpRequestResponseHolder(this.request, this.response)) .getAuthentication(); SecurityAssertions.assertThat(authentication) .hasAuthority("OAUTH2_USER") .isInstanceOf(OAuth2UserAuthority.class); } // gh-6009 @Test public void oauth2LoginWhenSuccessThenAuthenticationSuccessEventPublished() throws Exception { // setup application context loadConfig(OAuth2LoginConfig.class); // setup authorization request OAuth2AuthorizationRequest authorizationRequest = createOAuth2AuthorizationRequest(); this.authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest, this.request, this.response); // setup authentication parameters this.request.setParameter("code", "code123"); this.request.setParameter("state", authorizationRequest.getState()); // perform test this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain); // assertions assertThat(OAuth2LoginConfig.EVENTS).isNotEmpty(); assertThat(OAuth2LoginConfig.EVENTS).hasSize(1); assertThat(OAuth2LoginConfig.EVENTS.get(0)).isInstanceOf(AuthenticationSuccessEvent.class); } @Test public void oauth2LoginCustomWithConfigurer() throws Exception { // setup application context loadConfig(OAuth2LoginConfigCustomWithConfigurer.class); // setup authorization request OAuth2AuthorizationRequest authorizationRequest = createOAuth2AuthorizationRequest(); this.authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest, this.request, this.response); // setup authentication parameters this.request.setParameter("code", "code123"); this.request.setParameter("state", authorizationRequest.getState()); // perform test this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain); // assertions Authentication authentication = this.securityContextRepository .loadContext(new HttpRequestResponseHolder(this.request, this.response)) .getAuthentication(); SecurityAssertions.assertThat(authentication).hasAuthorities("OAUTH2_USER", "ROLE_OAUTH2_USER"); } @Test public void oauth2LoginCustomWithBeanRegistration() throws Exception { // setup application context loadConfig(OAuth2LoginConfigCustomWithBeanRegistration.class); // setup authorization request OAuth2AuthorizationRequest authorizationRequest = createOAuth2AuthorizationRequest(); this.authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest, this.request, this.response); // setup authentication parameters this.request.setParameter("code", "code123"); this.request.setParameter("state", authorizationRequest.getState()); // perform test this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain); // assertions Authentication authentication = this.securityContextRepository .loadContext(new HttpRequestResponseHolder(this.request, this.response)) .getAuthentication(); SecurityAssertions.assertThat(authentication).hasAuthorities("OAUTH2_USER", "ROLE_OAUTH2_USER"); } @Test public void oauth2LoginCustomWithUserServiceBeanRegistration() throws Exception { // setup application context loadConfig(OAuth2LoginConfigCustomUserServiceBeanRegistration.class); // setup authorization request OAuth2AuthorizationRequest authorizationRequest = createOAuth2AuthorizationRequest(); this.authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest, this.request, this.response); // setup authentication parameters this.request.setParameter("code", "code123"); this.request.setParameter("state", authorizationRequest.getState()); // perform test this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain); // assertions Authentication authentication = this.securityContextRepository .loadContext(new HttpRequestResponseHolder(this.request, this.response)) .getAuthentication(); SecurityAssertions.assertThat(authentication).hasAuthorities("OAUTH2_USER", "ROLE_OAUTH2_USER"); } // gh-5488 @Test public void oauth2LoginConfigLoginProcessingUrl() throws Exception { // setup application context loadConfig(OAuth2LoginConfigLoginProcessingUrl.class); // setup authorization request OAuth2AuthorizationRequest authorizationRequest = createOAuth2AuthorizationRequest(); this.request.setRequestURI("/login/oauth2/google"); this.authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest, this.request, this.response); // setup authentication parameters this.request.setParameter("code", "code123"); this.request.setParameter("state", authorizationRequest.getState()); // perform test this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain); // assertions Authentication authentication = this.securityContextRepository .loadContext(new HttpRequestResponseHolder(this.request, this.response)) .getAuthentication(); SecurityAssertions.assertThat(authentication) .hasAuthority("OAUTH2_USER") .isInstanceOf(OAuth2UserAuthority.class); } // gh-5521 @Test public void oauth2LoginWithCustomAuthorizationRequestParameters() throws Exception { loadConfig(OAuth2LoginConfigCustomAuthorizationRequestResolver.class); OAuth2AuthorizationRequestResolver resolver = this.context .getBean(OAuth2LoginConfigCustomAuthorizationRequestResolver.class).resolver; // @formatter:off OAuth2AuthorizationRequest result = OAuth2AuthorizationRequest.authorizationCode() .authorizationUri("https://accounts.google.com/authorize") .clientId("client-id") .state("adsfa") .authorizationRequestUri( "https://accounts.google.com/o/oauth2/v2/auth?response_type=code&client_id=clientId&scope=openid+profile+email&state=state&redirect_uri=http%3A%2F%2Flocalhost%2Flogin%2Foauth2%2Fcode%2Fgoogle&custom-param1=custom-value1") .build(); // @formatter:on given(resolver.resolve(any())).willReturn(result); String requestUri = "/oauth2/authorization/google"; this.request = TestMockHttpServletRequests.get(requestUri).build(); this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain); assertThat(this.response.getRedirectedUrl()).isEqualTo( "https://accounts.google.com/o/oauth2/v2/auth?response_type=code&client_id=clientId&scope=openid+profile+email&state=state&redirect_uri=http%3A%2F%2Flocalhost%2Flogin%2Foauth2%2Fcode%2Fgoogle&custom-param1=custom-value1"); } @Test public void oauth2LoginWithCustomAuthorizationRequestParametersAndResolverAsBean() throws Exception { loadConfig(OAuth2LoginConfigCustomAuthorizationRequestResolverBean.class); // @formatter:off // @formatter:on String requestUri = "/oauth2/authorization/google"; this.request = TestMockHttpServletRequests.get(requestUri).build(); this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain); assertThat(this.response.getRedirectedUrl()).isEqualTo( "https://accounts.google.com/o/oauth2/v2/auth?response_type=code&client_id=clientId&scope=openid+profile+email&state=state&redirect_uri=http%3A%2F%2Flocalhost%2Flogin%2Foauth2%2Fcode%2Fgoogle&custom-param1=custom-value1"); } @Test public void requestWhenOauth2LoginWithCustomAuthorizationRequestParametersThenParametersInRedirectedUrl() throws Exception { loadConfig(OAuth2LoginConfigCustomAuthorizationRequestResolverInLambda.class); OAuth2AuthorizationRequestResolver resolver = this.context .getBean(OAuth2LoginConfigCustomAuthorizationRequestResolverInLambda.class).resolver; // @formatter:off OAuth2AuthorizationRequest result = OAuth2AuthorizationRequest.authorizationCode() .authorizationUri("https://accounts.google.com/authorize") .clientId("client-id") .state("adsfa") .authorizationRequestUri( "https://accounts.google.com/o/oauth2/v2/auth?response_type=code&client_id=clientId&scope=openid+profile+email&state=state&redirect_uri=http%3A%2F%2Flocalhost%2Flogin%2Foauth2%2Fcode%2Fgoogle&custom-param1=custom-value1") .build(); // @formatter:on given(resolver.resolve(any())).willReturn(result); String requestUri = "/oauth2/authorization/google"; this.request = TestMockHttpServletRequests.get(requestUri).build(); this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain); assertThat(this.response.getRedirectedUrl()).isEqualTo( "https://accounts.google.com/o/oauth2/v2/auth?response_type=code&client_id=clientId&scope=openid+profile+email&state=state&redirect_uri=http%3A%2F%2Flocalhost%2Flogin%2Foauth2%2Fcode%2Fgoogle&custom-param1=custom-value1"); } @Test public void oauth2LoginWithAuthorizationRedirectStrategyThenCustomAuthorizationRedirectStrategyUsed() throws Exception { loadConfig(OAuth2LoginConfigCustomAuthorizationRedirectStrategy.class); RedirectStrategy redirectStrategy = this.context .getBean(OAuth2LoginConfigCustomAuthorizationRedirectStrategy.class).redirectStrategy; String requestUri = "/oauth2/authorization/google"; this.request = get(requestUri).build(); this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain); then(redirectStrategy).should().sendRedirect(any(), any(), anyString()); } @Test public void requestWhenOauth2LoginWithCustomAuthorizationRedirectStrategyThenCustomAuthorizationRedirectStrategyUsed() throws Exception { loadConfig(OAuth2LoginConfigCustomAuthorizationRedirectStrategyInLambda.class); RedirectStrategy redirectStrategy = this.context .getBean(OAuth2LoginConfigCustomAuthorizationRedirectStrategyInLambda.class).redirectStrategy; String requestUri = "/oauth2/authorization/google"; this.request = get(requestUri).build(); this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain); then(redirectStrategy).should().sendRedirect(any(), any(), anyString()); } // gh-5347 @Test public void oauth2LoginWithOneClientConfiguredThenRedirectForAuthorization() throws Exception { loadConfig(OAuth2LoginConfig.class); String requestUri = "/"; this.request = get(requestUri).build(); this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain); assertThat(this.response.getRedirectedUrl()).matches("/oauth2/authorization/google"); } // gh-6802 @Test public void oauth2LoginWithOneClientConfiguredAndFormLoginThenRedirectDefaultLoginPage() throws Exception { loadConfig(OAuth2LoginConfigFormLogin.class); String requestUri = "/"; this.request = get(requestUri).build(); this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain); assertThat(this.response.getRedirectedUrl()).matches("/login"); } // gh-5347 @Test public void oauth2LoginWithOneClientConfiguredAndRequestFaviconNotAuthenticatedThenRedirectDefaultLoginPage() throws Exception { loadConfig(OAuth2LoginConfig.class); String requestUri = "/favicon.ico"; this.request = get(requestUri).build(); this.request.addHeader(HttpHeaders.ACCEPT, new MediaType("image", "*").toString()); this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain); assertThat(this.response.getRedirectedUrl()).matches("/login"); } // gh-5347 @Test public void oauth2LoginWithMultipleClientsConfiguredThenRedirectDefaultLoginPage() throws Exception { loadConfig(OAuth2LoginConfigMultipleClients.class); String requestUri = "/"; this.request = get(requestUri).build(); this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain); assertThat(this.response.getRedirectedUrl()).matches("/login"); } // gh-6812 @Test public void oauth2LoginWithOneClientConfiguredAndRequestXHRNotAuthenticatedThenDoesNotRedirectForAuthorization() throws Exception { loadConfig(OAuth2LoginConfig.class); String requestUri = "/"; this.request = get(requestUri).build(); this.request.addHeader("X-Requested-With", "XMLHttpRequest"); this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain); assertThat(this.response.getRedirectedUrl()).doesNotMatch("http://localhost/oauth2/authorization/google"); } @Test public void oauth2LoginWithHttpBasicOneClientConfiguredAndRequestXHRNotAuthenticatedThenUnauthorized() throws Exception { loadConfig(OAuth2LoginWithHttpBasicConfig.class); String requestUri = "/"; this.request = get(requestUri).build(); this.request.addHeader("X-Requested-With", "XMLHttpRequest"); this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain); assertThat(this.response.getStatus()).isEqualTo(401); } @Test public void oauth2LoginWithXHREntryPointOneClientConfiguredAndRequestXHRNotAuthenticatedThenUnauthorized() throws Exception { loadConfig(OAuth2LoginWithXHREntryPointConfig.class); String requestUri = "/"; this.request = get(requestUri).build(); this.request.addHeader("X-Requested-With", "XMLHttpRequest"); this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain); assertThat(this.response.getStatus()).isEqualTo(401); } // gh-9457 @Test public void oauth2LoginWithOneAuthorizationCodeClientAndOtherClientsConfiguredThenRedirectForAuthorization() throws Exception { loadConfig(OAuth2LoginConfigAuthorizationCodeClientAndOtherClients.class); String requestUri = "/"; this.request = get(requestUri).build(); this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain); assertThat(this.response.getRedirectedUrl()).matches("/oauth2/authorization/google"); } @Test public void oauth2LoginWithCustomLoginPageThenRedirectCustomLoginPage() throws Exception { loadConfig(OAuth2LoginConfigCustomLoginPage.class); String requestUri = "/"; this.request = get(requestUri).build(); this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain); assertThat(this.response.getRedirectedUrl()).matches("/custom-login"); } @Test public void requestWhenOauth2LoginWithCustomLoginPageInLambdaThenRedirectCustomLoginPage() throws Exception { loadConfig(OAuth2LoginConfigCustomLoginPageInLambda.class); String requestUri = "/"; this.request = get(requestUri).build(); this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain); assertThat(this.response.getRedirectedUrl()).matches("/custom-login"); } @Test public void oidcLogin() throws Exception { // setup application context loadConfig(OAuth2LoginConfig.class, JwtDecoderFactoryConfig.class); // setup authorization request OAuth2AuthorizationRequest authorizationRequest = createOAuth2AuthorizationRequest("openid"); this.authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest, this.request, this.response); // setup authentication parameters this.request.setParameter("code", "code123"); this.request.setParameter("state", authorizationRequest.getState()); // perform test this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain); // assertions Authentication authentication = this.securityContextRepository .loadContext(new HttpRequestResponseHolder(this.request, this.response)) .getAuthentication(); SecurityAssertions.assertThat(authentication).hasAuthority("OIDC_USER").isInstanceOf(OidcUserAuthority.class); } @Test public void requestWhenOauth2LoginInLambdaAndOidcThenAuthenticationContainsOidcUserAuthority() throws Exception { // setup application context loadConfig(OAuth2LoginInLambdaConfig.class, JwtDecoderFactoryConfig.class); // setup authorization request OAuth2AuthorizationRequest authorizationRequest = createOAuth2AuthorizationRequest("openid"); this.authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest, this.request, this.response); // setup authentication parameters this.request.setParameter("code", "code123"); this.request.setParameter("state", authorizationRequest.getState()); // perform test this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain); // assertions Authentication authentication = this.securityContextRepository .loadContext(new HttpRequestResponseHolder(this.request, this.response)) .getAuthentication(); assertThat(authentication.getAuthorities()).hasSize(1); SecurityAssertions.assertThat(authentication).hasAuthority("OIDC_USER").isInstanceOf(OidcUserAuthority.class); } @Test public void oidcLoginCustomWithConfigurer() throws Exception { // setup application context loadConfig(OAuth2LoginConfigCustomWithConfigurer.class, JwtDecoderFactoryConfig.class); // setup authorization request OAuth2AuthorizationRequest authorizationRequest = createOAuth2AuthorizationRequest("openid"); this.authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest, this.request, this.response); // setup authentication parameters this.request.setParameter("code", "code123"); this.request.setParameter("state", authorizationRequest.getState()); // perform test this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain); // assertions Authentication authentication = this.securityContextRepository .loadContext(new HttpRequestResponseHolder(this.request, this.response)) .getAuthentication(); SecurityAssertions.assertThat(authentication).hasAuthorities("OIDC_USER", "ROLE_OIDC_USER"); } @Test public void oidcLoginCustomWithBeanRegistration() throws Exception { // setup application context loadConfig(OAuth2LoginConfigCustomWithBeanRegistration.class, JwtDecoderFactoryConfig.class); // setup authorization request OAuth2AuthorizationRequest authorizationRequest = createOAuth2AuthorizationRequest("openid"); this.authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest, this.request, this.response); // setup authentication parameters this.request.setParameter("code", "code123"); this.request.setParameter("state", authorizationRequest.getState()); // perform test this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain); // assertions Authentication authentication = this.securityContextRepository .loadContext(new HttpRequestResponseHolder(this.request, this.response)) .getAuthentication(); SecurityAssertions.assertThat(authentication).hasAuthorities("OIDC_USER", "ROLE_OIDC_USER"); } @Test public void oidcLoginCustomWithNoUniqueJwtDecoderFactory() { assertThatExceptionOfType(BeanCreationException.class) .isThrownBy(() -> loadConfig(OAuth2LoginConfig.class, NoUniqueJwtDecoderFactoryConfig.class)) .withRootCauseInstanceOf(NoUniqueBeanDefinitionException.class) .withMessageContaining("No qualifying bean of type " + "'org.springframework.security.oauth2.jwt.JwtDecoderFactory' " + "available: expected single matching bean but found 2: jwtDecoderFactory1,jwtDecoderFactory2"); } @Test public void logoutWhenUsingOidcLogoutHandlerThenRedirects() throws Exception { this.spring.register(OAuth2LoginConfigWithOidcLogoutSuccessHandler.class).autowire(); OAuth2AuthenticationToken token = new OAuth2AuthenticationToken(TestOidcUsers.create(), AuthorityUtils.NO_AUTHORITIES, "registration-id"); this.mvc.perform(post("/logout").with(authentication(token)).with(csrf())) .andExpect(redirectedUrl("https://logout?id_token_hint=id-token")); } @Test public void configureWhenOidcSessionRegistryThenUses() { this.spring.register(OAuth2LoginWithOidcSessionRegistry.class).autowire(); OidcSessionRegistry registry = this.spring.getContext().getBean(OidcSessionRegistry.class); this.spring.getContext().publishEvent(new HttpSessionDestroyedEvent(this.request.getSession())); verify(registry).removeSessionInformation(this.request.getSession().getId()); } // gh-14558 @Test public void oauth2LoginWhenDefaultsThenNoOidcSessionRegistry() { this.spring.register(OAuth2LoginConfig.class).autowire(); DelegatingApplicationListener listener = this.spring.getContext().getBean(DelegatingApplicationListener.class); List listeners = (List) ReflectionTestUtils .getField(listener, "listeners"); assertThat(listeners.stream() .filter((l) -> l.supportsEventType(SessionDestroyedEvent.class)) .collect(Collectors.toList())).isEmpty(); } @Test public void oidcLoginWhenOAuth2ClientBeansConfiguredThenNotShared() throws Exception { this.spring.register(OAuth2LoginConfigWithOAuth2Client.class, JwtDecoderFactoryConfig.class).autowire(); OAuth2AuthorizationRequest authorizationRequest = createOAuth2AuthorizationRequest("openid"); this.authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest, this.request, this.response); this.request.setParameter("code", "code123"); this.request.setParameter("state", authorizationRequest.getState()); this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain); Authentication authentication = this.securityContextRepository .loadContext(new HttpRequestResponseHolder(this.request, this.response)) .getAuthentication(); SecurityAssertions.assertThat(authentication).hasAuthority("OIDC_USER").isInstanceOf(OidcUserAuthority.class); // Ensure shared objects set for OAuth2 Client are not used ClientRegistrationRepository clientRegistrationRepository = this.spring.getContext() .getBean(ClientRegistrationRepository.class); OAuth2AuthorizedClientRepository authorizedClientRepository = this.spring.getContext() .getBean(OAuth2AuthorizedClientRepository.class); verifyNoInteractions(clientRegistrationRepository, authorizedClientRepository); } // gh-17175 @Test public void oauth2LoginWhenAuthenticationProviderPostProcessorThenUses() throws Exception { loadConfig(OAuth2LoginConfigCustomWithPostProcessor.class); // setup authorization request OAuth2AuthorizationRequest authorizationRequest = createOAuth2AuthorizationRequest(); this.authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest, this.request, this.response); // setup authentication parameters this.request.setParameter("code", "code123"); this.request.setParameter("state", authorizationRequest.getState()); // perform test this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain); // assertions verify(this.context.getBean(SpyObjectPostProcessor.class).spy).authenticate(any()); } // gh-16623 @Test public void oauth2LoginWithCustomSecurityContextRepository() { assertThatNoException().isThrownBy(() -> loadConfig(OAuth2LoginConfigSecurityContextRepository.class)); } private void loadConfig(Class... configs) { AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext(); applicationContext.register(configs); applicationContext.refresh(); applicationContext.getAutowireCapableBeanFactory().autowireBean(this); this.context = applicationContext; } private OAuth2AuthorizationRequest createOAuth2AuthorizationRequest(String... scopes) { return this.createOAuth2AuthorizationRequest(GOOGLE_CLIENT_REGISTRATION, scopes); } private OAuth2AuthorizationRequest createOAuth2AuthorizationRequest(ClientRegistration registration, String... scopes) { // @formatter:off return OAuth2AuthorizationRequest.authorizationCode() .authorizationUri(registration.getProviderDetails().getAuthorizationUri()) .clientId(registration.getClientId()) .state("state123") .redirectUri("http://localhost") .attributes(Collections.singletonMap(OAuth2ParameterNames.REGISTRATION_ID, registration.getRegistrationId())) .scope(scopes) .build(); // @formatter:on } private static OAuth2AccessTokenResponseClient createOauth2AccessTokenResponseClient() { return (request) -> { Map additionalParameters = new HashMap<>(); if (request.getAuthorizationExchange().getAuthorizationRequest().getScopes().contains("openid")) { additionalParameters.put(OidcParameterNames.ID_TOKEN, "token123"); } return OAuth2AccessTokenResponse.withToken("accessToken123") .tokenType(OAuth2AccessToken.TokenType.BEARER) .additionalParameters(additionalParameters) .build(); }; } private static OAuth2UserService createOauth2UserService() { Map userAttributes = Collections.singletonMap("name", "spring"); return (request) -> new DefaultOAuth2User(Collections.singleton(new OAuth2UserAuthority(userAttributes)), userAttributes, "name"); } private static OAuth2UserService createOidcUserService() { OidcIdToken idToken = TestOidcIdTokens.idToken().build(); return (request) -> new DefaultOidcUser(Collections.singleton(new OidcUserAuthority(idToken)), idToken); } private static GrantedAuthoritiesMapper createGrantedAuthoritiesMapper() { return (authorities) -> { boolean isOidc = OidcUserAuthority.class.isInstance(authorities.iterator().next()); List mappedAuthorities = new ArrayList<>(authorities); mappedAuthorities.add(new SimpleGrantedAuthority(isOidc ? "ROLE_OIDC_USER" : "ROLE_OAUTH2_USER")); return mappedAuthorities; }; } @Configuration @EnableWebSecurity static class OAuth2LoginConfig extends CommonSecurityFilterChainConfig implements ApplicationListener { static List EVENTS = new ArrayList<>(); @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .oauth2Login((login) -> login .clientRegistrationRepository( new InMemoryClientRegistrationRepository(GOOGLE_CLIENT_REGISTRATION))); // @formatter:on return super.configureFilterChain(http); } @Override public void onApplicationEvent(AuthenticationSuccessEvent event) { EVENTS.add(event); } } @Configuration @EnableWebSecurity static class OAuth2LoginConfigFormLogin extends CommonSecurityFilterChainConfig { private final InMemoryClientRegistrationRepository clientRegistrationRepository = new InMemoryClientRegistrationRepository( GOOGLE_CLIENT_REGISTRATION); @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .oauth2Login((login) -> login .clientRegistrationRepository(this.clientRegistrationRepository)) .formLogin(withDefaults()); // @formatter:on return super.configureFilterChain(http); } } @Configuration @EnableWebSecurity static class OAuth2LoginInLambdaConfig extends CommonLambdaSecurityFilterChainConfig implements ApplicationListener { static List EVENTS = new ArrayList<>(); @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .oauth2Login((oauth2) -> oauth2 .clientRegistrationRepository( new InMemoryClientRegistrationRepository(GOOGLE_CLIENT_REGISTRATION)) ); // @formatter:on return super.configureFilterChain(http); } @Override public void onApplicationEvent(AuthenticationSuccessEvent event) { EVENTS.add(event); } } @Configuration @EnableWebSecurity static class OAuth2LoginConfigCustomWithConfigurer extends CommonSecurityFilterChainConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .oauth2Login((login) -> login .clientRegistrationRepository( new InMemoryClientRegistrationRepository(GOOGLE_CLIENT_REGISTRATION)) .userInfoEndpoint((info) -> info .userAuthoritiesMapper(createGrantedAuthoritiesMapper()))); // @formatter:on return super.configureFilterChain(http); } } @Configuration @EnableWebSecurity static class OAuth2LoginConfigCustomWithBeanRegistration extends CommonSecurityFilterChainConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .oauth2Login(withDefaults()); // @formatter:on return super.configureFilterChain(http); } @Bean ClientRegistrationRepository clientRegistrationRepository() { return new InMemoryClientRegistrationRepository(GOOGLE_CLIENT_REGISTRATION); } @Bean GrantedAuthoritiesMapper grantedAuthoritiesMapper() { return createGrantedAuthoritiesMapper(); } } @Configuration @EnableWebSecurity static class OAuth2LoginConfigCustomUserServiceBeanRegistration { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((requests) -> requests .anyRequest().authenticated()) .securityContext((context) -> context .securityContextRepository(securityContextRepository())) .oauth2Login((login) -> login .tokenEndpoint((token) -> token .accessTokenResponseClient(createOauth2AccessTokenResponseClient()))); return http.build(); // @formatter:on } @Bean ClientRegistrationRepository clientRegistrationRepository() { return new InMemoryClientRegistrationRepository(GOOGLE_CLIENT_REGISTRATION); } @Bean GrantedAuthoritiesMapper grantedAuthoritiesMapper() { return createGrantedAuthoritiesMapper(); } @Bean SecurityContextRepository securityContextRepository() { return new HttpSessionSecurityContextRepository(); } @Bean HttpSessionOAuth2AuthorizationRequestRepository oauth2AuthorizationRequestRepository() { return new HttpSessionOAuth2AuthorizationRequestRepository(); } @Bean OAuth2UserService oauth2UserService() { return createOauth2UserService(); } @Bean OAuth2UserService oidcUserService() { return createOidcUserService(); } } @Configuration @EnableWebSecurity static class OAuth2LoginConfigLoginProcessingUrl extends CommonSecurityFilterChainConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .oauth2Login((login) -> login .clientRegistrationRepository( new InMemoryClientRegistrationRepository(GOOGLE_CLIENT_REGISTRATION)) .loginProcessingUrl("/login/oauth2/*")); // @formatter:on return super.configureFilterChain(http); } } @Configuration @EnableWebSecurity static class OAuth2LoginConfigSecurityContextRepository extends CommonSecurityFilterChainConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .oauth2Login((login) -> login .clientRegistrationRepository( new InMemoryClientRegistrationRepository(GOOGLE_CLIENT_REGISTRATION)) .securityContextRepository(new NullSecurityContextRepository())); // @formatter:on return super.configureFilterChain(http); } } @Configuration @EnableWebSecurity static class OAuth2LoginConfigCustomAuthorizationRequestResolver extends CommonSecurityFilterChainConfig { private ClientRegistrationRepository clientRegistrationRepository = new InMemoryClientRegistrationRepository( GOOGLE_CLIENT_REGISTRATION); OAuth2AuthorizationRequestResolver resolver = mock(OAuth2AuthorizationRequestResolver.class); @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .oauth2Login((login) -> login .clientRegistrationRepository(this.clientRegistrationRepository) .authorizationEndpoint((authorize) -> authorize .authorizationRequestResolver(this.resolver))); // @formatter:on return super.configureFilterChain(http); } } @Configuration @EnableWebSecurity static class OAuth2LoginConfigCustomAuthorizationRequestResolverBean extends CommonSecurityFilterChainConfig { private ClientRegistrationRepository clientRegistrationRepository = new InMemoryClientRegistrationRepository( GOOGLE_CLIENT_REGISTRATION); @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .oauth2Login((login) -> login .clientRegistrationRepository(this.clientRegistrationRepository) .authorizationEndpoint(Customizer.withDefaults())); // @formatter:on return super.configureFilterChain(http); } @Bean OAuth2AuthorizationRequestResolver resolver() { OAuth2AuthorizationRequestResolver resolver = mock(OAuth2AuthorizationRequestResolver.class); // @formatter:off OAuth2AuthorizationRequest result = OAuth2AuthorizationRequest.authorizationCode() .authorizationUri("https://accounts.google.com/authorize") .clientId("client-id") .state("adsfa") .authorizationRequestUri( "https://accounts.google.com/o/oauth2/v2/auth?response_type=code&client_id=clientId&scope=openid+profile+email&state=state&redirect_uri=http%3A%2F%2Flocalhost%2Flogin%2Foauth2%2Fcode%2Fgoogle&custom-param1=custom-value1") .build(); given(resolver.resolve(any())).willReturn(result); // @formatter:on return resolver; } } @Configuration @EnableWebSecurity static class OAuth2LoginConfigCustomAuthorizationRequestResolverInLambda extends CommonLambdaSecurityFilterChainConfig { private ClientRegistrationRepository clientRegistrationRepository = new InMemoryClientRegistrationRepository( GOOGLE_CLIENT_REGISTRATION); OAuth2AuthorizationRequestResolver resolver = mock(OAuth2AuthorizationRequestResolver.class); @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .oauth2Login((oauth2) -> oauth2 .clientRegistrationRepository(this.clientRegistrationRepository) .authorizationEndpoint((authorizationEndpoint) -> authorizationEndpoint .authorizationRequestResolver(this.resolver) ) ); // @formatter:on return super.configureFilterChain(http); } } @Configuration @EnableWebSecurity static class OAuth2LoginConfigCustomAuthorizationRedirectStrategy extends CommonSecurityFilterChainConfig { private final ClientRegistrationRepository clientRegistrationRepository = new InMemoryClientRegistrationRepository( GOOGLE_CLIENT_REGISTRATION); RedirectStrategy redirectStrategy = mock(RedirectStrategy.class); @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .oauth2Login((oauth2) -> oauth2 .clientRegistrationRepository(this.clientRegistrationRepository) .authorizationEndpoint((authorizationEndpoint) -> authorizationEndpoint .authorizationRedirectStrategy(this.redirectStrategy) ) ); // @formatter:on return super.configureFilterChain(http); } } @EnableWebSecurity static class OAuth2LoginConfigCustomAuthorizationRedirectStrategyInLambda extends CommonLambdaSecurityFilterChainConfig { private final ClientRegistrationRepository clientRegistrationRepository = new InMemoryClientRegistrationRepository( GOOGLE_CLIENT_REGISTRATION); RedirectStrategy redirectStrategy = mock(RedirectStrategy.class); @Bean SecurityFilterChain configureFilterChain(HttpSecurity http) throws Exception { // @formatter:off http .oauth2Login((oauth2) -> oauth2 .clientRegistrationRepository(this.clientRegistrationRepository) .authorizationEndpoint((authorizationEndpoint) -> authorizationEndpoint .authorizationRedirectStrategy(this.redirectStrategy) ) ); // @formatter:on return super.configureFilterChain(http); } } @Configuration @EnableWebSecurity static class OAuth2LoginConfigMultipleClients extends CommonSecurityFilterChainConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .oauth2Login((login) -> login .clientRegistrationRepository( new InMemoryClientRegistrationRepository( GOOGLE_CLIENT_REGISTRATION, GITHUB_CLIENT_REGISTRATION))); // @formatter:on return super.configureFilterChain(http); } } @Configuration @EnableWebSecurity static class OAuth2LoginConfigAuthorizationCodeClientAndOtherClients extends CommonSecurityFilterChainConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .oauth2Login((login) -> login .clientRegistrationRepository( new InMemoryClientRegistrationRepository( GOOGLE_CLIENT_REGISTRATION, CLIENT_CREDENTIALS_REGISTRATION))); // @formatter:on return super.configureFilterChain(http); } } @Configuration @EnableWebSecurity static class OAuth2LoginConfigCustomLoginPage extends CommonSecurityFilterChainConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .oauth2Login((login) -> login .clientRegistrationRepository( new InMemoryClientRegistrationRepository(GOOGLE_CLIENT_REGISTRATION)) .loginPage("/custom-login")); // @formatter:on return super.configureFilterChain(http); } } @Configuration @EnableWebSecurity static class OAuth2LoginConfigCustomLoginPageInLambda extends CommonLambdaSecurityFilterChainConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .oauth2Login((oauth2) -> oauth2 .clientRegistrationRepository( new InMemoryClientRegistrationRepository(GOOGLE_CLIENT_REGISTRATION)) .loginPage("/custom-login") ); // @formatter:on return super.configureFilterChain(http); } } @Configuration @EnableWebSecurity static class OAuth2LoginConfigWithOidcLogoutSuccessHandler extends CommonSecurityFilterChainConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .logout((logout) -> logout .logoutSuccessHandler(oidcLogoutSuccessHandler())); // @formatter:on return super.configureFilterChain(http); } @Bean OidcClientInitiatedLogoutSuccessHandler oidcLogoutSuccessHandler() { return new OidcClientInitiatedLogoutSuccessHandler(clientRegistrationRepository()); } @Bean ClientRegistrationRepository clientRegistrationRepository() { Map providerMetadata = Collections.singletonMap("end_session_endpoint", "https://logout"); return new InMemoryClientRegistrationRepository(TestClientRegistrations.clientRegistration() .providerConfigurationMetadata(providerMetadata) .build()); } } @Configuration @EnableWebSecurity static class OAuth2LoginWithHttpBasicConfig extends CommonSecurityFilterChainConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .oauth2Login((login) -> login .clientRegistrationRepository( new InMemoryClientRegistrationRepository(GOOGLE_CLIENT_REGISTRATION))) .httpBasic(withDefaults()); // @formatter:on return super.configureFilterChain(http); } } @Configuration @EnableWebSecurity static class OAuth2LoginWithOidcSessionRegistry { private final OidcSessionRegistry registry = mock(OidcSessionRegistry.class); @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .oauth2Login((oauth2) -> oauth2 .clientRegistrationRepository( new InMemoryClientRegistrationRepository(GOOGLE_CLIENT_REGISTRATION)) .oidcSessionRegistry(this.registry) ); // @formatter:on return http.build(); } @Bean OidcSessionRegistry oidcSessionRegistry() { return this.registry; } } @Configuration @EnableWebSecurity static class OAuth2LoginWithXHREntryPointConfig extends CommonSecurityFilterChainConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .oauth2Login((login) -> login .clientRegistrationRepository( new InMemoryClientRegistrationRepository(GOOGLE_CLIENT_REGISTRATION))) .exceptionHandling((handling) -> handling .defaultAuthenticationEntryPointFor( new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED), new RequestHeaderRequestMatcher("X-Requested-With", "XMLHttpRequest"))); // @formatter:on return super.configureFilterChain(http); } } @Configuration @EnableWebSecurity static class OAuth2LoginConfigWithOAuth2Client extends CommonLambdaSecurityFilterChainConfig { private final ClientRegistrationRepository clientRegistrationRepository = mock( ClientRegistrationRepository.class); private final OAuth2AuthorizedClientRepository authorizedClientRepository = mock( OAuth2AuthorizedClientRepository.class); @Bean SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { // @formatter:off http .oauth2Login((oauth2Login) -> oauth2Login .clientRegistrationRepository( new InMemoryClientRegistrationRepository(GOOGLE_CLIENT_REGISTRATION)) .authorizedClientRepository(new HttpSessionOAuth2AuthorizedClientRepository()) ) .oauth2Client((oauth2Client) -> oauth2Client .clientRegistrationRepository(this.clientRegistrationRepository) .authorizedClientRepository(this.authorizedClientRepository) ); // @formatter:on return super.configureFilterChain(http); } @Bean ClientRegistrationRepository clientRegistrationRepository() { return this.clientRegistrationRepository; } @Bean OAuth2AuthorizedClientRepository authorizedClientRepository() { return this.authorizedClientRepository; } } @Configuration @EnableWebSecurity static class OAuth2LoginConfigCustomWithPostProcessor { private final ClientRegistrationRepository clientRegistrationRepository = new InMemoryClientRegistrationRepository( GOOGLE_CLIENT_REGISTRATION); private final ObjectPostProcessor postProcessor = new SpyObjectPostProcessor(); @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .oauth2Login((oauth2Login) -> oauth2Login .clientRegistrationRepository(this.clientRegistrationRepository) .withObjectPostProcessor(this.postProcessor) ); // @formatter:on return http.build(); } @Bean ObjectPostProcessor mockPostProcessor() { return this.postProcessor; } @Bean HttpSessionOAuth2AuthorizationRequestRepository oauth2AuthorizationRequestRepository() { return new HttpSessionOAuth2AuthorizationRequestRepository(); } static class SpyObjectPostProcessor implements ObjectPostProcessor { AuthenticationProvider spy; @Override public O postProcess(O object) { O spy = Mockito.spy(object); this.spy = spy; return spy; } } } private abstract static class CommonSecurityFilterChainConfig { SecurityFilterChain configureFilterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((requests) -> requests .anyRequest().authenticated()) .securityContext((context) -> context .securityContextRepository(securityContextRepository())) .oauth2Login((login) -> login .tokenEndpoint((token) -> token .accessTokenResponseClient(createOauth2AccessTokenResponseClient())) .userInfoEndpoint((info) -> info .userService(createOauth2UserService()) .oidcUserService(createOidcUserService()))); // @formatter:on return http.build(); } @Bean SecurityContextRepository securityContextRepository() { return new HttpSessionSecurityContextRepository(); } @Bean HttpSessionOAuth2AuthorizationRequestRepository oauth2AuthorizationRequestRepository() { return new HttpSessionOAuth2AuthorizationRequestRepository(); } } private abstract static class CommonLambdaSecurityFilterChainConfig { SecurityFilterChain configureFilterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((authorize) -> authorize .anyRequest().authenticated() ) .securityContext((securityContext) -> securityContext .securityContextRepository(securityContextRepository()) ) .oauth2Login((oauth2) -> oauth2 .tokenEndpoint((tokenEndpoint) -> tokenEndpoint .accessTokenResponseClient(createOauth2AccessTokenResponseClient()) ) .userInfoEndpoint((userInfoEndpoint) -> userInfoEndpoint .userService(createOauth2UserService()) .oidcUserService(createOidcUserService()) ) ); // @formatter:on return http.build(); } @Bean SecurityContextRepository securityContextRepository() { return new HttpSessionSecurityContextRepository(); } @Bean HttpSessionOAuth2AuthorizationRequestRepository oauth2AuthorizationRequestRepository() { return new HttpSessionOAuth2AuthorizationRequestRepository(); } } @Configuration static class JwtDecoderFactoryConfig { @Bean JwtDecoderFactory jwtDecoderFactory() { return (clientRegistration) -> getJwtDecoder(); } private static JwtDecoder getJwtDecoder() { Map claims = new HashMap<>(); claims.put(IdTokenClaimNames.SUB, "sub123"); claims.put(IdTokenClaimNames.ISS, "http://localhost/iss"); claims.put(IdTokenClaimNames.AUD, Arrays.asList("clientId", "a", "u", "d")); claims.put(IdTokenClaimNames.AZP, "clientId"); Jwt jwt = TestJwts.jwt().claims((c) -> c.putAll(claims)).build(); JwtDecoder jwtDecoder = mock(JwtDecoder.class); given(jwtDecoder.decode(any())).willReturn(jwt); return jwtDecoder; } } @Configuration static class NoUniqueJwtDecoderFactoryConfig { @Bean JwtDecoderFactory jwtDecoderFactory1() { return (clientRegistration) -> JwtDecoderFactoryConfig.getJwtDecoder(); } @Bean JwtDecoderFactory jwtDecoderFactory2() { return (clientRegistration) -> JwtDecoderFactoryConfig.getJwtDecoder(); } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OidcBackChannelLogoutHandlerTests.java ================================================ /* * 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 org.springframework.security.config.annotation.web.configurers.oauth2.client; import org.junit.jupiter.api.Test; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.security.oauth2.client.oidc.authentication.logout.TestOidcLogoutTokens; import org.springframework.security.oauth2.client.oidc.session.InMemoryOidcSessionRegistry; import org.springframework.security.oauth2.client.oidc.session.OidcSessionRegistry; import org.springframework.security.oauth2.client.registration.TestClientRegistrations; import static org.assertj.core.api.Assertions.assertThat; public class OidcBackChannelLogoutHandlerTests { private final OidcSessionRegistry sessionRegistry = new InMemoryOidcSessionRegistry(); private final OidcBackChannelLogoutAuthentication token = new OidcBackChannelLogoutAuthentication( TestOidcLogoutTokens.withSubject("issuer", "subject").build(), TestClientRegistrations.clientRegistration().build()); // gh-14553 @Test public void computeLogoutEndpointWhenDifferentHostnameThenLocalhost() { OidcBackChannelLogoutHandler logoutHandler = new OidcBackChannelLogoutHandler(this.sessionRegistry); MockHttpServletRequest request = new MockHttpServletRequest("GET", "/back-channel/logout"); logoutHandler.setLogoutUri("{baseScheme}://localhost{basePort}/logout"); request.setServerName("host.docker.internal"); request.setServerPort(8090); String endpoint = logoutHandler.computeLogoutEndpoint(request, this.token); assertThat(endpoint).startsWith("http://localhost:8090/logout"); } @Test public void computeLogoutEndpointWhenUsingBaseUrlTemplateThenServerName() { OidcBackChannelLogoutHandler logoutHandler = new OidcBackChannelLogoutHandler(this.sessionRegistry); logoutHandler.setLogoutUri("{baseUrl}/logout"); MockHttpServletRequest request = new MockHttpServletRequest("GET", "/back-channel/logout"); request.setServerName("host.docker.internal"); request.setServerPort(8090); String endpoint = logoutHandler.computeLogoutEndpoint(request, this.token); assertThat(endpoint).startsWith("http://host.docker.internal:8090/logout"); } // gh-14609 @Test public void computeLogoutEndpointWhenLogoutUriThenUses() { OidcBackChannelLogoutHandler logoutHandler = new OidcBackChannelLogoutHandler(this.sessionRegistry); logoutHandler.setLogoutUri("http://localhost:8090/logout"); MockHttpServletRequest request = new MockHttpServletRequest("GET", "/back-channel/logout"); request.setScheme("https"); request.setServerName("server-one.com"); request.setServerPort(80); String endpoint = logoutHandler.computeLogoutEndpoint(request, this.token); assertThat(endpoint).startsWith("http://localhost:8090/logout"); } } ================================================ FILE: config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OidcLogoutConfigurerTests.java ================================================ /* * 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 org.springframework.security.config.annotation.web.configurers.oauth2.client; import java.io.IOException; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.interfaces.RSAPublicKey; import java.time.Instant; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Consumer; import com.nimbusds.jose.jwk.JWKSet; import com.nimbusds.jose.jwk.RSAKey; import com.nimbusds.jose.jwk.source.ImmutableJWKSet; import com.nimbusds.jose.jwk.source.JWKSource; import com.nimbusds.jose.proc.SecurityContext; import com.nimbusds.oauth2.sdk.Scope; import com.nimbusds.oauth2.sdk.token.BearerAccessToken; import com.nimbusds.openid.connect.sdk.token.OIDCTokens; import jakarta.annotation.PreDestroy; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpSession; import okhttp3.mockwebserver.Dispatcher; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; import okhttp3.mockwebserver.RecordedRequest; import org.htmlunit.util.UrlUtils; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.core.annotation.Order; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.mock.web.MockHttpSession; import org.springframework.mock.web.MockServletContext; import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.oauth2.client.oidc.authentication.logout.LogoutTokenClaimNames; import org.springframework.security.oauth2.client.oidc.authentication.logout.OidcLogoutToken; import org.springframework.security.oauth2.client.oidc.authentication.logout.TestOidcLogoutTokens; import org.springframework.security.oauth2.client.oidc.session.InMemoryOidcSessionRegistry; import org.springframework.security.oauth2.client.oidc.session.OidcSessionRegistry; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository; import org.springframework.security.oauth2.client.registration.TestClientRegistrations; import org.springframework.security.oauth2.core.oidc.OidcIdToken; import org.springframework.security.oauth2.core.oidc.TestOidcIdTokens; import org.springframework.security.oauth2.core.oidc.user.OidcUser; import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm; import org.springframework.security.oauth2.jwt.JwsHeader; import org.springframework.security.oauth2.jwt.JwtClaimsSet; import org.springframework.security.oauth2.jwt.JwtEncoder; import org.springframework.security.oauth2.jwt.JwtEncoderParameters; import org.springframework.security.oauth2.jwt.NimbusJwtEncoder; import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.logout.LogoutHandler; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.hamcrest.Matchers.containsString; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.willThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Tests for {@link OidcLogoutConfigurer} */ @ExtendWith(SpringTestContextExtension.class) public class OidcLogoutConfigurerTests { @Autowired private MockMvc mvc; @Autowired(required = false) private MockWebServer web; @Autowired private ClientRegistration clientRegistration; public final SpringTestContext spring = new SpringTestContext(this); @Test void logoutWhenDefaultsThenRemotelyInvalidatesSessions() throws Exception { this.spring.register(WebServerConfig.class, OidcProviderConfig.class, DefaultConfig.class).autowire(); String registrationId = this.clientRegistration.getRegistrationId(); MockHttpSession session = login(); String logoutToken = this.mvc.perform(get("/token/logout").session(session)) .andExpect(status().isOk()) .andReturn() .getResponse() .getContentAsString(); this.mvc .perform(post(this.web.url("/logout/connect/back-channel/" + registrationId).toString()) .param("logout_token", logoutToken)) .andExpect(status().isOk()); this.mvc.perform(get("/token/logout").session(session)).andExpect(status().isUnauthorized()); } @Test void logoutWhenInvalidLogoutTokenThenBadRequest() throws Exception { this.spring.register(WebServerConfig.class, OidcProviderConfig.class, DefaultConfig.class).autowire(); this.mvc.perform(get("/token/logout")).andExpect(status().isUnauthorized()); String registrationId = this.clientRegistration.getRegistrationId(); MvcResult result = this.mvc.perform(get("/oauth2/authorization/" + registrationId)) .andExpect(status().isFound()) .andReturn(); MockHttpSession session = (MockHttpSession) result.getRequest().getSession(); String redirectUrl = UrlUtils.decode(result.getResponse().getRedirectedUrl()); String state = this.mvc .perform(get(redirectUrl) .with(httpBasic(this.clientRegistration.getClientId(), this.clientRegistration.getClientSecret()))) .andReturn() .getResponse() .getContentAsString(); result = this.mvc .perform(get("/login/oauth2/code/" + registrationId).param("code", "code") .param("state", state) .session(session)) .andExpect(status().isFound()) .andReturn(); session = (MockHttpSession) result.getRequest().getSession(); this.mvc .perform(post(this.web.url("/logout/connect/back-channel/" + registrationId).toString()) .param("logout_token", "invalid")) .andExpect(status().isBadRequest()); this.mvc.perform(get("/token/logout").session(session)).andExpect(status().isOk()); } @Test void logoutWhenLogoutTokenSpecifiesOneSessionThenRemotelyInvalidatesOnlyThatSession() throws Exception { this.spring.register(WebServerConfig.class, OidcProviderConfig.class, DefaultConfig.class).autowire(); String registrationId = this.clientRegistration.getRegistrationId(); MockHttpSession one = login(); MockHttpSession two = login(); MockHttpSession three = login(); String logoutToken = this.mvc.perform(get("/token/logout").session(one)) .andExpect(status().isOk()) .andReturn() .getResponse() .getContentAsString(); this.mvc .perform(post(this.web.url("/logout/connect/back-channel/" + registrationId).toString()) .param("logout_token", logoutToken)) .andExpect(status().isOk()); this.mvc.perform(get("/token/logout").session(one)).andExpect(status().isUnauthorized()); this.mvc.perform(get("/token/logout").session(two)).andExpect(status().isOk()); logoutToken = this.mvc.perform(get("/token/logout/all").session(three)) .andExpect(status().isOk()) .andReturn() .getResponse() .getContentAsString(); this.mvc .perform(post(this.web.url("/logout/connect/back-channel/" + registrationId).toString()) .param("logout_token", logoutToken)) .andExpect(status().isOk()); this.mvc.perform(get("/token/logout").session(two)).andExpect(status().isUnauthorized()); this.mvc.perform(get("/token/logout").session(three)).andExpect(status().isUnauthorized()); } @Test void logoutWhenRemoteLogoutUriThenUses() throws Exception { this.spring.register(WebServerConfig.class, OidcProviderConfig.class, LogoutUriConfig.class).autowire(); String registrationId = this.clientRegistration.getRegistrationId(); MockHttpSession one = login(); String logoutToken = this.mvc.perform(get("/token/logout/all").session(one)) .andExpect(status().isOk()) .andReturn() .getResponse() .getContentAsString(); this.mvc .perform(post(this.web.url("/logout/connect/back-channel/" + registrationId).toString()) .param("logout_token", logoutToken)) .andExpect(status().isBadRequest()) .andExpect(content().string(containsString("partial_logout"))) .andExpect(content().string(containsString("not all sessions were terminated"))); this.mvc.perform(get("/token/logout").session(one)).andExpect(status().isOk()); } @Test void logoutWhenSelfRemoteLogoutUriThenUses() throws Exception { this.spring.register(WebServerConfig.class, OidcProviderConfig.class, SelfLogoutUriConfig.class).autowire(); String registrationId = this.clientRegistration.getRegistrationId(); MockHttpSession session = login(); String logoutToken = this.mvc.perform(get("/token/logout").session(session)) .andExpect(status().isOk()) .andReturn() .getResponse() .getContentAsString(); this.mvc .perform(post(this.web.url("/logout/connect/back-channel/" + registrationId).toString()) .param("logout_token", logoutToken)) .andExpect(status().isOk()); this.mvc.perform(get("/token/logout").session(session)).andExpect(status().isUnauthorized()); } @Test void logoutWhenDifferentCookieNameThenUses() throws Exception { this.spring.register(OidcProviderConfig.class, CookieConfig.class).autowire(); String registrationId = this.clientRegistration.getRegistrationId(); MockHttpSession session = login(); String logoutToken = this.mvc.perform(get("/token/logout").session(session)) .andExpect(status().isOk()) .andReturn() .getResponse() .getContentAsString(); this.mvc .perform(post(this.web.url("/logout/connect/back-channel/" + registrationId).toString()) .param("logout_token", logoutToken)) .andExpect(status().isOk()); this.mvc.perform(get("/token/logout").session(session)).andExpect(status().isUnauthorized()); } @Test void logoutWhenRemoteLogoutFailsThenReportsPartialLogout() throws Exception { this.spring.register(WebServerConfig.class, OidcProviderConfig.class, WithBrokenLogoutConfig.class).autowire(); LogoutHandler logoutHandler = this.spring.getContext().getBean(LogoutHandler.class); willThrow(IllegalStateException.class).given(logoutHandler).logout(any(), any(), any()); String registrationId = this.clientRegistration.getRegistrationId(); MockHttpSession one = login(); String logoutToken = this.mvc.perform(get("/token/logout/all").session(one)) .andExpect(status().isOk()) .andReturn() .getResponse() .getContentAsString(); this.mvc .perform(post(this.web.url("/logout/connect/back-channel/" + registrationId).toString()) .param("logout_token", logoutToken)) .andExpect(status().isBadRequest()) .andExpect(content().string(containsString("partial_logout"))); this.mvc.perform(get("/token/logout").session(one)).andExpect(status().isOk()); } @Test void logoutWhenCustomComponentsThenUses() throws Exception { this.spring.register(WebServerConfig.class, OidcProviderConfig.class, WithCustomComponentsConfig.class) .autowire(); String registrationId = this.clientRegistration.getRegistrationId(); MockHttpSession session = login(); String logoutToken = this.mvc.perform(get("/token/logout").session(session)) .andExpect(status().isOk()) .andReturn() .getResponse() .getContentAsString(); this.mvc .perform(post(this.web.url("/logout/connect/back-channel/" + registrationId).toString()) .param("logout_token", logoutToken)) .andExpect(status().isOk()); this.mvc.perform(get("/token/logout").session(session)).andExpect(status().isUnauthorized()); OidcSessionRegistry sessionRegistry = this.spring.getContext().getBean(OidcSessionRegistry.class); verify(sessionRegistry).saveSessionInformation(any()); verify(sessionRegistry).removeSessionInformation(any(OidcLogoutToken.class)); } @Test void logoutWhenProviderIssuerMissingThenThrowIllegalArgumentException() throws Exception { this.spring.register(WebServerConfig.class, OidcProviderConfig.class, ProviderIssuerMissingConfig.class) .autowire(); String registrationId = this.clientRegistration.getRegistrationId(); MockHttpSession session = login(); String logoutToken = this.mvc.perform(get("/token/logout").session(session)) .andExpect(status().isOk()) .andReturn() .getResponse() .getContentAsString(); assertThatIllegalArgumentException().isThrownBy( () -> this.mvc.perform(post(this.web.url("/logout/connect/back-channel/" + registrationId).toString()) .param("logout_token", logoutToken))); } private MockHttpSession login() throws Exception { MockMvcDispatcher dispatcher = (MockMvcDispatcher) this.web.getDispatcher(); this.mvc.perform(get("/token/logout")).andExpect(status().isUnauthorized()); String registrationId = this.clientRegistration.getRegistrationId(); MvcResult result = this.mvc.perform(get("/oauth2/authorization/" + registrationId)) .andExpect(status().isFound()) .andReturn(); MockHttpSession session = (MockHttpSession) result.getRequest().getSession(); String redirectUrl = UrlUtils.decode(result.getResponse().getRedirectedUrl()); String state = this.mvc .perform(get(redirectUrl) .with(httpBasic(this.clientRegistration.getClientId(), this.clientRegistration.getClientSecret()))) .andReturn() .getResponse() .getContentAsString(); result = this.mvc .perform(get("/login/oauth2/code/" + registrationId).param("code", "code") .param("state", state) .session(session)) .andExpect(status().isFound()) .andReturn(); session = (MockHttpSession) result.getRequest().getSession(); dispatcher.registerSession(session); return session; } @Configuration static class RegistrationConfig { @Autowired(required = false) MockWebServer web; @Bean ClientRegistration clientRegistration() { if (this.web == null) { return TestClientRegistrations.clientRegistration().build(); } String issuer = this.web.url("/").toString(); return TestClientRegistrations.clientRegistration() .issuerUri(issuer) .jwkSetUri(issuer + "jwks") .tokenUri(issuer + "token") .userInfoUri(issuer + "user") .scope("openid") .build(); } @Bean ClientRegistrationRepository clientRegistrationRepository(ClientRegistration clientRegistration) { return new InMemoryClientRegistrationRepository(clientRegistration); } } @Configuration @EnableWebSecurity @Import(RegistrationConfig.class) static class DefaultConfig { @Bean @Order(1) SecurityFilterChain filters(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated()) .oauth2Login(Customizer.withDefaults()) .oidcLogout((oidc) -> oidc.backChannel(Customizer.withDefaults())); // @formatter:on return http.build(); } } @Configuration @EnableWebSecurity @Import(RegistrationConfig.class) static class LogoutUriConfig { @Bean @Order(1) SecurityFilterChain filters(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated()) .oauth2Login(Customizer.withDefaults()) .oidcLogout((oidc) -> oidc .backChannel((backchannel) -> backchannel.logoutUri("http://localhost/wrong")) ); // @formatter:on return http.build(); } } @Configuration @EnableWebSecurity @Import(RegistrationConfig.class) static class SelfLogoutUriConfig { @Bean @Order(1) SecurityFilterChain filters(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated()) .oauth2Login(Customizer.withDefaults()) .oidcLogout((oidc) -> oidc .backChannel(Customizer.withDefaults()) ); // @formatter:on return http.build(); } } @Configuration @EnableWebSecurity @Import(RegistrationConfig.class) static class CookieConfig { private final MockWebServer server = new MockWebServer(); @Bean @Order(1) SecurityFilterChain filters(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated()) .oauth2Login(Customizer.withDefaults()) .oidcLogout((oidc) -> oidc .backChannel(Customizer.withDefaults()) ); // @formatter:on return http.build(); } @Bean OidcSessionRegistry sessionRegistry() { return new InMemoryOidcSessionRegistry(); } @Bean OidcBackChannelLogoutHandler oidcLogoutHandler(OidcSessionRegistry sessionRegistry) { OidcBackChannelLogoutHandler logoutHandler = new OidcBackChannelLogoutHandler(sessionRegistry); logoutHandler.setSessionCookieName("SESSION"); return logoutHandler; } @Bean MockWebServer web(ObjectProvider mvc) { MockMvcDispatcher dispatcher = new MockMvcDispatcher(mvc); dispatcher.setAssertion((rr) -> { String cookie = rr.getHeaders().get("Cookie"); if (cookie == null) { return; } assertThat(cookie).contains("SESSION").doesNotContain("JSESSIONID"); }); this.server.setDispatcher(dispatcher); return this.server; } @PreDestroy void shutdown() throws IOException { this.server.shutdown(); } } @Configuration @EnableWebSecurity @Import(RegistrationConfig.class) static class WithCustomComponentsConfig { OidcSessionRegistry sessionRegistry = spy(new InMemoryOidcSessionRegistry()); @Bean @Order(1) SecurityFilterChain filters(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated()) .oauth2Login(Customizer.withDefaults()) .oidcLogout((oidc) -> oidc.backChannel(Customizer.withDefaults())); // @formatter:on return http.build(); } @Bean OidcSessionRegistry sessionRegistry() { return this.sessionRegistry; } } @Configuration @EnableWebSecurity @Import(RegistrationConfig.class) static class WithBrokenLogoutConfig { private final LogoutHandler logoutHandler = mock(LogoutHandler.class); @Bean @Order(1) SecurityFilterChain filters(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated()) .logout((logout) -> logout.addLogoutHandler(this.logoutHandler)) .oauth2Login(Customizer.withDefaults()) .oidcLogout((oidc) -> oidc.backChannel(Customizer.withDefaults())); // @formatter:on return http.build(); } @Bean LogoutHandler logoutHandler() { return this.logoutHandler; } } @Configuration static class ProviderIssuerMissingRegistrationConfig { @Autowired(required = false) MockWebServer web; @Bean ClientRegistration clientRegistration() { if (this.web == null) { return TestClientRegistrations.clientRegistration().issuerUri(null).build(); } String issuer = this.web.url("/").toString(); return TestClientRegistrations.clientRegistration() .issuerUri(null) .jwkSetUri(issuer + "jwks") .tokenUri(issuer + "token") .userInfoUri(issuer + "user") .scope("openid") .build(); } @Bean ClientRegistrationRepository clientRegistrationRepository(ClientRegistration clientRegistration) { return new InMemoryClientRegistrationRepository(clientRegistration); } } @Configuration @EnableWebSecurity @Import(ProviderIssuerMissingRegistrationConfig.class) static class ProviderIssuerMissingConfig { @Bean @Order(1) SecurityFilterChain filters(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated()) .oauth2Login(Customizer.withDefaults()) .oidcLogout((oidc) -> oidc.backChannel(Customizer.withDefaults())); // @formatter:on return http.build(); } } @Configuration @EnableWebSecurity @EnableWebMvc @RestController static class OidcProviderConfig { private static final RSAKey key = key(); private static final JWKSource jwks = jwks(key); private static RSAKey key() { try { KeyPair pair = KeyPairGenerator.getInstance("RSA").generateKeyPair(); return new RSAKey.Builder((RSAPublicKey) pair.getPublic()).privateKey(pair.getPrivate()).build(); } catch (Exception ex) { throw new RuntimeException(ex); } } private static JWKSource jwks(RSAKey key) { try { return new ImmutableJWKSet<>(new JWKSet(key)); } catch (Exception ex) { throw new RuntimeException(ex); } } private final String username = "user"; private final JwtEncoder encoder = new NimbusJwtEncoder(jwks); private String nonce; @Autowired ClientRegistration registration; @Autowired(required = false) MockWebServer web; @Bean @Order(0) SecurityFilterChain authorizationServer(HttpSecurity http, ClientRegistration registration) throws Exception { // @formatter:off http .securityMatcher("/jwks", "/login/oauth/authorize", "/nonce", "/token", "/token/logout", "/user") .authorizeHttpRequests((authorize) -> authorize .requestMatchers("/jwks").permitAll() .anyRequest().authenticated() ) .httpBasic(Customizer.withDefaults()) .oauth2ResourceServer((oauth2) -> oauth2 .jwt((jwt) -> jwt.jwkSetUri(registration.getProviderDetails().getJwkSetUri())) ); // @formatter:off return http.build(); } @Bean UserDetailsService users(ClientRegistration registration) { return new InMemoryUserDetailsManager(User.withUsername(registration.getClientId()) .password("{noop}" + registration.getClientSecret()).authorities("APP").build()); } @GetMapping("/login/oauth/authorize") String nonce(@RequestParam("nonce") String nonce, @RequestParam("state") String state) { this.nonce = nonce; return state; } @PostMapping("/token") Map accessToken(HttpServletRequest request) { HttpSession session = request.getSession(); JwtEncoderParameters parameters = JwtEncoderParameters .from(JwtClaimsSet.builder().id("id").subject(this.username) .issuer(getIssuerUri()).issuedAt(Instant.now()) .expiresAt(Instant.now().plusSeconds(86400)).claim("scope", "openid").build()); String token = this.encoder.encode(parameters).getTokenValue(); return new OIDCTokens(idToken(session.getId()), new BearerAccessToken(token, 86400, new Scope("openid")), null) .toJSONObject(); } String idToken(String sessionId) { OidcIdToken token = TestOidcIdTokens.idToken().issuer(getIssuerUri()) .subject(this.username).expiresAt(Instant.now().plusSeconds(86400)) .audience(List.of(this.registration.getClientId())).nonce(this.nonce) .claim(LogoutTokenClaimNames.SID, sessionId).build(); JwtEncoderParameters parameters = JwtEncoderParameters .from(JwtClaimsSet.builder().claims((claims) -> claims.putAll(token.getClaims())).build()); return this.encoder.encode(parameters).getTokenValue(); } private String getIssuerUri() { if (this.web == null) { return TestClientRegistrations.clientRegistration().build().getProviderDetails().getIssuerUri(); } return this.web.url("/").toString(); } @GetMapping("/user") Map userinfo() { return Map.of("sub", this.username, "id", this.username); } @GetMapping("/jwks") String jwks() { return new JWKSet(key).toString(); } @GetMapping("/token/logout") String logoutToken(@AuthenticationPrincipal OidcUser user) { OidcLogoutToken token = TestOidcLogoutTokens.withUser(user) .audience(List.of(this.registration.getClientId())).build(); JwsHeader header = JwsHeader.with(SignatureAlgorithm.RS256).type("logout+jwt").build(); JwtClaimsSet claims = JwtClaimsSet.builder().claims((c) -> c.putAll(token.getClaims())).build(); JwtEncoderParameters parameters = JwtEncoderParameters.from(header, claims); return this.encoder.encode(parameters).getTokenValue(); } @GetMapping("/token/logout/all") String logoutTokenAll(@AuthenticationPrincipal OidcUser user) { OidcLogoutToken token = TestOidcLogoutTokens.withUser(user) .audience(List.of(this.registration.getClientId())) .claims((claims) -> claims.remove(LogoutTokenClaimNames.SID)).build(); JwsHeader header = JwsHeader.with(SignatureAlgorithm.RS256).type("JWT").build(); JwtClaimsSet claims = JwtClaimsSet.builder().claims((c) -> c.putAll(token.getClaims())).build(); JwtEncoderParameters parameters = JwtEncoderParameters.from(header, claims); return this.encoder.encode(parameters).getTokenValue(); } } @Configuration static class WebServerConfig { private final MockWebServer server = new MockWebServer(); @Bean MockWebServer web(ObjectProvider mvc) { this.server.setDispatcher(new MockMvcDispatcher(mvc)); return this.server; } @PreDestroy void shutdown() throws IOException { this.server.shutdown(); } } private static class MockMvcDispatcher extends Dispatcher { private final Map session = new ConcurrentHashMap<>(); private final ObjectProvider mvcProvider; private MockMvc mvc; private Consumer assertion = (rr) -> { }; MockMvcDispatcher(ObjectProvider mvc) { this.mvcProvider = mvc; } @Override public MockResponse dispatch(RecordedRequest request) throws InterruptedException { this.assertion.accept(request); this.mvc = this.mvcProvider.getObject(); String method = request.getMethod(); String path = request.getPath(); String csrf = request.getHeader("X-CSRF-TOKEN"); MockHttpSession session = session(request); MockHttpServletRequestBuilder builder; if ("GET".equals(method)) { builder = get(path); } else { builder = post(path).content(request.getBody().readUtf8()); if (csrf != null) { builder.header("X-CSRF-TOKEN", csrf); } else { builder.with(csrf()); } } for (Map.Entry> header : request.getHeaders().toMultimap().entrySet()) { builder.header(header.getKey(), header.getValue().iterator().next()); } try { MockHttpServletResponse mvcResponse = this.mvc.perform(builder.session(session)).andReturn().getResponse(); return toMockResponse(mvcResponse); } catch (Exception ex) { MockResponse response = new MockResponse(); response.setResponseCode(500); return response; } } void registerSession(MockHttpSession session) { this.session.put(session.getId(), session); } void setAssertion(Consumer assertion) { this.assertion = assertion; } private MockHttpSession session(RecordedRequest request) { String cookieHeaderValue = request.getHeader("Cookie"); if (cookieHeaderValue == null) { return new MockHttpSession(); } String[] cookies = cookieHeaderValue.split(";"); for (String cookie : cookies) { String[] parts = cookie.split("="); if ("JSESSIONID".equals(parts[0])) { return this.session.computeIfAbsent(parts[1], (k) -> new MockHttpSession(new MockServletContext(), parts[1])); } if ("SESSION".equals(parts[0])) { return this.session.computeIfAbsent(parts[1], (k) -> new MockHttpSession(new MockServletContext(), parts[1])); } } return new MockHttpSession(); } private MockResponse toMockResponse(MockHttpServletResponse mvcResponse) { MockResponse response = new MockResponse(); response.setResponseCode(mvcResponse.getStatus()); for (String name : mvcResponse.getHeaderNames()) { response.addHeader(name, mvcResponse.getHeaderValue(name)); } response.setBody(getContentAsString(mvcResponse)); return response; } private String getContentAsString(MockHttpServletResponse response) { try { return response.getContentAsString(); } catch (Exception ex) { throw new RuntimeException(ex); } } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OidcUserRefreshedEventListenerConfigurationTests.java ================================================ /* * 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 org.springframework.security.config.annotation.web.configurers.oauth2.client; import java.time.Instant; import java.time.temporal.ChronoUnit; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import jakarta.servlet.http.HttpServletRequest; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.oauth2.client.CommonOAuth2Provider; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextImpl; import org.springframework.security.oauth2.client.OAuth2AuthorizeRequest; import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager; import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient; import org.springframework.security.oauth2.client.endpoint.OAuth2RefreshTokenGrantRequest; import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest; import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserService; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository; import org.springframework.security.oauth2.core.OAuth2AccessToken; import org.springframework.security.oauth2.core.OAuth2RefreshToken; import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; import org.springframework.security.oauth2.core.endpoint.TestOAuth2AccessTokenResponses; import org.springframework.security.oauth2.core.oidc.IdTokenClaimNames; import org.springframework.security.oauth2.core.oidc.OidcIdToken; import org.springframework.security.oauth2.core.oidc.OidcScopes; import org.springframework.security.oauth2.core.oidc.StandardClaimNames; import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames; import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser; import org.springframework.security.oauth2.core.oidc.user.OidcUser; import org.springframework.security.oauth2.core.user.DefaultOAuth2User; import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.security.oauth2.jwt.JwtDecoder; import org.springframework.security.oauth2.jwt.JwtDecoderFactory; import org.springframework.security.oauth2.jwt.TestJwts; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.context.SecurityContextRepository; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.springframework.security.web.servlet.TestMockHttpServletRequests.get; /** * Tests for {@link OidcUserRefreshedEventListener} with {@link OAuth2LoginConfigurer}. * * @author Steve Riesenberg */ public class OidcUserRefreshedEventListenerConfigurationTests { // @formatter:off private static final ClientRegistration GOOGLE_CLIENT_REGISTRATION = CommonOAuth2Provider.GOOGLE .getBuilder("google") .clientId("clientId") .clientSecret("clientSecret") .build(); // @formatter:on // @formatter:off private static final ClientRegistration GITHUB_CLIENT_REGISTRATION = CommonOAuth2Provider.GITHUB .getBuilder("github") .clientId("clientId") .clientSecret("clientSecret") .build(); // @formatter:on private static final String SUBJECT = "surfer-dude"; private static final String ACCESS_TOKEN_VALUE = "hang-ten"; private static final String REFRESH_TOKEN_VALUE = "surfs-up"; private static final String ID_TOKEN_VALUE = "beach-break"; public final SpringTestContext spring = new SpringTestContext(this); @Autowired private SecurityContextRepository securityContextRepository; @Autowired private OAuth2AuthorizedClientRepository authorizedClientRepository; @Autowired private OAuth2AccessTokenResponseClient refreshTokenAccessTokenResponseClient; @Autowired private JwtDecoder jwtDecoder; @Autowired private OidcUserService oidcUserService; @Autowired private OAuth2AuthorizedClientManager authorizedClientManager; private MockHttpServletRequest request; private MockHttpServletResponse response; @BeforeEach public void setUp() { this.request = get("/").build(); this.response = new MockHttpServletResponse(); RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(this.request, this.response)); } @AfterEach public void cleanUp() { SecurityContextHolder.clearContext(); RequestContextHolder.resetRequestAttributes(); } @Test public void authorizeWhenAccessTokenResponseMissingOpenidScopeThenOidcUserNotRefreshed() { this.spring.register(OAuth2LoginWithOAuth2ClientConfig.class).autowire(); OAuth2AuthorizedClient authorizedClient = createAuthorizedClient(); OAuth2AccessTokenResponse accessTokenResponse = createAccessTokenResponse(); given(this.authorizedClientRepository.loadAuthorizedClient(anyString(), any(Authentication.class), any(HttpServletRequest.class))) .willReturn(authorizedClient); given(this.refreshTokenAccessTokenResponseClient.getTokenResponse(any(OAuth2RefreshTokenGrantRequest.class))) .willReturn(accessTokenResponse); OAuth2AuthenticationToken authentication = createAuthenticationToken(GOOGLE_CLIENT_REGISTRATION, createOidcUser()); OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest .withClientRegistrationId(GOOGLE_CLIENT_REGISTRATION.getRegistrationId()) .principal(authentication) .build(); OAuth2AuthorizedClient refreshedAuthorizedClient = this.authorizedClientManager.authorize(authorizeRequest); assertThat(refreshedAuthorizedClient).isNotNull(); verifyNoInteractions(this.securityContextRepository, this.jwtDecoder, this.oidcUserService); } @Test public void authorizeWhenAccessTokenResponseMissingIdTokenThenOidcUserNotRefreshed() { this.spring.register(OAuth2LoginWithOAuth2ClientConfig.class).autowire(); OAuth2AuthorizedClient authorizedClient = createAuthorizedClient(); OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.oidcAccessTokenResponse() .build(); given(this.authorizedClientRepository.loadAuthorizedClient(anyString(), any(Authentication.class), any(HttpServletRequest.class))) .willReturn(authorizedClient); given(this.refreshTokenAccessTokenResponseClient.getTokenResponse(any(OAuth2RefreshTokenGrantRequest.class))) .willReturn(accessTokenResponse); OAuth2AuthenticationToken authentication = createAuthenticationToken(GOOGLE_CLIENT_REGISTRATION, createOidcUser()); OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest .withClientRegistrationId(GOOGLE_CLIENT_REGISTRATION.getRegistrationId()) .principal(authentication) .build(); OAuth2AuthorizedClient refreshedAuthorizedClient = this.authorizedClientManager.authorize(authorizeRequest); assertThat(refreshedAuthorizedClient).isNotNull(); verifyNoInteractions(this.securityContextRepository, this.jwtDecoder, this.oidcUserService); } @Test public void authorizeWhenAuthenticationIsNotOAuth2ThenOidcUserNotRefreshed() { this.spring.register(OAuth2LoginWithOAuth2ClientConfig.class).autowire(); OAuth2AuthorizedClient authorizedClient = createAuthorizedClient(); OAuth2AccessTokenResponse accessTokenResponse = createAccessTokenResponse(OidcScopes.OPENID); given(this.authorizedClientRepository.loadAuthorizedClient(anyString(), any(Authentication.class), any(HttpServletRequest.class))) .willReturn(authorizedClient); given(this.refreshTokenAccessTokenResponseClient.getTokenResponse(any(OAuth2RefreshTokenGrantRequest.class))) .willReturn(accessTokenResponse); TestingAuthenticationToken authentication = new TestingAuthenticationToken(SUBJECT, null); SecurityContextImpl securityContext = new SecurityContextImpl(authentication); SecurityContextHolder.setContext(securityContext); OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest .withClientRegistrationId(GOOGLE_CLIENT_REGISTRATION.getRegistrationId()) .principal(authentication) .build(); OAuth2AuthorizedClient refreshedAuthorizedClient = this.authorizedClientManager.authorize(authorizeRequest); assertThat(refreshedAuthorizedClient).isNotNull(); verifyNoInteractions(this.securityContextRepository, this.jwtDecoder, this.oidcUserService); } @Test public void authorizeWhenAuthenticationIsCustomThenOidcUserNotRefreshed() { this.spring.register(OAuth2LoginWithOAuth2ClientConfig.class).autowire(); OAuth2AuthorizedClient authorizedClient = createAuthorizedClient(); OAuth2AccessTokenResponse accessTokenResponse = createAccessTokenResponse(OidcScopes.OPENID); given(this.authorizedClientRepository.loadAuthorizedClient(anyString(), any(Authentication.class), any(HttpServletRequest.class))) .willReturn(authorizedClient); given(this.refreshTokenAccessTokenResponseClient.getTokenResponse(any(OAuth2RefreshTokenGrantRequest.class))) .willReturn(accessTokenResponse); OidcUser oidcUser = createOidcUser(); OAuth2AuthenticationToken authentication = new CustomOAuth2AuthenticationToken(oidcUser, oidcUser.getAuthorities(), GOOGLE_CLIENT_REGISTRATION.getRegistrationId()); SecurityContextImpl securityContext = new SecurityContextImpl(authentication); SecurityContextHolder.setContext(securityContext); OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest .withClientRegistrationId(GOOGLE_CLIENT_REGISTRATION.getRegistrationId()) .principal(authentication) .build(); OAuth2AuthorizedClient refreshedAuthorizedClient = this.authorizedClientManager.authorize(authorizeRequest); assertThat(refreshedAuthorizedClient).isNotNull(); verifyNoInteractions(this.securityContextRepository, this.jwtDecoder, this.oidcUserService); } @Test public void authorizeWhenPrincipalIsOAuth2UserThenOidcUserNotRefreshed() { this.spring.register(OAuth2LoginWithOAuth2ClientConfig.class).autowire(); OAuth2AuthorizedClient authorizedClient = createAuthorizedClient(); OAuth2AccessTokenResponse accessTokenResponse = createAccessTokenResponse(OidcScopes.OPENID); given(this.authorizedClientRepository.loadAuthorizedClient(anyString(), any(Authentication.class), any(HttpServletRequest.class))) .willReturn(authorizedClient); given(this.refreshTokenAccessTokenResponseClient.getTokenResponse(any(OAuth2RefreshTokenGrantRequest.class))) .willReturn(accessTokenResponse); Map attributes = Map.of(StandardClaimNames.SUB, SUBJECT); OAuth2User oauth2User = new DefaultOAuth2User(AuthorityUtils.createAuthorityList("OAUTH2_USER"), attributes, StandardClaimNames.SUB); OAuth2AuthenticationToken authentication = new OAuth2AuthenticationToken(oauth2User, oauth2User.getAuthorities(), GOOGLE_CLIENT_REGISTRATION.getRegistrationId()); SecurityContextImpl securityContext = new SecurityContextImpl(authentication); SecurityContextHolder.setContext(securityContext); OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest .withClientRegistrationId(GOOGLE_CLIENT_REGISTRATION.getRegistrationId()) .principal(authentication) .build(); OAuth2AuthorizedClient refreshedAuthorizedClient = this.authorizedClientManager.authorize(authorizeRequest); assertThat(refreshedAuthorizedClient).isNotNull(); verifyNoInteractions(this.securityContextRepository, this.jwtDecoder, this.oidcUserService); } @Test public void authorizeWhenAuthenticationClientRegistrationIdDoesNotMatchThenOidcUserNotRefreshed() { this.spring.register(OAuth2LoginWithOAuth2ClientConfig.class).autowire(); OAuth2AuthorizedClient authorizedClient = createAuthorizedClient(); OAuth2AccessTokenResponse accessTokenResponse = createAccessTokenResponse(OidcScopes.OPENID); given(this.authorizedClientRepository.loadAuthorizedClient(anyString(), any(Authentication.class), any(HttpServletRequest.class))) .willReturn(authorizedClient); given(this.refreshTokenAccessTokenResponseClient.getTokenResponse(any(OAuth2RefreshTokenGrantRequest.class))) .willReturn(accessTokenResponse); OAuth2AuthenticationToken authentication = createAuthenticationToken(GITHUB_CLIENT_REGISTRATION, createOidcUser()); SecurityContextImpl securityContext = new SecurityContextImpl(authentication); SecurityContextHolder.setContext(securityContext); OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest .withClientRegistrationId(GOOGLE_CLIENT_REGISTRATION.getRegistrationId()) .principal(authentication) .build(); OAuth2AuthorizedClient refreshedAuthorizedClient = this.authorizedClientManager.authorize(authorizeRequest); assertThat(refreshedAuthorizedClient).isNotNull(); verifyNoInteractions(this.securityContextRepository, this.jwtDecoder, this.oidcUserService); } @Test public void authorizeWhenAccessTokenResponseIncludesIdTokenThenOidcUserRefreshed() { this.spring.register(OAuth2LoginWithOAuth2ClientConfig.class).autowire(); OAuth2AuthorizedClient authorizedClient = createAuthorizedClient(); OAuth2AccessTokenResponse accessTokenResponse = createAccessTokenResponse(OidcScopes.OPENID); Jwt jwt = createJwt().build(); given(this.authorizedClientRepository.loadAuthorizedClient(anyString(), any(Authentication.class), any(HttpServletRequest.class))) .willReturn(authorizedClient); given(this.refreshTokenAccessTokenResponseClient.getTokenResponse(any(OAuth2RefreshTokenGrantRequest.class))) .willReturn(accessTokenResponse); given(this.jwtDecoder.decode(anyString())).willReturn(jwt); given(this.oidcUserService.loadUser(any(OidcUserRequest.class))).willReturn(createOidcUser()); OAuth2AuthenticationToken authentication = createAuthenticationToken(GOOGLE_CLIENT_REGISTRATION, createOidcUser()); SecurityContextImpl securityContext = new SecurityContextImpl(authentication); SecurityContextHolder.setContext(securityContext); OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest .withClientRegistrationId(GOOGLE_CLIENT_REGISTRATION.getRegistrationId()) .principal(authentication) .build(); OAuth2AuthorizedClient refreshedAuthorizedClient = this.authorizedClientManager.authorize(authorizeRequest); assertThat(refreshedAuthorizedClient).isNotNull(); assertThat(refreshedAuthorizedClient).isNotSameAs(authorizedClient); assertThat(refreshedAuthorizedClient.getClientRegistration()).isEqualTo(GOOGLE_CLIENT_REGISTRATION); assertThat(refreshedAuthorizedClient.getAccessToken()).isEqualTo(accessTokenResponse.getAccessToken()); assertThat(refreshedAuthorizedClient.getRefreshToken()).isEqualTo(accessTokenResponse.getRefreshToken()); ArgumentCaptor refreshTokenGrantRequestCaptor = ArgumentCaptor .forClass(OAuth2RefreshTokenGrantRequest.class); ArgumentCaptor userRequestCaptor = ArgumentCaptor.forClass(OidcUserRequest.class); ArgumentCaptor securityContextCaptor = ArgumentCaptor.forClass(SecurityContext.class); verify(this.authorizedClientRepository).loadAuthorizedClient(GOOGLE_CLIENT_REGISTRATION.getRegistrationId(), authentication, this.request); verify(this.authorizedClientRepository).saveAuthorizedClient(refreshedAuthorizedClient, authentication, this.request, this.response); verify(this.refreshTokenAccessTokenResponseClient).getTokenResponse(refreshTokenGrantRequestCaptor.capture()); verify(this.jwtDecoder).decode(jwt.getTokenValue()); verify(this.oidcUserService).loadUser(userRequestCaptor.capture()); verify(this.securityContextRepository).saveContext(securityContextCaptor.capture(), eq(this.request), eq(this.response)); verifyNoMoreInteractions(this.authorizedClientRepository, this.jwtDecoder, this.oidcUserService, this.securityContextRepository); OAuth2RefreshTokenGrantRequest refreshTokenGrantRequest = refreshTokenGrantRequestCaptor.getValue(); assertThat(refreshTokenGrantRequest.getClientRegistration()) .isEqualTo(authorizedClient.getClientRegistration()); assertThat(refreshTokenGrantRequest.getRefreshToken()).isEqualTo(authorizedClient.getRefreshToken()); assertThat(refreshTokenGrantRequest.getAccessToken()).isEqualTo(authorizedClient.getAccessToken()); OidcUserRequest userRequest = userRequestCaptor.getValue(); assertThat(userRequest.getClientRegistration()).isEqualTo(GOOGLE_CLIENT_REGISTRATION); assertThat(userRequest.getAccessToken()).isEqualTo(accessTokenResponse.getAccessToken()); assertThat(userRequest.getIdToken().getTokenValue()).isEqualTo(jwt.getTokenValue()); SecurityContext refreshedSecurityContext = securityContextCaptor.getValue(); assertThat(refreshedSecurityContext).isNotNull(); assertThat(refreshedSecurityContext).isNotSameAs(securityContext); assertThat(refreshedSecurityContext).isSameAs(SecurityContextHolder.getContext()); assertThat(refreshedSecurityContext.getAuthentication()).isInstanceOf(OAuth2AuthenticationToken.class); assertThat(refreshedSecurityContext.getAuthentication()).isNotSameAs(authentication); assertThat(refreshedSecurityContext.getAuthentication().getPrincipal()).isInstanceOf(OidcUser.class); assertThat(refreshedSecurityContext.getAuthentication().getPrincipal()) .isNotSameAs(authentication.getPrincipal()); } private OAuth2AuthorizedClient createAuthorizedClient() { Instant issuedAt = Instant.now(); Instant expiresAt = issuedAt.plus(30, ChronoUnit.SECONDS); OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, ACCESS_TOKEN_VALUE, issuedAt, expiresAt, Set.of(OidcScopes.OPENID)); OAuth2RefreshToken refreshToken = new OAuth2RefreshToken(REFRESH_TOKEN_VALUE, issuedAt); return new OAuth2AuthorizedClient(GOOGLE_CLIENT_REGISTRATION, SUBJECT, accessToken, refreshToken); } private OAuth2AccessTokenResponse createAccessTokenResponse(String... scope) { Set scopes = Set.of(scope); Map additionalParameters = new HashMap<>(); if (scopes.contains(OidcScopes.OPENID)) { additionalParameters.put(OidcParameterNames.ID_TOKEN, ID_TOKEN_VALUE); } return OAuth2AccessTokenResponse.withToken(ACCESS_TOKEN_VALUE) .tokenType(OAuth2AccessToken.TokenType.BEARER) .scopes(scopes) .refreshToken(REFRESH_TOKEN_VALUE) .expiresIn(60L) .additionalParameters(additionalParameters) .build(); } private static Jwt.Builder createJwt() { Instant issuedAt = Instant.now(); Instant expiresAt = issuedAt.plus(1, ChronoUnit.MINUTES); return TestJwts.jwt() .issuer("https://surf.school") .subject(SUBJECT) .tokenValue(ID_TOKEN_VALUE) .issuedAt(issuedAt) .expiresAt(expiresAt) .audience(List.of("audience1", "audience2")); } private static OidcUser createOidcUser() { Instant issuedAt = Instant.now().minus(30, ChronoUnit.SECONDS); Instant expiresAt = issuedAt.plus(5, ChronoUnit.MINUTES); Map claims = new HashMap<>(); claims.put(IdTokenClaimNames.ISS, "https://surf.school"); claims.put(IdTokenClaimNames.SUB, SUBJECT); claims.put(IdTokenClaimNames.IAT, issuedAt); claims.put(IdTokenClaimNames.EXP, expiresAt); claims.put(IdTokenClaimNames.AUD, List.of("audience1", "audience2")); claims.put(IdTokenClaimNames.AUTH_TIME, issuedAt); claims.put(IdTokenClaimNames.NONCE, "nonce"); OidcIdToken idToken = new OidcIdToken(ID_TOKEN_VALUE, issuedAt, expiresAt, claims); return new DefaultOidcUser(AuthorityUtils.createAuthorityList("OIDC_USER"), idToken); } private OAuth2AuthenticationToken createAuthenticationToken(ClientRegistration clientRegistration, OidcUser oidcUser) { return new OAuth2AuthenticationToken(oidcUser, oidcUser.getAuthorities(), clientRegistration.getRegistrationId()); } @Configuration @EnableWebSecurity static class OAuth2LoginWithOAuth2ClientConfig { @Bean SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((authorize) -> authorize .anyRequest().authenticated() ) .securityContext((securityContext) -> securityContext .securityContextRepository(this.securityContextRepository()) ) .oauth2Login(Customizer.withDefaults()) .oauth2Client(Customizer.withDefaults()); // @formatter:on return http.build(); } @Bean SecurityContextRepository securityContextRepository() { return mock(SecurityContextRepository.class); } @Bean ClientRegistrationRepository clientRegistrationRepository() { return mock(ClientRegistrationRepository.class); } @Bean OAuth2AuthorizedClientRepository authorizedClientRepository() { return mock(OAuth2AuthorizedClientRepository.class); } @Bean @SuppressWarnings("unchecked") OAuth2AccessTokenResponseClient refreshTokenAccessTokenResponseClient() { return mock(OAuth2AccessTokenResponseClient.class); } @Bean JwtDecoder jwtDecoder() { return mock(JwtDecoder.class); } @Bean JwtDecoderFactory jwtDecoderFactory() { return (clientRegistration) -> jwtDecoder(); } @Bean OidcUserService oidcUserService() { return mock(OidcUserService.class); } } private static final class CustomOAuth2AuthenticationToken extends OAuth2AuthenticationToken { CustomOAuth2AuthenticationToken(OAuth2User principal, Collection authorities, String authorizedClientRegistrationId) { super(principal, authorities, authorizedClientRegistrationId); } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OidcUserRefreshedEventListenerTests.java ================================================ /* * 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 org.springframework.security.config.annotation.web.configurers.oauth2.client; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; import org.springframework.security.oauth2.client.oidc.authentication.event.OidcUserRefreshedEvent; import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; import org.springframework.security.oauth2.core.endpoint.TestOAuth2AccessTokenResponses; import org.springframework.security.oauth2.core.oidc.user.OidcUser; import org.springframework.security.oauth2.core.oidc.user.TestOidcUsers; import org.springframework.security.web.context.SecurityContextRepository; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.springframework.security.web.servlet.TestMockHttpServletRequests.get; /** * Tests for {@link OidcUserRefreshedEventListener}. * * @author Steve Riesenberg */ public class OidcUserRefreshedEventListenerTests { private OidcUserRefreshedEventListener eventListener; private SecurityContextRepository securityContextRepository; private MockHttpServletRequest request; private MockHttpServletResponse response; @BeforeEach public void setUp() { this.securityContextRepository = mock(SecurityContextRepository.class); this.eventListener = new OidcUserRefreshedEventListener(); this.eventListener.setSecurityContextRepository(this.securityContextRepository); this.request = get("/").build(); this.response = new MockHttpServletResponse(); } @AfterEach public void cleanUp() { SecurityContextHolder.clearContext(); RequestContextHolder.resetRequestAttributes(); } @Test public void setSecurityContextHolderStrategyWhenNullThenThrowsIllegalArgumentException() { assertThatIllegalArgumentException().isThrownBy(() -> this.eventListener.setSecurityContextHolderStrategy(null)) .withMessage("securityContextHolderStrategy cannot be null"); } @Test public void setSecurityContextRepositoryWhenNullThenThrowsIllegalArgumentException() { assertThatIllegalArgumentException().isThrownBy(() -> this.eventListener.setSecurityContextRepository(null)) .withMessage("securityContextRepository cannot be null"); } @Test public void onApplicationEventWhenRequestAttributesSetThenSecurityContextSaved() { RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(this.request, this.response)); OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.oidcAccessTokenResponse() .build(); OidcUser oldOidcUser = TestOidcUsers.create(); OidcUser newOidcUser = TestOidcUsers.create(); OAuth2AuthenticationToken authentication = new OAuth2AuthenticationToken(newOidcUser, newOidcUser.getAuthorities(), "test"); OidcUserRefreshedEvent event = new OidcUserRefreshedEvent(accessTokenResponse, oldOidcUser, newOidcUser, authentication); this.eventListener.onApplicationEvent(event); ArgumentCaptor securityContextCaptor = ArgumentCaptor.forClass(SecurityContext.class); verify(this.securityContextRepository).saveContext(securityContextCaptor.capture(), eq(this.request), eq(this.response)); verifyNoMoreInteractions(this.securityContextRepository); SecurityContext securityContext = securityContextCaptor.getValue(); assertThat(securityContext).isNotNull(); assertThat(securityContext).isSameAs(SecurityContextHolder.getContext()); assertThat(securityContext.getAuthentication()).isSameAs(authentication); } @Test public void onApplicationEventWhenRequestAttributesNotSetThenSecurityContextNotSaved() { OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.oidcAccessTokenResponse() .build(); OidcUser oldOidcUser = TestOidcUsers.create(); OidcUser newOidcUser = TestOidcUsers.create(); OAuth2AuthenticationToken authentication = new OAuth2AuthenticationToken(newOidcUser, newOidcUser.getAuthorities(), "test"); OidcUserRefreshedEvent event = new OidcUserRefreshedEvent(accessTokenResponse, oldOidcUser, newOidcUser, authentication); OidcUserRefreshedEventListener eventListener = new OidcUserRefreshedEventListener(); eventListener.setSecurityContextRepository(this.securityContextRepository); eventListener.onApplicationEvent(event); verifyNoInteractions(this.securityContextRepository); SecurityContext securityContext = SecurityContextHolder.getContext(); assertThat(securityContext).isNotNull(); assertThat(securityContext.getAuthentication()).isSameAs(authentication); } } ================================================ FILE: config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/AuthorizationServerContextFilterTests.java ================================================ /* * 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 org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicReference; import jakarta.servlet.FilterChain; import org.junit.jupiter.api.Test; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder; import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link AuthorizationServerContextFilter}. * * @author Joe Grandja */ class AuthorizationServerContextFilterTests { private static final String SCHEME = "https"; private static final String HOST = "example.com"; private static final int PORT = 8443; private static final String DEFAULT_ISSUER = SCHEME + "://" + HOST + ":" + PORT; private AuthorizationServerContextFilter filter; @Test void doFilterWhenDefaultEndpointsThenIssuerResolved() throws Exception { AuthorizationServerSettings authorizationServerSettings = AuthorizationServerSettings.builder().build(); this.filter = new AuthorizationServerContextFilter(authorizationServerSettings); String issuerPath = "/issuer1"; String issuerWithPath = DEFAULT_ISSUER.concat(issuerPath); Set endpointUris = getEndpointUris(authorizationServerSettings); for (String endpointUri : endpointUris) { assertResolvedIssuer(issuerPath.concat(endpointUri), issuerWithPath); } } @Test void doFilterWhenCustomEndpointsThenIssuerResolved() throws Exception { AuthorizationServerSettings authorizationServerSettings = AuthorizationServerSettings.builder() .authorizationEndpoint("/oauth2/v1/authorize") .deviceAuthorizationEndpoint("/oauth2/v1/device_authorization") .deviceVerificationEndpoint("/oauth2/v1/device_verification") .tokenEndpoint("/oauth2/v1/token") .jwkSetEndpoint("/oauth2/v1/jwks") .tokenRevocationEndpoint("/oauth2/v1/revoke") .tokenIntrospectionEndpoint("/oauth2/v1/introspect") .oidcClientRegistrationEndpoint("/connect/v1/register") .oidcUserInfoEndpoint("/v1/userinfo") .oidcLogoutEndpoint("/connect/v1/logout") .build(); this.filter = new AuthorizationServerContextFilter(authorizationServerSettings); String issuerPath = "/issuer2"; String issuerWithPath = DEFAULT_ISSUER.concat(issuerPath); Set endpointUris = getEndpointUris(authorizationServerSettings); for (String endpointUri : endpointUris) { assertResolvedIssuer(issuerPath.concat(endpointUri), issuerWithPath); } } @Test void doFilterWhenIssuerHasMultiplePathsThenIssuerResolved() throws Exception { AuthorizationServerSettings authorizationServerSettings = AuthorizationServerSettings.builder().build(); this.filter = new AuthorizationServerContextFilter(authorizationServerSettings); String issuerPath = "/path1/path2/issuer3"; String issuerWithPath = DEFAULT_ISSUER.concat(issuerPath); Set endpointUris = getEndpointUris(authorizationServerSettings); for (String endpointUri : endpointUris) { assertResolvedIssuer(issuerPath.concat(endpointUri), issuerWithPath); } } private void assertResolvedIssuer(String requestUri, String expectedIssuer) throws Exception { MockHttpServletRequest request = createRequest(requestUri); MockHttpServletResponse response = new MockHttpServletResponse(); AtomicReference resolvedIssuer = new AtomicReference<>(); FilterChain filterChain = (req, resp) -> resolvedIssuer .set(AuthorizationServerContextHolder.getContext().getIssuer()); this.filter.doFilter(request, response, filterChain); assertThat(resolvedIssuer.get()).isEqualTo(expectedIssuer); } private static Set getEndpointUris(AuthorizationServerSettings authorizationServerSettings) { Set endpointUris = new HashSet<>(); endpointUris.add("/.well-known/oauth-authorization-server"); endpointUris.add("/.well-known/openid-configuration"); for (Map.Entry setting : authorizationServerSettings.getSettings().entrySet()) { if (setting.getKey().endsWith("-endpoint")) { endpointUris.add((String) setting.getValue()); } } return endpointUris; } private static MockHttpServletRequest createRequest(String requestUri) { MockHttpServletRequest request = new MockHttpServletRequest(); request.setRequestURI(requestUri); request.setScheme(SCHEME); request.setServerName(HOST); request.setServerPort(PORT); return request; } } ================================================ FILE: config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/DefaultOAuth2TokenCustomizersTests.java ================================================ /* * 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 org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.core.ClientAuthenticationMethod; import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm; import org.springframework.security.oauth2.jwt.JwsHeader; import org.springframework.security.oauth2.jwt.JwtClaimNames; import org.springframework.security.oauth2.jwt.JwtClaimsSet; import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientCredentialsAuthenticationToken; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenExchangeActor; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenExchangeAuthenticationToken; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenExchangeCompositeAuthenticationToken; import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; import org.springframework.security.oauth2.server.authorization.settings.ClientSettings; import org.springframework.security.oauth2.server.authorization.settings.TokenSettings; import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext; import org.springframework.security.oauth2.server.authorization.util.TestX509Certificates; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.entry; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; /** * Tests for {@link DefaultOAuth2TokenCustomizers}. * * @author Steve Riesenberg * @author Joe Grandja */ class DefaultOAuth2TokenCustomizersTests { private static final String ISSUER_1 = "issuer-1"; private static final String ISSUER_2 = "issuer-2"; private JwsHeader.Builder jwsHeaderBuilder; private JwtClaimsSet.Builder jwtClaimsBuilder; @BeforeEach void setUp() { this.jwsHeaderBuilder = JwsHeader.with(SignatureAlgorithm.RS256); this.jwtClaimsBuilder = JwtClaimsSet.builder().issuer(ISSUER_1); } @Test void customizeWhenTokenTypeIsRefreshTokenThenNoClaimsAdded() { // @formatter:off JwtEncodingContext tokenContext = JwtEncodingContext.with(this.jwsHeaderBuilder, this.jwtClaimsBuilder) .tokenType(OAuth2TokenType.REFRESH_TOKEN) .build(); // @formatter:on DefaultOAuth2TokenCustomizers.jwtCustomizer().customize(tokenContext); JwtClaimsSet jwtClaimsSet = this.jwtClaimsBuilder.build(); assertThat(jwtClaimsSet.getClaims()).containsOnly(entry(JwtClaimNames.ISS, ISSUER_1)); } @Test void customizeWhenAuthorizationGrantIsNullThenNoClaimsAdded() { // @formatter:off JwtEncodingContext tokenContext = JwtEncodingContext.with(this.jwsHeaderBuilder, this.jwtClaimsBuilder) .tokenType(OAuth2TokenType.ACCESS_TOKEN) .build(); // @formatter:on DefaultOAuth2TokenCustomizers.jwtCustomizer().customize(tokenContext); JwtClaimsSet jwtClaimsSet = this.jwtClaimsBuilder.build(); assertThat(jwtClaimsSet.getClaims()).containsOnly(entry(JwtClaimNames.ISS, ISSUER_1)); } @Test void customizeWhenTokenExchangeGrantAndResourcesThenNoClaimsAdded() { OAuth2TokenExchangeAuthenticationToken tokenExchangeAuthentication = mock( OAuth2TokenExchangeAuthenticationToken.class); given(tokenExchangeAuthentication.getResources()).willReturn(Set.of("resource1", "resource2")); // @formatter:off JwtEncodingContext tokenContext = JwtEncodingContext.with(this.jwsHeaderBuilder, this.jwtClaimsBuilder) .tokenType(OAuth2TokenType.ACCESS_TOKEN) .authorizationGrant(tokenExchangeAuthentication) .build(); // @formatter:on DefaultOAuth2TokenCustomizers.jwtCustomizer().customize(tokenContext); JwtClaimsSet jwtClaimsSet = this.jwtClaimsBuilder.build(); // We do not populate claims (e.g. `aud`) based on the resource parameter assertThat(jwtClaimsSet.getClaims()).containsOnly(entry(JwtClaimNames.ISS, ISSUER_1)); } @Test void customizeWhenTokenExchangeGrantAndAudiencesThenNoClaimsAdded() { OAuth2TokenExchangeAuthenticationToken tokenExchangeAuthentication = mock( OAuth2TokenExchangeAuthenticationToken.class); given(tokenExchangeAuthentication.getAudiences()).willReturn(Set.of("audience1", "audience2")); // @formatter:off JwtEncodingContext tokenContext = JwtEncodingContext.with(this.jwsHeaderBuilder, this.jwtClaimsBuilder) .tokenType(OAuth2TokenType.ACCESS_TOKEN) .authorizationGrant(tokenExchangeAuthentication) .build(); // @formatter:on DefaultOAuth2TokenCustomizers.jwtCustomizer().customize(tokenContext); JwtClaimsSet jwtClaimsSet = this.jwtClaimsBuilder.build(); // NOTE: We do not populate claims (e.g. `aud`) based on the audience parameter assertThat(jwtClaimsSet.getClaims()).containsOnly(entry(JwtClaimNames.ISS, ISSUER_1)); } @Test void customizeWhenTokenExchangeGrantAndDelegationThenActClaimAdded() { OAuth2TokenExchangeAuthenticationToken tokenExchangeAuthentication = mock( OAuth2TokenExchangeAuthenticationToken.class); given(tokenExchangeAuthentication.getAudiences()).willReturn(Collections.emptySet()); Authentication subject = new TestingAuthenticationToken("subject", null); OAuth2TokenExchangeActor actor1 = new OAuth2TokenExchangeActor( Map.of(JwtClaimNames.ISS, ISSUER_1, JwtClaimNames.SUB, "actor1")); OAuth2TokenExchangeActor actor2 = new OAuth2TokenExchangeActor( Map.of(JwtClaimNames.ISS, ISSUER_2, JwtClaimNames.SUB, "actor2")); OAuth2TokenExchangeCompositeAuthenticationToken principal = new OAuth2TokenExchangeCompositeAuthenticationToken( subject, List.of(actor1, actor2)); // @formatter:off JwtEncodingContext tokenContext = JwtEncodingContext.with(this.jwsHeaderBuilder, this.jwtClaimsBuilder) .tokenType(OAuth2TokenType.ACCESS_TOKEN) .principal(principal) .authorizationGrant(tokenExchangeAuthentication) .build(); // @formatter:on DefaultOAuth2TokenCustomizers.jwtCustomizer().customize(tokenContext); JwtClaimsSet jwtClaimsSet = this.jwtClaimsBuilder.build(); assertThat(jwtClaimsSet.getClaims()).isNotEmpty(); assertThat(jwtClaimsSet.getClaims()).hasSize(2); assertThat(jwtClaimsSet.getClaims().get("act")).isNotNull(); @SuppressWarnings("unchecked") Map actClaim1 = (Map) jwtClaimsSet.getClaims().get("act"); assertThat(actClaim1.get(JwtClaimNames.ISS)).isEqualTo(ISSUER_1); assertThat(actClaim1.get(JwtClaimNames.SUB)).isEqualTo("actor1"); @SuppressWarnings("unchecked") Map actClaim2 = (Map) actClaim1.get("act"); assertThat(actClaim2.get(JwtClaimNames.ISS)).isEqualTo(ISSUER_2); assertThat(actClaim2.get(JwtClaimNames.SUB)).isEqualTo("actor2"); } @Test void customizeWhenPKIX509ClientCertificateAndCertificateBoundAccessTokensThenX5tClaimAdded() { // @formatter:off RegisteredClient registeredClient = TestRegisteredClients.registeredClient() .clientAuthenticationMethod(ClientAuthenticationMethod.TLS_CLIENT_AUTH) .clientSettings( ClientSettings.builder() .x509CertificateSubjectDN(TestX509Certificates.DEMO_CLIENT_PKI_CERTIFICATE[0].getSubjectX500Principal().getName()) .build() ) .tokenSettings( TokenSettings.builder() .x509CertificateBoundAccessTokens(true) .build() ) .build(); // @formatter:on OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, ClientAuthenticationMethod.TLS_CLIENT_AUTH, TestX509Certificates.DEMO_CLIENT_PKI_CERTIFICATE); OAuth2ClientCredentialsAuthenticationToken clientCredentialsAuthentication = new OAuth2ClientCredentialsAuthenticationToken( clientPrincipal, null, null); // @formatter:off JwtEncodingContext tokenContext = JwtEncodingContext.with(this.jwsHeaderBuilder, this.jwtClaimsBuilder) .tokenType(OAuth2TokenType.ACCESS_TOKEN) .registeredClient(registeredClient) .authorizationGrant(clientCredentialsAuthentication) .build(); // @formatter:on DefaultOAuth2TokenCustomizers.jwtCustomizer().customize(tokenContext); JwtClaimsSet jwtClaimsSet = this.jwtClaimsBuilder.build(); assertThat(jwtClaimsSet.getClaims()).isNotEmpty(); assertThat(jwtClaimsSet.getClaims()).hasSize(2); Map cnfClaim = jwtClaimsSet.getClaim("cnf"); assertThat(cnfClaim).isNotEmpty(); assertThat(cnfClaim.get("x5t#S256")).isNotNull(); } @Test void customizeWhenSelfSignedX509ClientCertificateAndCertificateBoundAccessTokensThenX5tClaimAdded() { // @formatter:off RegisteredClient registeredClient = TestRegisteredClients.registeredClient() .clientAuthenticationMethod(ClientAuthenticationMethod.SELF_SIGNED_TLS_CLIENT_AUTH) .clientSettings( ClientSettings.builder() .jwkSetUrl("https://client.example.com/jwks") .build() ) .tokenSettings( TokenSettings.builder() .x509CertificateBoundAccessTokens(true) .build() ) .build(); // @formatter:on OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, ClientAuthenticationMethod.SELF_SIGNED_TLS_CLIENT_AUTH, TestX509Certificates.DEMO_CLIENT_SELF_SIGNED_CERTIFICATE); OAuth2ClientCredentialsAuthenticationToken clientCredentialsAuthentication = new OAuth2ClientCredentialsAuthenticationToken( clientPrincipal, null, null); // @formatter:off JwtEncodingContext tokenContext = JwtEncodingContext.with(this.jwsHeaderBuilder, this.jwtClaimsBuilder) .tokenType(OAuth2TokenType.ACCESS_TOKEN) .registeredClient(registeredClient) .authorizationGrant(clientCredentialsAuthentication) .build(); // @formatter:on DefaultOAuth2TokenCustomizers.jwtCustomizer().customize(tokenContext); JwtClaimsSet jwtClaimsSet = this.jwtClaimsBuilder.build(); assertThat(jwtClaimsSet.getClaims()).isNotEmpty(); assertThat(jwtClaimsSet.getClaims()).hasSize(2); Map cnfClaim = jwtClaimsSet.getClaim("cnf"); assertThat(cnfClaim).isNotEmpty(); assertThat(cnfClaim.get("x5t#S256")).isNotNull(); } } ================================================ FILE: config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/JwkSetTests.java ================================================ /* * 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 org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization; import com.nimbusds.jose.jwk.JWKSet; import com.nimbusds.jose.jwk.source.JWKSource; import com.nimbusds.jose.proc.SecurityContext; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; import org.springframework.http.HttpHeaders; import org.springframework.jdbc.core.JdbcOperations; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.oauth2.jose.TestJwks; import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService; import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository; import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; import org.springframework.test.web.servlet.MockMvc; import static org.hamcrest.CoreMatchers.containsString; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Integration tests for the JWK Set endpoint. * * @author Florian Berthe */ @ExtendWith(SpringTestContextExtension.class) public class JwkSetTests { private static final String DEFAULT_JWK_SET_ENDPOINT_URI = "/oauth2/jwks"; private static EmbeddedDatabase db; private static JWKSource jwkSource; public final SpringTestContext spring = new SpringTestContext(this); @Autowired private MockMvc mvc; @Autowired private JdbcOperations jdbcOperations; @Autowired private AuthorizationServerSettings authorizationServerSettings; @BeforeAll public static void init() { JWKSet jwkSet = new JWKSet(TestJwks.DEFAULT_RSA_JWK); jwkSource = (jwkSelector, securityContext) -> jwkSelector.select(jwkSet); db = new EmbeddedDatabaseBuilder().generateUniqueName(true) .setType(EmbeddedDatabaseType.HSQL) .setScriptEncoding("UTF-8") .addScript("org/springframework/security/oauth2/server/authorization/oauth2-authorization-schema.sql") .addScript( "org/springframework/security/oauth2/server/authorization/client/oauth2-registered-client-schema.sql") .build(); } @AfterEach public void tearDown() { this.jdbcOperations.update("truncate table oauth2_authorization"); this.jdbcOperations.update("truncate table oauth2_registered_client"); } @AfterAll public static void destroy() { db.shutdown(); } @Test public void requestWhenJwkSetThenReturnKeys() throws Exception { this.spring.register(AuthorizationServerConfiguration.class).autowire(); assertJwkSetRequestThenReturnKeys(DEFAULT_JWK_SET_ENDPOINT_URI); } @Test public void requestWhenJwkSetCustomEndpointThenReturnKeys() throws Exception { this.spring.register(AuthorizationServerConfigurationCustomEndpoints.class).autowire(); assertJwkSetRequestThenReturnKeys(this.authorizationServerSettings.getJwkSetEndpoint()); } @Test public void requestWhenJwkSetRequestIncludesIssuerPathThenReturnKeys() throws Exception { this.spring.register(AuthorizationServerConfigurationCustomEndpoints.class).autowire(); String issuer = "https://example.com:8443/issuer1"; assertJwkSetRequestThenReturnKeys(issuer.concat(this.authorizationServerSettings.getJwkSetEndpoint())); } private void assertJwkSetRequestThenReturnKeys(String jwkSetEndpointUri) throws Exception { this.mvc.perform(get(jwkSetEndpointUri)) .andExpect(status().isOk()) .andExpect(header().string(HttpHeaders.CACHE_CONTROL, containsString("no-store"))) .andExpect(header().string(HttpHeaders.PRAGMA, containsString("no-cache"))) .andExpect(jsonPath("$.keys").isNotEmpty()) .andExpect(jsonPath("$.keys").isArray()); } @EnableWebSecurity @Import(OAuth2AuthorizationServerConfiguration.class) static class AuthorizationServerConfiguration { @Bean OAuth2AuthorizationService authorizationService(JdbcOperations jdbcOperations, RegisteredClientRepository registeredClientRepository) { return new JdbcOAuth2AuthorizationService(jdbcOperations, registeredClientRepository); } @Bean RegisteredClientRepository registeredClientRepository(JdbcOperations jdbcOperations) { return new JdbcRegisteredClientRepository(jdbcOperations); } @Bean JdbcOperations jdbcOperations() { return new JdbcTemplate(db); } @Bean JWKSource jwkSource() { return jwkSource; } } @EnableWebSecurity @Import(OAuth2AuthorizationServerConfiguration.class) static class AuthorizationServerConfigurationCustomEndpoints extends AuthorizationServerConfiguration { @Bean AuthorizationServerSettings authorizationServerSettings() { return AuthorizationServerSettings.builder() .jwkSetEndpoint("/test/jwks") .multipleIssuersAllowed(true) .build(); } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2AuthorizationCodeGrantTests.java ================================================ /* * 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 org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.security.Principal; import java.text.MessageFormat; import java.time.Instant; import java.time.temporal.ChronoUnit; import java.util.Arrays; import java.util.Base64; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; import java.util.function.Consumer; import com.jayway.jsonpath.JsonPath; import com.nimbusds.jose.jwk.JWKSet; import com.nimbusds.jose.jwk.source.JWKSource; import com.nimbusds.jose.proc.SecurityContext; import org.assertj.core.matcher.AssertionMatcher; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.jdbc.core.JdbcOperations; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; import org.springframework.lang.Nullable; import org.springframework.mock.http.client.MockClientHttpResponse; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.crypto.keygen.Base64StringKeyGenerator; import org.springframework.security.crypto.keygen.StringKeyGenerator; import org.springframework.security.crypto.password.NoOpPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.core.OAuth2AccessToken; import org.springframework.security.oauth2.core.OAuth2RefreshToken; import org.springframework.security.oauth2.core.OAuth2Token; import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType; import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; import org.springframework.security.oauth2.core.endpoint.PkceParameterNames; import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter; import org.springframework.security.oauth2.jose.TestJwks; import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm; import org.springframework.security.oauth2.jwt.JwsHeader; import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.security.oauth2.jwt.JwtClaimsSet; import org.springframework.security.oauth2.jwt.JwtDecoder; import org.springframework.security.oauth2.jwt.JwtEncoder; import org.springframework.security.oauth2.jwt.JwtEncoderParameters; import org.springframework.security.oauth2.jwt.NimbusJwtEncoder; import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationConsentService; import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService; import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationCode; import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsent; import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService; import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationProvider; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationToken; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationConsentAuthenticationContext; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationConsentAuthenticationProvider; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationConsentAuthenticationToken; import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository; import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository.RegisteredClientParametersMapper; import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; import org.springframework.security.oauth2.server.authorization.settings.ClientSettings; import org.springframework.security.oauth2.server.authorization.token.DelegatingOAuth2TokenGenerator; import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext; import org.springframework.security.oauth2.server.authorization.token.JwtGenerator; import org.springframework.security.oauth2.server.authorization.token.OAuth2RefreshTokenGenerator; import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenContext; import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer; import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator; import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2AuthorizationCodeRequestAuthenticationConverter; import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2AuthorizationConsentAuthenticationConverter; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.AuthenticationConverter; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.security.web.context.HttpSessionSecurityContextRepository; import org.springframework.security.web.context.SecurityContextRepository; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.util.StringUtils; import org.springframework.web.util.UriComponents; import org.springframework.web.util.UriComponentsBuilder; import org.springframework.web.util.UriUtils; import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.CoreMatchers.containsString; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Integration tests for the OAuth 2.0 Authorization Code Grant. * * @author Joe Grandja * @author Daniel Garnier-Moiroux * @author Dmitriy Dubson * @author Steve Riesenberg * @author Greg Li */ @ExtendWith(SpringTestContextExtension.class) public class OAuth2AuthorizationCodeGrantTests { private static final String DEFAULT_AUTHORIZATION_ENDPOINT_URI = "/oauth2/authorize"; private static final String DEFAULT_TOKEN_ENDPOINT_URI = "/oauth2/token"; // See RFC 7636: Appendix B. Example for the S256 code_challenge_method // https://tools.ietf.org/html/rfc7636#appendix-B private static final String S256_CODE_VERIFIER = "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"; private static final String S256_CODE_CHALLENGE = "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM"; private static final String AUTHORITIES_CLAIM = "authorities"; private static final String STATE_URL_UNENCODED = "awrD0fCnEcTUPFgmyy2SU89HZNcnAJ60ZW6l39YI0KyVjmIZ+004pwm9j55li7BoydXYysH4enZMF21Q"; private static final String STATE_URL_ENCODED = "awrD0fCnEcTUPFgmyy2SU89HZNcnAJ60ZW6l39YI0KyVjmIZ%2B004pwm9j55li7BoydXYysH4enZMF21Q"; private static final OAuth2TokenType AUTHORIZATION_CODE_TOKEN_TYPE = new OAuth2TokenType(OAuth2ParameterNames.CODE); private static final OAuth2TokenType STATE_TOKEN_TYPE = new OAuth2TokenType(OAuth2ParameterNames.STATE); private static EmbeddedDatabase db; private static JWKSource jwkSource; private static NimbusJwtEncoder jwtEncoder; private static NimbusJwtEncoder dPoPProofJwtEncoder; private static AuthorizationServerSettings authorizationServerSettings; private static HttpMessageConverter accessTokenHttpResponseConverter = new OAuth2AccessTokenResponseHttpMessageConverter(); private static AuthenticationConverter authorizationRequestConverter; private static Consumer> authorizationRequestConvertersConsumer; private static AuthenticationProvider authorizationRequestAuthenticationProvider; private static Consumer> authorizationRequestAuthenticationProvidersConsumer; private static AuthenticationSuccessHandler authorizationResponseHandler; private static AuthenticationFailureHandler authorizationErrorResponseHandler; private static SecurityContextRepository securityContextRepository; private static String consentPage = "/oauth2/consent"; public final SpringTestContext spring = new SpringTestContext(this); @Autowired private MockMvc mvc; @Autowired private JdbcOperations jdbcOperations; @Autowired private RegisteredClientRepository registeredClientRepository; @Autowired private OAuth2AuthorizationService authorizationService; @Autowired private JwtDecoder jwtDecoder; @Autowired(required = false) private OAuth2TokenGenerator tokenGenerator; @BeforeAll public static void init() { JWKSet jwkSet = new JWKSet(TestJwks.DEFAULT_RSA_JWK); jwkSource = (jwkSelector, securityContext) -> jwkSelector.select(jwkSet); jwtEncoder = new NimbusJwtEncoder(jwkSource); JWKSet clientJwkSet = new JWKSet(TestJwks.DEFAULT_EC_JWK); JWKSource clientJwkSource = (jwkSelector, securityContext) -> jwkSelector.select(clientJwkSet); dPoPProofJwtEncoder = new NimbusJwtEncoder(clientJwkSource); authorizationServerSettings = AuthorizationServerSettings.builder() .authorizationEndpoint("/test/authorize") .tokenEndpoint("/test/token") .build(); authorizationRequestConverter = mock(AuthenticationConverter.class); authorizationRequestConvertersConsumer = mock(Consumer.class); authorizationRequestAuthenticationProvider = mock(AuthenticationProvider.class); authorizationRequestAuthenticationProvidersConsumer = mock(Consumer.class); authorizationResponseHandler = mock(AuthenticationSuccessHandler.class); authorizationErrorResponseHandler = mock(AuthenticationFailureHandler.class); securityContextRepository = spy(new HttpSessionSecurityContextRepository()); db = new EmbeddedDatabaseBuilder().generateUniqueName(true) .setType(EmbeddedDatabaseType.HSQL) .setScriptEncoding("UTF-8") .addScript("org/springframework/security/oauth2/server/authorization/oauth2-authorization-schema.sql") .addScript( "org/springframework/security/oauth2/server/authorization/oauth2-authorization-consent-schema.sql") .addScript( "org/springframework/security/oauth2/server/authorization/client/oauth2-registered-client-schema.sql") .build(); } @BeforeEach public void setup() { reset(securityContextRepository); } @AfterEach public void tearDown() { this.jdbcOperations.update("truncate table oauth2_authorization"); this.jdbcOperations.update("truncate table oauth2_authorization_consent"); this.jdbcOperations.update("truncate table oauth2_registered_client"); } @AfterAll public static void destroy() { db.shutdown(); } @Test public void requestWhenAuthorizationRequestNotAuthenticatedThenUnauthorized() throws Exception { this.spring.register(AuthorizationServerConfiguration.class).autowire(); RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); this.registeredClientRepository.save(registeredClient); this.mvc .perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI) .queryParams(getAuthorizationRequestParameters(registeredClient))) .andExpect(status().isUnauthorized()) .andReturn(); } @Test public void requestWhenRegisteredClientMissingThenBadRequest() throws Exception { this.spring.register(AuthorizationServerConfiguration.class).autowire(); RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); this.mvc .perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI) .queryParams(getAuthorizationRequestParameters(registeredClient))) .andExpect(status().isBadRequest()) .andReturn(); } @Test public void requestWhenAuthorizationRequestAuthenticatedThenRedirectToClient() throws Exception { this.spring.register(AuthorizationServerConfiguration.class).autowire(); assertAuthorizationRequestRedirectsToClient(DEFAULT_AUTHORIZATION_ENDPOINT_URI); } @Test public void requestWhenAuthorizationRequestCustomEndpointThenRedirectToClient() throws Exception { this.spring.register(AuthorizationServerConfigurationCustomEndpoints.class).autowire(); assertAuthorizationRequestRedirectsToClient(authorizationServerSettings.getAuthorizationEndpoint()); } private void assertAuthorizationRequestRedirectsToClient(String authorizationEndpointUri) throws Exception { RegisteredClient registeredClient = TestRegisteredClients.registeredClient().redirectUris((redirectUris) -> { redirectUris.clear(); redirectUris.add("https://example.com/callback-1?param=encoded%20parameter%20value"); // gh-1011 }).build(); this.registeredClientRepository.save(registeredClient); MultiValueMap authorizationRequestParameters = getAuthorizationRequestParameters( registeredClient); MvcResult mvcResult = this.mvc .perform(get(authorizationEndpointUri).queryParams(authorizationRequestParameters).with(user("user"))) .andExpect(status().is3xxRedirection()) .andReturn(); String redirectedUrl = mvcResult.getResponse().getRedirectedUrl(); String redirectUri = authorizationRequestParameters.getFirst(OAuth2ParameterNames.REDIRECT_URI); String code = extractParameterFromRedirectUri(redirectedUrl, "code"); assertThat(redirectedUrl).isEqualTo(redirectUri + "&code=" + code + "&state=" + STATE_URL_ENCODED); String authorizationCode = extractParameterFromRedirectUri(redirectedUrl, "code"); OAuth2Authorization authorization = this.authorizationService.findByToken(authorizationCode, AUTHORIZATION_CODE_TOKEN_TYPE); assertThat(authorization).isNotNull(); assertThat(authorization.getAuthorizationGrantType()).isEqualTo(AuthorizationGrantType.AUTHORIZATION_CODE); } @Test public void requestWhenTokenRequestValidThenReturnAccessTokenResponse() throws Exception { this.spring.register(AuthorizationServerConfiguration.class).autowire(); RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); this.registeredClientRepository.save(registeredClient); OAuth2Authorization authorization = createAuthorization(registeredClient); this.authorizationService.save(authorization); OAuth2AccessTokenResponse accessTokenResponse = assertTokenRequestReturnsAccessTokenResponse(registeredClient, authorization, DEFAULT_TOKEN_ENDPOINT_URI); // Assert user authorities was propagated as claim in JWT Jwt jwt = this.jwtDecoder.decode(accessTokenResponse.getAccessToken().getTokenValue()); List authoritiesClaim = jwt.getClaim(AUTHORITIES_CLAIM); Authentication principal = authorization.getAttribute(Principal.class.getName()); Set userAuthorities = new HashSet<>(); for (GrantedAuthority authority : principal.getAuthorities()) { userAuthorities.add(authority.getAuthority()); } assertThat(authoritiesClaim).containsExactlyInAnyOrderElementsOf(userAuthorities); } @Test public void requestWhenTokenRequestCustomEndpointThenReturnAccessTokenResponse() throws Exception { this.spring.register(AuthorizationServerConfigurationCustomEndpoints.class).autowire(); RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); this.registeredClientRepository.save(registeredClient); OAuth2Authorization authorization = createAuthorization(registeredClient); this.authorizationService.save(authorization); assertTokenRequestReturnsAccessTokenResponse(registeredClient, authorization, authorizationServerSettings.getTokenEndpoint()); } private OAuth2AccessTokenResponse assertTokenRequestReturnsAccessTokenResponse(RegisteredClient registeredClient, OAuth2Authorization authorization, String tokenEndpointUri) throws Exception { MvcResult mvcResult = this.mvc .perform(post(tokenEndpointUri).params(getTokenRequestParameters(registeredClient, authorization)) .header(HttpHeaders.AUTHORIZATION, getAuthorizationHeader(registeredClient))) .andExpect(status().isOk()) .andExpect(header().string(HttpHeaders.CACHE_CONTROL, containsString("no-store"))) .andExpect(header().string(HttpHeaders.PRAGMA, containsString("no-cache"))) .andExpect(jsonPath("$.access_token").isNotEmpty()) .andExpect(jsonPath("$.token_type").isNotEmpty()) .andExpect(jsonPath("$.expires_in").isNotEmpty()) .andExpect(jsonPath("$.refresh_token").isNotEmpty()) .andExpect(jsonPath("$.scope").isNotEmpty()) .andReturn(); OAuth2Authorization accessTokenAuthorization = this.authorizationService.findById(authorization.getId()); assertThat(accessTokenAuthorization).isNotNull(); assertThat(accessTokenAuthorization.getAccessToken()).isNotNull(); assertThat(accessTokenAuthorization.getRefreshToken()).isNotNull(); OAuth2Authorization.Token authorizationCodeToken = accessTokenAuthorization .getToken(OAuth2AuthorizationCode.class); assertThat(authorizationCodeToken).isNotNull(); assertThat(authorizationCodeToken.getMetadata().get(OAuth2Authorization.Token.INVALIDATED_METADATA_NAME)) .isEqualTo(true); MockHttpServletResponse servletResponse = mvcResult.getResponse(); MockClientHttpResponse httpResponse = new MockClientHttpResponse(servletResponse.getContentAsByteArray(), HttpStatus.valueOf(servletResponse.getStatus())); return accessTokenHttpResponseConverter.read(OAuth2AccessTokenResponse.class, httpResponse); } @Test public void requestWhenPublicClientWithPkceThenReturnAccessTokenResponse() throws Exception { this.spring.register(AuthorizationServerConfiguration.class).autowire(); RegisteredClient registeredClient = TestRegisteredClients.registeredPublicClient().build(); this.registeredClientRepository.save(registeredClient); MvcResult mvcResult = this.mvc .perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI) .queryParams(getAuthorizationRequestParameters(registeredClient)) .with(user("user"))) .andExpect(status().is3xxRedirection()) .andReturn(); String redirectedUrl = mvcResult.getResponse().getRedirectedUrl(); assertThat(redirectedUrl).matches("https://example.com\\?code=.{15,}&state=" + STATE_URL_ENCODED); String authorizationCode = extractParameterFromRedirectUri(redirectedUrl, "code"); OAuth2Authorization authorizationCodeAuthorization = this.authorizationService.findByToken(authorizationCode, AUTHORIZATION_CODE_TOKEN_TYPE); assertThat(authorizationCodeAuthorization).isNotNull(); assertThat(authorizationCodeAuthorization.getAuthorizationGrantType()) .isEqualTo(AuthorizationGrantType.AUTHORIZATION_CODE); this.mvc .perform(post(DEFAULT_TOKEN_ENDPOINT_URI) .params(getTokenRequestParameters(registeredClient, authorizationCodeAuthorization)) .param(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId())) .andExpect(header().string(HttpHeaders.CACHE_CONTROL, containsString("no-store"))) .andExpect(header().string(HttpHeaders.PRAGMA, containsString("no-cache"))) .andExpect(status().isOk()) .andExpect(jsonPath("$.access_token").isNotEmpty()) .andExpect(jsonPath("$.token_type").isNotEmpty()) .andExpect(jsonPath("$.expires_in").isNotEmpty()) .andExpect(jsonPath("$.refresh_token").doesNotExist()) .andExpect(jsonPath("$.scope").isNotEmpty()); OAuth2Authorization accessTokenAuthorization = this.authorizationService .findById(authorizationCodeAuthorization.getId()); assertThat(accessTokenAuthorization).isNotNull(); assertThat(accessTokenAuthorization.getAccessToken()).isNotNull(); OAuth2Authorization.Token authorizationCodeToken = accessTokenAuthorization .getToken(OAuth2AuthorizationCode.class); assertThat(authorizationCodeToken).isNotNull(); assertThat(authorizationCodeToken.getMetadata().get(OAuth2Authorization.Token.INVALIDATED_METADATA_NAME)) .isEqualTo(true); } // gh-1430 @Test public void requestWhenPublicClientWithPkceAndCustomRefreshTokenGeneratorThenReturnRefreshToken() throws Exception { this.spring.register(AuthorizationServerConfigurationWithCustomRefreshTokenGenerator.class).autowire(); RegisteredClient registeredClient = TestRegisteredClients.registeredPublicClient() .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN) .build(); this.registeredClientRepository.save(registeredClient); MvcResult mvcResult = this.mvc .perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI) .queryParams(getAuthorizationRequestParameters(registeredClient)) .with(user("user"))) .andExpect(status().is3xxRedirection()) .andReturn(); String redirectedUrl = mvcResult.getResponse().getRedirectedUrl(); assertThat(redirectedUrl).matches("https://example.com\\?code=.{15,}&state=" + STATE_URL_ENCODED); String authorizationCode = extractParameterFromRedirectUri(redirectedUrl, "code"); OAuth2Authorization authorizationCodeAuthorization = this.authorizationService.findByToken(authorizationCode, AUTHORIZATION_CODE_TOKEN_TYPE); assertThat(authorizationCodeAuthorization).isNotNull(); assertThat(authorizationCodeAuthorization.getAuthorizationGrantType()) .isEqualTo(AuthorizationGrantType.AUTHORIZATION_CODE); this.mvc .perform(post(DEFAULT_TOKEN_ENDPOINT_URI) .params(getTokenRequestParameters(registeredClient, authorizationCodeAuthorization)) .param(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId())) .andExpect(header().string(HttpHeaders.CACHE_CONTROL, containsString("no-store"))) .andExpect(header().string(HttpHeaders.PRAGMA, containsString("no-cache"))) .andExpect(status().isOk()) .andExpect(jsonPath("$.access_token").isNotEmpty()) .andExpect(jsonPath("$.token_type").isNotEmpty()) .andExpect(jsonPath("$.expires_in").isNotEmpty()) .andExpect(jsonPath("$.refresh_token").isNotEmpty()) .andExpect(jsonPath("$.scope").isNotEmpty()); OAuth2Authorization authorization = this.authorizationService.findById(authorizationCodeAuthorization.getId()); assertThat(authorization).isNotNull(); assertThat(authorization.getAccessToken()).isNotNull(); assertThat(authorization.getRefreshToken()).isNotNull(); OAuth2Authorization.Token authorizationCodeToken = authorization .getToken(OAuth2AuthorizationCode.class); assertThat(authorizationCodeToken).isNotNull(); assertThat(authorizationCodeToken.getMetadata().get(OAuth2Authorization.Token.INVALIDATED_METADATA_NAME)) .isEqualTo(true); } // gh-1680 @Test public void requestWhenPublicClientWithPkceAndEmptyCodeThenBadRequest() throws Exception { this.spring.register(AuthorizationServerConfiguration.class).autowire(); RegisteredClient registeredClient = TestRegisteredClients.registeredPublicClient().build(); this.registeredClientRepository.save(registeredClient); MultiValueMap tokenRequestParameters = new LinkedMultiValueMap<>(); tokenRequestParameters.set(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.AUTHORIZATION_CODE.getValue()); tokenRequestParameters.set(OAuth2ParameterNames.CODE, ""); tokenRequestParameters.set(OAuth2ParameterNames.REDIRECT_URI, registeredClient.getRedirectUris().iterator().next()); tokenRequestParameters.set(PkceParameterNames.CODE_VERIFIER, S256_CODE_VERIFIER); this.mvc .perform(post(DEFAULT_TOKEN_ENDPOINT_URI).params(tokenRequestParameters) .param(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId())) .andExpect(status().isBadRequest()); } @Test public void requestWhenConfidentialClientWithPkceAndMissingCodeVerifierThenBadRequest() throws Exception { this.spring.register(AuthorizationServerConfiguration.class).autowire(); RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); this.registeredClientRepository.save(registeredClient); MultiValueMap authorizationRequestParameters = getAuthorizationRequestParameters( registeredClient); MvcResult mvcResult = this.mvc .perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI).queryParams(authorizationRequestParameters) .with(user("user"))) .andExpect(status().is3xxRedirection()) .andReturn(); String redirectedUrl = mvcResult.getResponse().getRedirectedUrl(); String expectedRedirectUri = authorizationRequestParameters.getFirst(OAuth2ParameterNames.REDIRECT_URI); assertThat(redirectedUrl).matches(expectedRedirectUri + "\\?code=.{15,}&state=" + STATE_URL_ENCODED); String authorizationCode = extractParameterFromRedirectUri(redirectedUrl, "code"); OAuth2Authorization authorizationCodeAuthorization = this.authorizationService.findByToken(authorizationCode, AUTHORIZATION_CODE_TOKEN_TYPE); assertThat(authorizationCodeAuthorization).isNotNull(); assertThat(authorizationCodeAuthorization.getAuthorizationGrantType()) .isEqualTo(AuthorizationGrantType.AUTHORIZATION_CODE); MultiValueMap tokenRequestParameters = getTokenRequestParameters(registeredClient, authorizationCodeAuthorization); tokenRequestParameters.remove(PkceParameterNames.CODE_VERIFIER); this.mvc .perform(post(DEFAULT_TOKEN_ENDPOINT_URI).params(tokenRequestParameters) .param(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()) .header(HttpHeaders.AUTHORIZATION, getAuthorizationHeader(registeredClient))) .andExpect(status().isBadRequest()); } // gh-1011 @Test public void requestWhenConfidentialClientWithPkceAndMissingCodeChallengeThenErrorResponseEncoded() throws Exception { this.spring.register(AuthorizationServerConfiguration.class).autowire(); String redirectUri = "https://example.com/callback-1?param=encoded%20parameter%20value"; RegisteredClient registeredClient = TestRegisteredClients.registeredClient().redirectUris((redirectUris) -> { redirectUris.clear(); redirectUris.add(redirectUri); }).build(); this.registeredClientRepository.save(registeredClient); MultiValueMap authorizationRequestParameters = getAuthorizationRequestParameters( registeredClient); authorizationRequestParameters.remove(PkceParameterNames.CODE_CHALLENGE); MvcResult mvcResult = this.mvc .perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI).queryParams(authorizationRequestParameters) .with(user("user"))) .andExpect(status().is3xxRedirection()) .andReturn(); String redirectedUrl = mvcResult.getResponse().getRedirectedUrl(); String expectedRedirectUri = redirectUri + "&" + "error=invalid_request&" + "error_description=" + UriUtils.encode("OAuth 2.0 Parameter: code_challenge", StandardCharsets.UTF_8) + "&" + "error_uri=" + UriUtils.encode("https://datatracker.ietf.org/doc/html/rfc7636#section-4.4.1", StandardCharsets.UTF_8) + "&" + "state=" + STATE_URL_ENCODED; assertThat(redirectedUrl).isEqualTo(expectedRedirectUri); } @Test public void requestWhenConfidentialClientWithPkceAndMissingCodeChallengeButCodeVerifierProvidedThenBadRequest() throws Exception { this.spring.register(AuthorizationServerConfiguration.class).autowire(); RegisteredClient registeredClient = TestRegisteredClients.registeredClient() .clientSettings(ClientSettings.builder().requireProofKey(false).build()) .build(); this.registeredClientRepository.save(registeredClient); MultiValueMap authorizationRequestParameters = getAuthorizationRequestParameters( registeredClient); authorizationRequestParameters.remove(PkceParameterNames.CODE_CHALLENGE); MvcResult mvcResult = this.mvc .perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI).queryParams(authorizationRequestParameters) .with(user("user"))) .andExpect(status().is3xxRedirection()) .andReturn(); String redirectedUrl = mvcResult.getResponse().getRedirectedUrl(); String expectedRedirectUri = authorizationRequestParameters.getFirst(OAuth2ParameterNames.REDIRECT_URI); assertThat(redirectedUrl).matches(expectedRedirectUri + "\\?code=.{15,}&state=" + STATE_URL_ENCODED); String authorizationCode = extractParameterFromRedirectUri(redirectedUrl, "code"); OAuth2Authorization authorizationCodeAuthorization = this.authorizationService.findByToken(authorizationCode, AUTHORIZATION_CODE_TOKEN_TYPE); assertThat(authorizationCodeAuthorization).isNotNull(); assertThat(authorizationCodeAuthorization.getAuthorizationGrantType()) .isEqualTo(AuthorizationGrantType.AUTHORIZATION_CODE); this.mvc .perform(post(DEFAULT_TOKEN_ENDPOINT_URI) .params(getTokenRequestParameters(registeredClient, authorizationCodeAuthorization)) .header(HttpHeaders.AUTHORIZATION, getAuthorizationHeader(registeredClient))) .andExpect(status().isBadRequest()); } @Test public void requestWhenCustomTokenGeneratorThenUsed() throws Exception { this.spring.register(AuthorizationServerConfigurationWithTokenGenerator.class).autowire(); RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); this.registeredClientRepository.save(registeredClient); OAuth2Authorization authorization = createAuthorization(registeredClient); this.authorizationService.save(authorization); this.mvc .perform(post(DEFAULT_TOKEN_ENDPOINT_URI).params(getTokenRequestParameters(registeredClient, authorization)) .header(HttpHeaders.AUTHORIZATION, getAuthorizationHeader(registeredClient))) .andExpect(status().isOk()); verify(this.tokenGenerator, times(2)).generate(any()); } @Test public void requestWhenRequiresConsentThenDisplaysConsentPage() throws Exception { this.spring.register(AuthorizationServerConfiguration.class).autowire(); RegisteredClient registeredClient = TestRegisteredClients.registeredClient().scopes((scopes) -> { scopes.clear(); scopes.add("message.read"); scopes.add("message.write"); }).clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build()).build(); this.registeredClientRepository.save(registeredClient); String consentPage = this.mvc .perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI) .queryParams(getAuthorizationRequestParameters(registeredClient)) .with(user("user"))) .andExpect(status().is2xxSuccessful()) .andReturn() .getResponse() .getContentAsString(); assertThat(consentPage).contains("Consent required"); assertThat(consentPage).contains(scopeCheckbox("message.read")); assertThat(consentPage).contains(scopeCheckbox("message.write")); } @Test public void requestWhenConsentRequestThenReturnAccessTokenResponse() throws Exception { this.spring.register(AuthorizationServerConfiguration.class).autowire(); RegisteredClient registeredClient = TestRegisteredClients.registeredClient().scopes((scopes) -> { scopes.clear(); scopes.add("message.read"); scopes.add("message.write"); }).clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build()).build(); this.registeredClientRepository.save(registeredClient); OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient) .principalName("user") .build(); Map additionalParameters = new HashMap<>(); additionalParameters.put(PkceParameterNames.CODE_CHALLENGE, S256_CODE_CHALLENGE); additionalParameters.put(PkceParameterNames.CODE_CHALLENGE_METHOD, "S256"); OAuth2AuthorizationRequest authorizationRequest = authorization .getAttribute(OAuth2AuthorizationRequest.class.getName()); OAuth2AuthorizationRequest updatedAuthorizationRequest = OAuth2AuthorizationRequest.from(authorizationRequest) .state(STATE_URL_UNENCODED) .additionalParameters(additionalParameters) .build(); authorization = OAuth2Authorization.from(authorization) .attribute(OAuth2AuthorizationRequest.class.getName(), updatedAuthorizationRequest) .build(); this.authorizationService.save(authorization); MvcResult mvcResult = this.mvc .perform(post(DEFAULT_AUTHORIZATION_ENDPOINT_URI) .param(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()) .param(OAuth2ParameterNames.SCOPE, "message.read") .param(OAuth2ParameterNames.SCOPE, "message.write") .param(OAuth2ParameterNames.STATE, authorization.getAttribute(OAuth2ParameterNames.STATE)) .with(user("user"))) .andExpect(status().is3xxRedirection()) .andReturn(); String redirectedUrl = mvcResult.getResponse().getRedirectedUrl(); assertThat(redirectedUrl) .matches(authorizationRequest.getRedirectUri() + "\\?code=.{15,}&state=" + STATE_URL_ENCODED); String authorizationCode = extractParameterFromRedirectUri(redirectedUrl, "code"); OAuth2Authorization authorizationCodeAuthorization = this.authorizationService.findByToken(authorizationCode, AUTHORIZATION_CODE_TOKEN_TYPE); this.mvc .perform(post(DEFAULT_TOKEN_ENDPOINT_URI) .params(getTokenRequestParameters(registeredClient, authorizationCodeAuthorization)) .header(HttpHeaders.AUTHORIZATION, getAuthorizationHeader(registeredClient))) .andExpect(status().isOk()) .andExpect(header().string(HttpHeaders.CACHE_CONTROL, containsString("no-store"))) .andExpect(header().string(HttpHeaders.PRAGMA, containsString("no-cache"))) .andExpect(jsonPath("$.access_token").isNotEmpty()) .andExpect(jsonPath("$.token_type").isNotEmpty()) .andExpect(jsonPath("$.expires_in").isNotEmpty()) .andExpect(jsonPath("$.refresh_token").isNotEmpty()) .andExpect(jsonPath("$.scope").isNotEmpty()) .andReturn(); } @Test public void requestWhenCustomConsentPageConfiguredThenRedirect() throws Exception { this.spring.register(AuthorizationServerConfigurationCustomConsentPage.class).autowire(); RegisteredClient registeredClient = TestRegisteredClients.registeredClient().scopes((scopes) -> { scopes.clear(); scopes.add("message.read"); scopes.add("message.write"); }).clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build()).build(); this.registeredClientRepository.save(registeredClient); MvcResult mvcResult = this.mvc .perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI) .queryParams(getAuthorizationRequestParameters(registeredClient)) .with(user("user"))) .andExpect(status().is3xxRedirection()) .andReturn(); String redirectedUrl = mvcResult.getResponse().getRedirectedUrl(); assertThat(redirectedUrl).matches("http://localhost/oauth2/consent\\?scope=.+&client_id=.+&state=.+"); String locationHeader = URLDecoder.decode(redirectedUrl, StandardCharsets.UTF_8.name()); UriComponents uriComponents = UriComponentsBuilder.fromUriString(locationHeader).build(); MultiValueMap redirectQueryParams = uriComponents.getQueryParams(); assertThat(uriComponents.getPath()).isEqualTo(consentPage); assertThat(redirectQueryParams.getFirst(OAuth2ParameterNames.SCOPE)).isEqualTo("message.read message.write"); assertThat(redirectQueryParams.getFirst(OAuth2ParameterNames.CLIENT_ID)) .isEqualTo(registeredClient.getClientId()); String state = extractParameterFromRedirectUri(redirectedUrl, "state"); OAuth2Authorization authorization = this.authorizationService.findByToken(state, STATE_TOKEN_TYPE); assertThat(authorization).isNotNull(); } @Test public void requestWhenCustomConsentCustomizerConfiguredThenUsed() throws Exception { this.spring.register(AuthorizationServerConfigurationCustomConsentRequest.class).autowire(); RegisteredClient registeredClient = TestRegisteredClients.registeredClient() .clientSettings(ClientSettings.builder() .requireAuthorizationConsent(true) .setting("custom.allowed-authorities", "authority-1 authority-2") .build()) .build(); this.registeredClientRepository.save(registeredClient); OAuth2Authorization authorization = createAuthorization(registeredClient); OAuth2AuthorizationRequest authorizationRequest = authorization .getAttribute(OAuth2AuthorizationRequest.class.getName()); OAuth2AuthorizationRequest updatedAuthorizationRequest = OAuth2AuthorizationRequest.from(authorizationRequest) .state(STATE_URL_UNENCODED) .build(); authorization = OAuth2Authorization.from(authorization) .attribute(OAuth2AuthorizationRequest.class.getName(), updatedAuthorizationRequest) .build(); this.authorizationService.save(authorization); MvcResult mvcResult = this.mvc .perform(post(DEFAULT_AUTHORIZATION_ENDPOINT_URI) .param(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()) .param("authority", "authority-1 authority-2") .param(OAuth2ParameterNames.STATE, authorization.getAttribute(OAuth2ParameterNames.STATE)) .with(user("principal"))) .andExpect(status().is3xxRedirection()) .andReturn(); String redirectedUrl = mvcResult.getResponse().getRedirectedUrl(); assertThat(redirectedUrl) .matches(authorizationRequest.getRedirectUri() + "\\?code=.{15,}&state=" + STATE_URL_ENCODED); String authorizationCode = extractParameterFromRedirectUri(redirectedUrl, "code"); OAuth2Authorization authorizationCodeAuthorization = this.authorizationService.findByToken(authorizationCode, AUTHORIZATION_CODE_TOKEN_TYPE); mvcResult = this.mvc .perform(post(DEFAULT_TOKEN_ENDPOINT_URI) .params(getTokenRequestParameters(registeredClient, authorizationCodeAuthorization)) .header(HttpHeaders.AUTHORIZATION, getAuthorizationHeader(registeredClient))) .andExpect(status().isOk()) .andExpect(header().string(HttpHeaders.CACHE_CONTROL, containsString("no-store"))) .andExpect(header().string(HttpHeaders.PRAGMA, containsString("no-cache"))) .andExpect(jsonPath("$.access_token").isNotEmpty()) .andExpect(jsonPath("$.access_token").value(new AssertionMatcher() { @Override public void assertion(String accessToken) throws AssertionError { Jwt jwt = OAuth2AuthorizationCodeGrantTests.this.jwtDecoder.decode(accessToken); assertThat(jwt.getClaimAsStringList(AUTHORITIES_CLAIM)).containsExactlyInAnyOrder("authority-1", "authority-2"); } })) .andExpect(jsonPath("$.token_type").isNotEmpty()) .andExpect(jsonPath("$.expires_in").isNotEmpty()) .andExpect(jsonPath("$.refresh_token").isNotEmpty()) .andExpect(jsonPath("$.scope").doesNotExist()) .andReturn(); } @Test public void requestWhenAuthorizationEndpointCustomizedThenUsed() throws Exception { this.spring.register(AuthorizationServerConfigurationCustomAuthorizationEndpoint.class).autowire(); RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); this.registeredClientRepository.save(registeredClient); TestingAuthenticationToken principal = new TestingAuthenticationToken("principalName", "password"); Map additionalParameters = new HashMap<>(); additionalParameters.put(PkceParameterNames.CODE_CHALLENGE, S256_CODE_CHALLENGE); additionalParameters.put(PkceParameterNames.CODE_CHALLENGE_METHOD, "S256"); OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication = new OAuth2AuthorizationCodeRequestAuthenticationToken( "https://provider.com/oauth2/authorize", registeredClient.getClientId(), principal, registeredClient.getRedirectUris().iterator().next(), STATE_URL_UNENCODED, registeredClient.getScopes(), additionalParameters); OAuth2AuthorizationCode authorizationCode = new OAuth2AuthorizationCode("code", Instant.now(), Instant.now().plus(5, ChronoUnit.MINUTES)); OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthenticationResult = new OAuth2AuthorizationCodeRequestAuthenticationToken( "https://provider.com/oauth2/authorize", registeredClient.getClientId(), principal, authorizationCode, registeredClient.getRedirectUris().iterator().next(), STATE_URL_UNENCODED, registeredClient.getScopes()); given(authorizationRequestConverter.convert(any())).willReturn(authorizationCodeRequestAuthentication); given(authorizationRequestAuthenticationProvider .supports(eq(OAuth2AuthorizationCodeRequestAuthenticationToken.class))).willReturn(true); given(authorizationRequestAuthenticationProvider.authenticate(any())) .willReturn(authorizationCodeRequestAuthenticationResult); this.mvc .perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI) .queryParams(getAuthorizationRequestParameters(registeredClient)) .with(user("user"))) .andExpect(status().isOk()); verify(authorizationRequestConverter).convert(any()); @SuppressWarnings("unchecked") ArgumentCaptor> authenticationConvertersCaptor = ArgumentCaptor .forClass(List.class); verify(authorizationRequestConvertersConsumer).accept(authenticationConvertersCaptor.capture()); List authenticationConverters = authenticationConvertersCaptor.getValue(); assertThat(authenticationConverters).allMatch((converter) -> converter == authorizationRequestConverter || converter instanceof OAuth2AuthorizationCodeRequestAuthenticationConverter || converter instanceof OAuth2AuthorizationConsentAuthenticationConverter); verify(authorizationRequestAuthenticationProvider).authenticate(eq(authorizationCodeRequestAuthentication)); @SuppressWarnings("unchecked") ArgumentCaptor> authenticationProvidersCaptor = ArgumentCaptor .forClass(List.class); verify(authorizationRequestAuthenticationProvidersConsumer).accept(authenticationProvidersCaptor.capture()); List authenticationProviders = authenticationProvidersCaptor.getValue(); assertThat(authenticationProviders) .allMatch((provider) -> provider == authorizationRequestAuthenticationProvider || provider instanceof OAuth2AuthorizationCodeRequestAuthenticationProvider || provider instanceof OAuth2AuthorizationConsentAuthenticationProvider); verify(authorizationResponseHandler).onAuthenticationSuccess(any(), any(), eq(authorizationCodeRequestAuthenticationResult)); } // gh-482 @Test public void requestWhenClientObtainsAccessTokenThenClientAuthenticationNotPersisted() throws Exception { this.spring.register(AuthorizationServerConfigurationWithSecurityContextRepository.class).autowire(); RegisteredClient registeredClient = TestRegisteredClients.registeredPublicClient().build(); this.registeredClientRepository.save(registeredClient); MvcResult mvcResult = this.mvc .perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI) .queryParams(getAuthorizationRequestParameters(registeredClient)) .with(user("user"))) .andExpect(status().is3xxRedirection()) .andReturn(); ArgumentCaptor securityContextCaptor = ArgumentCaptor .forClass(org.springframework.security.core.context.SecurityContext.class); verify(securityContextRepository, times(1)).saveContext(securityContextCaptor.capture(), any(), any()); assertThat(securityContextCaptor.getValue().getAuthentication()) .isInstanceOf(UsernamePasswordAuthenticationToken.class); reset(securityContextRepository); String authorizationCode = extractParameterFromRedirectUri(mvcResult.getResponse().getRedirectedUrl(), "code"); OAuth2Authorization authorizationCodeAuthorization = this.authorizationService.findByToken(authorizationCode, AUTHORIZATION_CODE_TOKEN_TYPE); mvcResult = this.mvc .perform(post(DEFAULT_TOKEN_ENDPOINT_URI) .params(getTokenRequestParameters(registeredClient, authorizationCodeAuthorization)) .param(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId())) .andExpect(header().string(HttpHeaders.CACHE_CONTROL, containsString("no-store"))) .andExpect(header().string(HttpHeaders.PRAGMA, containsString("no-cache"))) .andExpect(status().isOk()) .andExpect(jsonPath("$.access_token").isNotEmpty()) .andExpect(jsonPath("$.token_type").isNotEmpty()) .andExpect(jsonPath("$.expires_in").isNotEmpty()) .andExpect(jsonPath("$.refresh_token").doesNotExist()) .andExpect(jsonPath("$.scope").isNotEmpty()) .andReturn(); org.springframework.security.core.context.SecurityContext securityContext = securityContextRepository .loadDeferredContext(mvcResult.getRequest()) .get(); assertThat(securityContext.getAuthentication()).isNull(); } @Test public void requestWhenAuthorizationAndTokenRequestIncludesIssuerPathThenIssuerResolvedWithPath() throws Exception { this.spring.register(AuthorizationServerConfigurationWithMultipleIssuersAllowed.class).autowire(); RegisteredClient registeredClient = TestRegisteredClients.registeredPublicClient().build(); this.registeredClientRepository.save(registeredClient); String issuer = "https://example.com:8443/issuer1"; MvcResult mvcResult = this.mvc .perform(get(issuer.concat(DEFAULT_AUTHORIZATION_ENDPOINT_URI)) .queryParams(getAuthorizationRequestParameters(registeredClient)) .with(user("user"))) .andExpect(status().is3xxRedirection()) .andReturn(); String authorizationCode = extractParameterFromRedirectUri(mvcResult.getResponse().getRedirectedUrl(), "code"); OAuth2Authorization authorizationCodeAuthorization = this.authorizationService.findByToken(authorizationCode, AUTHORIZATION_CODE_TOKEN_TYPE); this.mvc .perform(post(issuer.concat(DEFAULT_TOKEN_ENDPOINT_URI)) .params(getTokenRequestParameters(registeredClient, authorizationCodeAuthorization)) .param(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId())) .andExpect(header().string(HttpHeaders.CACHE_CONTROL, containsString("no-store"))) .andExpect(header().string(HttpHeaders.PRAGMA, containsString("no-cache"))) .andExpect(status().isOk()) .andExpect(jsonPath("$.access_token").isNotEmpty()) .andExpect(jsonPath("$.token_type").isNotEmpty()) .andExpect(jsonPath("$.expires_in").isNotEmpty()) .andExpect(jsonPath("$.refresh_token").doesNotExist()) .andExpect(jsonPath("$.scope").isNotEmpty()) .andReturn(); ArgumentCaptor tokenContextCaptor = ArgumentCaptor.forClass(OAuth2TokenContext.class); verify(this.tokenGenerator).generate(tokenContextCaptor.capture()); OAuth2TokenContext tokenContext = tokenContextCaptor.getValue(); assertThat(tokenContext.getAuthorizationServerContext().getIssuer()).isEqualTo(issuer); } @Test public void requestWhenTokenRequestWithDPoPProofThenReturnDPoPBoundAccessToken() throws Exception { this.spring.register(AuthorizationServerConfiguration.class).autowire(); RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); this.registeredClientRepository.save(registeredClient); OAuth2Authorization authorization = createAuthorization(registeredClient); this.authorizationService.save(authorization); String tokenEndpointUri = "http://localhost" + DEFAULT_TOKEN_ENDPOINT_URI; String dPoPProof = generateDPoPProof(tokenEndpointUri); this.mvc .perform(post(DEFAULT_TOKEN_ENDPOINT_URI).params(getTokenRequestParameters(registeredClient, authorization)) .header(HttpHeaders.AUTHORIZATION, getAuthorizationHeader(registeredClient)) .header(OAuth2AccessToken.TokenType.DPOP.getValue(), dPoPProof)) .andExpect(status().isOk()) .andExpect(jsonPath("$.token_type").value(OAuth2AccessToken.TokenType.DPOP.getValue())); authorization = this.authorizationService.findById(authorization.getId()); assertThat(authorization.getAccessToken().getClaims()).containsKey("cnf"); @SuppressWarnings("unchecked") Map cnfClaims = (Map) authorization.getAccessToken().getClaims().get("cnf"); assertThat(cnfClaims).containsKey("jkt"); String jwkThumbprintClaim = (String) cnfClaims.get("jkt"); assertThat(jwkThumbprintClaim).isEqualTo(TestJwks.DEFAULT_EC_JWK.toPublicJWK().computeThumbprint().toString()); } @Test public void requestWhenPushedAuthorizationRequestThenReturnAccessTokenResponse() throws Exception { this.spring.register(AuthorizationServerConfigurationWithPushedAuthorizationRequests.class).autowire(); RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); this.registeredClientRepository.save(registeredClient); MvcResult mvcResult = this.mvc .perform(post("/oauth2/par").params(getAuthorizationRequestParameters(registeredClient)) .header(HttpHeaders.AUTHORIZATION, getAuthorizationHeader(registeredClient))) .andExpect(header().string(HttpHeaders.CACHE_CONTROL, containsString("no-store"))) .andExpect(header().string(HttpHeaders.PRAGMA, containsString("no-cache"))) .andExpect(status().isCreated()) .andExpect(jsonPath("$.request_uri").isNotEmpty()) .andExpect(jsonPath("$.expires_in").isNotEmpty()) .andReturn(); String requestUri = JsonPath.read(mvcResult.getResponse().getContentAsString(), "$.request_uri"); mvcResult = this.mvc .perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI) .queryParam(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()) .queryParam(OAuth2ParameterNames.REQUEST_URI, requestUri) .with(user("user"))) .andExpect(status().is3xxRedirection()) .andReturn(); String authorizationCode = extractParameterFromRedirectUri(mvcResult.getResponse().getRedirectedUrl(), "code"); OAuth2Authorization authorizationCodeAuthorization = this.authorizationService.findByToken(authorizationCode, AUTHORIZATION_CODE_TOKEN_TYPE); this.mvc .perform(post(DEFAULT_TOKEN_ENDPOINT_URI) .params(getTokenRequestParameters(registeredClient, authorizationCodeAuthorization)) .param(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()) .header(HttpHeaders.AUTHORIZATION, getAuthorizationHeader(registeredClient))) .andExpect(header().string(HttpHeaders.CACHE_CONTROL, containsString("no-store"))) .andExpect(header().string(HttpHeaders.PRAGMA, containsString("no-cache"))) .andExpect(status().isOk()) .andExpect(jsonPath("$.access_token").isNotEmpty()) .andExpect(jsonPath("$.token_type").isNotEmpty()) .andExpect(jsonPath("$.expires_in").isNotEmpty()) .andExpect(jsonPath("$.refresh_token").isNotEmpty()) .andExpect(jsonPath("$.scope").isNotEmpty()) .andReturn(); OAuth2Authorization accessTokenAuthorization = this.authorizationService .findById(authorizationCodeAuthorization.getId()); assertThat(accessTokenAuthorization).isNotNull(); assertThat(accessTokenAuthorization.getAccessToken()).isNotNull(); OAuth2Authorization.Token authorizationCodeToken = accessTokenAuthorization .getToken(OAuth2AuthorizationCode.class); assertThat(authorizationCodeToken).isNotNull(); assertThat(authorizationCodeToken.getMetadata().get(OAuth2Authorization.Token.INVALIDATED_METADATA_NAME)) .isEqualTo(true); } // gh-2182 @Test public void requestWhenPushedAuthorizationRequestAndRequiresConsentThenDisplaysConsentPage() throws Exception { this.spring.register(AuthorizationServerConfigurationWithPushedAuthorizationRequests.class).autowire(); RegisteredClient registeredClient = TestRegisteredClients.registeredClient().scopes((scopes) -> { scopes.clear(); scopes.add("message.read"); scopes.add("message.write"); }).clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build()).build(); this.registeredClientRepository.save(registeredClient); MvcResult mvcResult = this.mvc .perform(post("/oauth2/par").params(getAuthorizationRequestParameters(registeredClient)) .header(HttpHeaders.AUTHORIZATION, getAuthorizationHeader(registeredClient))) .andExpect(header().string(HttpHeaders.CACHE_CONTROL, containsString("no-store"))) .andExpect(header().string(HttpHeaders.PRAGMA, containsString("no-cache"))) .andExpect(status().isCreated()) .andExpect(jsonPath("$.request_uri").isNotEmpty()) .andExpect(jsonPath("$.expires_in").isNotEmpty()) .andReturn(); String requestUri = JsonPath.read(mvcResult.getResponse().getContentAsString(), "$.request_uri"); String consentPage = this.mvc .perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI) .queryParam(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()) .queryParam(OAuth2ParameterNames.REQUEST_URI, requestUri) .with(user("user"))) .andExpect(status().is2xxSuccessful()) .andReturn() .getResponse() .getContentAsString(); assertThat(consentPage).contains("Consent required"); assertThat(consentPage).contains(scopeCheckbox("message.read")); assertThat(consentPage).contains(scopeCheckbox("message.write")); } // gh-2182 @Test public void requestWhenPushedAuthorizationRequestAndCustomConsentPageConfiguredThenRedirect() throws Exception { this.spring.register(AuthorizationServerConfigurationWithPushedAuthorizationRequestsAndCustomConsentPage.class) .autowire(); RegisteredClient registeredClient = TestRegisteredClients.registeredClient().scopes((scopes) -> { scopes.clear(); scopes.add("message.read"); scopes.add("message.write"); }).clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build()).build(); this.registeredClientRepository.save(registeredClient); MvcResult mvcResult = this.mvc .perform(post("/oauth2/par").params(getAuthorizationRequestParameters(registeredClient)) .header(HttpHeaders.AUTHORIZATION, getAuthorizationHeader(registeredClient))) .andExpect(header().string(HttpHeaders.CACHE_CONTROL, containsString("no-store"))) .andExpect(header().string(HttpHeaders.PRAGMA, containsString("no-cache"))) .andExpect(status().isCreated()) .andExpect(jsonPath("$.request_uri").isNotEmpty()) .andExpect(jsonPath("$.expires_in").isNotEmpty()) .andReturn(); String requestUri = JsonPath.read(mvcResult.getResponse().getContentAsString(), "$.request_uri"); mvcResult = this.mvc .perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI) .queryParam(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()) .queryParam(OAuth2ParameterNames.REQUEST_URI, requestUri) .with(user("user"))) .andExpect(status().is3xxRedirection()) .andReturn(); String redirectedUrl = mvcResult.getResponse().getRedirectedUrl(); assertThat(redirectedUrl).matches("http://localhost/oauth2/consent\\?scope=.+&client_id=.+&state=.+"); String locationHeader = URLDecoder.decode(redirectedUrl, StandardCharsets.UTF_8.name()); UriComponents uriComponents = UriComponentsBuilder.fromUriString(locationHeader).build(); MultiValueMap redirectQueryParams = uriComponents.getQueryParams(); assertThat(uriComponents.getPath()).isEqualTo(consentPage); assertThat(redirectQueryParams.getFirst(OAuth2ParameterNames.SCOPE)).isEqualTo("message.read message.write"); assertThat(redirectQueryParams.getFirst(OAuth2ParameterNames.CLIENT_ID)) .isEqualTo(registeredClient.getClientId()); String state = extractParameterFromRedirectUri(redirectedUrl, "state"); OAuth2Authorization authorization = this.authorizationService.findByToken(state, STATE_TOKEN_TYPE); assertThat(authorization).isNotNull(); } private static OAuth2Authorization createAuthorization(RegisteredClient registeredClient) { Map additionalParameters = new HashMap<>(); additionalParameters.put(PkceParameterNames.CODE_CHALLENGE, S256_CODE_CHALLENGE); additionalParameters.put(PkceParameterNames.CODE_CHALLENGE_METHOD, "S256"); return TestOAuth2Authorizations.authorization(registeredClient, additionalParameters).build(); } private static String generateDPoPProof(String tokenEndpointUri) { // @formatter:off Map publicJwk = TestJwks.DEFAULT_EC_JWK .toPublicJWK() .toJSONObject(); JwsHeader jwsHeader = JwsHeader.with(SignatureAlgorithm.ES256) .type("dpop+jwt") .jwk(publicJwk) .build(); JwtClaimsSet claims = JwtClaimsSet.builder() .issuedAt(Instant.now()) .claim("htm", "POST") .claim("htu", tokenEndpointUri) .id(UUID.randomUUID().toString()) .build(); // @formatter:on Jwt jwt = dPoPProofJwtEncoder.encode(JwtEncoderParameters.from(jwsHeader, claims)); return jwt.getTokenValue(); } private static MultiValueMap getAuthorizationRequestParameters(RegisteredClient registeredClient) { MultiValueMap parameters = new LinkedMultiValueMap<>(); parameters.set(OAuth2ParameterNames.RESPONSE_TYPE, OAuth2AuthorizationResponseType.CODE.getValue()); parameters.set(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()); parameters.set(OAuth2ParameterNames.REDIRECT_URI, registeredClient.getRedirectUris().iterator().next()); parameters.set(OAuth2ParameterNames.SCOPE, StringUtils.collectionToDelimitedString(registeredClient.getScopes(), " ")); parameters.set(OAuth2ParameterNames.STATE, STATE_URL_UNENCODED); parameters.set(PkceParameterNames.CODE_CHALLENGE, S256_CODE_CHALLENGE); parameters.set(PkceParameterNames.CODE_CHALLENGE_METHOD, "S256"); return parameters; } private static MultiValueMap getTokenRequestParameters(RegisteredClient registeredClient, OAuth2Authorization authorization) { MultiValueMap parameters = new LinkedMultiValueMap<>(); parameters.set(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.AUTHORIZATION_CODE.getValue()); parameters.set(OAuth2ParameterNames.CODE, authorization.getToken(OAuth2AuthorizationCode.class).getToken().getTokenValue()); parameters.set(OAuth2ParameterNames.REDIRECT_URI, registeredClient.getRedirectUris().iterator().next()); parameters.set(PkceParameterNames.CODE_VERIFIER, S256_CODE_VERIFIER); return parameters; } private static String getAuthorizationHeader(RegisteredClient registeredClient) throws Exception { String clientId = registeredClient.getClientId(); String clientSecret = registeredClient.getClientSecret(); clientId = URLEncoder.encode(clientId, StandardCharsets.UTF_8); clientSecret = URLEncoder.encode(clientSecret, StandardCharsets.UTF_8); String credentialsString = clientId + ":" + clientSecret; byte[] encodedBytes = Base64.getEncoder().encode(credentialsString.getBytes(StandardCharsets.UTF_8)); return "Basic " + new String(encodedBytes, StandardCharsets.UTF_8); } private static String scopeCheckbox(String scope) { return MessageFormat.format( "", scope); } private String extractParameterFromRedirectUri(String redirectUri, String param) throws UnsupportedEncodingException { String locationHeader = URLDecoder.decode(redirectUri, StandardCharsets.UTF_8.name()); UriComponents uriComponents = UriComponentsBuilder.fromUriString(locationHeader).build(); return uriComponents.getQueryParams().getFirst(param); } @EnableWebSecurity @Import(OAuth2AuthorizationServerConfiguration.class) static class AuthorizationServerConfiguration { @Bean OAuth2AuthorizationService authorizationService(JdbcOperations jdbcOperations, RegisteredClientRepository registeredClientRepository) { return new JdbcOAuth2AuthorizationService(jdbcOperations, registeredClientRepository); } @Bean OAuth2AuthorizationConsentService authorizationConsentService(JdbcOperations jdbcOperations, RegisteredClientRepository registeredClientRepository) { return new JdbcOAuth2AuthorizationConsentService(jdbcOperations, registeredClientRepository); } @Bean @SuppressWarnings("removal") RegisteredClientRepository registeredClientRepository(JdbcOperations jdbcOperations) { JdbcRegisteredClientRepository jdbcRegisteredClientRepository = new JdbcRegisteredClientRepository( jdbcOperations); RegisteredClientParametersMapper registeredClientParametersMapper = new RegisteredClientParametersMapper(); jdbcRegisteredClientRepository.setRegisteredClientParametersMapper(registeredClientParametersMapper); return jdbcRegisteredClientRepository; } @Bean JdbcOperations jdbcOperations() { return new JdbcTemplate(db); } @Bean JWKSource jwkSource() { return jwkSource; } @Bean JwtDecoder jwtDecoder(JWKSource jwkSource) { return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource); } @Bean OAuth2TokenCustomizer jwtCustomizer() { return (context) -> { if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(context.getAuthorizationGrantType()) && OAuth2TokenType.ACCESS_TOKEN.equals(context.getTokenType())) { Authentication principal = context.getPrincipal(); Set authorities = new HashSet<>(); for (GrantedAuthority authority : principal.getAuthorities()) { authorities.add(authority.getAuthority()); } context.getClaims().claim(AUTHORITIES_CLAIM, authorities); } }; } @Bean PasswordEncoder passwordEncoder() { return NoOpPasswordEncoder.getInstance(); } } @EnableWebSecurity @Import(OAuth2AuthorizationServerConfiguration.class) static class AuthorizationServerConfigurationWithCustomRefreshTokenGenerator extends AuthorizationServerConfiguration { @Bean JwtEncoder jwtEncoder() { return jwtEncoder; } @Bean OAuth2TokenGenerator tokenGenerator() { JwtGenerator jwtGenerator = new JwtGenerator(jwtEncoder()); jwtGenerator.setJwtCustomizer(jwtCustomizer()); OAuth2TokenGenerator refreshTokenGenerator = new CustomRefreshTokenGenerator(); return new DelegatingOAuth2TokenGenerator(jwtGenerator, refreshTokenGenerator); } private static final class CustomRefreshTokenGenerator implements OAuth2TokenGenerator { private final StringKeyGenerator refreshTokenGenerator = new Base64StringKeyGenerator( Base64.getUrlEncoder().withoutPadding(), 96); @Nullable @Override public OAuth2RefreshToken generate(OAuth2TokenContext context) { if (!OAuth2TokenType.REFRESH_TOKEN.equals(context.getTokenType())) { return null; } Instant issuedAt = Instant.now(); Instant expiresAt = issuedAt .plus(context.getRegisteredClient().getTokenSettings().getRefreshTokenTimeToLive()); return new OAuth2RefreshToken(this.refreshTokenGenerator.generateKey(), issuedAt, expiresAt); } } } @EnableWebSecurity @Configuration(proxyBeanMethods = false) static class AuthorizationServerConfigurationWithSecurityContextRepository extends AuthorizationServerConfiguration { // @formatter:off @Bean SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { http .oauth2AuthorizationServer(Customizer.withDefaults()) .authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated() ) .securityContext((securityContext) -> securityContext.securityContextRepository(securityContextRepository)); return http.build(); } // @formatter:on } @EnableWebSecurity @Import(OAuth2AuthorizationServerConfiguration.class) static class AuthorizationServerConfigurationWithTokenGenerator extends AuthorizationServerConfiguration { @Bean JwtEncoder jwtEncoder() { return jwtEncoder; } @Bean OAuth2TokenGenerator tokenGenerator() { JwtGenerator jwtGenerator = new JwtGenerator(jwtEncoder()); jwtGenerator.setJwtCustomizer(jwtCustomizer()); OAuth2RefreshTokenGenerator refreshTokenGenerator = new OAuth2RefreshTokenGenerator(); OAuth2TokenGenerator delegatingTokenGenerator = new DelegatingOAuth2TokenGenerator( jwtGenerator, refreshTokenGenerator); return spy(new OAuth2TokenGenerator() { @Override public OAuth2Token generate(OAuth2TokenContext context) { return delegatingTokenGenerator.generate(context); } }); } } @EnableWebSecurity @Import(OAuth2AuthorizationServerConfiguration.class) static class AuthorizationServerConfigurationCustomEndpoints extends AuthorizationServerConfiguration { @Bean AuthorizationServerSettings authorizationServerSettings() { return authorizationServerSettings; } } @EnableWebSecurity @Configuration(proxyBeanMethods = false) static class AuthorizationServerConfigurationCustomConsentPage extends AuthorizationServerConfiguration { // @formatter:off @Bean SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { http .oauth2AuthorizationServer((authorizationServer) -> authorizationServer .authorizationEndpoint((authorizationEndpoint) -> authorizationEndpoint.consentPage(consentPage)) ) .authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated() ); return http.build(); } // @formatter:on } @EnableWebSecurity @Configuration(proxyBeanMethods = false) static class AuthorizationServerConfigurationCustomConsentRequest extends AuthorizationServerConfiguration { @Autowired private OAuth2AuthorizationConsentService authorizationConsentService; // @formatter:off @Bean SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { http .oauth2AuthorizationServer((authorizationServer) -> authorizationServer .authorizationEndpoint((authorizationEndpoint) -> authorizationEndpoint.authenticationProviders(configureAuthenticationProviders())) ) .authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated() ); return http.build(); } // @formatter:on @Bean @Override OAuth2TokenCustomizer jwtCustomizer() { return (context) -> { if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(context.getAuthorizationGrantType()) && OAuth2TokenType.ACCESS_TOKEN.equals(context.getTokenType())) { OAuth2AuthorizationConsent authorizationConsent = this.authorizationConsentService .findById(context.getRegisteredClient().getId(), context.getPrincipal().getName()); Set authorities = new HashSet<>(); for (GrantedAuthority authority : authorizationConsent.getAuthorities()) { authorities.add(authority.getAuthority()); } context.getClaims().claim(AUTHORITIES_CLAIM, authorities); } }; } private Consumer> configureAuthenticationProviders() { return (authenticationProviders) -> authenticationProviders.forEach((authenticationProvider) -> { if (authenticationProvider instanceof OAuth2AuthorizationConsentAuthenticationProvider) { ((OAuth2AuthorizationConsentAuthenticationProvider) authenticationProvider) .setAuthorizationConsentCustomizer(new AuthorizationConsentCustomizer()); } }); } static class AuthorizationConsentCustomizer implements Consumer { @Override public void accept( OAuth2AuthorizationConsentAuthenticationContext authorizationConsentAuthenticationContext) { OAuth2AuthorizationConsent.Builder authorizationConsentBuilder = authorizationConsentAuthenticationContext .getAuthorizationConsent(); OAuth2AuthorizationConsentAuthenticationToken authorizationConsentAuthentication = authorizationConsentAuthenticationContext .getAuthentication(); Map additionalParameters = authorizationConsentAuthentication.getAdditionalParameters(); RegisteredClient registeredClient = authorizationConsentAuthenticationContext.getRegisteredClient(); ClientSettings clientSettings = registeredClient.getClientSettings(); Set requestedAuthorities = authorities((String) additionalParameters.get("authority")); Set allowedAuthorities = authorities(clientSettings.getSetting("custom.allowed-authorities")); for (String requestedAuthority : requestedAuthorities) { if (allowedAuthorities.contains(requestedAuthority)) { authorizationConsentBuilder.authority(new SimpleGrantedAuthority(requestedAuthority)); } } } private static Set authorities(String param) { Set authorities = new HashSet<>(); if (param != null) { List authorityValues = Arrays.asList(param.split(" ")); authorities.addAll(authorityValues); } return authorities; } } } @EnableWebSecurity @Configuration(proxyBeanMethods = false) static class AuthorizationServerConfigurationCustomAuthorizationEndpoint extends AuthorizationServerConfiguration { // @formatter:off @Bean SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { http .oauth2AuthorizationServer((authorizationServer) -> authorizationServer .authorizationEndpoint((authorizationEndpoint) -> authorizationEndpoint .authorizationRequestConverter(authorizationRequestConverter) .authorizationRequestConverters(authorizationRequestConvertersConsumer) .authenticationProvider(authorizationRequestAuthenticationProvider) .authenticationProviders(authorizationRequestAuthenticationProvidersConsumer) .authorizationResponseHandler(authorizationResponseHandler) .errorResponseHandler(authorizationErrorResponseHandler)) ) .authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated() ); return http.build(); } // @formatter:on } @EnableWebSecurity @Import(OAuth2AuthorizationServerConfiguration.class) static class AuthorizationServerConfigurationWithMultipleIssuersAllowed extends AuthorizationServerConfigurationWithTokenGenerator { @Bean AuthorizationServerSettings authorizationServerSettings() { return AuthorizationServerSettings.builder().multipleIssuersAllowed(true).build(); } } @EnableWebSecurity @Configuration(proxyBeanMethods = false) static class AuthorizationServerConfigurationWithPushedAuthorizationRequests extends AuthorizationServerConfiguration { // @formatter:off @Bean SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { http .oauth2AuthorizationServer((authorizationServer) -> authorizationServer .pushedAuthorizationRequestEndpoint(Customizer.withDefaults()) ) .authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated() ); return http.build(); } // @formatter:on } @EnableWebSecurity @Configuration(proxyBeanMethods = false) static class AuthorizationServerConfigurationWithPushedAuthorizationRequestsAndCustomConsentPage extends AuthorizationServerConfiguration { // @formatter:off @Bean SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { http .oauth2AuthorizationServer((authorizationServer) -> authorizationServer .pushedAuthorizationRequestEndpoint(Customizer.withDefaults()) .authorizationEndpoint((authorizationEndpoint) -> authorizationEndpoint.consentPage(consentPage)) ) .authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated() ); return http.build(); } // @formatter:on } } ================================================ FILE: config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2AuthorizationServerMetadataTests.java ================================================ /* * 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 org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization; import java.util.function.Consumer; import com.nimbusds.jose.jwk.JWKSet; import com.nimbusds.jose.jwk.source.JWKSource; import com.nimbusds.jose.proc.SecurityContext; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.jdbc.core.JdbcOperations; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.jose.TestJwks; import org.springframework.security.oauth2.jwt.JwtDecoder; import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationServerMetadata; import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationServerMetadataClaimNames; import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository; import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; import org.springframework.security.web.SecurityFilterChain; import org.springframework.test.web.servlet.MockMvc; import static org.hamcrest.CoreMatchers.hasItems; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Integration tests for the OAuth 2.0 Authorization Server Metadata endpoint. * * @author Daniel Garnier-Moiroux */ @ExtendWith(SpringTestContextExtension.class) public class OAuth2AuthorizationServerMetadataTests { private static final String DEFAULT_OAUTH2_AUTHORIZATION_SERVER_METADATA_ENDPOINT_URI = "/.well-known/oauth-authorization-server"; private static final String ISSUER = "https://example.com"; private static EmbeddedDatabase db; private static JWKSource jwkSource; public final SpringTestContext spring = new SpringTestContext(this); @Autowired private AuthorizationServerSettings authorizationServerSettings; @Autowired private MockMvc mvc; @Autowired private JdbcOperations jdbcOperations; @BeforeAll public static void setupClass() { JWKSet jwkSet = new JWKSet(TestJwks.DEFAULT_RSA_JWK); jwkSource = (jwkSelector, securityContext) -> jwkSelector.select(jwkSet); db = new EmbeddedDatabaseBuilder().generateUniqueName(true) .setType(EmbeddedDatabaseType.HSQL) .setScriptEncoding("UTF-8") .addScript("org/springframework/security/oauth2/server/authorization/oauth2-authorization-schema.sql") .addScript( "org/springframework/security/oauth2/server/authorization/client/oauth2-registered-client-schema.sql") .build(); } @AfterEach public void tearDown() { this.jdbcOperations.update("truncate table oauth2_authorization"); this.jdbcOperations.update("truncate table oauth2_registered_client"); } @AfterAll public static void destroy() { db.shutdown(); } @Test public void requestWhenAuthorizationServerMetadataRequestAndIssuerSetThenUsed() throws Exception { this.spring.register(AuthorizationServerConfiguration.class).autowire(); this.mvc.perform(get(ISSUER.concat(DEFAULT_OAUTH2_AUTHORIZATION_SERVER_METADATA_ENDPOINT_URI))) .andExpect(status().is2xxSuccessful()) .andExpect(jsonPath("issuer").value(ISSUER)) .andReturn(); } @Test public void requestWhenAuthorizationServerMetadataRequestIncludesIssuerPathThenMetadataResponseHasIssuerPath() throws Exception { this.spring.register(AuthorizationServerConfigurationWithMultipleIssuersAllowed.class).autowire(); String host = "https://example.com:8443"; String issuerPath = "/issuer1"; String issuer = host.concat(issuerPath); this.mvc.perform(get(host.concat(DEFAULT_OAUTH2_AUTHORIZATION_SERVER_METADATA_ENDPOINT_URI).concat(issuerPath))) .andExpect(status().is2xxSuccessful()) .andExpect(jsonPath("issuer").value(issuer)) .andReturn(); issuerPath = "/path1/issuer2"; issuer = host.concat(issuerPath); this.mvc.perform(get(host.concat(DEFAULT_OAUTH2_AUTHORIZATION_SERVER_METADATA_ENDPOINT_URI).concat(issuerPath))) .andExpect(status().is2xxSuccessful()) .andExpect(jsonPath("issuer").value(issuer)) .andReturn(); issuerPath = "/path1/path2/issuer3"; issuer = host.concat(issuerPath); this.mvc.perform(get(host.concat(DEFAULT_OAUTH2_AUTHORIZATION_SERVER_METADATA_ENDPOINT_URI).concat(issuerPath))) .andExpect(status().is2xxSuccessful()) .andExpect(jsonPath("issuer").value(issuer)) .andReturn(); } // gh-616 @Test public void requestWhenAuthorizationServerMetadataRequestAndMetadataCustomizerSetThenReturnCustomMetadataResponse() throws Exception { this.spring.register(AuthorizationServerConfigurationWithMetadataCustomizer.class).autowire(); this.mvc.perform(get(ISSUER.concat(DEFAULT_OAUTH2_AUTHORIZATION_SERVER_METADATA_ENDPOINT_URI))) .andExpect(status().is2xxSuccessful()) .andExpect(jsonPath(OAuth2AuthorizationServerMetadataClaimNames.SCOPES_SUPPORTED, hasItems("scope1", "scope2"))); } @Test public void requestWhenAuthorizationServerMetadataRequestAndClientRegistrationEnabledThenMetadataResponseIncludesRegistrationEndpoint() throws Exception { this.spring.register(AuthorizationServerConfigurationWithClientRegistrationEnabled.class).autowire(); this.mvc.perform(get(ISSUER.concat(DEFAULT_OAUTH2_AUTHORIZATION_SERVER_METADATA_ENDPOINT_URI))) .andExpect(status().is2xxSuccessful()) .andExpect(jsonPath("$.registration_endpoint") .value(ISSUER.concat(this.authorizationServerSettings.getClientRegistrationEndpoint()))); } @Test public void requestWhenAuthorizationServerMetadataRequestAndDeviceCodeGrantEnabledThenMetadataResponseIncludesDeviceAuthorizationEndpoint() throws Exception { this.spring.register(AuthorizationServerConfigurationWithDeviceCodeGrantEnabled.class).autowire(); this.mvc.perform(get(ISSUER.concat(DEFAULT_OAUTH2_AUTHORIZATION_SERVER_METADATA_ENDPOINT_URI))) .andExpect(status().is2xxSuccessful()) .andExpect(jsonPath("$.device_authorization_endpoint") .value(ISSUER.concat(this.authorizationServerSettings.getDeviceAuthorizationEndpoint()))) .andExpect(jsonPath("$.grant_types_supported[4]").value(AuthorizationGrantType.DEVICE_CODE.getValue())); } @Test public void requestWhenAuthorizationServerMetadataRequestAndPushedAuthorizationRequestEnabledThenMetadataResponseIncludesPushedAuthorizationRequestEndpoint() throws Exception { this.spring.register(AuthorizationServerConfigurationWithPushedAuthorizationRequestEnabled.class).autowire(); this.mvc.perform(get(ISSUER.concat(DEFAULT_OAUTH2_AUTHORIZATION_SERVER_METADATA_ENDPOINT_URI))) .andExpect(status().is2xxSuccessful()) .andExpect(jsonPath("$.pushed_authorization_request_endpoint") .value(ISSUER.concat(this.authorizationServerSettings.getPushedAuthorizationRequestEndpoint()))); } @EnableWebSecurity @Import(OAuth2AuthorizationServerConfiguration.class) static class AuthorizationServerConfiguration { @Bean RegisteredClientRepository registeredClientRepository(JdbcOperations jdbcOperations) { RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); JdbcRegisteredClientRepository registeredClientRepository = new JdbcRegisteredClientRepository( jdbcOperations); registeredClientRepository.save(registeredClient); return registeredClientRepository; } @Bean JdbcOperations jdbcOperations() { return new JdbcTemplate(db); } @Bean JWKSource jwkSource() { return jwkSource; } @Bean JwtDecoder jwtDecoder(JWKSource jwkSource) { return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource); } @Bean AuthorizationServerSettings authorizationServerSettings() { return AuthorizationServerSettings.builder().issuer(ISSUER).build(); } } @EnableWebSecurity @Configuration(proxyBeanMethods = false) static class AuthorizationServerConfigurationWithMetadataCustomizer extends AuthorizationServerConfiguration { // @formatter:off @Bean SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { http .oauth2AuthorizationServer((authorizationServer) -> authorizationServer .authorizationServerMetadataEndpoint((authorizationServerMetadataEndpoint) -> authorizationServerMetadataEndpoint .authorizationServerMetadataCustomizer(authorizationServerMetadataCustomizer())) ) .authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated() ); return http.build(); } // @formatter:on private Consumer authorizationServerMetadataCustomizer() { return (authorizationServerMetadata) -> authorizationServerMetadata.scope("scope1").scope("scope2"); } } @EnableWebSecurity @Import(OAuth2AuthorizationServerConfiguration.class) static class AuthorizationServerConfigurationWithMultipleIssuersAllowed extends AuthorizationServerConfiguration { @Bean AuthorizationServerSettings authorizationServerSettings() { return AuthorizationServerSettings.builder().multipleIssuersAllowed(true).build(); } } @EnableWebSecurity @Configuration(proxyBeanMethods = false) static class AuthorizationServerConfigurationWithClientRegistrationEnabled extends AuthorizationServerConfiguration { // @formatter:off @Bean SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { http .oauth2AuthorizationServer((authorizationServer) -> authorizationServer .clientRegistrationEndpoint(Customizer.withDefaults()) ) .authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated() ); return http.build(); } // @formatter:on } @EnableWebSecurity @Configuration(proxyBeanMethods = false) static class AuthorizationServerConfigurationWithDeviceCodeGrantEnabled extends AuthorizationServerConfiguration { // @formatter:off @Bean SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { http .oauth2AuthorizationServer((authorizationServer) -> authorizationServer .deviceAuthorizationEndpoint(Customizer.withDefaults()) ) .authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated() ); return http.build(); } // @formatter:on } @EnableWebSecurity @Configuration(proxyBeanMethods = false) static class AuthorizationServerConfigurationWithPushedAuthorizationRequestEnabled extends AuthorizationServerConfiguration { // @formatter:off @Bean SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { http .oauth2AuthorizationServer((authorizationServer) -> authorizationServer .pushedAuthorizationRequestEndpoint(Customizer.withDefaults()) ) .authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated() ); return http.build(); } // @formatter:on } } ================================================ FILE: config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2ClientCredentialsGrantTests.java ================================================ /* * 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 org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization; import java.io.IOException; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.time.Duration; import java.time.Instant; import java.util.Base64; import java.util.List; import java.util.Map; import java.util.UUID; import java.util.function.Consumer; import com.nimbusds.jose.jwk.JWKSet; import com.nimbusds.jose.jwk.source.JWKSource; import com.nimbusds.jose.proc.SecurityContext; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Primary; import org.springframework.http.HttpHeaders; import org.springframework.jdbc.core.JdbcOperations; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.crypto.factory.PasswordEncoderFactories; import org.springframework.security.crypto.password.NoOpPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.core.ClientAuthenticationMethod; import org.springframework.security.oauth2.core.OAuth2AccessToken; import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; import org.springframework.security.oauth2.jose.TestJwks; import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm; import org.springframework.security.oauth2.jwt.JwsHeader; import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.security.oauth2.jwt.JwtClaimsSet; import org.springframework.security.oauth2.jwt.JwtEncoderParameters; import org.springframework.security.oauth2.jwt.NimbusJwtEncoder; import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService; import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; import org.springframework.security.oauth2.server.authorization.authentication.ClientSecretAuthenticationProvider; import org.springframework.security.oauth2.server.authorization.authentication.JwtClientAssertionAuthenticationProvider; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AccessTokenAuthenticationToken; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeAuthenticationProvider; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientCredentialsAuthenticationProvider; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientCredentialsAuthenticationToken; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2DeviceCodeAuthenticationProvider; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2RefreshTokenAuthenticationProvider; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenExchangeAuthenticationProvider; import org.springframework.security.oauth2.server.authorization.authentication.PublicClientAuthenticationProvider; import org.springframework.security.oauth2.server.authorization.authentication.X509ClientCertificateAuthenticationProvider; import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository; import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository.RegisteredClientParametersMapper; import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; import org.springframework.security.oauth2.server.authorization.settings.ClientSettings; import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext; import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer; import org.springframework.security.oauth2.server.authorization.util.TestX509Certificates; import org.springframework.security.oauth2.server.authorization.web.authentication.ClientSecretBasicAuthenticationConverter; import org.springframework.security.oauth2.server.authorization.web.authentication.ClientSecretPostAuthenticationConverter; import org.springframework.security.oauth2.server.authorization.web.authentication.JwtClientAssertionAuthenticationConverter; import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2AuthorizationCodeAuthenticationConverter; import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2ClientCredentialsAuthenticationConverter; import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2DeviceCodeAuthenticationConverter; import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2RefreshTokenAuthenticationConverter; import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2TokenExchangeAuthenticationConverter; import org.springframework.security.oauth2.server.authorization.web.authentication.PublicClientAuthenticationConverter; import org.springframework.security.oauth2.server.authorization.web.authentication.X509ClientCertificateAuthenticationConverter; import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.AuthenticationConverter; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Integration tests for the OAuth 2.0 Client Credentials Grant. * * @author Alexey Nesterov * @author Joe Grandja */ @ExtendWith(SpringTestContextExtension.class) public class OAuth2ClientCredentialsGrantTests { private static final String DEFAULT_TOKEN_ENDPOINT_URI = "/oauth2/token"; private static EmbeddedDatabase db; private static JWKSource jwkSource; private static OAuth2TokenCustomizer jwtCustomizer; private static NimbusJwtEncoder dPoPProofJwtEncoder; private static AuthenticationConverter authenticationConverter; private static Consumer> authenticationConvertersConsumer; private static AuthenticationProvider authenticationProvider; private static Consumer> authenticationProvidersConsumer; private static AuthenticationSuccessHandler authenticationSuccessHandler; private static AuthenticationFailureHandler authenticationFailureHandler; private static PasswordEncoder passwordEncoder; public final SpringTestContext spring = new SpringTestContext(this); @Autowired private MockMvc mvc; @Autowired private JdbcOperations jdbcOperations; @Autowired private RegisteredClientRepository registeredClientRepository; @BeforeAll public static void init() { JWKSet jwkSet = new JWKSet(TestJwks.DEFAULT_RSA_JWK); jwkSource = (jwkSelector, securityContext) -> jwkSelector.select(jwkSet); jwtCustomizer = mock(OAuth2TokenCustomizer.class); JWKSet clientJwkSet = new JWKSet(TestJwks.DEFAULT_EC_JWK); JWKSource clientJwkSource = (jwkSelector, securityContext) -> jwkSelector.select(clientJwkSet); dPoPProofJwtEncoder = new NimbusJwtEncoder(clientJwkSource); authenticationConverter = mock(AuthenticationConverter.class); authenticationConvertersConsumer = mock(Consumer.class); authenticationProvider = mock(AuthenticationProvider.class); authenticationProvidersConsumer = mock(Consumer.class); authenticationSuccessHandler = mock(AuthenticationSuccessHandler.class); authenticationFailureHandler = mock(AuthenticationFailureHandler.class); passwordEncoder = mock(PasswordEncoder.class); given(passwordEncoder.matches(any(), any())).willReturn(true); given(passwordEncoder.upgradeEncoding(any())).willReturn(false); db = new EmbeddedDatabaseBuilder().generateUniqueName(true) .setType(EmbeddedDatabaseType.HSQL) .setScriptEncoding("UTF-8") .addScript("org/springframework/security/oauth2/server/authorization/oauth2-authorization-schema.sql") .addScript( "org/springframework/security/oauth2/server/authorization/client/oauth2-registered-client-schema.sql") .build(); } @SuppressWarnings("unchecked") @BeforeEach public void setup() { reset(jwtCustomizer); reset(authenticationConverter); reset(authenticationConvertersConsumer); reset(authenticationProvider); reset(authenticationProvidersConsumer); reset(authenticationSuccessHandler); reset(authenticationFailureHandler); } @AfterEach public void tearDown() { this.jdbcOperations.update("truncate table oauth2_authorization"); this.jdbcOperations.update("truncate table oauth2_registered_client"); } @AfterAll public static void destroy() { db.shutdown(); } @Test public void requestWhenTokenRequestNotAuthenticatedThenUnauthorized() throws Exception { this.spring.register(AuthorizationServerConfiguration.class).autowire(); this.mvc .perform(MockMvcRequestBuilders.post(DEFAULT_TOKEN_ENDPOINT_URI) .param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue())) .andExpect(status().isUnauthorized()); } @Test public void requestWhenTokenRequestValidThenTokenResponse() throws Exception { this.spring.register(AuthorizationServerConfiguration.class).autowire(); RegisteredClient registeredClient = TestRegisteredClients.registeredClient2().build(); this.registeredClientRepository.save(registeredClient); this.mvc .perform(post(DEFAULT_TOKEN_ENDPOINT_URI) .param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) .param(OAuth2ParameterNames.SCOPE, "scope1 scope2") .header(HttpHeaders.AUTHORIZATION, "Basic " + encodeBasicAuth(registeredClient.getClientId(), registeredClient.getClientSecret()))) .andExpect(status().isOk()) .andExpect(jsonPath("$.access_token").isNotEmpty()) .andExpect(jsonPath("$.scope").value("scope1 scope2")); verify(jwtCustomizer).customize(any()); } @Test public void requestWhenTokenRequestPostsClientCredentialsThenTokenResponse() throws Exception { this.spring.register(AuthorizationServerConfiguration.class).autowire(); RegisteredClient registeredClient = TestRegisteredClients.registeredClient2().build(); this.registeredClientRepository.save(registeredClient); this.mvc .perform(post(DEFAULT_TOKEN_ENDPOINT_URI) .param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) .param(OAuth2ParameterNames.SCOPE, "scope1 scope2") .param(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()) .param(OAuth2ParameterNames.CLIENT_SECRET, registeredClient.getClientSecret())) .andExpect(status().isOk()) .andExpect(jsonPath("$.access_token").isNotEmpty()) .andExpect(jsonPath("$.scope").value("scope1 scope2")); verify(jwtCustomizer).customize(any()); } @Test public void requestWhenTokenRequestPostsClientCredentialsAndRequiresUpgradingThenClientSecretUpgraded() throws Exception { this.spring.register(AuthorizationServerConfigurationCustomPasswordEncoder.class).autowire(); String clientSecret = "secret-2"; RegisteredClient registeredClient = TestRegisteredClients.registeredClient2() .clientSecret("{noop}" + clientSecret) .build(); this.registeredClientRepository.save(registeredClient); this.mvc .perform(post(DEFAULT_TOKEN_ENDPOINT_URI) .param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) .param(OAuth2ParameterNames.SCOPE, "scope1 scope2") .param(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()) .param(OAuth2ParameterNames.CLIENT_SECRET, clientSecret)) .andExpect(status().isOk()) .andExpect(jsonPath("$.access_token").isNotEmpty()) .andExpect(jsonPath("$.scope").value("scope1 scope2")); verify(jwtCustomizer).customize(any()); RegisteredClient updatedRegisteredClient = this.registeredClientRepository .findByClientId(registeredClient.getClientId()); assertThat(updatedRegisteredClient.getClientSecret()).startsWith("{bcrypt}"); } @Test public void requestWhenTokenRequestWithPKIX509ClientCertificateThenTokenResponse() throws Exception { this.spring.register(AuthorizationServerConfiguration.class).autowire(); // @formatter:off RegisteredClient registeredClient = TestRegisteredClients.registeredClient2() .clientAuthenticationMethod(ClientAuthenticationMethod.TLS_CLIENT_AUTH) .clientSettings( ClientSettings.builder() .x509CertificateSubjectDN(TestX509Certificates.DEMO_CLIENT_PKI_CERTIFICATE[0].getSubjectX500Principal().getName()) .build() ) .build(); // @formatter:on this.registeredClientRepository.save(registeredClient); this.mvc .perform(post(DEFAULT_TOKEN_ENDPOINT_URI) .with(SecurityMockMvcRequestPostProcessors.x509(TestX509Certificates.DEMO_CLIENT_PKI_CERTIFICATE)) .param(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()) .param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) .param(OAuth2ParameterNames.SCOPE, "scope1 scope2")) .andExpect(status().isOk()) .andExpect(jsonPath("$.access_token").isNotEmpty()) .andExpect(jsonPath("$.scope").value("scope1 scope2")); verify(jwtCustomizer).customize(any()); } // gh-1635 @Test public void requestWhenTokenRequestIncludesBasicClientCredentialsAndX509ClientCertificateThenTokenResponse() throws Exception { this.spring.register(AuthorizationServerConfiguration.class).autowire(); RegisteredClient registeredClient = TestRegisteredClients.registeredClient2().build(); this.registeredClientRepository.save(registeredClient); this.mvc .perform(post(DEFAULT_TOKEN_ENDPOINT_URI) .with(SecurityMockMvcRequestPostProcessors.x509(TestX509Certificates.DEMO_CLIENT_PKI_CERTIFICATE)) .param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) .param(OAuth2ParameterNames.SCOPE, "scope1 scope2") .header(HttpHeaders.AUTHORIZATION, "Basic " + encodeBasicAuth(registeredClient.getClientId(), registeredClient.getClientSecret()))) .andExpect(status().isOk()) .andExpect(jsonPath("$.access_token").isNotEmpty()) .andExpect(jsonPath("$.scope").value("scope1 scope2")); verify(jwtCustomizer).customize(any()); } @Test public void requestWhenTokenEndpointCustomizedThenUsed() throws Exception { this.spring.register(AuthorizationServerConfigurationCustomTokenEndpoint.class).autowire(); RegisteredClient registeredClient = TestRegisteredClients.registeredClient2().build(); this.registeredClientRepository.save(registeredClient); OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); OAuth2ClientCredentialsAuthenticationToken clientCredentialsAuthentication = new OAuth2ClientCredentialsAuthenticationToken( clientPrincipal, null, null); given(authenticationConverter.convert(any())).willReturn(clientCredentialsAuthentication); OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, "token", Instant.now(), Instant.now().plus(Duration.ofHours(1))); OAuth2AccessTokenAuthenticationToken accessTokenAuthentication = new OAuth2AccessTokenAuthenticationToken( registeredClient, clientPrincipal, accessToken); given(authenticationProvider.supports(eq(OAuth2ClientCredentialsAuthenticationToken.class))).willReturn(true); given(authenticationProvider.authenticate(any())).willReturn(accessTokenAuthentication); this.mvc .perform(post(DEFAULT_TOKEN_ENDPOINT_URI) .param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) .header(HttpHeaders.AUTHORIZATION, "Basic " + encodeBasicAuth(registeredClient.getClientId(), registeredClient.getClientSecret()))) .andExpect(status().isOk()); verify(authenticationConverter).convert(any()); @SuppressWarnings("unchecked") ArgumentCaptor> authenticationConvertersCaptor = ArgumentCaptor .forClass(List.class); verify(authenticationConvertersConsumer).accept(authenticationConvertersCaptor.capture()); List authenticationConverters = authenticationConvertersCaptor.getValue(); assertThat(authenticationConverters).allMatch((converter) -> converter == authenticationConverter || converter instanceof OAuth2AuthorizationCodeAuthenticationConverter || converter instanceof OAuth2RefreshTokenAuthenticationConverter || converter instanceof OAuth2ClientCredentialsAuthenticationConverter || converter instanceof OAuth2DeviceCodeAuthenticationConverter || converter instanceof OAuth2TokenExchangeAuthenticationConverter); verify(authenticationProvider).authenticate(eq(clientCredentialsAuthentication)); @SuppressWarnings("unchecked") ArgumentCaptor> authenticationProvidersCaptor = ArgumentCaptor .forClass(List.class); verify(authenticationProvidersConsumer).accept(authenticationProvidersCaptor.capture()); List authenticationProviders = authenticationProvidersCaptor.getValue(); assertThat(authenticationProviders).allMatch((provider) -> provider == authenticationProvider || provider instanceof OAuth2AuthorizationCodeAuthenticationProvider || provider instanceof OAuth2RefreshTokenAuthenticationProvider || provider instanceof OAuth2ClientCredentialsAuthenticationProvider || provider instanceof OAuth2DeviceCodeAuthenticationProvider || provider instanceof OAuth2TokenExchangeAuthenticationProvider); verify(authenticationSuccessHandler).onAuthenticationSuccess(any(), any(), eq(accessTokenAuthentication)); } @Test public void requestWhenClientAuthenticationCustomizedThenUsed() throws Exception { this.spring.register(AuthorizationServerConfigurationCustomClientAuthentication.class).autowire(); RegisteredClient registeredClient = TestRegisteredClients.registeredClient2().build(); this.registeredClientRepository.save(registeredClient); OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, new ClientAuthenticationMethod("custom"), null); given(authenticationConverter.convert(any())).willReturn(clientPrincipal); given(authenticationProvider.supports(eq(OAuth2ClientAuthenticationToken.class))).willReturn(true); given(authenticationProvider.authenticate(any())).willReturn(clientPrincipal); this.mvc .perform(post(DEFAULT_TOKEN_ENDPOINT_URI).param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue())) .andExpect(status().isOk()); verify(authenticationConverter).convert(any()); @SuppressWarnings("unchecked") ArgumentCaptor> authenticationConvertersCaptor = ArgumentCaptor .forClass(List.class); verify(authenticationConvertersConsumer).accept(authenticationConvertersCaptor.capture()); List authenticationConverters = authenticationConvertersCaptor.getValue(); assertThat(authenticationConverters).allMatch((converter) -> converter == authenticationConverter || converter instanceof JwtClientAssertionAuthenticationConverter || converter instanceof ClientSecretBasicAuthenticationConverter || converter instanceof ClientSecretPostAuthenticationConverter || converter instanceof PublicClientAuthenticationConverter || converter instanceof X509ClientCertificateAuthenticationConverter); verify(authenticationProvider).authenticate(eq(clientPrincipal)); @SuppressWarnings("unchecked") ArgumentCaptor> authenticationProvidersCaptor = ArgumentCaptor .forClass(List.class); verify(authenticationProvidersConsumer).accept(authenticationProvidersCaptor.capture()); List authenticationProviders = authenticationProvidersCaptor.getValue(); assertThat(authenticationProviders).allMatch((provider) -> provider == authenticationProvider || provider instanceof JwtClientAssertionAuthenticationProvider || provider instanceof X509ClientCertificateAuthenticationProvider || provider instanceof ClientSecretAuthenticationProvider || provider instanceof PublicClientAuthenticationProvider); verify(authenticationSuccessHandler).onAuthenticationSuccess(any(), any(), eq(clientPrincipal)); } @Test public void requestWhenTokenRequestIncludesIssuerPathThenIssuerResolvedWithPath() throws Exception { this.spring.register(AuthorizationServerConfigurationWithMultipleIssuersAllowed.class).autowire(); RegisteredClient registeredClient = TestRegisteredClients.registeredClient2().build(); this.registeredClientRepository.save(registeredClient); String issuer = "https://example.com:8443/issuer1"; this.mvc .perform(post(issuer.concat(DEFAULT_TOKEN_ENDPOINT_URI)) .param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) .param(OAuth2ParameterNames.SCOPE, "scope1 scope2") .header(HttpHeaders.AUTHORIZATION, "Basic " + encodeBasicAuth(registeredClient.getClientId(), registeredClient.getClientSecret()))) .andExpect(status().isOk()) .andExpect(jsonPath("$.access_token").isNotEmpty()) .andExpect(jsonPath("$.scope").value("scope1 scope2")); ArgumentCaptor jwtEncodingContextCaptor = ArgumentCaptor.forClass(JwtEncodingContext.class); verify(jwtCustomizer).customize(jwtEncodingContextCaptor.capture()); JwtEncodingContext jwtEncodingContext = jwtEncodingContextCaptor.getValue(); assertThat(jwtEncodingContext.getAuthorizationServerContext().getIssuer()).isEqualTo(issuer); } @Test public void requestWhenTokenRequestWithDPoPProofThenReturnDPoPBoundAccessToken() throws Exception { this.spring.register(AuthorizationServerConfiguration.class).autowire(); RegisteredClient registeredClient = TestRegisteredClients.registeredClient2().build(); this.registeredClientRepository.save(registeredClient); String tokenEndpointUri = "http://localhost" + DEFAULT_TOKEN_ENDPOINT_URI; String dPoPProof = generateDPoPProof(tokenEndpointUri); this.mvc .perform(post(DEFAULT_TOKEN_ENDPOINT_URI) .param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) .param(OAuth2ParameterNames.SCOPE, "scope1 scope2") .header(HttpHeaders.AUTHORIZATION, "Basic " + encodeBasicAuth(registeredClient.getClientId(), registeredClient.getClientSecret())) .header(OAuth2AccessToken.TokenType.DPOP.getValue(), dPoPProof)) .andExpect(status().isOk()) .andExpect(jsonPath("$.token_type").value(OAuth2AccessToken.TokenType.DPOP.getValue())); } @Test public void requestWhenTokenRequestWithMultiplePasswordEncodersThenPrimaryPasswordEncoderUsed() throws Exception { this.spring.register(AuthorizationServerConfigurationWithMultiplePasswordEncoders.class).autowire(); RegisteredClient registeredClient = TestRegisteredClients.registeredClient2().build(); this.registeredClientRepository.save(registeredClient); this.mvc .perform(post(DEFAULT_TOKEN_ENDPOINT_URI) .param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) .param(OAuth2ParameterNames.SCOPE, "scope1 scope2") .header(HttpHeaders.AUTHORIZATION, "Basic " + encodeBasicAuth(registeredClient.getClientId(), registeredClient.getClientSecret()))) .andExpect(status().isOk()) .andExpect(jsonPath("$.access_token").isNotEmpty()) .andExpect(jsonPath("$.scope").value("scope1 scope2")); verify(passwordEncoder).matches(any(), any()); } private static String generateDPoPProof(String tokenEndpointUri) { // @formatter:off Map publicJwk = TestJwks.DEFAULT_EC_JWK .toPublicJWK() .toJSONObject(); JwsHeader jwsHeader = JwsHeader.with(SignatureAlgorithm.ES256) .type("dpop+jwt") .jwk(publicJwk) .build(); JwtClaimsSet claims = JwtClaimsSet.builder() .issuedAt(Instant.now()) .claim("htm", "POST") .claim("htu", tokenEndpointUri) .id(UUID.randomUUID().toString()) .build(); // @formatter:on Jwt jwt = dPoPProofJwtEncoder.encode(JwtEncoderParameters.from(jwsHeader, claims)); return jwt.getTokenValue(); } private static String encodeBasicAuth(String clientId, String secret) throws Exception { clientId = URLEncoder.encode(clientId, StandardCharsets.UTF_8.name()); secret = URLEncoder.encode(secret, StandardCharsets.UTF_8.name()); String credentialsString = clientId + ":" + secret; byte[] encodedBytes = Base64.getEncoder().encode(credentialsString.getBytes(StandardCharsets.UTF_8)); return new String(encodedBytes, StandardCharsets.UTF_8); } @EnableWebSecurity @Import(OAuth2AuthorizationServerConfiguration.class) static class AuthorizationServerConfiguration { @Bean OAuth2AuthorizationService authorizationService(JdbcOperations jdbcOperations, RegisteredClientRepository registeredClientRepository) { return new JdbcOAuth2AuthorizationService(jdbcOperations, registeredClientRepository); } @Bean @SuppressWarnings("removal") RegisteredClientRepository registeredClientRepository(JdbcOperations jdbcOperations) { JdbcRegisteredClientRepository jdbcRegisteredClientRepository = new JdbcRegisteredClientRepository( jdbcOperations); RegisteredClientParametersMapper registeredClientParametersMapper = new RegisteredClientParametersMapper(); jdbcRegisteredClientRepository.setRegisteredClientParametersMapper(registeredClientParametersMapper); return jdbcRegisteredClientRepository; } @Bean JdbcOperations jdbcOperations() { return new JdbcTemplate(db); } @Bean JWKSource jwkSource() { return jwkSource; } @Bean OAuth2TokenCustomizer jwtCustomizer() { return jwtCustomizer; } @Bean PasswordEncoder passwordEncoder() { return NoOpPasswordEncoder.getInstance(); } } @EnableWebSecurity @Configuration(proxyBeanMethods = false) static class AuthorizationServerConfigurationCustomTokenEndpoint extends AuthorizationServerConfiguration { // @formatter:off @Bean SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { http .oauth2AuthorizationServer((authorizationServer) -> authorizationServer .tokenEndpoint((tokenEndpoint) -> tokenEndpoint .accessTokenRequestConverter(authenticationConverter) .accessTokenRequestConverters(authenticationConvertersConsumer) .authenticationProvider(authenticationProvider) .authenticationProviders(authenticationProvidersConsumer) .accessTokenResponseHandler(authenticationSuccessHandler) .errorResponseHandler(authenticationFailureHandler)) ) .authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated() ); return http.build(); } // @formatter:on } @EnableWebSecurity @Configuration(proxyBeanMethods = false) static class AuthorizationServerConfigurationCustomPasswordEncoder extends AuthorizationServerConfiguration { @Override PasswordEncoder passwordEncoder() { return PasswordEncoderFactories.createDelegatingPasswordEncoder(); } } @EnableWebSecurity @Configuration(proxyBeanMethods = false) static class AuthorizationServerConfigurationCustomClientAuthentication extends AuthorizationServerConfiguration { // @formatter:off @Bean SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { authenticationSuccessHandler = spy(authenticationSuccessHandler()); http .oauth2AuthorizationServer((authorizationServer) -> authorizationServer .clientAuthentication((clientAuthentication) -> clientAuthentication .authenticationConverter(authenticationConverter) .authenticationConverters(authenticationConvertersConsumer) .authenticationProvider(authenticationProvider) .authenticationProviders(authenticationProvidersConsumer) .authenticationSuccessHandler(authenticationSuccessHandler) .errorResponseHandler(authenticationFailureHandler)) ) .authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated() ); return http.build(); } // @formatter:on private AuthenticationSuccessHandler authenticationSuccessHandler() { return new AuthenticationSuccessHandler() { @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { org.springframework.security.core.context.SecurityContext securityContext = SecurityContextHolder .createEmptyContext(); securityContext.setAuthentication(authentication); SecurityContextHolder.setContext(securityContext); } }; } } @EnableWebSecurity @Import(OAuth2AuthorizationServerConfiguration.class) static class AuthorizationServerConfigurationWithMultipleIssuersAllowed extends AuthorizationServerConfiguration { @Bean AuthorizationServerSettings authorizationServerSettings() { return AuthorizationServerSettings.builder().multipleIssuersAllowed(true).build(); } } @EnableWebSecurity @Configuration(proxyBeanMethods = false) static class AuthorizationServerConfigurationWithMultiplePasswordEncoders extends AuthorizationServerConfiguration { @Primary @Bean PasswordEncoder primaryPasswordEncoder() { return passwordEncoder; } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2ClientRegistrationTests.java ================================================ /* * 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 org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization; import java.time.Duration; import java.time.Instant; import java.time.temporal.ChronoUnit; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.function.Consumer; import com.nimbusds.jose.jwk.JWKSet; import com.nimbusds.jose.jwk.source.JWKSource; import com.nimbusds.jose.proc.SecurityContext; import jakarta.servlet.http.HttpServletResponse; import okhttp3.mockwebserver.MockWebServer; import org.assertj.core.data.TemporalUnitWithinOffset; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.convert.converter.Converter; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.server.ServletServerHttpResponse; import org.springframework.jdbc.core.JdbcOperations; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; import org.springframework.mock.http.MockHttpOutputMessage; import org.springframework.mock.http.client.MockClientHttpResponse; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.crypto.factory.PasswordEncoderFactories; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.core.ClientAuthenticationMethod; import org.springframework.security.oauth2.core.OAuth2AccessToken; import org.springframework.security.oauth2.core.OAuth2AuthenticationException; import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType; import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter; import org.springframework.security.oauth2.jose.TestJwks; import org.springframework.security.oauth2.jwt.JwtDecoder; import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService; import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; import org.springframework.security.oauth2.server.authorization.OAuth2ClientRegistration; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientRegistrationAuthenticationProvider; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientRegistrationAuthenticationToken; import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository; import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository.RegisteredClientParametersMapper; import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; import org.springframework.security.oauth2.server.authorization.converter.OAuth2ClientRegistrationRegisteredClientConverter; import org.springframework.security.oauth2.server.authorization.converter.RegisteredClientOAuth2ClientRegistrationConverter; import org.springframework.security.oauth2.server.authorization.http.converter.OAuth2ClientRegistrationHttpMessageConverter; import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; import org.springframework.security.oauth2.server.authorization.settings.ClientSettings; import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2ClientRegistrationAuthenticationConverter; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.AuthenticationConverter; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.util.CollectionUtils; import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.CoreMatchers.containsString; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.willAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.jwt; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Integration tests for OAuth 2.0 Dynamic Client Registration. * * @author Joe Grandja */ @ExtendWith(SpringTestContextExtension.class) public class OAuth2ClientRegistrationTests { private static final String ISSUER = "https://example.com:8443/issuer1"; private static final String DEFAULT_TOKEN_ENDPOINT_URI = "/oauth2/token"; private static final String DEFAULT_OAUTH2_CLIENT_REGISTRATION_ENDPOINT_URI = "/oauth2/register"; private static final HttpMessageConverter accessTokenHttpResponseConverter = new OAuth2AccessTokenResponseHttpMessageConverter(); private static final HttpMessageConverter clientRegistrationHttpMessageConverter = new OAuth2ClientRegistrationHttpMessageConverter(); private static EmbeddedDatabase db; private static JWKSource jwkSource; public final SpringTestContext spring = new SpringTestContext(this); @Autowired private MockMvc mvc; @Autowired private JdbcOperations jdbcOperations; @Autowired private RegisteredClientRepository registeredClientRepository; private static AuthenticationConverter authenticationConverter; private static Consumer> authenticationConvertersConsumer; private static AuthenticationProvider authenticationProvider; private static Consumer> authenticationProvidersConsumer; private static AuthenticationSuccessHandler authenticationSuccessHandler; private static AuthenticationFailureHandler authenticationFailureHandler; private MockWebServer server; @BeforeAll public static void init() { JWKSet jwkSet = new JWKSet(TestJwks.DEFAULT_RSA_JWK); jwkSource = (jwkSelector, securityContext) -> jwkSelector.select(jwkSet); db = new EmbeddedDatabaseBuilder().generateUniqueName(true) .setType(EmbeddedDatabaseType.HSQL) .setScriptEncoding("UTF-8") .addScript("org/springframework/security/oauth2/server/authorization/oauth2-authorization-schema.sql") .addScript( "org/springframework/security/oauth2/server/authorization/client/oauth2-registered-client-schema.sql") .build(); authenticationConverter = mock(AuthenticationConverter.class); authenticationConvertersConsumer = mock(Consumer.class); authenticationProvider = mock(AuthenticationProvider.class); authenticationProvidersConsumer = mock(Consumer.class); authenticationSuccessHandler = mock(AuthenticationSuccessHandler.class); authenticationFailureHandler = mock(AuthenticationFailureHandler.class); } @BeforeEach public void setup() throws Exception { this.server = new MockWebServer(); this.server.start(); given(authenticationProvider.supports(OAuth2ClientRegistrationAuthenticationToken.class)).willReturn(true); } @AfterEach public void tearDown() throws Exception { this.server.shutdown(); this.jdbcOperations.update("truncate table oauth2_authorization"); this.jdbcOperations.update("truncate table oauth2_registered_client"); reset(authenticationConverter); reset(authenticationConvertersConsumer); reset(authenticationProvider); reset(authenticationProvidersConsumer); reset(authenticationSuccessHandler); reset(authenticationFailureHandler); } @AfterAll public static void destroy() { db.shutdown(); } @Test public void requestWhenClientRegistrationRequestAuthorizedThenClientRegistrationResponse() throws Exception { this.spring.register(AuthorizationServerConfiguration.class).autowire(); // @formatter:off OAuth2ClientRegistration clientRegistration = OAuth2ClientRegistration.builder() .clientName("client-name") .redirectUri("https://client.example.com") .grantType(AuthorizationGrantType.AUTHORIZATION_CODE.getValue()) .grantType(AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) .scope("scope1") .scope("scope2") .build(); // @formatter:on OAuth2ClientRegistration clientRegistrationResponse = registerClient(clientRegistration); assertClientRegistrationResponse(clientRegistration, clientRegistrationResponse); } @Test public void requestWhenOpenClientRegistrationRequestThenClientRegistrationResponse() throws Exception { this.spring.register(OpenClientRegistrationConfiguration.class).autowire(); // @formatter:off OAuth2ClientRegistration clientRegistration = OAuth2ClientRegistration.builder() .clientName("client-name") .redirectUri("https://client.example.com") .grantType(AuthorizationGrantType.AUTHORIZATION_CODE.getValue()) .grantType(AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) .scope("scope1") .scope("scope2") .build(); // @formatter:on MvcResult mvcResult = this.mvc .perform(post(ISSUER.concat(DEFAULT_OAUTH2_CLIENT_REGISTRATION_ENDPOINT_URI)) .contentType(MediaType.APPLICATION_JSON) .content(getClientRegistrationRequestContent(clientRegistration))) .andExpect(status().isCreated()) .andExpect(header().string(HttpHeaders.CACHE_CONTROL, containsString("no-store"))) .andExpect(header().string(HttpHeaders.PRAGMA, containsString("no-cache"))) .andReturn(); OAuth2ClientRegistration clientRegistrationResponse = readClientRegistrationResponse(mvcResult.getResponse()); assertClientRegistrationResponse(clientRegistration, clientRegistrationResponse); } @Test public void requestWhenClientRegistrationEndpointCustomizedThenUsed() throws Exception { this.spring.register(CustomClientRegistrationConfiguration.class).autowire(); // @formatter:off OAuth2ClientRegistration clientRegistration = OAuth2ClientRegistration.builder() .clientName("client-name") .redirectUri("https://client.example.com") .grantType(AuthorizationGrantType.AUTHORIZATION_CODE.getValue()) .grantType(AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) .scope("scope1") .scope("scope2") .build(); // @formatter:on willAnswer((invocation) -> { HttpServletResponse response = invocation.getArgument(1, HttpServletResponse.class); ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response); httpResponse.setStatusCode(HttpStatus.CREATED); new OAuth2ClientRegistrationHttpMessageConverter().write(clientRegistration, null, httpResponse); return null; }).given(authenticationSuccessHandler).onAuthenticationSuccess(any(), any(), any()); registerClient(clientRegistration); verify(authenticationConverter).convert(any()); ArgumentCaptor> authenticationConvertersCaptor = ArgumentCaptor .forClass(List.class); verify(authenticationConvertersConsumer).accept(authenticationConvertersCaptor.capture()); List authenticationConverters = authenticationConvertersCaptor.getValue(); assertThat(authenticationConverters).hasSize(2) .allMatch((converter) -> converter == authenticationConverter || converter instanceof OAuth2ClientRegistrationAuthenticationConverter); verify(authenticationProvider).authenticate(any()); ArgumentCaptor> authenticationProvidersCaptor = ArgumentCaptor .forClass(List.class); verify(authenticationProvidersConsumer).accept(authenticationProvidersCaptor.capture()); List authenticationProviders = authenticationProvidersCaptor.getValue(); assertThat(authenticationProviders).hasSize(2) .allMatch((provider) -> provider == authenticationProvider || provider instanceof OAuth2ClientRegistrationAuthenticationProvider); verify(authenticationSuccessHandler).onAuthenticationSuccess(any(), any(), any()); verifyNoInteractions(authenticationFailureHandler); } @Test public void requestWhenClientRegistrationEndpointCustomizedWithAuthenticationFailureHandlerThenUsed() throws Exception { this.spring.register(CustomClientRegistrationConfiguration.class).autowire(); given(authenticationProvider.authenticate(any())).willThrow(new OAuth2AuthenticationException("error")); this.mvc.perform(post(ISSUER.concat(DEFAULT_OAUTH2_CLIENT_REGISTRATION_ENDPOINT_URI)).with(jwt())); verify(authenticationFailureHandler).onAuthenticationFailure(any(), any(), any()); verifyNoInteractions(authenticationSuccessHandler); } @Test public void requestWhenClientRegistersWithSecretThenClientAuthenticationSuccess() throws Exception { this.spring.register(AuthorizationServerConfiguration.class).autowire(); // @formatter:off OAuth2ClientRegistration clientRegistration = OAuth2ClientRegistration.builder() .clientName("client-name") .redirectUri("https://client.example.com") .grantType(AuthorizationGrantType.AUTHORIZATION_CODE.getValue()) .grantType(AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) .scope("scope1") .scope("scope2") .build(); // @formatter:on OAuth2ClientRegistration clientRegistrationResponse = registerClient(clientRegistration); this.mvc .perform(post(ISSUER.concat(DEFAULT_TOKEN_ENDPOINT_URI)) .param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) .param(OAuth2ParameterNames.SCOPE, "scope1") .with(httpBasic(clientRegistrationResponse.getClientId(), clientRegistrationResponse.getClientSecret()))) .andExpect(status().isOk()) .andExpect(jsonPath("$.access_token").isNotEmpty()) .andExpect(jsonPath("$.scope").value("scope1")) .andReturn(); } @Test public void requestWhenClientRegistersWithCustomMetadataThenSavedToRegisteredClient() throws Exception { this.spring.register(CustomClientMetadataConfiguration.class).autowire(); // @formatter:off OAuth2ClientRegistration clientRegistration = OAuth2ClientRegistration.builder() .clientName("client-name") .redirectUri("https://client.example.com") .grantType(AuthorizationGrantType.AUTHORIZATION_CODE.getValue()) .grantType(AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) .scope("scope1") .scope("scope2") .claim("custom-metadata-name-1", "value-1") .claim("custom-metadata-name-2", "value-2") .claim("non-registered-custom-metadata", "value-3") .build(); // @formatter:on OAuth2ClientRegistration clientRegistrationResponse = registerClient(clientRegistration); RegisteredClient registeredClient = this.registeredClientRepository .findByClientId(clientRegistrationResponse.getClientId()); assertClientRegistrationResponse(clientRegistration, clientRegistrationResponse); assertThat(clientRegistrationResponse.getClaim("custom-metadata-name-1")).isEqualTo("value-1"); assertThat(clientRegistrationResponse.getClaim("custom-metadata-name-2")).isEqualTo("value-2"); assertThat(clientRegistrationResponse.getClaim("non-registered-custom-metadata")).isNull(); assertThat(registeredClient.getClientSettings().getSetting("custom-metadata-name-1")) .isEqualTo("value-1"); assertThat(registeredClient.getClientSettings().getSetting("custom-metadata-name-2")) .isEqualTo("value-2"); assertThat(registeredClient.getClientSettings().getSetting("non-registered-custom-metadata")).isNull(); } @Test public void requestWhenClientRegistersWithSecretExpirationThenClientRegistrationResponse() throws Exception { this.spring.register(ClientSecretExpirationConfiguration.class).autowire(); // @formatter:off OAuth2ClientRegistration clientRegistration = OAuth2ClientRegistration.builder() .clientName("client-name") .redirectUri("https://client.example.com") .grantType(AuthorizationGrantType.AUTHORIZATION_CODE.getValue()) .grantType(AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) .scope("scope1") .scope("scope2") .build(); // @formatter:on OAuth2ClientRegistration clientRegistrationResponse = registerClient(clientRegistration); Instant expectedSecretExpiryDate = Instant.now().plus(Duration.ofHours(24)); TemporalUnitWithinOffset allowedDelta = new TemporalUnitWithinOffset(1, ChronoUnit.MINUTES); // Returned response contains expiration date assertThat(clientRegistrationResponse.getClientSecretExpiresAt()).isNotNull() .isCloseTo(expectedSecretExpiryDate, allowedDelta); RegisteredClient registeredClient = this.registeredClientRepository .findByClientId(clientRegistrationResponse.getClientId()); // Persisted RegisteredClient contains expiration date assertThat(registeredClient).isNotNull(); assertThat(registeredClient.getClientSecretExpiresAt()).isNotNull() .isCloseTo(expectedSecretExpiryDate, allowedDelta); } @Test public void requestWhenClientRegistersWithCustomTokenSettingsThenSavedToRegisteredClient() throws Exception { this.spring.register(CustomTokenSettingsConfiguration.class).autowire(); // @formatter:off OAuth2ClientRegistration clientRegistration = OAuth2ClientRegistration.builder() .clientName("client-name") .redirectUri("https://client.example.com") .grantType(AuthorizationGrantType.AUTHORIZATION_CODE.getValue()) .grantType(AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) .scope("scope1") .scope("scope2") .build(); // @formatter:on OAuth2ClientRegistration clientRegistrationResponse = registerClient(clientRegistration); RegisteredClient registeredClient = this.registeredClientRepository .findByClientId(clientRegistrationResponse.getClientId()); assertThat(registeredClient).isNotNull(); assertThat(registeredClient.getTokenSettings().getAccessTokenTimeToLive()).isEqualTo(Duration.ofMinutes(60)); } private OAuth2ClientRegistration registerClient(OAuth2ClientRegistration clientRegistration) throws Exception { // ***** (1) Obtain the "initial" access token used for registering the client String clientRegistrationScope = "client.create"; // @formatter:off RegisteredClient clientRegistrar = RegisteredClient.withId("client-registrar-1") .clientId("client-registrar-1") .clientSecret("{noop}secret") .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS) .scope(clientRegistrationScope) .build(); // @formatter:on this.registeredClientRepository.save(clientRegistrar); MvcResult mvcResult = this.mvc .perform(post(ISSUER.concat(DEFAULT_TOKEN_ENDPOINT_URI)) .param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) .param(OAuth2ParameterNames.SCOPE, clientRegistrationScope) .with(httpBasic("client-registrar-1", "secret"))) .andExpect(status().isOk()) .andExpect(jsonPath("$.access_token").isNotEmpty()) .andExpect(jsonPath("$.scope").value(clientRegistrationScope)) .andReturn(); OAuth2AccessToken accessToken = readAccessTokenResponse(mvcResult.getResponse()).getAccessToken(); // ***** (2) Register the client HttpHeaders httpHeaders = new HttpHeaders(); httpHeaders.setBearerAuth(accessToken.getTokenValue()); // Register the client mvcResult = this.mvc .perform(post(ISSUER.concat(DEFAULT_OAUTH2_CLIENT_REGISTRATION_ENDPOINT_URI)).headers(httpHeaders) .contentType(MediaType.APPLICATION_JSON) .content(getClientRegistrationRequestContent(clientRegistration))) .andExpect(status().isCreated()) .andExpect(header().string(HttpHeaders.CACHE_CONTROL, containsString("no-store"))) .andExpect(header().string(HttpHeaders.PRAGMA, containsString("no-cache"))) .andReturn(); return readClientRegistrationResponse(mvcResult.getResponse()); } private static void assertClientRegistrationResponse(OAuth2ClientRegistration clientRegistrationRequest, OAuth2ClientRegistration clientRegistrationResponse) { assertThat(clientRegistrationResponse.getClientId()).isNotNull(); assertThat(clientRegistrationResponse.getClientIdIssuedAt()).isNotNull(); assertThat(clientRegistrationResponse.getClientSecret()).isNotNull(); assertThat(clientRegistrationResponse.getClientSecretExpiresAt()).isNull(); assertThat(clientRegistrationResponse.getClientName()).isEqualTo(clientRegistrationRequest.getClientName()); assertThat(clientRegistrationResponse.getRedirectUris()) .containsExactlyInAnyOrderElementsOf(clientRegistrationRequest.getRedirectUris()); assertThat(clientRegistrationResponse.getGrantTypes()) .containsExactlyInAnyOrderElementsOf(clientRegistrationRequest.getGrantTypes()); assertThat(clientRegistrationResponse.getResponseTypes()) .containsExactly(OAuth2AuthorizationResponseType.CODE.getValue()); assertThat(clientRegistrationResponse.getScopes()) .containsExactlyInAnyOrderElementsOf(clientRegistrationRequest.getScopes()); assertThat(clientRegistrationResponse.getTokenEndpointAuthenticationMethod()) .isEqualTo(ClientAuthenticationMethod.CLIENT_SECRET_BASIC.getValue()); } private static OAuth2AccessTokenResponse readAccessTokenResponse(MockHttpServletResponse response) throws Exception { MockClientHttpResponse httpResponse = new MockClientHttpResponse(response.getContentAsByteArray(), HttpStatus.valueOf(response.getStatus())); return accessTokenHttpResponseConverter.read(OAuth2AccessTokenResponse.class, httpResponse); } private static byte[] getClientRegistrationRequestContent(OAuth2ClientRegistration clientRegistration) throws Exception { MockHttpOutputMessage httpRequest = new MockHttpOutputMessage(); clientRegistrationHttpMessageConverter.write(clientRegistration, null, httpRequest); return httpRequest.getBodyAsBytes(); } private static OAuth2ClientRegistration readClientRegistrationResponse(MockHttpServletResponse response) throws Exception { MockClientHttpResponse httpResponse = new MockClientHttpResponse(response.getContentAsByteArray(), HttpStatus.valueOf(response.getStatus())); return clientRegistrationHttpMessageConverter.read(OAuth2ClientRegistration.class, httpResponse); } @EnableWebSecurity @Configuration(proxyBeanMethods = false) static class CustomClientRegistrationConfiguration extends AuthorizationServerConfiguration { // @formatter:off @Bean @Override public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { http .oauth2AuthorizationServer((authorizationServer) -> authorizationServer .clientRegistrationEndpoint((clientRegistration) -> clientRegistration .clientRegistrationRequestConverter(authenticationConverter) .clientRegistrationRequestConverters(authenticationConvertersConsumer) .authenticationProvider(authenticationProvider) .authenticationProviders(authenticationProvidersConsumer) .clientRegistrationResponseHandler(authenticationSuccessHandler) .errorResponseHandler(authenticationFailureHandler) ) ) .authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated() ); return http.build(); } // @formatter:on } @EnableWebSecurity @Configuration(proxyBeanMethods = false) static class CustomClientMetadataConfiguration extends AuthorizationServerConfiguration { // @formatter:off @Bean @Override public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { http .oauth2AuthorizationServer((authorizationServer) -> authorizationServer .clientRegistrationEndpoint((clientRegistration) -> clientRegistration .authenticationProviders(configureClientRegistrationConverters()) ) ) .authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated() ); return http.build(); } // @formatter:on private Consumer> configureClientRegistrationConverters() { // @formatter:off return (authenticationProviders) -> authenticationProviders.forEach((authenticationProvider) -> { List supportedCustomClientMetadata = List.of("custom-metadata-name-1", "custom-metadata-name-2"); if (authenticationProvider instanceof OAuth2ClientRegistrationAuthenticationProvider provider) { provider.setRegisteredClientConverter(new CustomRegisteredClientConverter(supportedCustomClientMetadata)); provider.setClientRegistrationConverter(new CustomClientRegistrationConverter(supportedCustomClientMetadata)); } }); // @formatter:on } } @EnableWebSecurity @Configuration(proxyBeanMethods = false) static class ClientSecretExpirationConfiguration extends AuthorizationServerConfiguration { // @formatter:off @Bean @Override public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { http .oauth2AuthorizationServer((authorizationServer) -> authorizationServer .clientRegistrationEndpoint((clientRegistration) -> clientRegistration .authenticationProviders(configureClientRegistrationConverters()) ) ) .authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated() ); return http.build(); } // @formatter:on private Consumer> configureClientRegistrationConverters() { // @formatter:off return (authenticationProviders) -> authenticationProviders.forEach((authenticationProvider) -> { if (authenticationProvider instanceof OAuth2ClientRegistrationAuthenticationProvider provider) { provider.setRegisteredClientConverter(new ClientSecretExpirationRegisteredClientConverter()); } }); // @formatter:on } } @EnableWebSecurity @Configuration(proxyBeanMethods = false) static class CustomTokenSettingsConfiguration extends AuthorizationServerConfiguration { // @formatter:off @Bean @Override public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { http .oauth2AuthorizationServer((authorizationServer) -> authorizationServer .clientRegistrationEndpoint((clientRegistration) -> clientRegistration .authenticationProviders(configureClientRegistrationConverters()) ) ) .authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated() ); return http.build(); } // @formatter:on private Consumer> configureClientRegistrationConverters() { // @formatter:off return (authenticationProviders) -> authenticationProviders.forEach((authenticationProvider) -> { if (authenticationProvider instanceof OAuth2ClientRegistrationAuthenticationProvider provider) { OAuth2ClientRegistrationRegisteredClientConverter clientRegistrationRegisteredClientConverter = new OAuth2ClientRegistrationRegisteredClientConverter(); clientRegistrationRegisteredClientConverter.setTokenSettingsCustomizer((tokenSettings) -> tokenSettings.accessTokenTimeToLive(Duration.ofMinutes(60))); provider.setRegisteredClientConverter(clientRegistrationRegisteredClientConverter); } }); // @formatter:on } } @EnableWebSecurity @Configuration(proxyBeanMethods = false) static class OpenClientRegistrationConfiguration extends AuthorizationServerConfiguration { // @formatter:off @Bean @Override public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { http .oauth2AuthorizationServer((authorizationServer) -> authorizationServer .clientRegistrationEndpoint((clientRegistration) -> clientRegistration .openRegistrationAllowed(true) ) ) .authorizeHttpRequests((authorize) -> authorize .requestMatchers("/**/oauth2/register").permitAll() .anyRequest().authenticated() ); return http.build(); } // @formatter:on } @EnableWebSecurity @Configuration(proxyBeanMethods = false) static class AuthorizationServerConfiguration { // @formatter:off @Bean SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { http .oauth2AuthorizationServer((authorizationServer) -> authorizationServer .clientRegistrationEndpoint(Customizer.withDefaults()) ) .authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated() ); return http.build(); } // @formatter:on @Bean @SuppressWarnings("removal") RegisteredClientRepository registeredClientRepository(JdbcOperations jdbcOperations) { RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); RegisteredClientParametersMapper registeredClientParametersMapper = new RegisteredClientParametersMapper(); JdbcRegisteredClientRepository registeredClientRepository = new JdbcRegisteredClientRepository( jdbcOperations); registeredClientRepository.setRegisteredClientParametersMapper(registeredClientParametersMapper); registeredClientRepository.save(registeredClient); return registeredClientRepository; } @Bean OAuth2AuthorizationService authorizationService(JdbcOperations jdbcOperations, RegisteredClientRepository registeredClientRepository) { return new JdbcOAuth2AuthorizationService(jdbcOperations, registeredClientRepository); } @Bean JdbcOperations jdbcOperations() { return new JdbcTemplate(db); } @Bean JWKSource jwkSource() { return jwkSource; } @Bean JwtDecoder jwtDecoder(JWKSource jwkSource) { return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource); } @Bean AuthorizationServerSettings authorizationServerSettings() { return AuthorizationServerSettings.builder().multipleIssuersAllowed(true).build(); } @Bean PasswordEncoder passwordEncoder() { return PasswordEncoderFactories.createDelegatingPasswordEncoder(); } } private static final class CustomRegisteredClientConverter implements Converter { private final OAuth2ClientRegistrationRegisteredClientConverter delegate = new OAuth2ClientRegistrationRegisteredClientConverter(); private final List supportedCustomClientMetadata; private CustomRegisteredClientConverter(List supportedCustomClientMetadata) { this.supportedCustomClientMetadata = supportedCustomClientMetadata; } @Override public RegisteredClient convert(OAuth2ClientRegistration clientRegistration) { RegisteredClient registeredClient = this.delegate.convert(clientRegistration); ClientSettings.Builder clientSettingsBuilder = ClientSettings .withSettings(registeredClient.getClientSettings().getSettings()); if (!CollectionUtils.isEmpty(this.supportedCustomClientMetadata)) { clientRegistration.getClaims().forEach((claim, value) -> { if (this.supportedCustomClientMetadata.contains(claim)) { clientSettingsBuilder.setting(claim, value); } }); } return RegisteredClient.from(registeredClient).clientSettings(clientSettingsBuilder.build()).build(); } } private static final class CustomClientRegistrationConverter implements Converter { private final RegisteredClientOAuth2ClientRegistrationConverter delegate = new RegisteredClientOAuth2ClientRegistrationConverter(); private final List supportedCustomClientMetadata; private CustomClientRegistrationConverter(List supportedCustomClientMetadata) { this.supportedCustomClientMetadata = supportedCustomClientMetadata; } @Override public OAuth2ClientRegistration convert(RegisteredClient registeredClient) { OAuth2ClientRegistration clientRegistration = this.delegate.convert(registeredClient); Map clientMetadata = new HashMap<>(clientRegistration.getClaims()); if (!CollectionUtils.isEmpty(this.supportedCustomClientMetadata)) { Map clientSettings = registeredClient.getClientSettings().getSettings(); this.supportedCustomClientMetadata.forEach((customClaim) -> { if (clientSettings.containsKey(customClaim)) { clientMetadata.put(customClaim, clientSettings.get(customClaim)); } }); } return OAuth2ClientRegistration.withClaims(clientMetadata).build(); } } /** * This customization adds client secret expiration time by setting * {@code RegisteredClient.clientSecretExpiresAt} during * {@code OAuth2ClientRegistration} -> {@code RegisteredClient} conversion */ private static final class ClientSecretExpirationRegisteredClientConverter implements Converter { private static final OAuth2ClientRegistrationRegisteredClientConverter delegate = new OAuth2ClientRegistrationRegisteredClientConverter(); @Override public RegisteredClient convert(OAuth2ClientRegistration clientRegistration) { RegisteredClient registeredClient = delegate.convert(clientRegistration); RegisteredClient.Builder registeredClientBuilder = RegisteredClient.from(registeredClient); Instant clientSecretExpiresAt = Instant.now().plus(Duration.ofHours(24)); registeredClientBuilder.clientSecretExpiresAt(clientSecretExpiresAt); return registeredClientBuilder.build(); } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2DeviceCodeGrantTests.java ================================================ /* * 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 org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization; import java.security.Principal; import java.time.Instant; import java.util.Map; import java.util.UUID; import java.util.function.Consumer; import java.util.function.Function; import com.nimbusds.jose.jwk.JWKSet; import com.nimbusds.jose.jwk.source.JWKSource; import com.nimbusds.jose.proc.SecurityContext; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.jdbc.core.JdbcOperations; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; import org.springframework.mock.http.client.MockClientHttpResponse; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.crypto.password.NoOpPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.core.OAuth2AccessToken; import org.springframework.security.oauth2.core.OAuth2DeviceCode; import org.springframework.security.oauth2.core.OAuth2Token; import org.springframework.security.oauth2.core.OAuth2UserCode; import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; import org.springframework.security.oauth2.core.endpoint.OAuth2DeviceAuthorizationResponse; import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter; import org.springframework.security.oauth2.core.http.converter.OAuth2DeviceAuthorizationResponseHttpMessageConverter; import org.springframework.security.oauth2.jose.TestJwks; import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm; import org.springframework.security.oauth2.jwt.JwsHeader; import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.security.oauth2.jwt.JwtClaimsSet; import org.springframework.security.oauth2.jwt.JwtEncoderParameters; import org.springframework.security.oauth2.jwt.NimbusJwtEncoder; import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationConsentService; import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService; import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsent; import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService; import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository; import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; import org.springframework.security.web.SecurityFilterChain; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.util.StringUtils; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Integration tests for OAuth 2.0 Device Grant. * * @author Steve Riesenberg */ @ExtendWith(SpringTestContextExtension.class) public class OAuth2DeviceCodeGrantTests { private static final String DEFAULT_DEVICE_AUTHORIZATION_ENDPOINT_URI = "/oauth2/device_authorization"; private static final String DEFAULT_DEVICE_VERIFICATION_ENDPOINT_URI = "/oauth2/device_verification"; private static final String DEFAULT_TOKEN_ENDPOINT_URI = "/oauth2/token"; private static final OAuth2TokenType DEVICE_CODE_TOKEN_TYPE = new OAuth2TokenType(OAuth2ParameterNames.DEVICE_CODE); private static final String USER_CODE = "ABCD-EFGH"; private static final String STATE = "123"; private static final String DEVICE_CODE = "abc-XYZ"; private static EmbeddedDatabase db; private static JWKSource jwkSource; private static NimbusJwtEncoder dPoPProofJwtEncoder; private static final HttpMessageConverter deviceAuthorizationResponseHttpMessageConverter = new OAuth2DeviceAuthorizationResponseHttpMessageConverter(); private static final HttpMessageConverter accessTokenResponseHttpMessageConverter = new OAuth2AccessTokenResponseHttpMessageConverter(); public final SpringTestContext spring = new SpringTestContext(this); @Autowired private MockMvc mvc; @Autowired private JdbcOperations jdbcOperations; @Autowired private RegisteredClientRepository registeredClientRepository; @Autowired private OAuth2AuthorizationService authorizationService; @Autowired private OAuth2AuthorizationConsentService authorizationConsentService; @BeforeAll public static void init() { JWKSet jwkSet = new JWKSet(TestJwks.DEFAULT_RSA_JWK); jwkSource = (jwkSelector, securityContext) -> jwkSelector.select(jwkSet); JWKSet clientJwkSet = new JWKSet(TestJwks.DEFAULT_EC_JWK); JWKSource clientJwkSource = (jwkSelector, securityContext) -> jwkSelector.select(clientJwkSet); dPoPProofJwtEncoder = new NimbusJwtEncoder(clientJwkSource); // @formatter:off db = new EmbeddedDatabaseBuilder() .generateUniqueName(true) .setType(EmbeddedDatabaseType.HSQL) .setScriptEncoding("UTF-8") .addScript("org/springframework/security/oauth2/server/authorization/oauth2-authorization-schema.sql") .addScript("org/springframework/security/oauth2/server/authorization/oauth2-authorization-consent-schema.sql") .addScript("org/springframework/security/oauth2/server/authorization/client/oauth2-registered-client-schema.sql") .build(); // @formatter:on } @AfterEach public void tearDown() { this.jdbcOperations.update("truncate table oauth2_authorization"); this.jdbcOperations.update("truncate table oauth2_authorization_consent"); this.jdbcOperations.update("truncate table oauth2_registered_client"); } @AfterAll public static void destroy() { db.shutdown(); } @Test public void requestWhenDeviceAuthorizationRequestNotAuthenticatedThenUnauthorized() throws Exception { this.spring.register(AuthorizationServerConfiguration.class).autowire(); // @formatter:off RegisteredClient registeredClient = TestRegisteredClients.registeredClient() .authorizationGrantType(AuthorizationGrantType.DEVICE_CODE) .build(); // @formatter:on this.registeredClientRepository.save(registeredClient); MultiValueMap parameters = new LinkedMultiValueMap<>(); parameters.set(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()); parameters.set(OAuth2ParameterNames.SCOPE, StringUtils.collectionToDelimitedString(registeredClient.getScopes(), " ")); // @formatter:off this.mvc.perform(post(DEFAULT_DEVICE_AUTHORIZATION_ENDPOINT_URI) .params(parameters)) .andExpect(status().isUnauthorized()); // @formatter:on } @Test public void requestWhenRegisteredClientMissingThenUnauthorized() throws Exception { this.spring.register(AuthorizationServerConfiguration.class).autowire(); // @formatter:off RegisteredClient registeredClient = TestRegisteredClients.registeredClient() .authorizationGrantType(AuthorizationGrantType.DEVICE_CODE) .build(); // @formatter:on MultiValueMap parameters = new LinkedMultiValueMap<>(); parameters.set(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()); parameters.set(OAuth2ParameterNames.SCOPE, StringUtils.collectionToDelimitedString(registeredClient.getScopes(), " ")); // @formatter:off this.mvc.perform(post(DEFAULT_DEVICE_AUTHORIZATION_ENDPOINT_URI) .params(parameters) .headers(withClientAuth(registeredClient))) .andExpect(status().isUnauthorized()); // @formatter:on } @Test public void requestWhenDeviceAuthorizationRequestValidThenReturnDeviceAuthorizationResponse() throws Exception { this.spring.register(AuthorizationServerConfigurationWithMultipleIssuersAllowed.class).autowire(); // @formatter:off RegisteredClient registeredClient = TestRegisteredClients.registeredClient() .authorizationGrantType(AuthorizationGrantType.DEVICE_CODE) .build(); // @formatter:on this.registeredClientRepository.save(registeredClient); MultiValueMap parameters = new LinkedMultiValueMap<>(); parameters.set(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()); parameters.set(OAuth2ParameterNames.SCOPE, StringUtils.collectionToDelimitedString(registeredClient.getScopes(), " ")); String issuer = "https://example.com:8443/issuer1"; // @formatter:off MvcResult mvcResult = this.mvc.perform(post(issuer.concat(DEFAULT_DEVICE_AUTHORIZATION_ENDPOINT_URI)) .params(parameters) .headers(withClientAuth(registeredClient))) .andExpect(status().isOk()) .andExpect(jsonPath("$.device_code").isNotEmpty()) .andExpect(jsonPath("$.user_code").isNotEmpty()) .andExpect(jsonPath("$.expires_in").isNumber()) .andExpect(jsonPath("$.verification_uri").isNotEmpty()) .andExpect(jsonPath("$.verification_uri_complete").isNotEmpty()) .andReturn(); // @formatter:on MockHttpServletResponse servletResponse = mvcResult.getResponse(); MockClientHttpResponse httpResponse = new MockClientHttpResponse(servletResponse.getContentAsByteArray(), HttpStatus.OK); OAuth2DeviceAuthorizationResponse deviceAuthorizationResponse = deviceAuthorizationResponseHttpMessageConverter .read(OAuth2DeviceAuthorizationResponse.class, httpResponse); String userCode = deviceAuthorizationResponse.getUserCode().getTokenValue(); assertThat(userCode).matches("[A-Z]{4}-[A-Z]{4}"); assertThat(deviceAuthorizationResponse.getVerificationUri()) .isEqualTo("https://example.com:8443/oauth2/device_verification"); assertThat(deviceAuthorizationResponse.getVerificationUriComplete()) .isEqualTo("https://example.com:8443/oauth2/device_verification?user_code=" + userCode); String deviceCode = deviceAuthorizationResponse.getDeviceCode().getTokenValue(); OAuth2Authorization authorization = this.authorizationService.findByToken(deviceCode, DEVICE_CODE_TOKEN_TYPE); assertThat(authorization.getToken(OAuth2DeviceCode.class)).isNotNull(); assertThat(authorization.getToken(OAuth2UserCode.class)).isNotNull(); } @Test public void requestWhenDeviceVerificationRequestUnauthenticatedThenUnauthorized() throws Exception { this.spring.register(AuthorizationServerConfiguration.class).autowire(); // @formatter:off RegisteredClient registeredClient = TestRegisteredClients.registeredClient() .authorizationGrantType(AuthorizationGrantType.DEVICE_CODE) .build(); // @formatter:on this.registeredClientRepository.save(registeredClient); Instant issuedAt = Instant.now(); Instant expiresAt = issuedAt.plusSeconds(300); // @formatter:off OAuth2Authorization authorization = OAuth2Authorization.withRegisteredClient(registeredClient) .principalName(registeredClient.getClientId()) .authorizationGrantType(AuthorizationGrantType.DEVICE_CODE) .token(new OAuth2DeviceCode(DEVICE_CODE, issuedAt, expiresAt)) .token(new OAuth2UserCode(USER_CODE, issuedAt, expiresAt)) .attribute(OAuth2ParameterNames.SCOPE, registeredClient.getScopes()) .build(); // @formatter:on this.authorizationService.save(authorization); MultiValueMap parameters = new LinkedMultiValueMap<>(); parameters.set(OAuth2ParameterNames.USER_CODE, USER_CODE); // @formatter:off this.mvc.perform(get(DEFAULT_DEVICE_VERIFICATION_ENDPOINT_URI) .queryParams(parameters)) .andExpect(status().isUnauthorized()); // @formatter:on } @Test public void requestWhenDeviceVerificationRequestValidThenDisplaysConsentPage() throws Exception { this.spring.register(AuthorizationServerConfigurationWithMultipleIssuersAllowed.class).autowire(); // @formatter:off RegisteredClient registeredClient = TestRegisteredClients.registeredClient() .authorizationGrantType(AuthorizationGrantType.DEVICE_CODE) .build(); // @formatter:on this.registeredClientRepository.save(registeredClient); Instant issuedAt = Instant.now(); Instant expiresAt = issuedAt.plusSeconds(300); // @formatter:off OAuth2Authorization authorization = OAuth2Authorization.withRegisteredClient(registeredClient) .principalName(registeredClient.getClientId()) .authorizationGrantType(AuthorizationGrantType.DEVICE_CODE) .token(new OAuth2DeviceCode(DEVICE_CODE, issuedAt, expiresAt)) .token(new OAuth2UserCode(USER_CODE, issuedAt, expiresAt)) .attribute(OAuth2ParameterNames.SCOPE, registeredClient.getScopes()) .build(); // @formatter:on this.authorizationService.save(authorization); MultiValueMap parameters = new LinkedMultiValueMap<>(); parameters.set(OAuth2ParameterNames.USER_CODE, USER_CODE); String issuer = "https://example.com:8443/issuer1"; // @formatter:off MvcResult mvcResult = this.mvc.perform(get(issuer.concat(DEFAULT_DEVICE_VERIFICATION_ENDPOINT_URI)) .queryParams(parameters) .with(user("user"))) .andExpect(status().isOk()) .andExpect(content().contentTypeCompatibleWith(MediaType.TEXT_HTML)) .andReturn(); // @formatter:on String responseHtml = mvcResult.getResponse().getContentAsString(); assertThat(responseHtml).contains("Consent required"); OAuth2Authorization updatedAuthorization = this.authorizationService.findById(authorization.getId()); assertThat(updatedAuthorization.getPrincipalName()).isEqualTo("user"); assertThat(updatedAuthorization).isNotNull(); // @formatter:off assertThat(updatedAuthorization.getToken(OAuth2UserCode.class)) .extracting(isInvalidated()) .isEqualTo(false); // @formatter:on } @Test public void requestWhenDeviceAuthorizationConsentRequestUnauthenticatedThenUnauthorized() throws Exception { this.spring.register(AuthorizationServerConfiguration.class).autowire(); // @formatter:off RegisteredClient registeredClient = TestRegisteredClients.registeredClient() .authorizationGrantType(AuthorizationGrantType.DEVICE_CODE) .build(); // @formatter:on this.registeredClientRepository.save(registeredClient); Instant issuedAt = Instant.now(); Instant expiresAt = issuedAt.plusSeconds(300); // @formatter:off OAuth2Authorization authorization = OAuth2Authorization.withRegisteredClient(registeredClient) .principalName("user") .authorizationGrantType(AuthorizationGrantType.DEVICE_CODE) .token(new OAuth2DeviceCode(DEVICE_CODE, issuedAt, expiresAt)) .token(new OAuth2UserCode(USER_CODE, issuedAt, expiresAt)) .attribute(OAuth2ParameterNames.SCOPE, registeredClient.getScopes()) .attribute(OAuth2ParameterNames.STATE, STATE) .build(); // @formatter:on this.authorizationService.save(authorization); MultiValueMap parameters = new LinkedMultiValueMap<>(); parameters.set(OAuth2ParameterNames.USER_CODE, USER_CODE); parameters.set(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()); parameters.set(OAuth2ParameterNames.SCOPE, registeredClient.getScopes().iterator().next()); parameters.set(OAuth2ParameterNames.STATE, STATE); // @formatter:off this.mvc.perform(post(DEFAULT_DEVICE_VERIFICATION_ENDPOINT_URI) .params(parameters)) .andExpect(status().isUnauthorized()); // @formatter:on } @Test public void requestWhenDeviceAuthorizationConsentRequestValidThenRedirectsToSuccessPage() throws Exception { this.spring.register(AuthorizationServerConfiguration.class).autowire(); // @formatter:off RegisteredClient registeredClient = TestRegisteredClients.registeredClient() .authorizationGrantType(AuthorizationGrantType.DEVICE_CODE) .build(); // @formatter:on this.registeredClientRepository.save(registeredClient); Instant issuedAt = Instant.now(); Instant expiresAt = issuedAt.plusSeconds(300); // @formatter:off OAuth2Authorization authorization = OAuth2Authorization.withRegisteredClient(registeredClient) .principalName("user") .authorizationGrantType(AuthorizationGrantType.DEVICE_CODE) .token(new OAuth2DeviceCode(DEVICE_CODE, issuedAt, expiresAt)) .token(new OAuth2UserCode(USER_CODE, issuedAt, expiresAt)) .attribute(OAuth2ParameterNames.SCOPE, registeredClient.getScopes()) .attribute(OAuth2ParameterNames.STATE, STATE) .build(); // @formatter:on this.authorizationService.save(authorization); MultiValueMap parameters = new LinkedMultiValueMap<>(); parameters.set(OAuth2ParameterNames.USER_CODE, USER_CODE); parameters.set(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()); parameters.set(OAuth2ParameterNames.SCOPE, registeredClient.getScopes().iterator().next()); parameters.set(OAuth2ParameterNames.STATE, STATE); // @formatter:off MvcResult mvcResult = this.mvc.perform(post(DEFAULT_DEVICE_VERIFICATION_ENDPOINT_URI) .params(parameters) .with(user("user"))) .andExpect(status().is3xxRedirection()) .andReturn(); // @formatter:on assertThat(mvcResult.getResponse().getHeader(HttpHeaders.LOCATION)).isEqualTo("/?success"); OAuth2Authorization updatedAuthorization = this.authorizationService.findById(authorization.getId()); assertThat(updatedAuthorization).isNotNull(); // @formatter:off assertThat(updatedAuthorization.getToken(OAuth2UserCode.class)) .extracting(isInvalidated()) .isEqualTo(true); // @formatter:on } @Test public void requestWhenAccessTokenRequestUnauthenticatedThenUnauthorized() throws Exception { this.spring.register(AuthorizationServerConfiguration.class).autowire(); // @formatter:off RegisteredClient registeredClient = TestRegisteredClients.registeredClient() .authorizationGrantType(AuthorizationGrantType.DEVICE_CODE) .build(); // @formatter:on this.registeredClientRepository.save(registeredClient); Instant issuedAt = Instant.now(); Instant expiresAt = issuedAt.plusSeconds(300); // @formatter:off OAuth2Authorization authorization = OAuth2Authorization.withRegisteredClient(registeredClient) .principalName(registeredClient.getClientId()) .authorizationGrantType(AuthorizationGrantType.DEVICE_CODE) .token(new OAuth2DeviceCode(DEVICE_CODE, issuedAt, expiresAt)) .token(new OAuth2UserCode(USER_CODE, issuedAt, expiresAt), withInvalidated()) .authorizedScopes(registeredClient.getScopes()) .attribute(Principal.class.getName(), new UsernamePasswordAuthenticationToken("user", null)) .build(); // @formatter:on this.authorizationService.save(authorization); MultiValueMap parameters = new LinkedMultiValueMap<>(); parameters.set(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.DEVICE_CODE.getValue()); parameters.set(OAuth2ParameterNames.DEVICE_CODE, DEVICE_CODE); // @formatter:off this.mvc.perform(post(DEFAULT_TOKEN_ENDPOINT_URI) .params(parameters)) .andExpect(status().isUnauthorized()); // @formatter:on } @Test public void requestWhenAccessTokenRequestValidThenReturnAccessTokenResponse() throws Exception { this.spring.register(AuthorizationServerConfiguration.class).autowire(); // @formatter:off RegisteredClient registeredClient = TestRegisteredClients.registeredClient() .authorizationGrantType(AuthorizationGrantType.DEVICE_CODE) .build(); // @formatter:on this.registeredClientRepository.save(registeredClient); Instant issuedAt = Instant.now(); Instant expiresAt = issuedAt.plusSeconds(300); // @formatter:off OAuth2Authorization authorization = OAuth2Authorization.withRegisteredClient(registeredClient) .principalName(registeredClient.getClientId()) .authorizationGrantType(AuthorizationGrantType.DEVICE_CODE) .token(new OAuth2DeviceCode(DEVICE_CODE, issuedAt, expiresAt)) .token(new OAuth2UserCode(USER_CODE, issuedAt, expiresAt), withInvalidated()) .authorizedScopes(registeredClient.getScopes()) .attribute(Principal.class.getName(), new UsernamePasswordAuthenticationToken("user", null)) .build(); // @formatter:on this.authorizationService.save(authorization); // @formatter:off OAuth2AuthorizationConsent authorizationConsent = OAuth2AuthorizationConsent.withId(registeredClient.getClientId(), "user") .scope(registeredClient.getScopes().iterator().next()) .build(); // @formatter:on this.authorizationConsentService.save(authorizationConsent); MultiValueMap parameters = new LinkedMultiValueMap<>(); parameters.set(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.DEVICE_CODE.getValue()); parameters.set(OAuth2ParameterNames.DEVICE_CODE, DEVICE_CODE); // @formatter:off MvcResult mvcResult = this.mvc.perform(post(DEFAULT_TOKEN_ENDPOINT_URI) .params(parameters) .headers(withClientAuth(registeredClient))) .andExpect(status().isOk()) .andExpect(jsonPath("$.access_token").isNotEmpty()) .andExpect(jsonPath("$.refresh_token").isNotEmpty()) .andExpect(jsonPath("$.expires_in").isNumber()) .andExpect(jsonPath("$.scope").isNotEmpty()) .andExpect(jsonPath("$.token_type").isNotEmpty()) .andReturn(); // @formatter:on OAuth2Authorization updatedAuthorization = this.authorizationService.findById(authorization.getId()); assertThat(updatedAuthorization).isNotNull(); assertThat(updatedAuthorization.getAccessToken()).isNotNull(); assertThat(updatedAuthorization.getRefreshToken()).isNotNull(); // @formatter:off assertThat(updatedAuthorization.getToken(OAuth2DeviceCode.class)) .extracting(isInvalidated()) .isEqualTo(true); // @formatter:on MockHttpServletResponse servletResponse = mvcResult.getResponse(); MockClientHttpResponse httpResponse = new MockClientHttpResponse(servletResponse.getContentAsByteArray(), HttpStatus.OK); OAuth2AccessTokenResponse accessTokenResponse = accessTokenResponseHttpMessageConverter .read(OAuth2AccessTokenResponse.class, httpResponse); String accessToken = accessTokenResponse.getAccessToken().getTokenValue(); OAuth2Authorization accessTokenAuthorization = this.authorizationService.findByToken(accessToken, OAuth2TokenType.ACCESS_TOKEN); assertThat(accessTokenAuthorization).isEqualTo(updatedAuthorization); } @Test public void requestWhenAccessTokenRequestWithDPoPProofThenReturnDPoPBoundAccessToken() throws Exception { this.spring.register(AuthorizationServerConfiguration.class).autowire(); // @formatter:off RegisteredClient registeredClient = TestRegisteredClients.registeredClient() .authorizationGrantType(AuthorizationGrantType.DEVICE_CODE) .build(); // @formatter:on this.registeredClientRepository.save(registeredClient); Instant issuedAt = Instant.now(); Instant expiresAt = issuedAt.plusSeconds(300); // @formatter:off OAuth2Authorization authorization = OAuth2Authorization.withRegisteredClient(registeredClient) .principalName(registeredClient.getClientId()) .authorizationGrantType(AuthorizationGrantType.DEVICE_CODE) .token(new OAuth2DeviceCode(DEVICE_CODE, issuedAt, expiresAt)) .token(new OAuth2UserCode(USER_CODE, issuedAt, expiresAt), withInvalidated()) .authorizedScopes(registeredClient.getScopes()) .attribute(Principal.class.getName(), new UsernamePasswordAuthenticationToken("user", null)) .build(); // @formatter:on this.authorizationService.save(authorization); // @formatter:off OAuth2AuthorizationConsent authorizationConsent = OAuth2AuthorizationConsent.withId(registeredClient.getClientId(), "user") .scope(registeredClient.getScopes().iterator().next()) .build(); // @formatter:on this.authorizationConsentService.save(authorizationConsent); MultiValueMap parameters = new LinkedMultiValueMap<>(); parameters.set(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.DEVICE_CODE.getValue()); parameters.set(OAuth2ParameterNames.DEVICE_CODE, DEVICE_CODE); String tokenEndpointUri = "http://localhost" + DEFAULT_TOKEN_ENDPOINT_URI; String dPoPProof = generateDPoPProof(tokenEndpointUri); // @formatter:off this.mvc.perform(post(DEFAULT_TOKEN_ENDPOINT_URI) .params(parameters) .headers(withClientAuth(registeredClient)) .header(OAuth2AccessToken.TokenType.DPOP.getValue(), dPoPProof)) .andExpect(status().isOk()) .andExpect(jsonPath("$.token_type").value(OAuth2AccessToken.TokenType.DPOP.getValue())); // @formatter:on authorization = this.authorizationService.findById(authorization.getId()); assertThat(authorization.getAccessToken().getClaims()).containsKey("cnf"); @SuppressWarnings("unchecked") Map cnfClaims = (Map) authorization.getAccessToken().getClaims().get("cnf"); assertThat(cnfClaims).containsKey("jkt"); String jwkThumbprintClaim = (String) cnfClaims.get("jkt"); assertThat(jwkThumbprintClaim).isEqualTo(TestJwks.DEFAULT_EC_JWK.toPublicJWK().computeThumbprint().toString()); } private static String generateDPoPProof(String tokenEndpointUri) { // @formatter:off Map publicJwk = TestJwks.DEFAULT_EC_JWK .toPublicJWK() .toJSONObject(); JwsHeader jwsHeader = JwsHeader.with(SignatureAlgorithm.ES256) .type("dpop+jwt") .jwk(publicJwk) .build(); JwtClaimsSet claims = JwtClaimsSet.builder() .issuedAt(Instant.now()) .claim("htm", "POST") .claim("htu", tokenEndpointUri) .id(UUID.randomUUID().toString()) .build(); // @formatter:on Jwt jwt = dPoPProofJwtEncoder.encode(JwtEncoderParameters.from(jwsHeader, claims)); return jwt.getTokenValue(); } private static HttpHeaders withClientAuth(RegisteredClient registeredClient) { HttpHeaders headers = new HttpHeaders(); headers.setBasicAuth(registeredClient.getClientId(), registeredClient.getClientSecret()); return headers; } private static Consumer> withInvalidated() { return (metadata) -> metadata.put(OAuth2Authorization.Token.INVALIDATED_METADATA_NAME, true); } private static Function, Boolean> isInvalidated() { return (token) -> token.getMetadata(OAuth2Authorization.Token.INVALIDATED_METADATA_NAME); } @EnableWebSecurity @Configuration(proxyBeanMethods = false) static class AuthorizationServerConfiguration { // @formatter:off @Bean SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { http .oauth2AuthorizationServer((authorizationServer) -> authorizationServer .deviceAuthorizationEndpoint(Customizer.withDefaults()) ) .authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated() ); return http.build(); } // @formatter:on @Bean RegisteredClientRepository registeredClientRepository(JdbcOperations jdbcOperations) { return new JdbcRegisteredClientRepository(jdbcOperations); } @Bean OAuth2AuthorizationService authorizationService(JdbcOperations jdbcOperations, RegisteredClientRepository registeredClientRepository) { return new JdbcOAuth2AuthorizationService(jdbcOperations, registeredClientRepository); } @Bean OAuth2AuthorizationConsentService authorizationConsentService(JdbcOperations jdbcOperations, RegisteredClientRepository registeredClientRepository) { return new JdbcOAuth2AuthorizationConsentService(jdbcOperations, registeredClientRepository); } @Bean JdbcOperations jdbcOperations() { return new JdbcTemplate(db); } @Bean JWKSource jwkSource() { return jwkSource; } @Bean AuthorizationServerSettings authorizationServerSettings() { return AuthorizationServerSettings.builder().build(); } @Bean PasswordEncoder passwordEncoder() { return NoOpPasswordEncoder.getInstance(); } } @EnableWebSecurity @Configuration(proxyBeanMethods = false) static class AuthorizationServerConfigurationWithMultipleIssuersAllowed extends AuthorizationServerConfiguration { // @formatter:off @Bean SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { http .oauth2AuthorizationServer((authorizationServer) -> authorizationServer .deviceAuthorizationEndpoint(Customizer.withDefaults()) .deviceVerificationEndpoint(Customizer.withDefaults()) ) .authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated() ); return http.build(); } // @formatter:on @Bean AuthorizationServerSettings authorizationServerSettings() { return AuthorizationServerSettings.builder().multipleIssuersAllowed(true).build(); } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2RefreshTokenGrantTests.java ================================================ /* * 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 org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.Principal; import java.security.PublicKey; import java.time.Instant; import java.util.Base64; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; import com.nimbusds.jose.jwk.JWKSet; import com.nimbusds.jose.jwk.source.JWKSource; import com.nimbusds.jose.proc.SecurityContext; import jakarta.servlet.http.HttpServletRequest; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.jdbc.core.JdbcOperations; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; import org.springframework.lang.Nullable; import org.springframework.mock.http.client.MockClientHttpResponse; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.Transient; import org.springframework.security.crypto.password.NoOpPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.core.ClientAuthenticationMethod; import org.springframework.security.oauth2.core.OAuth2AccessToken; import org.springframework.security.oauth2.core.OAuth2AuthenticationException; import org.springframework.security.oauth2.core.OAuth2Error; import org.springframework.security.oauth2.core.OAuth2ErrorCodes; import org.springframework.security.oauth2.core.OAuth2Token; import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter; import org.springframework.security.oauth2.jose.TestJwks; import org.springframework.security.oauth2.jose.TestKeys; import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm; import org.springframework.security.oauth2.jwt.JwsHeader; import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.security.oauth2.jwt.JwtClaimsSet; import org.springframework.security.oauth2.jwt.JwtEncoderParameters; import org.springframework.security.oauth2.jwt.NimbusJwtDecoder; import org.springframework.security.oauth2.jwt.NimbusJwtEncoder; import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService; import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken; import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository; import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository.RegisteredClientParametersMapper; import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext; import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.AuthenticationConverter; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.util.Assert; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.util.StringUtils; import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.CoreMatchers.containsString; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Integration tests for the OAuth 2.0 Refresh Token Grant. * * @author Alexey Nesterov * @since 7.0 */ @ExtendWith(SpringTestContextExtension.class) public class OAuth2RefreshTokenGrantTests { private static final String DEFAULT_TOKEN_ENDPOINT_URI = "/oauth2/token"; private static final String DEFAULT_TOKEN_REVOCATION_ENDPOINT_URI = "/oauth2/revoke"; private static final String AUTHORITIES_CLAIM = "authorities"; private static EmbeddedDatabase db; private static JWKSource jwkSource; private static NimbusJwtDecoder jwtDecoder; private static NimbusJwtEncoder dPoPProofJwtEncoder; private static HttpMessageConverter accessTokenHttpResponseConverter = new OAuth2AccessTokenResponseHttpMessageConverter(); public final SpringTestContext spring = new SpringTestContext(this); @Autowired private MockMvc mvc; @Autowired private JdbcOperations jdbcOperations; @Autowired private RegisteredClientRepository registeredClientRepository; @Autowired private OAuth2AuthorizationService authorizationService; @BeforeAll public static void init() { JWKSet jwkSet = new JWKSet(TestJwks.DEFAULT_RSA_JWK); jwkSource = (jwkSelector, securityContext) -> jwkSelector.select(jwkSet); jwtDecoder = NimbusJwtDecoder.withPublicKey(TestKeys.DEFAULT_PUBLIC_KEY).build(); JWKSet clientJwkSet = new JWKSet(TestJwks.DEFAULT_EC_JWK); JWKSource clientJwkSource = (jwkSelector, securityContext) -> jwkSelector.select(clientJwkSet); dPoPProofJwtEncoder = new NimbusJwtEncoder(clientJwkSource); db = new EmbeddedDatabaseBuilder().generateUniqueName(true) .setType(EmbeddedDatabaseType.HSQL) .setScriptEncoding("UTF-8") .addScript("org/springframework/security/oauth2/server/authorization/oauth2-authorization-schema.sql") .addScript( "org/springframework/security/oauth2/server/authorization/client/oauth2-registered-client-schema.sql") .build(); } @AfterEach public void tearDown() { this.jdbcOperations.update("truncate table oauth2_authorization"); this.jdbcOperations.update("truncate table oauth2_registered_client"); } @AfterAll public static void destroy() { db.shutdown(); } @Test public void requestWhenRefreshTokenRequestValidThenReturnAccessTokenResponse() throws Exception { this.spring.register(AuthorizationServerConfiguration.class).autowire(); RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); this.registeredClientRepository.save(registeredClient); OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build(); this.authorizationService.save(authorization); MvcResult mvcResult = this.mvc .perform(post(DEFAULT_TOKEN_ENDPOINT_URI).params(getRefreshTokenRequestParameters(authorization)) .header(HttpHeaders.AUTHORIZATION, "Basic " + encodeBasicAuth(registeredClient.getClientId(), registeredClient.getClientSecret()))) .andExpect(status().isOk()) .andExpect(header().string(HttpHeaders.CACHE_CONTROL, containsString("no-store"))) .andExpect(header().string(HttpHeaders.PRAGMA, containsString("no-cache"))) .andExpect(jsonPath("$.access_token").isNotEmpty()) .andExpect(jsonPath("$.token_type").isNotEmpty()) .andExpect(jsonPath("$.expires_in").isNotEmpty()) .andExpect(jsonPath("$.refresh_token").isNotEmpty()) .andExpect(jsonPath("$.scope").isNotEmpty()) .andReturn(); MockHttpServletResponse servletResponse = mvcResult.getResponse(); MockClientHttpResponse httpResponse = new MockClientHttpResponse(servletResponse.getContentAsByteArray(), HttpStatus.valueOf(servletResponse.getStatus())); OAuth2AccessTokenResponse accessTokenResponse = accessTokenHttpResponseConverter .read(OAuth2AccessTokenResponse.class, httpResponse); // Assert user authorities was propagated as claim in JWT Jwt jwt = jwtDecoder.decode(accessTokenResponse.getAccessToken().getTokenValue()); List authoritiesClaim = jwt.getClaim(AUTHORITIES_CLAIM); Authentication principal = authorization.getAttribute(Principal.class.getName()); Set userAuthorities = new HashSet<>(); for (GrantedAuthority authority : principal.getAuthorities()) { userAuthorities.add(authority.getAuthority()); } assertThat(authoritiesClaim).containsExactlyInAnyOrderElementsOf(userAuthorities); } // gh-432 @Test public void requestWhenRevokeAndRefreshThenAccessTokenActive() throws Exception { this.spring.register(AuthorizationServerConfiguration.class).autowire(); RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); this.registeredClientRepository.save(registeredClient); OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build(); this.authorizationService.save(authorization); OAuth2AccessToken token = authorization.getAccessToken().getToken(); OAuth2TokenType tokenType = OAuth2TokenType.ACCESS_TOKEN; this.mvc .perform(post(DEFAULT_TOKEN_REVOCATION_ENDPOINT_URI) .params(getTokenRevocationRequestParameters(token, tokenType)) .header(HttpHeaders.AUTHORIZATION, "Basic " + encodeBasicAuth(registeredClient.getClientId(), registeredClient.getClientSecret()))) .andExpect(status().isOk()); this.mvc .perform(post(DEFAULT_TOKEN_ENDPOINT_URI).params(getRefreshTokenRequestParameters(authorization)) .header(HttpHeaders.AUTHORIZATION, "Basic " + encodeBasicAuth(registeredClient.getClientId(), registeredClient.getClientSecret()))) .andExpect(status().isOk()); OAuth2Authorization updatedAuthorization = this.authorizationService.findById(authorization.getId()); OAuth2Authorization.Token accessToken = updatedAuthorization.getAccessToken(); assertThat(accessToken.isActive()).isTrue(); } // gh-1430 @Test public void requestWhenRefreshTokenRequestWithPublicClientThenReturnAccessTokenResponse() throws Exception { this.spring.register(AuthorizationServerConfigurationWithPublicClientAuthentication.class).autowire(); RegisteredClient registeredClient = TestRegisteredClients.registeredPublicClient() .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN) .build(); this.registeredClientRepository.save(registeredClient); OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build(); this.authorizationService.save(authorization); this.mvc .perform(post(DEFAULT_TOKEN_ENDPOINT_URI).params(getRefreshTokenRequestParameters(authorization)) .param(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId())) .andExpect(status().isOk()) .andExpect(header().string(HttpHeaders.CACHE_CONTROL, containsString("no-store"))) .andExpect(header().string(HttpHeaders.PRAGMA, containsString("no-cache"))) .andExpect(jsonPath("$.access_token").isNotEmpty()) .andExpect(jsonPath("$.token_type").isNotEmpty()) .andExpect(jsonPath("$.expires_in").isNotEmpty()) .andExpect(jsonPath("$.refresh_token").isNotEmpty()) .andExpect(jsonPath("$.scope").isNotEmpty()); } @Test public void requestWhenRefreshTokenRequestWithPublicClientAndDPoPProofThenReturnDPoPBoundAccessToken() throws Exception { this.spring.register(AuthorizationServerConfigurationWithPublicClientAuthentication.class).autowire(); RegisteredClient registeredClient = TestRegisteredClients.registeredPublicClient() .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN) .build(); this.registeredClientRepository.save(registeredClient); OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.DPOP, "dpop-bound-access-token", Instant.now(), Instant.now().plusSeconds(300)); Map accessTokenClaims = new HashMap<>(); Map cnfClaim = new HashMap<>(); cnfClaim.put("jkt", TestJwks.DEFAULT_EC_JWK.toPublicJWK().computeThumbprint().toString()); accessTokenClaims.put("cnf", cnfClaim); OAuth2Authorization authorization = TestOAuth2Authorizations .authorization(registeredClient, accessToken, accessTokenClaims) .build(); this.authorizationService.save(authorization); String tokenEndpointUri = "http://localhost" + DEFAULT_TOKEN_ENDPOINT_URI; String dPoPProof = generateDPoPProof(tokenEndpointUri); this.mvc .perform(post(DEFAULT_TOKEN_ENDPOINT_URI).params(getRefreshTokenRequestParameters(authorization)) .param(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()) .header(OAuth2AccessToken.TokenType.DPOP.getValue(), dPoPProof)) .andExpect(status().isOk()) .andExpect(jsonPath("$.token_type").value(OAuth2AccessToken.TokenType.DPOP.getValue())); authorization = this.authorizationService.findById(authorization.getId()); assertThat(authorization.getAccessToken().getClaims()).containsKey("cnf"); @SuppressWarnings("unchecked") Map cnfClaims = (Map) authorization.getAccessToken().getClaims().get("cnf"); assertThat(cnfClaims).containsKey("jkt"); String jwkThumbprintClaim = (String) cnfClaims.get("jkt"); assertThat(jwkThumbprintClaim).isEqualTo(TestJwks.DEFAULT_EC_JWK.toPublicJWK().computeThumbprint().toString()); } @Test public void requestWhenRefreshTokenRequestWithPublicClientAndDPoPProofAndAccessTokenNotBoundThenBadRequest() throws Exception { this.spring.register(AuthorizationServerConfigurationWithPublicClientAuthentication.class).autowire(); RegisteredClient registeredClient = TestRegisteredClients.registeredPublicClient() .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN) .build(); this.registeredClientRepository.save(registeredClient); OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build(); this.authorizationService.save(authorization); String tokenEndpointUri = "http://localhost" + DEFAULT_TOKEN_ENDPOINT_URI; String dPoPProof = generateDPoPProof(tokenEndpointUri); this.mvc .perform(post(DEFAULT_TOKEN_ENDPOINT_URI).params(getRefreshTokenRequestParameters(authorization)) .param(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()) .header(OAuth2AccessToken.TokenType.DPOP.getValue(), dPoPProof)) .andExpect(status().isBadRequest()) .andExpect(jsonPath("$.error").value(OAuth2ErrorCodes.INVALID_DPOP_PROOF)) .andExpect(jsonPath("$.error_description").value("jkt claim is missing.")); } @Test public void requestWhenRefreshTokenRequestWithPublicClientAndDPoPProofAndDifferentPublicKeyThenBadRequest() throws Exception { this.spring.register(AuthorizationServerConfigurationWithPublicClientAuthentication.class).autowire(); RegisteredClient registeredClient = TestRegisteredClients.registeredPublicClient() .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN) .build(); this.registeredClientRepository.save(registeredClient); OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.DPOP, "dpop-bound-access-token", Instant.now(), Instant.now().plusSeconds(300)); Map accessTokenClaims = new HashMap<>(); // Bind access token to different public key PublicKey publicKey = TestJwks.DEFAULT_RSA_JWK.toPublicKey(); Map cnfClaim = new HashMap<>(); cnfClaim.put("jkt", computeSHA256(publicKey)); accessTokenClaims.put("cnf", cnfClaim); OAuth2Authorization authorization = TestOAuth2Authorizations .authorization(registeredClient, accessToken, accessTokenClaims) .build(); this.authorizationService.save(authorization); String tokenEndpointUri = "http://localhost" + DEFAULT_TOKEN_ENDPOINT_URI; String dPoPProof = generateDPoPProof(tokenEndpointUri); this.mvc .perform(post(DEFAULT_TOKEN_ENDPOINT_URI).params(getRefreshTokenRequestParameters(authorization)) .param(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()) .header(OAuth2AccessToken.TokenType.DPOP.getValue(), dPoPProof)) .andExpect(status().isBadRequest()) .andExpect(jsonPath("$.error").value(OAuth2ErrorCodes.INVALID_DPOP_PROOF)) .andExpect(jsonPath("$.error_description").value("jwk header is invalid.")); } @Test public void requestWhenRefreshTokenRequestWithDPoPProofThenReturnDPoPBoundAccessToken() throws Exception { this.spring.register(AuthorizationServerConfiguration.class).autowire(); RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); this.registeredClientRepository.save(registeredClient); OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build(); this.authorizationService.save(authorization); String tokenEndpointUri = "http://localhost" + DEFAULT_TOKEN_ENDPOINT_URI; String dPoPProof = generateDPoPProof(tokenEndpointUri); this.mvc .perform(post(DEFAULT_TOKEN_ENDPOINT_URI).params(getRefreshTokenRequestParameters(authorization)) .header(HttpHeaders.AUTHORIZATION, "Basic " + encodeBasicAuth(registeredClient.getClientId(), registeredClient.getClientSecret())) .header(OAuth2AccessToken.TokenType.DPOP.getValue(), dPoPProof)) .andExpect(status().isOk()) .andExpect(jsonPath("$.token_type").value(OAuth2AccessToken.TokenType.DPOP.getValue())); authorization = this.authorizationService.findById(authorization.getId()); assertThat(authorization.getAccessToken().getClaims()).containsKey("cnf"); @SuppressWarnings("unchecked") Map cnfClaims = (Map) authorization.getAccessToken().getClaims().get("cnf"); assertThat(cnfClaims).containsKey("jkt"); } private static String generateDPoPProof(String tokenEndpointUri) { // @formatter:off Map publicJwk = TestJwks.DEFAULT_EC_JWK .toPublicJWK() .toJSONObject(); JwsHeader jwsHeader = JwsHeader.with(SignatureAlgorithm.ES256) .type("dpop+jwt") .jwk(publicJwk) .build(); JwtClaimsSet claims = JwtClaimsSet.builder() .issuedAt(Instant.now()) .claim("htm", "POST") .claim("htu", tokenEndpointUri) .id(UUID.randomUUID().toString()) .build(); // @formatter:on Jwt jwt = dPoPProofJwtEncoder.encode(JwtEncoderParameters.from(jwsHeader, claims)); return jwt.getTokenValue(); } private static String computeSHA256(PublicKey publicKey) throws Exception { MessageDigest md = MessageDigest.getInstance("SHA-256"); byte[] digest = md.digest(publicKey.getEncoded()); return Base64.getUrlEncoder().withoutPadding().encodeToString(digest); } private static MultiValueMap getRefreshTokenRequestParameters(OAuth2Authorization authorization) { MultiValueMap parameters = new LinkedMultiValueMap<>(); parameters.set(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.REFRESH_TOKEN.getValue()); parameters.set(OAuth2ParameterNames.REFRESH_TOKEN, authorization.getRefreshToken().getToken().getTokenValue()); return parameters; } private static MultiValueMap getTokenRevocationRequestParameters(OAuth2Token token, OAuth2TokenType tokenType) { MultiValueMap parameters = new LinkedMultiValueMap<>(); parameters.set(OAuth2ParameterNames.TOKEN, token.getTokenValue()); parameters.set(OAuth2ParameterNames.TOKEN_TYPE_HINT, tokenType.getValue()); return parameters; } private static String encodeBasicAuth(String clientId, String secret) throws Exception { clientId = URLEncoder.encode(clientId, StandardCharsets.UTF_8.name()); secret = URLEncoder.encode(secret, StandardCharsets.UTF_8.name()); String credentialsString = clientId + ":" + secret; byte[] encodedBytes = Base64.getEncoder().encode(credentialsString.getBytes(StandardCharsets.UTF_8)); return new String(encodedBytes, StandardCharsets.UTF_8); } @EnableWebSecurity @Import(OAuth2AuthorizationServerConfiguration.class) static class AuthorizationServerConfiguration { @Bean OAuth2AuthorizationService authorizationService(JdbcOperations jdbcOperations, RegisteredClientRepository registeredClientRepository) { return new JdbcOAuth2AuthorizationService(jdbcOperations, registeredClientRepository); } @Bean @SuppressWarnings("removal") RegisteredClientRepository registeredClientRepository(JdbcOperations jdbcOperations) { JdbcRegisteredClientRepository jdbcRegisteredClientRepository = new JdbcRegisteredClientRepository( jdbcOperations); RegisteredClientParametersMapper registeredClientParametersMapper = new RegisteredClientParametersMapper(); jdbcRegisteredClientRepository.setRegisteredClientParametersMapper(registeredClientParametersMapper); return jdbcRegisteredClientRepository; } @Bean JdbcOperations jdbcOperations() { return new JdbcTemplate(db); } @Bean JWKSource jwkSource() { return jwkSource; } @Bean OAuth2TokenCustomizer jwtCustomizer() { return (context) -> { if (AuthorizationGrantType.REFRESH_TOKEN.equals(context.getAuthorizationGrantType())) { Authentication principal = context.getPrincipal(); Set authorities = new HashSet<>(); for (GrantedAuthority authority : principal.getAuthorities()) { authorities.add(authority.getAuthority()); } context.getClaims().claim(AUTHORITIES_CLAIM, authorities); } }; } @Bean PasswordEncoder passwordEncoder() { return NoOpPasswordEncoder.getInstance(); } } @EnableWebSecurity @Configuration(proxyBeanMethods = false) static class AuthorizationServerConfigurationWithPublicClientAuthentication extends AuthorizationServerConfiguration { // @formatter:off @Bean SecurityFilterChain authorizationServerSecurityFilterChain( HttpSecurity http, RegisteredClientRepository registeredClientRepository) throws Exception { http .oauth2AuthorizationServer((authorizationServer) -> authorizationServer .clientAuthentication((clientAuthentication) -> clientAuthentication .authenticationConverter( new PublicClientRefreshTokenAuthenticationConverter()) .authenticationProvider( new PublicClientRefreshTokenAuthenticationProvider(registeredClientRepository))) ) .authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated() ); return http.build(); } // @formatter:on } @Transient private static final class PublicClientRefreshTokenAuthenticationToken extends OAuth2ClientAuthenticationToken { private PublicClientRefreshTokenAuthenticationToken(String clientId) { super(clientId, ClientAuthenticationMethod.NONE, null, null); } private PublicClientRefreshTokenAuthenticationToken(RegisteredClient registeredClient) { super(registeredClient, ClientAuthenticationMethod.NONE, null); } } private static final class PublicClientRefreshTokenAuthenticationConverter implements AuthenticationConverter { @Nullable @Override public Authentication convert(HttpServletRequest request) { // grant_type (REQUIRED) String grantType = request.getParameter(OAuth2ParameterNames.GRANT_TYPE); if (!AuthorizationGrantType.REFRESH_TOKEN.getValue().equals(grantType)) { return null; } // client_id (REQUIRED) String clientId = request.getParameter(OAuth2ParameterNames.CLIENT_ID); if (!StringUtils.hasText(clientId)) { return null; } return new PublicClientRefreshTokenAuthenticationToken(clientId); } } private static final class PublicClientRefreshTokenAuthenticationProvider implements AuthenticationProvider { private final RegisteredClientRepository registeredClientRepository; private PublicClientRefreshTokenAuthenticationProvider(RegisteredClientRepository registeredClientRepository) { Assert.notNull(registeredClientRepository, "registeredClientRepository cannot be null"); this.registeredClientRepository = registeredClientRepository; } @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { PublicClientRefreshTokenAuthenticationToken publicClientAuthentication = (PublicClientRefreshTokenAuthenticationToken) authentication; if (!ClientAuthenticationMethod.NONE.equals(publicClientAuthentication.getClientAuthenticationMethod())) { return null; } String clientId = publicClientAuthentication.getPrincipal().toString(); RegisteredClient registeredClient = this.registeredClientRepository.findByClientId(clientId); if (registeredClient == null) { throwInvalidClient(OAuth2ParameterNames.CLIENT_ID); } if (!registeredClient.getClientAuthenticationMethods() .contains(publicClientAuthentication.getClientAuthenticationMethod())) { throwInvalidClient("authentication_method"); } return new PublicClientRefreshTokenAuthenticationToken(registeredClient); } @Override public boolean supports(Class authentication) { return PublicClientRefreshTokenAuthenticationToken.class.isAssignableFrom(authentication); } private static void throwInvalidClient(String parameterName) { OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.INVALID_CLIENT, "Public client authentication failed: " + parameterName, null); throw new OAuth2AuthenticationException(error); } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2TokenExchangeGrantTests.java ================================================ /* * 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 org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization; import java.security.Principal; import java.time.Instant; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; import java.util.function.Consumer; import java.util.function.Function; import com.nimbusds.jose.jwk.JWKSet; import com.nimbusds.jose.jwk.source.JWKSource; import com.nimbusds.jose.proc.SecurityContext; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.jdbc.core.JdbcOperations; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; import org.springframework.mock.http.client.MockClientHttpResponse; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.core.Authentication; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.userdetails.User; import org.springframework.security.crypto.password.NoOpPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.core.OAuth2AccessToken; import org.springframework.security.oauth2.core.OAuth2Token; import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter; import org.springframework.security.oauth2.jose.TestJwks; import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm; import org.springframework.security.oauth2.jwt.JwsHeader; import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.security.oauth2.jwt.JwtClaimsSet; import org.springframework.security.oauth2.jwt.JwtEncoderParameters; import org.springframework.security.oauth2.jwt.NimbusJwtEncoder; import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationConsentService; import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService; import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService; import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenExchangeCompositeAuthenticationToken; import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository; import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenClaimNames; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.util.StringUtils; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Integration tests for OAuth 2.0 Token Exchange Grant. * * @author Steve Riesenberg */ @ExtendWith(SpringTestContextExtension.class) public class OAuth2TokenExchangeGrantTests { private static final String DEFAULT_TOKEN_ENDPOINT_URI = "/oauth2/token"; private static final String RESOURCE = "https://mydomain.com/resource"; private static final String AUDIENCE = "audience"; private static final String SUBJECT_TOKEN = "EfYu_0jEL"; private static final String ACTOR_TOKEN = "JlNE_xR1f"; private static final String ACCESS_TOKEN_TYPE_VALUE = "urn:ietf:params:oauth:token-type:access_token"; private static final String JWT_TOKEN_TYPE_VALUE = "urn:ietf:params:oauth:token-type:jwt"; private static NimbusJwtEncoder dPoPProofJwtEncoder; public final SpringTestContext spring = new SpringTestContext(this); private final HttpMessageConverter accessTokenResponseHttpMessageConverter = new OAuth2AccessTokenResponseHttpMessageConverter(); @Autowired private MockMvc mvc; @Autowired private JdbcOperations jdbcOperations; @Autowired private RegisteredClientRepository registeredClientRepository; @Autowired private OAuth2AuthorizationService authorizationService; @BeforeAll public static void init() { JWKSet jwkSet = new JWKSet(TestJwks.DEFAULT_RSA_JWK); AuthorizationServerConfiguration.JWK_SOURCE = (jwkSelector, securityContext) -> jwkSelector.select(jwkSet); JWKSet clientJwkSet = new JWKSet(TestJwks.DEFAULT_EC_JWK); JWKSource clientJwkSource = (jwkSelector, securityContext) -> jwkSelector.select(clientJwkSet); dPoPProofJwtEncoder = new NimbusJwtEncoder(clientJwkSource); // @formatter:off AuthorizationServerConfiguration.DB = new EmbeddedDatabaseBuilder() .generateUniqueName(true) .setType(EmbeddedDatabaseType.HSQL) .setScriptEncoding("UTF-8") .addScript("org/springframework/security/oauth2/server/authorization/oauth2-authorization-schema.sql") .addScript("org/springframework/security/oauth2/server/authorization/oauth2-authorization-consent-schema.sql") .addScript("org/springframework/security/oauth2/server/authorization/client/oauth2-registered-client-schema.sql") .build(); // @formatter:on } @AfterEach public void tearDown() { this.jdbcOperations.update("truncate table oauth2_authorization"); this.jdbcOperations.update("truncate table oauth2_authorization_consent"); this.jdbcOperations.update("truncate table oauth2_registered_client"); } @AfterAll public static void destroy() { AuthorizationServerConfiguration.DB.shutdown(); } @Test public void requestWhenAccessTokenRequestNotAuthenticatedThenUnauthorized() throws Exception { this.spring.register(AuthorizationServerConfiguration.class).autowire(); RegisteredClient registeredClient = TestRegisteredClients.registeredClient() .authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE) .build(); this.registeredClientRepository.save(registeredClient); MultiValueMap parameters = new LinkedMultiValueMap<>(); parameters.set(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue()); parameters.set(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()); parameters.set(OAuth2ParameterNames.SCOPE, StringUtils.collectionToDelimitedString(registeredClient.getScopes(), " ")); // @formatter:off this.mvc.perform(post(DEFAULT_TOKEN_ENDPOINT_URI).params(parameters)) .andExpect(status().isUnauthorized()); // @formatter:on } @Test public void requestWhenAccessTokenRequestValidAndNoActorTokenThenReturnAccessTokenResponseForImpersonation() throws Exception { this.spring.register(AuthorizationServerConfiguration.class).autowire(); RegisteredClient registeredClient = TestRegisteredClients.registeredClient() .authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE) .build(); this.registeredClientRepository.save(registeredClient); UsernamePasswordAuthenticationToken userPrincipal = createUserPrincipal("user"); OAuth2Authorization subjectAuthorization = TestOAuth2Authorizations.authorization(registeredClient) .attribute(Principal.class.getName(), userPrincipal) .build(); this.authorizationService.save(subjectAuthorization); MultiValueMap parameters = new LinkedMultiValueMap<>(); parameters.set(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue()); parameters.set(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()); parameters.set(OAuth2ParameterNames.REQUESTED_TOKEN_TYPE, JWT_TOKEN_TYPE_VALUE); parameters.set(OAuth2ParameterNames.SUBJECT_TOKEN, subjectAuthorization.getAccessToken().getToken().getTokenValue()); parameters.set(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE, JWT_TOKEN_TYPE_VALUE); parameters.set(OAuth2ParameterNames.RESOURCE, RESOURCE); parameters.set(OAuth2ParameterNames.AUDIENCE, AUDIENCE); parameters.set(OAuth2ParameterNames.SCOPE, StringUtils.collectionToDelimitedString(registeredClient.getScopes(), " ")); // @formatter:off MvcResult mvcResult = this.mvc.perform(post(DEFAULT_TOKEN_ENDPOINT_URI) .params(parameters) .headers(withClientAuth(registeredClient))) .andExpect(status().isOk()) .andExpect(jsonPath("$.access_token").isNotEmpty()) .andExpect(jsonPath("$.refresh_token").doesNotExist()) .andExpect(jsonPath("$.expires_in").isNumber()) .andExpect(jsonPath("$.scope").isNotEmpty()) .andExpect(jsonPath("$.token_type").isNotEmpty()) .andExpect(jsonPath("$.issued_token_type").isNotEmpty()) .andReturn(); // @formatter:on MockHttpServletResponse servletResponse = mvcResult.getResponse(); MockClientHttpResponse httpResponse = new MockClientHttpResponse(servletResponse.getContentAsByteArray(), HttpStatus.OK); OAuth2AccessTokenResponse accessTokenResponse = this.accessTokenResponseHttpMessageConverter .read(OAuth2AccessTokenResponse.class, httpResponse); String accessToken = accessTokenResponse.getAccessToken().getTokenValue(); OAuth2Authorization authorization = this.authorizationService.findByToken(accessToken, OAuth2TokenType.ACCESS_TOKEN); assertThat(authorization).isNotNull(); assertThat(authorization.getAccessToken()).isNotNull(); assertThat(authorization.getAccessToken().getClaims()).isNotNull(); // We do not populate claims (e.g. `aud`) based on the resource or audience // parameters assertThat(authorization.getAccessToken().getClaims().get(OAuth2TokenClaimNames.AUD)) .isEqualTo(List.of(registeredClient.getClientId())); assertThat(authorization.getRefreshToken()).isNull(); assertThat(authorization.getAttribute(Principal.class.getName())).isEqualTo(userPrincipal); } @Test public void requestWhenAccessTokenRequestValidAndActorTokenThenReturnAccessTokenResponseForDelegation() throws Exception { this.spring.register(AuthorizationServerConfiguration.class).autowire(); RegisteredClient registeredClient = TestRegisteredClients.registeredClient() .authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE) .build(); this.registeredClientRepository.save(registeredClient); UsernamePasswordAuthenticationToken userPrincipal = createUserPrincipal("user"); UsernamePasswordAuthenticationToken adminPrincipal = createUserPrincipal("admin"); Map actorTokenClaims = new HashMap<>(); actorTokenClaims.put(OAuth2TokenClaimNames.ISS, "issuer2"); actorTokenClaims.put(OAuth2TokenClaimNames.SUB, "admin"); Map subjectTokenClaims = new HashMap<>(); subjectTokenClaims.put(OAuth2TokenClaimNames.ISS, "issuer1"); subjectTokenClaims.put(OAuth2TokenClaimNames.SUB, "user"); subjectTokenClaims.put("may_act", actorTokenClaims); OAuth2AccessToken subjectToken = createAccessToken(SUBJECT_TOKEN); OAuth2AccessToken actorToken = createAccessToken(ACTOR_TOKEN); // @formatter:off OAuth2Authorization subjectAuthorization = TestOAuth2Authorizations.authorization(registeredClient, subjectToken, subjectTokenClaims) .id(UUID.randomUUID().toString()) .attribute(Principal.class.getName(), userPrincipal) .build(); OAuth2Authorization actorAuthorization = TestOAuth2Authorizations.authorization(registeredClient, actorToken, actorTokenClaims) .id(UUID.randomUUID().toString()) .attribute(Principal.class.getName(), adminPrincipal) .build(); // @formatter:on this.authorizationService.save(subjectAuthorization); this.authorizationService.save(actorAuthorization); MultiValueMap parameters = new LinkedMultiValueMap<>(); parameters.set(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue()); parameters.set(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()); parameters.set(OAuth2ParameterNames.REQUESTED_TOKEN_TYPE, JWT_TOKEN_TYPE_VALUE); parameters.set(OAuth2ParameterNames.SUBJECT_TOKEN, SUBJECT_TOKEN); parameters.set(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE, JWT_TOKEN_TYPE_VALUE); parameters.set(OAuth2ParameterNames.ACTOR_TOKEN, ACTOR_TOKEN); parameters.set(OAuth2ParameterNames.ACTOR_TOKEN_TYPE, ACCESS_TOKEN_TYPE_VALUE); parameters.set(OAuth2ParameterNames.SCOPE, StringUtils.collectionToDelimitedString(registeredClient.getScopes(), " ")); // @formatter:off MvcResult mvcResult = this.mvc.perform(post(DEFAULT_TOKEN_ENDPOINT_URI) .params(parameters) .headers(withClientAuth(registeredClient))) .andExpect(status().isOk()) .andExpect(jsonPath("$.access_token").isNotEmpty()) .andExpect(jsonPath("$.refresh_token").doesNotExist()) .andExpect(jsonPath("$.expires_in").isNumber()) .andExpect(jsonPath("$.scope").isNotEmpty()) .andExpect(jsonPath("$.token_type").isNotEmpty()) .andExpect(jsonPath("$.issued_token_type").isNotEmpty()) .andReturn(); // @formatter:on MockHttpServletResponse servletResponse = mvcResult.getResponse(); MockClientHttpResponse httpResponse = new MockClientHttpResponse(servletResponse.getContentAsByteArray(), HttpStatus.OK); OAuth2AccessTokenResponse accessTokenResponse = this.accessTokenResponseHttpMessageConverter .read(OAuth2AccessTokenResponse.class, httpResponse); String accessToken = accessTokenResponse.getAccessToken().getTokenValue(); OAuth2Authorization authorization = this.authorizationService.findByToken(accessToken, OAuth2TokenType.ACCESS_TOKEN); assertThat(authorization).isNotNull(); assertThat(authorization.getAccessToken()).isNotNull(); assertThat(authorization.getAccessToken().getClaims()).isNotNull(); assertThat(authorization.getAccessToken().getClaims().get("act")).isNotNull(); assertThat(authorization.getRefreshToken()).isNull(); assertThat(authorization.getAttribute(Principal.class.getName())) .isInstanceOf(OAuth2TokenExchangeCompositeAuthenticationToken.class); } @Test public void requestWhenAccessTokenRequestWithDPoPProofThenReturnDPoPBoundAccessToken() throws Exception { this.spring.register(AuthorizationServerConfiguration.class).autowire(); RegisteredClient registeredClient = TestRegisteredClients.registeredClient() .authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE) .build(); this.registeredClientRepository.save(registeredClient); UsernamePasswordAuthenticationToken userPrincipal = createUserPrincipal("user"); UsernamePasswordAuthenticationToken adminPrincipal = createUserPrincipal("admin"); Map actorTokenClaims = new HashMap<>(); actorTokenClaims.put(OAuth2TokenClaimNames.ISS, "issuer2"); actorTokenClaims.put(OAuth2TokenClaimNames.SUB, "admin"); Map subjectTokenClaims = new HashMap<>(); subjectTokenClaims.put(OAuth2TokenClaimNames.ISS, "issuer1"); subjectTokenClaims.put(OAuth2TokenClaimNames.SUB, "user"); subjectTokenClaims.put("may_act", actorTokenClaims); OAuth2AccessToken subjectToken = createAccessToken(SUBJECT_TOKEN); OAuth2AccessToken actorToken = createAccessToken(ACTOR_TOKEN); // @formatter:off OAuth2Authorization subjectAuthorization = TestOAuth2Authorizations.authorization(registeredClient, subjectToken, subjectTokenClaims) .id(UUID.randomUUID().toString()) .attribute(Principal.class.getName(), userPrincipal) .build(); OAuth2Authorization actorAuthorization = TestOAuth2Authorizations.authorization(registeredClient, actorToken, actorTokenClaims) .id(UUID.randomUUID().toString()) .attribute(Principal.class.getName(), adminPrincipal) .build(); // @formatter:on this.authorizationService.save(subjectAuthorization); this.authorizationService.save(actorAuthorization); MultiValueMap parameters = new LinkedMultiValueMap<>(); parameters.set(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue()); parameters.set(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()); parameters.set(OAuth2ParameterNames.REQUESTED_TOKEN_TYPE, JWT_TOKEN_TYPE_VALUE); parameters.set(OAuth2ParameterNames.SUBJECT_TOKEN, SUBJECT_TOKEN); parameters.set(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE, JWT_TOKEN_TYPE_VALUE); parameters.set(OAuth2ParameterNames.ACTOR_TOKEN, ACTOR_TOKEN); parameters.set(OAuth2ParameterNames.ACTOR_TOKEN_TYPE, ACCESS_TOKEN_TYPE_VALUE); parameters.set(OAuth2ParameterNames.SCOPE, StringUtils.collectionToDelimitedString(registeredClient.getScopes(), " ")); String tokenEndpointUri = "http://localhost" + DEFAULT_TOKEN_ENDPOINT_URI; String dPoPProof = generateDPoPProof(tokenEndpointUri); // @formatter:off this.mvc.perform(post(DEFAULT_TOKEN_ENDPOINT_URI) .params(parameters) .headers(withClientAuth(registeredClient)) .header(OAuth2AccessToken.TokenType.DPOP.getValue(), dPoPProof)) .andExpect(status().isOk()) .andExpect(jsonPath("$.token_type").value(OAuth2AccessToken.TokenType.DPOP.getValue())); // @formatter:on } private static OAuth2AccessToken createAccessToken(String tokenValue) { Instant issuedAt = Instant.now(); Instant expiresAt = issuedAt.plusSeconds(300); return new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, tokenValue, issuedAt, expiresAt); } private static UsernamePasswordAuthenticationToken createUserPrincipal(String username) { User user = new User(username, "", AuthorityUtils.createAuthorityList("ROLE_USER")); return UsernamePasswordAuthenticationToken.authenticated(user, null, user.getAuthorities()); } private static HttpHeaders withClientAuth(RegisteredClient registeredClient) { HttpHeaders headers = new HttpHeaders(); headers.setBasicAuth(registeredClient.getClientId(), registeredClient.getClientSecret()); return headers; } private static Consumer> withInvalidated() { return (metadata) -> metadata.put(OAuth2Authorization.Token.INVALIDATED_METADATA_NAME, true); } private static Function, Boolean> isInvalidated() { return (token) -> token.getMetadata(OAuth2Authorization.Token.INVALIDATED_METADATA_NAME); } private static String generateDPoPProof(String tokenEndpointUri) { // @formatter:off Map publicJwk = TestJwks.DEFAULT_EC_JWK .toPublicJWK() .toJSONObject(); JwsHeader jwsHeader = JwsHeader.with(SignatureAlgorithm.ES256) .type("dpop+jwt") .jwk(publicJwk) .build(); JwtClaimsSet claims = JwtClaimsSet.builder() .issuedAt(Instant.now()) .claim("htm", "POST") .claim("htu", tokenEndpointUri) .id(UUID.randomUUID().toString()) .build(); // @formatter:on Jwt jwt = dPoPProofJwtEncoder.encode(JwtEncoderParameters.from(jwsHeader, claims)); return jwt.getTokenValue(); } @EnableWebSecurity @Import(OAuth2AuthorizationServerConfiguration.class) static class AuthorizationServerConfiguration { static JWKSource JWK_SOURCE; static EmbeddedDatabase DB; @Bean RegisteredClientRepository registeredClientRepository(JdbcOperations jdbcOperations) { return new JdbcRegisteredClientRepository(jdbcOperations); } @Bean OAuth2AuthorizationService authorizationService(JdbcOperations jdbcOperations, RegisteredClientRepository registeredClientRepository) { return new JdbcOAuth2AuthorizationService(jdbcOperations, registeredClientRepository); } @Bean OAuth2AuthorizationConsentService authorizationConsentService(JdbcOperations jdbcOperations, RegisteredClientRepository registeredClientRepository) { return new JdbcOAuth2AuthorizationConsentService(jdbcOperations, registeredClientRepository); } @Bean JdbcOperations jdbcOperations() { return new JdbcTemplate(DB); } @Bean JWKSource jwkSource() { return JWK_SOURCE; } @Bean PasswordEncoder passwordEncoder() { return NoOpPasswordEncoder.getInstance(); } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2TokenIntrospectionTests.java ================================================ /* * 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 org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.time.Duration; import java.time.Instant; import java.util.ArrayList; import java.util.Arrays; import java.util.Base64; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.function.Consumer; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.jdbc.core.JdbcOperations; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; import org.springframework.mock.http.client.MockClientHttpResponse; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.core.Authentication; import org.springframework.security.crypto.password.NoOpPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.core.ClientAuthenticationMethod; import org.springframework.security.oauth2.core.OAuth2AccessToken; import org.springframework.security.oauth2.core.OAuth2RefreshToken; import org.springframework.security.oauth2.core.OAuth2Token; import org.springframework.security.oauth2.core.OAuth2TokenIntrospectionClaimNames; import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter; import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationConsentService; import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService; import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationCode; import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService; import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; import org.springframework.security.oauth2.server.authorization.OAuth2TokenIntrospection; import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenIntrospectionAuthenticationProvider; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenIntrospectionAuthenticationToken; import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository; import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository.RegisteredClientParametersMapper; import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; import org.springframework.security.oauth2.server.authorization.http.converter.OAuth2TokenIntrospectionHttpMessageConverter; import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; import org.springframework.security.oauth2.server.authorization.settings.ClientSettings; import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat; import org.springframework.security.oauth2.server.authorization.settings.TokenSettings; import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenClaimsContext; import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenClaimsSet; import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer; import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2TokenIntrospectionAuthenticationConverter; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.AuthenticationConverter; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Integration tests for the OAuth 2.0 Token Introspection endpoint. * * @author Gerardo Roza * @author Joe Grandja */ @ExtendWith(SpringTestContextExtension.class) public class OAuth2TokenIntrospectionTests { private static EmbeddedDatabase db; private static OAuth2TokenCustomizer accessTokenCustomizer; private static AuthenticationConverter authenticationConverter; private static Consumer> authenticationConvertersConsumer; private static AuthenticationProvider authenticationProvider; private static Consumer> authenticationProvidersConsumer; private static AuthenticationSuccessHandler authenticationSuccessHandler; private static AuthenticationFailureHandler authenticationFailureHandler; private static final HttpMessageConverter tokenIntrospectionHttpResponseConverter = new OAuth2TokenIntrospectionHttpMessageConverter(); private static final HttpMessageConverter accessTokenHttpResponseConverter = new OAuth2AccessTokenResponseHttpMessageConverter(); public final SpringTestContext spring = new SpringTestContext(this); @Autowired private MockMvc mvc; @Autowired private JdbcOperations jdbcOperations; @Autowired private RegisteredClientRepository registeredClientRepository; @Autowired private OAuth2AuthorizationService authorizationService; @Autowired private AuthorizationServerSettings authorizationServerSettings; @BeforeAll public static void init() { authenticationConverter = mock(AuthenticationConverter.class); authenticationConvertersConsumer = mock(Consumer.class); authenticationProvider = mock(AuthenticationProvider.class); authenticationProvidersConsumer = mock(Consumer.class); authenticationSuccessHandler = mock(AuthenticationSuccessHandler.class); authenticationFailureHandler = mock(AuthenticationFailureHandler.class); accessTokenCustomizer = mock(OAuth2TokenCustomizer.class); db = new EmbeddedDatabaseBuilder().generateUniqueName(true) .setType(EmbeddedDatabaseType.HSQL) .setScriptEncoding("UTF-8") .addScript("org/springframework/security/oauth2/server/authorization/oauth2-authorization-schema.sql") .addScript( "org/springframework/security/oauth2/server/authorization/client/oauth2-registered-client-schema.sql") .build(); } @SuppressWarnings("unchecked") @BeforeEach public void setup() { reset(authenticationConverter); reset(authenticationConvertersConsumer); reset(authenticationProvider); reset(authenticationProvidersConsumer); reset(authenticationSuccessHandler); reset(authenticationFailureHandler); reset(accessTokenCustomizer); } @AfterEach public void tearDown() { this.jdbcOperations.update("truncate table oauth2_authorization"); this.jdbcOperations.update("truncate table oauth2_registered_client"); } @AfterAll public static void destroy() { db.shutdown(); } @Test public void requestWhenIntrospectValidAccessTokenThenActive() throws Exception { this.spring.register(AuthorizationServerConfiguration.class).autowire(); RegisteredClient introspectRegisteredClient = TestRegisteredClients.registeredClient2() .clientSecret("secret-2") .build(); this.registeredClientRepository.save(introspectRegisteredClient); RegisteredClient authorizedRegisteredClient = TestRegisteredClients.registeredClient().build(); Instant issuedAt = Instant.now(); Instant expiresAt = issuedAt.plus(Duration.ofHours(1)); OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, "access-token", issuedAt, expiresAt, new HashSet<>(Arrays.asList("scope1", "scope2"))); // @formatter:off OAuth2TokenClaimsSet accessTokenClaims = OAuth2TokenClaimsSet.builder() .issuer("https://provider.com") .subject("subject") .audience(Collections.singletonList(authorizedRegisteredClient.getClientId())) .issuedAt(issuedAt) .notBefore(issuedAt) .expiresAt(expiresAt) .claim(OAuth2TokenIntrospectionClaimNames.SCOPE, accessToken.getScopes()) .id("id") .build(); // @formatter:on OAuth2Authorization authorization = TestOAuth2Authorizations .authorization(authorizedRegisteredClient, accessToken, accessTokenClaims.getClaims()) .build(); this.registeredClientRepository.save(authorizedRegisteredClient); this.authorizationService.save(authorization); // @formatter:off MvcResult mvcResult = this.mvc.perform(post(this.authorizationServerSettings.getTokenIntrospectionEndpoint()) .params(getTokenIntrospectionRequestParameters(accessToken, OAuth2TokenType.ACCESS_TOKEN)) .header(HttpHeaders.AUTHORIZATION, getAuthorizationHeader(introspectRegisteredClient))) .andExpect(status().isOk()) .andReturn(); // @formatter:on OAuth2TokenIntrospection tokenIntrospectionResponse = readTokenIntrospectionResponse(mvcResult); assertThat(tokenIntrospectionResponse.isActive()).isTrue(); assertThat(tokenIntrospectionResponse.getClientId()).isEqualTo(authorizedRegisteredClient.getClientId()); assertThat(tokenIntrospectionResponse.getUsername()).isNull(); assertThat(tokenIntrospectionResponse.getIssuedAt()).isBetween(accessTokenClaims.getIssuedAt().minusSeconds(1), accessTokenClaims.getIssuedAt().plusSeconds(1)); assertThat(tokenIntrospectionResponse.getExpiresAt()).isBetween( accessTokenClaims.getExpiresAt().minusSeconds(1), accessTokenClaims.getExpiresAt().plusSeconds(1)); assertThat(tokenIntrospectionResponse.getScopes()).containsExactlyInAnyOrderElementsOf(accessToken.getScopes()); assertThat(tokenIntrospectionResponse.getTokenType()).isEqualTo(accessToken.getTokenType().getValue()); assertThat(tokenIntrospectionResponse.getNotBefore()).isBetween( accessTokenClaims.getNotBefore().minusSeconds(1), accessTokenClaims.getNotBefore().plusSeconds(1)); assertThat(tokenIntrospectionResponse.getSubject()).isEqualTo(accessTokenClaims.getSubject()); assertThat(tokenIntrospectionResponse.getAudience()) .containsExactlyInAnyOrderElementsOf(accessTokenClaims.getAudience()); assertThat(tokenIntrospectionResponse.getIssuer()).isEqualTo(accessTokenClaims.getIssuer()); assertThat(tokenIntrospectionResponse.getId()).isEqualTo(accessTokenClaims.getId()); } @Test public void requestWhenIntrospectValidRefreshTokenThenActive() throws Exception { this.spring.register(AuthorizationServerConfiguration.class).autowire(); RegisteredClient introspectRegisteredClient = TestRegisteredClients.registeredClient2() .clientSecret("secret-2") .build(); this.registeredClientRepository.save(introspectRegisteredClient); RegisteredClient authorizedRegisteredClient = TestRegisteredClients.registeredClient().build(); OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(authorizedRegisteredClient).build(); OAuth2RefreshToken refreshToken = authorization.getRefreshToken().getToken(); this.registeredClientRepository.save(authorizedRegisteredClient); this.authorizationService.save(authorization); // @formatter:off MvcResult mvcResult = this.mvc.perform(post(this.authorizationServerSettings.getTokenIntrospectionEndpoint()) .params(getTokenIntrospectionRequestParameters(refreshToken, OAuth2TokenType.REFRESH_TOKEN)) .header(HttpHeaders.AUTHORIZATION, getAuthorizationHeader(introspectRegisteredClient))) .andExpect(status().isOk()) .andReturn(); // @formatter:on OAuth2TokenIntrospection tokenIntrospectionResponse = readTokenIntrospectionResponse(mvcResult); assertThat(tokenIntrospectionResponse.isActive()).isTrue(); assertThat(tokenIntrospectionResponse.getClientId()).isEqualTo(authorizedRegisteredClient.getClientId()); assertThat(tokenIntrospectionResponse.getUsername()).isNull(); assertThat(tokenIntrospectionResponse.getIssuedAt()).isBetween(refreshToken.getIssuedAt().minusSeconds(1), refreshToken.getIssuedAt().plusSeconds(1)); assertThat(tokenIntrospectionResponse.getExpiresAt()).isBetween(refreshToken.getExpiresAt().minusSeconds(1), refreshToken.getExpiresAt().plusSeconds(1)); assertThat(tokenIntrospectionResponse.getScopes()).isNull(); assertThat(tokenIntrospectionResponse.getTokenType()).isNull(); assertThat(tokenIntrospectionResponse.getNotBefore()).isNull(); assertThat(tokenIntrospectionResponse.getSubject()).isNull(); assertThat(tokenIntrospectionResponse.getAudience()).isNull(); assertThat(tokenIntrospectionResponse.getIssuer()).isNull(); assertThat(tokenIntrospectionResponse.getId()).isNull(); } @Test public void requestWhenObtainReferenceAccessTokenAndIntrospectThenActive() throws Exception { this.spring.register(AuthorizationServerConfiguration.class).autowire(); // @formatter:off TokenSettings tokenSettings = TokenSettings.builder() .accessTokenFormat(OAuth2TokenFormat.REFERENCE) .build(); RegisteredClient authorizedRegisteredClient = TestRegisteredClients.registeredClient() .tokenSettings(tokenSettings) .clientSettings(ClientSettings.builder().requireProofKey(false).build()) .build(); // @formatter:on this.registeredClientRepository.save(authorizedRegisteredClient); OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(authorizedRegisteredClient).build(); this.authorizationService.save(authorization); // @formatter:off MvcResult mvcResult = this.mvc.perform(post(this.authorizationServerSettings.getTokenEndpoint()) .params(getAuthorizationCodeTokenRequestParameters(authorizedRegisteredClient, authorization)) .header(HttpHeaders.AUTHORIZATION, getAuthorizationHeader(authorizedRegisteredClient))) .andExpect(status().isOk()) .andReturn(); // @formatter:on OAuth2AccessTokenResponse accessTokenResponse = readAccessTokenResponse(mvcResult); OAuth2AccessToken accessToken = accessTokenResponse.getAccessToken(); RegisteredClient introspectRegisteredClient = TestRegisteredClients.registeredClient2().build(); this.registeredClientRepository.save(introspectRegisteredClient); // @formatter:off mvcResult = this.mvc.perform(post(this.authorizationServerSettings.getTokenIntrospectionEndpoint()) .params(getTokenIntrospectionRequestParameters(accessToken, OAuth2TokenType.ACCESS_TOKEN)) .header(HttpHeaders.AUTHORIZATION, getAuthorizationHeader(introspectRegisteredClient))) .andExpect(status().isOk()) .andReturn(); // @formatter:on OAuth2TokenIntrospection tokenIntrospectionResponse = readTokenIntrospectionResponse(mvcResult); ArgumentCaptor accessTokenClaimsContextCaptor = ArgumentCaptor .forClass(OAuth2TokenClaimsContext.class); verify(accessTokenCustomizer).customize(accessTokenClaimsContextCaptor.capture()); OAuth2TokenClaimsContext accessTokenClaimsContext = accessTokenClaimsContextCaptor.getValue(); OAuth2TokenClaimsSet accessTokenClaims = accessTokenClaimsContext.getClaims().build(); assertThat(tokenIntrospectionResponse.isActive()).isTrue(); assertThat(tokenIntrospectionResponse.getClientId()).isEqualTo(authorizedRegisteredClient.getClientId()); assertThat(tokenIntrospectionResponse.getUsername()).isNull(); assertThat(tokenIntrospectionResponse.getIssuedAt()).isBetween(accessTokenClaims.getIssuedAt().minusSeconds(1), accessTokenClaims.getIssuedAt().plusSeconds(1)); assertThat(tokenIntrospectionResponse.getExpiresAt()).isBetween( accessTokenClaims.getExpiresAt().minusSeconds(1), accessTokenClaims.getExpiresAt().plusSeconds(1)); List scopes = new ArrayList<>(accessTokenClaims.getClaim(OAuth2ParameterNames.SCOPE)); assertThat(tokenIntrospectionResponse.getScopes()).containsExactlyInAnyOrderElementsOf(scopes); assertThat(tokenIntrospectionResponse.getTokenType()).isEqualTo(accessToken.getTokenType().getValue()); assertThat(tokenIntrospectionResponse.getNotBefore()).isBetween( accessTokenClaims.getNotBefore().minusSeconds(1), accessTokenClaims.getNotBefore().plusSeconds(1)); assertThat(tokenIntrospectionResponse.getSubject()).isEqualTo(accessTokenClaims.getSubject()); assertThat(tokenIntrospectionResponse.getAudience()) .containsExactlyInAnyOrderElementsOf(accessTokenClaims.getAudience()); assertThat(tokenIntrospectionResponse.getIssuer()).isEqualTo(accessTokenClaims.getIssuer()); assertThat(tokenIntrospectionResponse.getId()).isEqualTo(accessTokenClaims.getId()); } @Test public void requestWhenTokenIntrospectionEndpointCustomizedThenUsed() throws Exception { this.spring.register(AuthorizationServerConfigurationCustomTokenIntrospectionEndpoint.class).autowire(); RegisteredClient introspectRegisteredClient = TestRegisteredClients.registeredClient2().build(); this.registeredClientRepository.save(introspectRegisteredClient); RegisteredClient authorizedRegisteredClient = TestRegisteredClients.registeredClient().build(); this.registeredClientRepository.save(authorizedRegisteredClient); OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(authorizedRegisteredClient).build(); this.authorizationService.save(authorization); OAuth2AccessToken accessToken = authorization.getAccessToken().getToken(); Authentication clientPrincipal = new OAuth2ClientAuthenticationToken(introspectRegisteredClient, ClientAuthenticationMethod.CLIENT_SECRET_BASIC, introspectRegisteredClient.getClientSecret()); OAuth2TokenIntrospectionAuthenticationToken tokenIntrospectionAuthentication = new OAuth2TokenIntrospectionAuthenticationToken( accessToken.getTokenValue(), clientPrincipal, null, null); given(authenticationConverter.convert(any())).willReturn(tokenIntrospectionAuthentication); given(authenticationProvider.supports(eq(OAuth2TokenIntrospectionAuthenticationToken.class))).willReturn(true); given(authenticationProvider.authenticate(any())).willReturn(tokenIntrospectionAuthentication); // @formatter:off this.mvc.perform(post(this.authorizationServerSettings.getTokenIntrospectionEndpoint()) .params(getTokenIntrospectionRequestParameters(accessToken, OAuth2TokenType.ACCESS_TOKEN)) .header(HttpHeaders.AUTHORIZATION, getAuthorizationHeader(introspectRegisteredClient))) .andExpect(status().isOk()); // @formatter:on verify(authenticationConverter).convert(any()); @SuppressWarnings("unchecked") ArgumentCaptor> authenticationConvertersCaptor = ArgumentCaptor .forClass(List.class); verify(authenticationConvertersConsumer).accept(authenticationConvertersCaptor.capture()); List authenticationConverters = authenticationConvertersCaptor.getValue(); assertThat(authenticationConverters).allMatch((converter) -> converter == authenticationConverter || converter instanceof OAuth2TokenIntrospectionAuthenticationConverter); verify(authenticationProvider).authenticate(eq(tokenIntrospectionAuthentication)); @SuppressWarnings("unchecked") ArgumentCaptor> authenticationProvidersCaptor = ArgumentCaptor .forClass(List.class); verify(authenticationProvidersConsumer).accept(authenticationProvidersCaptor.capture()); List authenticationProviders = authenticationProvidersCaptor.getValue(); assertThat(authenticationProviders).allMatch((provider) -> provider == authenticationProvider || provider instanceof OAuth2TokenIntrospectionAuthenticationProvider); verify(authenticationSuccessHandler).onAuthenticationSuccess(any(), any(), eq(tokenIntrospectionAuthentication)); } @Test public void requestWhenIntrospectionRequestIncludesIssuerPathThenActive() throws Exception { this.spring.register(AuthorizationServerConfigurationCustomTokenIntrospectionEndpoint.class).autowire(); RegisteredClient introspectRegisteredClient = TestRegisteredClients.registeredClient2().build(); this.registeredClientRepository.save(introspectRegisteredClient); RegisteredClient authorizedRegisteredClient = TestRegisteredClients.registeredClient().build(); this.registeredClientRepository.save(authorizedRegisteredClient); OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(authorizedRegisteredClient).build(); this.authorizationService.save(authorization); OAuth2AccessToken accessToken = authorization.getAccessToken().getToken(); Authentication clientPrincipal = new OAuth2ClientAuthenticationToken(introspectRegisteredClient, ClientAuthenticationMethod.CLIENT_SECRET_BASIC, introspectRegisteredClient.getClientSecret()); OAuth2TokenIntrospectionAuthenticationToken tokenIntrospectionAuthentication = new OAuth2TokenIntrospectionAuthenticationToken( accessToken.getTokenValue(), clientPrincipal, null, null); given(authenticationConverter.convert(any())).willReturn(tokenIntrospectionAuthentication); given(authenticationProvider.supports(eq(OAuth2TokenIntrospectionAuthenticationToken.class))).willReturn(true); given(authenticationProvider.authenticate(any())).willReturn(tokenIntrospectionAuthentication); String issuer = "https://example.com:8443/issuer1"; // @formatter:off this.mvc.perform(post(issuer.concat(this.authorizationServerSettings.getTokenIntrospectionEndpoint())) .params(getTokenIntrospectionRequestParameters(accessToken, OAuth2TokenType.ACCESS_TOKEN)) .header(HttpHeaders.AUTHORIZATION, getAuthorizationHeader(introspectRegisteredClient))) .andExpect(status().isOk()); // @formatter:on } private static MultiValueMap getTokenIntrospectionRequestParameters(OAuth2Token token, OAuth2TokenType tokenType) { MultiValueMap parameters = new LinkedMultiValueMap<>(); parameters.set(OAuth2ParameterNames.TOKEN, token.getTokenValue()); parameters.set(OAuth2ParameterNames.TOKEN_TYPE_HINT, tokenType.getValue()); return parameters; } private static OAuth2TokenIntrospection readTokenIntrospectionResponse(MvcResult mvcResult) throws Exception { MockHttpServletResponse servletResponse = mvcResult.getResponse(); MockClientHttpResponse httpResponse = new MockClientHttpResponse(servletResponse.getContentAsByteArray(), HttpStatus.valueOf(servletResponse.getStatus())); return tokenIntrospectionHttpResponseConverter.read(OAuth2TokenIntrospection.class, httpResponse); } private static MultiValueMap getAuthorizationCodeTokenRequestParameters( RegisteredClient registeredClient, OAuth2Authorization authorization) { MultiValueMap parameters = new LinkedMultiValueMap<>(); parameters.set(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.AUTHORIZATION_CODE.getValue()); parameters.set(OAuth2ParameterNames.CODE, authorization.getToken(OAuth2AuthorizationCode.class).getToken().getTokenValue()); parameters.set(OAuth2ParameterNames.REDIRECT_URI, registeredClient.getRedirectUris().iterator().next()); return parameters; } private static OAuth2AccessTokenResponse readAccessTokenResponse(MvcResult mvcResult) throws Exception { MockHttpServletResponse servletResponse = mvcResult.getResponse(); MockClientHttpResponse httpResponse = new MockClientHttpResponse(servletResponse.getContentAsByteArray(), HttpStatus.valueOf(servletResponse.getStatus())); return accessTokenHttpResponseConverter.read(OAuth2AccessTokenResponse.class, httpResponse); } private static String getAuthorizationHeader(RegisteredClient registeredClient) throws Exception { String clientId = registeredClient.getClientId(); String clientSecret = registeredClient.getClientSecret(); clientId = URLEncoder.encode(clientId, StandardCharsets.UTF_8.name()); clientSecret = URLEncoder.encode(clientSecret, StandardCharsets.UTF_8.name()); String credentialsString = clientId + ":" + clientSecret; byte[] encodedBytes = Base64.getEncoder().encode(credentialsString.getBytes(StandardCharsets.UTF_8)); return "Basic " + new String(encodedBytes, StandardCharsets.UTF_8); } @EnableWebSecurity @Import(OAuth2AuthorizationServerConfiguration.class) static class AuthorizationServerConfiguration { @Bean OAuth2AuthorizationService authorizationService(JdbcOperations jdbcOperations, RegisteredClientRepository registeredClientRepository) { return new JdbcOAuth2AuthorizationService(jdbcOperations, registeredClientRepository); } @Bean OAuth2AuthorizationConsentService authorizationConsentService(JdbcOperations jdbcOperations, RegisteredClientRepository registeredClientRepository) { return new JdbcOAuth2AuthorizationConsentService(jdbcOperations, registeredClientRepository); } @Bean @SuppressWarnings("removal") RegisteredClientRepository registeredClientRepository(JdbcOperations jdbcOperations) { JdbcRegisteredClientRepository jdbcRegisteredClientRepository = new JdbcRegisteredClientRepository( jdbcOperations); RegisteredClientParametersMapper registeredClientParametersMapper = new RegisteredClientParametersMapper(); jdbcRegisteredClientRepository.setRegisteredClientParametersMapper(registeredClientParametersMapper); return jdbcRegisteredClientRepository; } @Bean JdbcOperations jdbcOperations() { return new JdbcTemplate(db); } @Bean AuthorizationServerSettings authorizationServerSettings() { return AuthorizationServerSettings.builder().tokenIntrospectionEndpoint("/test/introspect").build(); } @Bean OAuth2TokenCustomizer accessTokenCustomizer() { return accessTokenCustomizer; } @Bean PasswordEncoder passwordEncoder() { return NoOpPasswordEncoder.getInstance(); } } @EnableWebSecurity @Configuration(proxyBeanMethods = false) static class AuthorizationServerConfigurationCustomTokenIntrospectionEndpoint extends AuthorizationServerConfiguration { // @formatter:off @Bean SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { http .oauth2AuthorizationServer((authorizationServer) -> authorizationServer .tokenIntrospectionEndpoint((tokenIntrospectionEndpoint) -> tokenIntrospectionEndpoint .introspectionRequestConverter(authenticationConverter) .introspectionRequestConverters(authenticationConvertersConsumer) .authenticationProvider(authenticationProvider) .authenticationProviders(authenticationProvidersConsumer) .introspectionResponseHandler(authenticationSuccessHandler) .errorResponseHandler(authenticationFailureHandler)) ) .authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated() ); return http.build(); } // @formatter:on @Override AuthorizationServerSettings authorizationServerSettings() { return AuthorizationServerSettings.builder() .multipleIssuersAllowed(true) .tokenIntrospectionEndpoint("/test/introspect") .build(); } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2TokenRevocationTests.java ================================================ /* * 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 org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.Base64; import java.util.List; import java.util.function.Consumer; import com.nimbusds.jose.jwk.JWKSet; import com.nimbusds.jose.jwk.source.JWKSource; import com.nimbusds.jose.proc.SecurityContext; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.http.HttpHeaders; import org.springframework.jdbc.core.JdbcOperations; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.core.Authentication; import org.springframework.security.crypto.password.NoOpPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.oauth2.core.ClientAuthenticationMethod; import org.springframework.security.oauth2.core.OAuth2AccessToken; import org.springframework.security.oauth2.core.OAuth2RefreshToken; import org.springframework.security.oauth2.core.OAuth2Token; import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; import org.springframework.security.oauth2.jose.TestJwks; import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService; import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenRevocationAuthenticationProvider; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenRevocationAuthenticationToken; import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository; import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository.RegisteredClientParametersMapper; import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2TokenRevocationAuthenticationConverter; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.AuthenticationConverter; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.test.web.servlet.MockMvc; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Integration tests for the OAuth 2.0 Token Revocation endpoint. * * @author Joe Grandja */ @ExtendWith(SpringTestContextExtension.class) public class OAuth2TokenRevocationTests { private static final String DEFAULT_TOKEN_REVOCATION_ENDPOINT_URI = "/oauth2/revoke"; private static EmbeddedDatabase db; private static JWKSource jwkSource; private static AuthenticationConverter authenticationConverter; private static Consumer> authenticationConvertersConsumer; private static AuthenticationProvider authenticationProvider; private static Consumer> authenticationProvidersConsumer; private static AuthenticationSuccessHandler authenticationSuccessHandler; private static AuthenticationFailureHandler authenticationFailureHandler; public final SpringTestContext spring = new SpringTestContext(this); @Autowired private MockMvc mvc; @Autowired private JdbcOperations jdbcOperations; @Autowired private RegisteredClientRepository registeredClientRepository; @Autowired private OAuth2AuthorizationService authorizationService; @BeforeAll public static void init() { JWKSet jwkSet = new JWKSet(TestJwks.DEFAULT_RSA_JWK); jwkSource = (jwkSelector, securityContext) -> jwkSelector.select(jwkSet); authenticationConverter = mock(AuthenticationConverter.class); authenticationConvertersConsumer = mock(Consumer.class); authenticationProvider = mock(AuthenticationProvider.class); authenticationProvidersConsumer = mock(Consumer.class); authenticationSuccessHandler = mock(AuthenticationSuccessHandler.class); authenticationFailureHandler = mock(AuthenticationFailureHandler.class); db = new EmbeddedDatabaseBuilder().generateUniqueName(true) .setType(EmbeddedDatabaseType.HSQL) .setScriptEncoding("UTF-8") .addScript("org/springframework/security/oauth2/server/authorization/oauth2-authorization-schema.sql") .addScript( "org/springframework/security/oauth2/server/authorization/client/oauth2-registered-client-schema.sql") .build(); } @AfterEach public void tearDown() { this.jdbcOperations.update("truncate table oauth2_authorization"); this.jdbcOperations.update("truncate table oauth2_registered_client"); } @AfterAll public static void destroy() { db.shutdown(); } @Test public void requestWhenRevokeRefreshTokenThenRevoked() throws Exception { this.spring.register(AuthorizationServerConfiguration.class).autowire(); RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); this.registeredClientRepository.save(registeredClient); OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build(); OAuth2RefreshToken token = authorization.getRefreshToken().getToken(); OAuth2TokenType tokenType = OAuth2TokenType.REFRESH_TOKEN; this.authorizationService.save(authorization); this.mvc .perform(post(DEFAULT_TOKEN_REVOCATION_ENDPOINT_URI) .params(getTokenRevocationRequestParameters(token, tokenType)) .header(HttpHeaders.AUTHORIZATION, "Basic " + encodeBasicAuth(registeredClient.getClientId(), registeredClient.getClientSecret()))) .andExpect(status().isOk()); OAuth2Authorization updatedAuthorization = this.authorizationService.findById(authorization.getId()); OAuth2Authorization.Token refreshToken = updatedAuthorization.getRefreshToken(); assertThat(refreshToken.isInvalidated()).isTrue(); OAuth2Authorization.Token accessToken = updatedAuthorization.getAccessToken(); assertThat(accessToken.isInvalidated()).isTrue(); } @Test public void requestWhenRevokeAccessTokenThenRevoked() throws Exception { this.spring.register(AuthorizationServerConfiguration.class).autowire(); RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); this.registeredClientRepository.save(registeredClient); OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build(); OAuth2AccessToken token = authorization.getAccessToken().getToken(); OAuth2TokenType tokenType = OAuth2TokenType.ACCESS_TOKEN; this.authorizationService.save(authorization); this.mvc .perform(post(DEFAULT_TOKEN_REVOCATION_ENDPOINT_URI) .params(getTokenRevocationRequestParameters(token, tokenType)) .header(HttpHeaders.AUTHORIZATION, "Basic " + encodeBasicAuth(registeredClient.getClientId(), registeredClient.getClientSecret()))) .andExpect(status().isOk()); OAuth2Authorization updatedAuthorization = this.authorizationService.findById(authorization.getId()); OAuth2Authorization.Token accessToken = updatedAuthorization.getAccessToken(); assertThat(accessToken.isInvalidated()).isTrue(); OAuth2Authorization.Token refreshToken = updatedAuthorization.getRefreshToken(); assertThat(refreshToken.isInvalidated()).isFalse(); } @Test public void requestWhenRevokeAccessTokenAndRequestIncludesIssuerPathThenRevoked() throws Exception { this.spring.register(AuthorizationServerConfigurationWithMultipleIssuersAllowed.class).autowire(); RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); this.registeredClientRepository.save(registeredClient); OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build(); OAuth2AccessToken token = authorization.getAccessToken().getToken(); OAuth2TokenType tokenType = OAuth2TokenType.ACCESS_TOKEN; this.authorizationService.save(authorization); String issuer = "https://example.com:8443/issuer1"; // @formatter:off this.mvc.perform(post(issuer.concat(DEFAULT_TOKEN_REVOCATION_ENDPOINT_URI)) .params(getTokenRevocationRequestParameters(token, tokenType)) .header(HttpHeaders.AUTHORIZATION, "Basic " + encodeBasicAuth( registeredClient.getClientId(), registeredClient.getClientSecret()))) .andExpect(status().isOk()); // @formatter:on OAuth2Authorization updatedAuthorization = this.authorizationService.findById(authorization.getId()); OAuth2Authorization.Token accessToken = updatedAuthorization.getAccessToken(); assertThat(accessToken.isInvalidated()).isTrue(); OAuth2Authorization.Token refreshToken = updatedAuthorization.getRefreshToken(); assertThat(refreshToken.isInvalidated()).isFalse(); } @Test public void requestWhenTokenRevocationEndpointCustomizedThenUsed() throws Exception { this.spring.register(AuthorizationServerConfigurationCustomTokenRevocationEndpoint.class).autowire(); RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); this.registeredClientRepository.save(registeredClient); OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build(); OAuth2AccessToken token = authorization.getAccessToken().getToken(); OAuth2TokenType tokenType = OAuth2TokenType.ACCESS_TOKEN; this.authorizationService.save(authorization); Authentication clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); OAuth2TokenRevocationAuthenticationToken tokenRevocationAuthentication = new OAuth2TokenRevocationAuthenticationToken( token, clientPrincipal); given(authenticationConverter.convert(any())).willReturn(tokenRevocationAuthentication); given(authenticationProvider.supports(eq(OAuth2TokenRevocationAuthenticationToken.class))).willReturn(true); given(authenticationProvider.authenticate(any())).willReturn(tokenRevocationAuthentication); this.mvc .perform(post(DEFAULT_TOKEN_REVOCATION_ENDPOINT_URI) .params(getTokenRevocationRequestParameters(token, tokenType)) .header(HttpHeaders.AUTHORIZATION, "Basic " + encodeBasicAuth(registeredClient.getClientId(), registeredClient.getClientSecret()))) .andExpect(status().isOk()); verify(authenticationConverter).convert(any()); @SuppressWarnings("unchecked") ArgumentCaptor> authenticationConvertersCaptor = ArgumentCaptor .forClass(List.class); verify(authenticationConvertersConsumer).accept(authenticationConvertersCaptor.capture()); List authenticationConverters = authenticationConvertersCaptor.getValue(); assertThat(authenticationConverters).allMatch((converter) -> converter == authenticationConverter || converter instanceof OAuth2TokenRevocationAuthenticationConverter); verify(authenticationProvider).authenticate(eq(tokenRevocationAuthentication)); @SuppressWarnings("unchecked") ArgumentCaptor> authenticationProvidersCaptor = ArgumentCaptor .forClass(List.class); verify(authenticationProvidersConsumer).accept(authenticationProvidersCaptor.capture()); List authenticationProviders = authenticationProvidersCaptor.getValue(); assertThat(authenticationProviders).allMatch((provider) -> provider == authenticationProvider || provider instanceof OAuth2TokenRevocationAuthenticationProvider); verify(authenticationSuccessHandler).onAuthenticationSuccess(any(), any(), eq(tokenRevocationAuthentication)); } private static MultiValueMap getTokenRevocationRequestParameters(OAuth2Token token, OAuth2TokenType tokenType) { MultiValueMap parameters = new LinkedMultiValueMap<>(); parameters.set(OAuth2ParameterNames.TOKEN, token.getTokenValue()); parameters.set(OAuth2ParameterNames.TOKEN_TYPE_HINT, tokenType.getValue()); return parameters; } private static String encodeBasicAuth(String clientId, String secret) throws Exception { clientId = URLEncoder.encode(clientId, StandardCharsets.UTF_8.name()); secret = URLEncoder.encode(secret, StandardCharsets.UTF_8.name()); String credentialsString = clientId + ":" + secret; byte[] encodedBytes = Base64.getEncoder().encode(credentialsString.getBytes(StandardCharsets.UTF_8)); return new String(encodedBytes, StandardCharsets.UTF_8); } @EnableWebSecurity @Import(OAuth2AuthorizationServerConfiguration.class) static class AuthorizationServerConfiguration { @Bean OAuth2AuthorizationService authorizationService(JdbcOperations jdbcOperations, RegisteredClientRepository registeredClientRepository) { return new JdbcOAuth2AuthorizationService(jdbcOperations, registeredClientRepository); } @Bean @SuppressWarnings("removal") RegisteredClientRepository registeredClientRepository(JdbcOperations jdbcOperations) { JdbcRegisteredClientRepository jdbcRegisteredClientRepository = new JdbcRegisteredClientRepository( jdbcOperations); RegisteredClientParametersMapper registeredClientParametersMapper = new RegisteredClientParametersMapper(); jdbcRegisteredClientRepository.setRegisteredClientParametersMapper(registeredClientParametersMapper); return jdbcRegisteredClientRepository; } @Bean JdbcOperations jdbcOperations() { return new JdbcTemplate(db); } @Bean JWKSource jwkSource() { return jwkSource; } @Bean PasswordEncoder passwordEncoder() { return NoOpPasswordEncoder.getInstance(); } } @EnableWebSecurity @Configuration(proxyBeanMethods = false) static class AuthorizationServerConfigurationCustomTokenRevocationEndpoint extends AuthorizationServerConfiguration { // @formatter:off @Bean SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { http .oauth2AuthorizationServer((authorizationServer) -> authorizationServer .tokenRevocationEndpoint((tokenRevocationEndpoint) -> tokenRevocationEndpoint .revocationRequestConverter(authenticationConverter) .revocationRequestConverters(authenticationConvertersConsumer) .authenticationProvider(authenticationProvider) .authenticationProviders(authenticationProvidersConsumer) .revocationResponseHandler(authenticationSuccessHandler) .errorResponseHandler(authenticationFailureHandler)) ) .authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated() ); return http.build(); } // @formatter:on } @EnableWebSecurity @Import(OAuth2AuthorizationServerConfiguration.class) static class AuthorizationServerConfigurationWithMultipleIssuersAllowed extends AuthorizationServerConfiguration { @Bean AuthorizationServerSettings authorizationServerSettings() { return AuthorizationServerSettings.builder().multipleIssuersAllowed(true).build(); } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OidcClientRegistrationTests.java ================================================ /* * 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 org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization; import java.time.Duration; import java.time.Instant; import java.time.temporal.ChronoUnit; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.function.Consumer; import javax.crypto.spec.SecretKeySpec; import com.nimbusds.jose.jwk.JWKSet; import com.nimbusds.jose.jwk.source.JWKSource; import com.nimbusds.jose.proc.SecurityContext; import jakarta.servlet.http.HttpServletResponse; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; import org.assertj.core.data.TemporalUnitWithinOffset; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.convert.converter.Converter; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.server.ServletServerHttpResponse; import org.springframework.jdbc.core.JdbcOperations; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; import org.springframework.mock.http.MockHttpOutputMessage; import org.springframework.mock.http.client.MockClientHttpResponse; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.crypto.factory.PasswordEncoderFactories; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.core.ClientAuthenticationMethod; import org.springframework.security.oauth2.core.OAuth2AccessToken; import org.springframework.security.oauth2.core.OAuth2AuthenticationException; import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType; import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter; import org.springframework.security.oauth2.jose.TestJwks; import org.springframework.security.oauth2.jose.jws.MacAlgorithm; import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm; import org.springframework.security.oauth2.jwt.JwsHeader; import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.security.oauth2.jwt.JwtClaimsSet; import org.springframework.security.oauth2.jwt.JwtDecoder; import org.springframework.security.oauth2.jwt.JwtEncoder; import org.springframework.security.oauth2.jwt.JwtEncoderParameters; import org.springframework.security.oauth2.jwt.NimbusJwtEncoder; import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService; import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository; import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository.RegisteredClientParametersMapper; import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; import org.springframework.security.oauth2.server.authorization.oidc.OidcClientRegistration; import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcClientConfigurationAuthenticationProvider; import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcClientRegistrationAuthenticationProvider; import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcClientRegistrationAuthenticationToken; import org.springframework.security.oauth2.server.authorization.oidc.converter.OidcClientRegistrationRegisteredClientConverter; import org.springframework.security.oauth2.server.authorization.oidc.converter.RegisteredClientOidcClientRegistrationConverter; import org.springframework.security.oauth2.server.authorization.oidc.http.converter.OidcClientRegistrationHttpMessageConverter; import org.springframework.security.oauth2.server.authorization.oidc.web.authentication.OidcClientRegistrationAuthenticationConverter; import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; import org.springframework.security.oauth2.server.authorization.settings.ClientSettings; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.AuthenticationConverter; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.util.CollectionUtils; import org.springframework.web.util.UriComponentsBuilder; import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.CoreMatchers.containsString; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.willAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.jwt; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Integration tests for OpenID Connect Dynamic Client Registration 1.0. * * @author Ovidiu Popa * @author Joe Grandja * @author Dmitriy Dubson */ @ExtendWith(SpringTestContextExtension.class) public class OidcClientRegistrationTests { private static final String ISSUER = "https://example.com:8443/issuer1"; private static final String DEFAULT_TOKEN_ENDPOINT_URI = "/oauth2/token"; private static final String DEFAULT_OIDC_CLIENT_REGISTRATION_ENDPOINT_URI = "/connect/register"; private static final HttpMessageConverter accessTokenHttpResponseConverter = new OAuth2AccessTokenResponseHttpMessageConverter(); private static final HttpMessageConverter clientRegistrationHttpMessageConverter = new OidcClientRegistrationHttpMessageConverter(); private static EmbeddedDatabase db; private static JWKSource jwkSource; private static JWKSet clientJwkSet; private static JwtEncoder jwtClientAssertionEncoder; public final SpringTestContext spring = new SpringTestContext(this); @Autowired private MockMvc mvc; @Autowired private JdbcOperations jdbcOperations; @Autowired private RegisteredClientRepository registeredClientRepository; @Autowired private AuthorizationServerSettings authorizationServerSettings; private static AuthenticationConverter authenticationConverter; private static Consumer> authenticationConvertersConsumer; private static AuthenticationProvider authenticationProvider; private static Consumer> authenticationProvidersConsumer; private static AuthenticationSuccessHandler authenticationSuccessHandler; private static AuthenticationFailureHandler authenticationFailureHandler; private MockWebServer server; private String clientJwkSetUrl; @BeforeAll public static void init() { JWKSet jwkSet = new JWKSet(TestJwks.DEFAULT_RSA_JWK); jwkSource = (jwkSelector, securityContext) -> jwkSelector.select(jwkSet); clientJwkSet = new JWKSet(TestJwks.generateRsa().build()); jwtClientAssertionEncoder = new NimbusJwtEncoder( (jwkSelector, securityContext) -> jwkSelector.select(clientJwkSet)); db = new EmbeddedDatabaseBuilder().generateUniqueName(true) .setType(EmbeddedDatabaseType.HSQL) .setScriptEncoding("UTF-8") .addScript("org/springframework/security/oauth2/server/authorization/oauth2-authorization-schema.sql") .addScript( "org/springframework/security/oauth2/server/authorization/client/oauth2-registered-client-schema.sql") .build(); authenticationConverter = mock(AuthenticationConverter.class); authenticationConvertersConsumer = mock(Consumer.class); authenticationProvider = mock(AuthenticationProvider.class); authenticationProvidersConsumer = mock(Consumer.class); authenticationSuccessHandler = mock(AuthenticationSuccessHandler.class); authenticationFailureHandler = mock(AuthenticationFailureHandler.class); } @BeforeEach public void setup() throws Exception { this.server = new MockWebServer(); this.server.start(); this.clientJwkSetUrl = this.server.url("/jwks").toString(); // @formatter:off MockResponse response = new MockResponse() .setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) .setBody(clientJwkSet.toString()); // @formatter:on this.server.enqueue(response); given(authenticationProvider.supports(OidcClientRegistrationAuthenticationToken.class)).willReturn(true); } @AfterEach public void tearDown() throws Exception { this.server.shutdown(); this.jdbcOperations.update("truncate table oauth2_authorization"); this.jdbcOperations.update("truncate table oauth2_registered_client"); reset(authenticationConverter); reset(authenticationConvertersConsumer); reset(authenticationProvider); reset(authenticationProvidersConsumer); reset(authenticationSuccessHandler); reset(authenticationFailureHandler); } @AfterAll public static void destroy() { db.shutdown(); } @Test public void requestWhenClientRegistrationRequestAuthorizedThenClientRegistrationResponse() throws Exception { this.spring.register(AuthorizationServerConfiguration.class).autowire(); // @formatter:off OidcClientRegistration clientRegistration = OidcClientRegistration.builder() .clientName("client-name") .redirectUri("https://client.example.com") .grantType(AuthorizationGrantType.AUTHORIZATION_CODE.getValue()) .grantType(AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) .scope("scope1") .scope("scope2") .build(); // @formatter:on OidcClientRegistration clientRegistrationResponse = registerClient(clientRegistration); assertThat(clientRegistrationResponse.getClientId()).isNotNull(); assertThat(clientRegistrationResponse.getClientIdIssuedAt()).isNotNull(); assertThat(clientRegistrationResponse.getClientSecret()).isNotNull(); assertThat(clientRegistrationResponse.getClientSecretExpiresAt()).isNull(); assertThat(clientRegistrationResponse.getClientName()).isEqualTo(clientRegistration.getClientName()); assertThat(clientRegistrationResponse.getRedirectUris()) .containsExactlyInAnyOrderElementsOf(clientRegistration.getRedirectUris()); assertThat(clientRegistrationResponse.getGrantTypes()) .containsExactlyInAnyOrderElementsOf(clientRegistration.getGrantTypes()); assertThat(clientRegistrationResponse.getResponseTypes()) .containsExactly(OAuth2AuthorizationResponseType.CODE.getValue()); assertThat(clientRegistrationResponse.getScopes()) .containsExactlyInAnyOrderElementsOf(clientRegistration.getScopes()); assertThat(clientRegistrationResponse.getTokenEndpointAuthenticationMethod()) .isEqualTo(ClientAuthenticationMethod.CLIENT_SECRET_BASIC.getValue()); assertThat(clientRegistrationResponse.getIdTokenSignedResponseAlgorithm()) .isEqualTo(SignatureAlgorithm.RS256.getName()); assertThat(clientRegistrationResponse.getRegistrationClientUrl()).isNotNull(); assertThat(clientRegistrationResponse.getRegistrationAccessToken()).isNotEmpty(); } @Test public void requestWhenClientConfigurationRequestAuthorizedThenClientRegistrationResponse() throws Exception { this.spring.register(AuthorizationServerConfiguration.class).autowire(); // @formatter:off OidcClientRegistration clientRegistration = OidcClientRegistration.builder() .clientName("client-name") .redirectUri("https://client.example.com") .grantType(AuthorizationGrantType.AUTHORIZATION_CODE.getValue()) .grantType(AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) .scope("scope1") .scope("scope2") .build(); // @formatter:on OidcClientRegistration clientRegistrationResponse = registerClient(clientRegistration); HttpHeaders httpHeaders = new HttpHeaders(); httpHeaders.setBearerAuth(clientRegistrationResponse.getRegistrationAccessToken()); MvcResult mvcResult = this.mvc .perform(get(clientRegistrationResponse.getRegistrationClientUrl().toURI()).headers(httpHeaders)) .andExpect(status().isOk()) .andExpect(header().string(HttpHeaders.CACHE_CONTROL, containsString("no-store"))) .andExpect(header().string(HttpHeaders.PRAGMA, containsString("no-cache"))) .andReturn(); OidcClientRegistration clientConfigurationResponse = readClientRegistrationResponse(mvcResult.getResponse()); assertThat(clientConfigurationResponse.getClientId()).isEqualTo(clientRegistrationResponse.getClientId()); assertThat(clientConfigurationResponse.getClientIdIssuedAt()) .isEqualTo(clientRegistrationResponse.getClientIdIssuedAt()); assertThat(clientConfigurationResponse.getClientSecret()).isNotNull(); assertThat(clientConfigurationResponse.getClientSecretExpiresAt()) .isEqualTo(clientRegistrationResponse.getClientSecretExpiresAt()); assertThat(clientConfigurationResponse.getClientName()).isEqualTo(clientRegistrationResponse.getClientName()); assertThat(clientConfigurationResponse.getRedirectUris()) .containsExactlyInAnyOrderElementsOf(clientRegistrationResponse.getRedirectUris()); assertThat(clientConfigurationResponse.getGrantTypes()) .containsExactlyInAnyOrderElementsOf(clientRegistrationResponse.getGrantTypes()); assertThat(clientConfigurationResponse.getResponseTypes()) .containsExactlyInAnyOrderElementsOf(clientRegistrationResponse.getResponseTypes()); assertThat(clientConfigurationResponse.getScopes()) .containsExactlyInAnyOrderElementsOf(clientRegistrationResponse.getScopes()); assertThat(clientConfigurationResponse.getTokenEndpointAuthenticationMethod()) .isEqualTo(clientRegistrationResponse.getTokenEndpointAuthenticationMethod()); assertThat(clientConfigurationResponse.getIdTokenSignedResponseAlgorithm()) .isEqualTo(clientRegistrationResponse.getIdTokenSignedResponseAlgorithm()); assertThat(clientConfigurationResponse.getRegistrationClientUrl()) .isEqualTo(clientRegistrationResponse.getRegistrationClientUrl()); assertThat(clientConfigurationResponse.getRegistrationAccessToken()).isNull(); } @Test public void requestWhenClientRegistrationEndpointCustomizedThenUsed() throws Exception { this.spring.register(CustomClientRegistrationConfiguration.class).autowire(); // @formatter:off OidcClientRegistration clientRegistration = OidcClientRegistration.builder() .clientName("client-name") .redirectUri("https://client.example.com") .grantType(AuthorizationGrantType.AUTHORIZATION_CODE.getValue()) .grantType(AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) .scope("scope1") .scope("scope2") .build(); // @formatter:on willAnswer((invocation) -> { HttpServletResponse response = invocation.getArgument(1, HttpServletResponse.class); ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response); httpResponse.setStatusCode(HttpStatus.CREATED); new OidcClientRegistrationHttpMessageConverter().write(clientRegistration, null, httpResponse); return null; }).given(authenticationSuccessHandler).onAuthenticationSuccess(any(), any(), any()); registerClient(clientRegistration); verify(authenticationConverter).convert(any()); ArgumentCaptor> authenticationConvertersCaptor = ArgumentCaptor .forClass(List.class); verify(authenticationConvertersConsumer).accept(authenticationConvertersCaptor.capture()); List authenticationConverters = authenticationConvertersCaptor.getValue(); assertThat(authenticationConverters).hasSize(2) .allMatch((converter) -> converter == authenticationConverter || converter instanceof OidcClientRegistrationAuthenticationConverter); verify(authenticationProvider).authenticate(any()); ArgumentCaptor> authenticationProvidersCaptor = ArgumentCaptor .forClass(List.class); verify(authenticationProvidersConsumer).accept(authenticationProvidersCaptor.capture()); List authenticationProviders = authenticationProvidersCaptor.getValue(); assertThat(authenticationProviders).hasSize(3) .allMatch((provider) -> provider == authenticationProvider || provider instanceof OidcClientRegistrationAuthenticationProvider || provider instanceof OidcClientConfigurationAuthenticationProvider); verify(authenticationSuccessHandler).onAuthenticationSuccess(any(), any(), any()); verifyNoInteractions(authenticationFailureHandler); } @Test public void requestWhenClientRegistrationEndpointCustomizedWithAuthenticationFailureHandlerThenUsed() throws Exception { this.spring.register(CustomClientRegistrationConfiguration.class).autowire(); given(authenticationProvider.authenticate(any())).willThrow(new OAuth2AuthenticationException("error")); this.mvc.perform(get(ISSUER.concat(DEFAULT_OIDC_CLIENT_REGISTRATION_ENDPOINT_URI)) .param(OAuth2ParameterNames.CLIENT_ID, "invalid") .with(jwt())); verify(authenticationFailureHandler).onAuthenticationFailure(any(), any(), any()); verifyNoInteractions(authenticationSuccessHandler); } // gh-1056 @Test public void requestWhenClientRegistersWithSecretThenClientAuthenticationSuccess() throws Exception { this.spring.register(AuthorizationServerConfiguration.class).autowire(); // @formatter:off OidcClientRegistration clientRegistration = OidcClientRegistration.builder() .clientName("client-name") .redirectUri("https://client.example.com") .grantType(AuthorizationGrantType.AUTHORIZATION_CODE.getValue()) .grantType(AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) .scope("scope1") .scope("scope2") .build(); // @formatter:on OidcClientRegistration clientRegistrationResponse = registerClient(clientRegistration); this.mvc .perform(post(ISSUER.concat(DEFAULT_TOKEN_ENDPOINT_URI)) .param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) .param(OAuth2ParameterNames.SCOPE, "scope1") .with(httpBasic(clientRegistrationResponse.getClientId(), clientRegistrationResponse.getClientSecret()))) .andExpect(status().isOk()) .andExpect(jsonPath("$.access_token").isNotEmpty()) .andExpect(jsonPath("$.scope").value("scope1")) .andReturn(); } // gh-1344 @Test public void requestWhenClientRegistersWithClientSecretJwtThenClientAuthenticationSuccess() throws Exception { this.spring.register(AuthorizationServerConfiguration.class).autowire(); // @formatter:off OidcClientRegistration clientRegistration = OidcClientRegistration.builder() .clientName("client-name") .redirectUri("https://client.example.com") .grantType(AuthorizationGrantType.AUTHORIZATION_CODE.getValue()) .grantType(AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) .tokenEndpointAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_JWT.getValue()) .scope("scope1") .scope("scope2") .build(); // @formatter:on OidcClientRegistration clientRegistrationResponse = registerClient(clientRegistration); JwsHeader jwsHeader = JwsHeader.with(MacAlgorithm.HS256).build(); Instant issuedAt = Instant.now(); Instant expiresAt = issuedAt.plus(1, ChronoUnit.HOURS); JwtClaimsSet jwtClaimsSet = JwtClaimsSet.builder() .issuer(clientRegistrationResponse.getClientId()) .subject(clientRegistrationResponse.getClientId()) .audience(Collections.singletonList(asUrl(ISSUER, this.authorizationServerSettings.getTokenEndpoint()))) .issuedAt(issuedAt) .expiresAt(expiresAt) .build(); JWKSet jwkSet = new JWKSet( TestJwks.jwk(new SecretKeySpec(clientRegistrationResponse.getClientSecret().getBytes(), "HS256")) .build()); JwtEncoder jwtClientAssertionEncoder = new NimbusJwtEncoder( (jwkSelector, securityContext) -> jwkSelector.select(jwkSet)); Jwt jwtAssertion = jwtClientAssertionEncoder.encode(JwtEncoderParameters.from(jwsHeader, jwtClaimsSet)); this.mvc .perform(post(ISSUER.concat(DEFAULT_TOKEN_ENDPOINT_URI)) .param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) .param(OAuth2ParameterNames.SCOPE, "scope1") .param(OAuth2ParameterNames.CLIENT_ASSERTION_TYPE, "urn:ietf:params:oauth:client-assertion-type:jwt-bearer") .param(OAuth2ParameterNames.CLIENT_ASSERTION, jwtAssertion.getTokenValue()) .param(OAuth2ParameterNames.CLIENT_ID, clientRegistrationResponse.getClientId())) .andExpect(status().isOk()) .andExpect(jsonPath("$.access_token").isNotEmpty()) .andExpect(jsonPath("$.scope").value("scope1")); } @Test public void requestWhenClientRegistersWithCustomMetadataThenSavedToRegisteredClient() throws Exception { this.spring.register(CustomClientMetadataConfiguration.class).autowire(); // @formatter:off OidcClientRegistration clientRegistration = OidcClientRegistration.builder() .clientName("client-name") .redirectUri("https://client.example.com") .grantType(AuthorizationGrantType.AUTHORIZATION_CODE.getValue()) .grantType(AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) .scope("scope1") .scope("scope2") .claim("custom-metadata-name-1", "value-1") .claim("custom-metadata-name-2", "value-2") .claim("non-registered-custom-metadata", "value-3") .build(); // @formatter:on OidcClientRegistration clientRegistrationResponse = registerClient(clientRegistration); RegisteredClient registeredClient = this.registeredClientRepository .findByClientId(clientRegistrationResponse.getClientId()); assertThat(clientRegistrationResponse.getClaim("custom-metadata-name-1")).isEqualTo("value-1"); assertThat(clientRegistrationResponse.getClaim("custom-metadata-name-2")).isEqualTo("value-2"); assertThat(clientRegistrationResponse.getClaim("non-registered-custom-metadata")).isNull(); assertThat(registeredClient.getClientSettings().getSetting("custom-metadata-name-1")) .isEqualTo("value-1"); assertThat(registeredClient.getClientSettings().getSetting("custom-metadata-name-2")) .isEqualTo("value-2"); assertThat(registeredClient.getClientSettings().getSetting("non-registered-custom-metadata")).isNull(); } // gh-2111 @Test public void requestWhenClientRegistersWithSecretExpirationThenClientRegistrationResponse() throws Exception { this.spring.register(ClientSecretExpirationConfiguration.class).autowire(); // @formatter:off OidcClientRegistration clientRegistration = OidcClientRegistration.builder() .clientName("client-name") .redirectUri("https://client.example.com") .grantType(AuthorizationGrantType.AUTHORIZATION_CODE.getValue()) .grantType(AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) .scope("scope1") .scope("scope2") .build(); // @formatter:on OidcClientRegistration clientRegistrationResponse = registerClient(clientRegistration); Instant expectedSecretExpiryDate = Instant.now().plus(Duration.ofHours(24)); TemporalUnitWithinOffset allowedDelta = new TemporalUnitWithinOffset(1, ChronoUnit.MINUTES); // Returned response contains expiration date assertThat(clientRegistrationResponse.getClientSecretExpiresAt()).isNotNull() .isCloseTo(expectedSecretExpiryDate, allowedDelta); RegisteredClient registeredClient = this.registeredClientRepository .findByClientId(clientRegistrationResponse.getClientId()); // Persisted RegisteredClient contains expiration date assertThat(registeredClient).isNotNull(); assertThat(registeredClient.getClientSecretExpiresAt()).isNotNull() .isCloseTo(expectedSecretExpiryDate, allowedDelta); } private OidcClientRegistration registerClient(OidcClientRegistration clientRegistration) throws Exception { // ***** (1) Obtain the "initial" access token used for registering the client String clientRegistrationScope = "client.create"; // @formatter:off RegisteredClient clientRegistrar = RegisteredClient.withId("client-registrar-1") .clientId("client-registrar-1") .clientAuthenticationMethod(ClientAuthenticationMethod.PRIVATE_KEY_JWT) .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS) .scope(clientRegistrationScope) .clientSettings( ClientSettings.builder() .jwkSetUrl(this.clientJwkSetUrl) .tokenEndpointAuthenticationSigningAlgorithm(SignatureAlgorithm.RS256) .build() ) .build(); // @formatter:on this.registeredClientRepository.save(clientRegistrar); // @formatter:off JwsHeader jwsHeader = JwsHeader.with(SignatureAlgorithm.RS256) .build(); JwtClaimsSet jwtClaimsSet = jwtClientAssertionClaims(clientRegistrar) .build(); // @formatter:on Jwt jwtAssertion = jwtClientAssertionEncoder.encode(JwtEncoderParameters.from(jwsHeader, jwtClaimsSet)); MvcResult mvcResult = this.mvc .perform(post(ISSUER.concat(DEFAULT_TOKEN_ENDPOINT_URI)) .param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) .param(OAuth2ParameterNames.SCOPE, clientRegistrationScope) .param(OAuth2ParameterNames.CLIENT_ASSERTION_TYPE, "urn:ietf:params:oauth:client-assertion-type:jwt-bearer") .param(OAuth2ParameterNames.CLIENT_ASSERTION, jwtAssertion.getTokenValue()) .param(OAuth2ParameterNames.CLIENT_ID, clientRegistrar.getClientId())) .andExpect(status().isOk()) .andExpect(jsonPath("$.access_token").isNotEmpty()) .andExpect(jsonPath("$.scope").value(clientRegistrationScope)) .andReturn(); OAuth2AccessToken accessToken = readAccessTokenResponse(mvcResult.getResponse()).getAccessToken(); // ***** (2) Register the client HttpHeaders httpHeaders = new HttpHeaders(); httpHeaders.setBearerAuth(accessToken.getTokenValue()); // Register the client mvcResult = this.mvc .perform(post(ISSUER.concat(DEFAULT_OIDC_CLIENT_REGISTRATION_ENDPOINT_URI)).headers(httpHeaders) .contentType(MediaType.APPLICATION_JSON) .content(getClientRegistrationRequestContent(clientRegistration))) .andExpect(status().isCreated()) .andExpect(header().string(HttpHeaders.CACHE_CONTROL, containsString("no-store"))) .andExpect(header().string(HttpHeaders.PRAGMA, containsString("no-cache"))) .andReturn(); return readClientRegistrationResponse(mvcResult.getResponse()); } private JwtClaimsSet.Builder jwtClientAssertionClaims(RegisteredClient registeredClient) { Instant issuedAt = Instant.now(); Instant expiresAt = issuedAt.plus(1, ChronoUnit.HOURS); return JwtClaimsSet.builder() .issuer(registeredClient.getClientId()) .subject(registeredClient.getClientId()) .audience(Collections.singletonList(asUrl(ISSUER, this.authorizationServerSettings.getTokenEndpoint()))) .issuedAt(issuedAt) .expiresAt(expiresAt); } private static String asUrl(String uri, String path) { return UriComponentsBuilder.fromUriString(uri).path(path).build().toUriString(); } private static OAuth2AccessTokenResponse readAccessTokenResponse(MockHttpServletResponse response) throws Exception { MockClientHttpResponse httpResponse = new MockClientHttpResponse(response.getContentAsByteArray(), HttpStatus.valueOf(response.getStatus())); return accessTokenHttpResponseConverter.read(OAuth2AccessTokenResponse.class, httpResponse); } private static byte[] getClientRegistrationRequestContent(OidcClientRegistration clientRegistration) throws Exception { MockHttpOutputMessage httpRequest = new MockHttpOutputMessage(); clientRegistrationHttpMessageConverter.write(clientRegistration, null, httpRequest); return httpRequest.getBodyAsBytes(); } private static OidcClientRegistration readClientRegistrationResponse(MockHttpServletResponse response) throws Exception { MockClientHttpResponse httpResponse = new MockClientHttpResponse(response.getContentAsByteArray(), HttpStatus.valueOf(response.getStatus())); return clientRegistrationHttpMessageConverter.read(OidcClientRegistration.class, httpResponse); } @EnableWebSecurity @Configuration(proxyBeanMethods = false) static class CustomClientRegistrationConfiguration extends AuthorizationServerConfiguration { // @formatter:off @Bean @Override public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { http .oauth2AuthorizationServer((authorizationServer) -> authorizationServer .oidc((oidc) -> oidc .clientRegistrationEndpoint((clientRegistration) -> clientRegistration .clientRegistrationRequestConverter(authenticationConverter) .clientRegistrationRequestConverters(authenticationConvertersConsumer) .authenticationProvider(authenticationProvider) .authenticationProviders(authenticationProvidersConsumer) .clientRegistrationResponseHandler(authenticationSuccessHandler) .errorResponseHandler(authenticationFailureHandler) ) ) ) .authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated() ); return http.build(); } // @formatter:on } @EnableWebSecurity @Configuration(proxyBeanMethods = false) static class CustomClientMetadataConfiguration extends AuthorizationServerConfiguration { // @formatter:off @Bean @Override public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { http .oauth2AuthorizationServer((authorizationServer) -> authorizationServer .oidc((oidc) -> oidc .clientRegistrationEndpoint((clientRegistration) -> clientRegistration .authenticationProviders(configureClientRegistrationConverters()) ) ) ) .authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated() ); return http.build(); } // @formatter:on private Consumer> configureClientRegistrationConverters() { // @formatter:off return (authenticationProviders) -> authenticationProviders.forEach((authenticationProvider) -> { List supportedCustomClientMetadata = List.of("custom-metadata-name-1", "custom-metadata-name-2"); if (authenticationProvider instanceof OidcClientRegistrationAuthenticationProvider provider) { provider.setRegisteredClientConverter(new CustomRegisteredClientConverter(supportedCustomClientMetadata)); provider.setClientRegistrationConverter(new CustomClientRegistrationConverter(supportedCustomClientMetadata)); } }); // @formatter:on } } @EnableWebSecurity @Configuration(proxyBeanMethods = false) static class ClientSecretExpirationConfiguration extends AuthorizationServerConfiguration { // @formatter:off @Bean @Override public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { http .oauth2AuthorizationServer((authorizationServer) -> authorizationServer .oidc((oidc) -> oidc .clientRegistrationEndpoint((clientRegistration) -> clientRegistration .authenticationProviders(configureClientRegistrationConverters()) ) ) ) .authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated() ); return http.build(); } // @formatter:on private Consumer> configureClientRegistrationConverters() { // @formatter:off return (authenticationProviders) -> authenticationProviders.forEach((authenticationProvider) -> { if (authenticationProvider instanceof OidcClientRegistrationAuthenticationProvider provider) { provider.setRegisteredClientConverter(new ClientSecretExpirationRegisteredClientConverter()); } }); // @formatter:on } } @EnableWebSecurity @Configuration(proxyBeanMethods = false) static class AuthorizationServerConfiguration { // @formatter:off @Bean SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { http .oauth2AuthorizationServer((authorizationServer) -> authorizationServer .oidc((oidc) -> oidc .clientRegistrationEndpoint(Customizer.withDefaults()) ) ) .authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated() ); return http.build(); } // @formatter:on @Bean @SuppressWarnings("removal") RegisteredClientRepository registeredClientRepository(JdbcOperations jdbcOperations) { RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); RegisteredClientParametersMapper registeredClientParametersMapper = new RegisteredClientParametersMapper(); JdbcRegisteredClientRepository registeredClientRepository = new JdbcRegisteredClientRepository( jdbcOperations); registeredClientRepository.setRegisteredClientParametersMapper(registeredClientParametersMapper); registeredClientRepository.save(registeredClient); return registeredClientRepository; } @Bean OAuth2AuthorizationService authorizationService(JdbcOperations jdbcOperations, RegisteredClientRepository registeredClientRepository) { return new JdbcOAuth2AuthorizationService(jdbcOperations, registeredClientRepository); } @Bean JdbcOperations jdbcOperations() { return new JdbcTemplate(db); } @Bean JWKSource jwkSource() { return jwkSource; } @Bean JwtDecoder jwtDecoder(JWKSource jwkSource) { return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource); } @Bean AuthorizationServerSettings authorizationServerSettings() { return AuthorizationServerSettings.builder().multipleIssuersAllowed(true).build(); } @Bean PasswordEncoder passwordEncoder() { return PasswordEncoderFactories.createDelegatingPasswordEncoder(); } } private static final class CustomRegisteredClientConverter implements Converter { private final OidcClientRegistrationRegisteredClientConverter delegate = new OidcClientRegistrationRegisteredClientConverter(); private final List supportedCustomClientMetadata; private CustomRegisteredClientConverter(List supportedCustomClientMetadata) { this.supportedCustomClientMetadata = supportedCustomClientMetadata; } @Override public RegisteredClient convert(OidcClientRegistration clientRegistration) { RegisteredClient registeredClient = this.delegate.convert(clientRegistration); ClientSettings.Builder clientSettingsBuilder = ClientSettings .withSettings(registeredClient.getClientSettings().getSettings()); if (!CollectionUtils.isEmpty(this.supportedCustomClientMetadata)) { clientRegistration.getClaims().forEach((claim, value) -> { if (this.supportedCustomClientMetadata.contains(claim)) { clientSettingsBuilder.setting(claim, value); } }); } return RegisteredClient.from(registeredClient).clientSettings(clientSettingsBuilder.build()).build(); } } private static final class CustomClientRegistrationConverter implements Converter { private final RegisteredClientOidcClientRegistrationConverter delegate = new RegisteredClientOidcClientRegistrationConverter(); private final List supportedCustomClientMetadata; private CustomClientRegistrationConverter(List supportedCustomClientMetadata) { this.supportedCustomClientMetadata = supportedCustomClientMetadata; } @Override public OidcClientRegistration convert(RegisteredClient registeredClient) { OidcClientRegistration clientRegistration = this.delegate.convert(registeredClient); Map clientMetadata = new HashMap<>(clientRegistration.getClaims()); if (!CollectionUtils.isEmpty(this.supportedCustomClientMetadata)) { Map clientSettings = registeredClient.getClientSettings().getSettings(); this.supportedCustomClientMetadata.forEach((customClaim) -> { if (clientSettings.containsKey(customClaim)) { clientMetadata.put(customClaim, clientSettings.get(customClaim)); } }); } return OidcClientRegistration.withClaims(clientMetadata).build(); } } /** * This customization adds client secret expiration time by setting * {@code RegisteredClient.clientSecretExpiresAt} during * {@code OidcClientRegistration} -> {@code RegisteredClient} conversion */ private static final class ClientSecretExpirationRegisteredClientConverter implements Converter { private static final OidcClientRegistrationRegisteredClientConverter delegate = new OidcClientRegistrationRegisteredClientConverter(); @Override public RegisteredClient convert(OidcClientRegistration clientRegistration) { RegisteredClient registeredClient = delegate.convert(clientRegistration); RegisteredClient.Builder registeredClientBuilder = RegisteredClient.from(registeredClient); Instant clientSecretExpiresAt = Instant.now().plus(Duration.ofHours(24)); registeredClientBuilder.clientSecretExpiresAt(clientSecretExpiresAt); return registeredClientBuilder.build(); } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OidcProviderConfigurationTests.java ================================================ /* * 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 org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization; import java.util.function.Consumer; import com.nimbusds.jose.jwk.JWKSet; import com.nimbusds.jose.jwk.source.ImmutableJWKSet; import com.nimbusds.jose.jwk.source.JWKSource; import com.nimbusds.jose.proc.SecurityContext; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.core.ClientAuthenticationMethod; import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType; import org.springframework.security.oauth2.core.oidc.OidcScopes; import org.springframework.security.oauth2.jose.TestJwks; import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm; import org.springframework.security.oauth2.jwt.JwtDecoder; import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationServerMetadataClaimNames; import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository; import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; import org.springframework.security.oauth2.server.authorization.oidc.OidcProviderConfiguration; import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; import org.springframework.security.web.SecurityFilterChain; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.ResultMatcher; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.hamcrest.CoreMatchers.hasItems; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Integration tests for the OpenID Connect 1.0 Provider Configuration endpoint. * * @author Sahariar Alam Khandoker * @author Joe Grandja * @author Daniel Garnier-Moiroux */ @ExtendWith(SpringTestContextExtension.class) public class OidcProviderConfigurationTests { private static final String DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI = "/.well-known/openid-configuration"; private static final String ISSUER = "https://example.com"; public final SpringTestContext spring = new SpringTestContext(this); @Autowired private AuthorizationServerSettings authorizationServerSettings; @Autowired private MockMvc mvc; @Test public void requestWhenConfigurationRequestAndIssuerSetThenReturnDefaultConfigurationResponse() throws Exception { this.spring.register(AuthorizationServerConfiguration.class).autowire(); this.mvc.perform(get(ISSUER.concat(DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI))) .andExpect(status().is2xxSuccessful()) .andExpectAll(defaultConfigurationMatchers(ISSUER)); } @Test public void requestWhenConfigurationRequestIncludesIssuerPathThenConfigurationResponseHasIssuerPath() throws Exception { this.spring.register(AuthorizationServerConfigurationWithMultipleIssuersAllowed.class).autowire(); String issuer = "https://example.com:8443/issuer1"; this.mvc.perform(get(issuer.concat(DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI))) .andExpect(status().is2xxSuccessful()) .andExpectAll(defaultConfigurationMatchers(issuer)); issuer = "https://example.com:8443/path1/issuer2"; this.mvc.perform(get(issuer.concat(DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI))) .andExpect(status().is2xxSuccessful()) .andExpectAll(defaultConfigurationMatchers(issuer)); issuer = "https://example.com:8443/path1/path2/issuer3"; this.mvc.perform(get(issuer.concat(DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI))) .andExpect(status().is2xxSuccessful()) .andExpectAll(defaultConfigurationMatchers(issuer)); } // gh-632 @Test public void requestWhenConfigurationRequestAndUserAuthenticatedThenReturnConfigurationResponse() throws Exception { this.spring.register(AuthorizationServerConfiguration.class).autowire(); this.mvc.perform(get(ISSUER.concat(DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI)).with(user("user"))) .andExpect(status().is2xxSuccessful()) .andExpectAll(defaultConfigurationMatchers(ISSUER)); } // gh-616 @Test public void requestWhenConfigurationRequestAndConfigurationCustomizerSetThenReturnCustomConfigurationResponse() throws Exception { this.spring.register(AuthorizationServerConfigurationWithProviderConfigurationCustomizer.class).autowire(); this.mvc.perform(get(ISSUER.concat(DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI))) .andExpect(status().is2xxSuccessful()) .andExpect(jsonPath(OAuth2AuthorizationServerMetadataClaimNames.SCOPES_SUPPORTED, hasItems(OidcScopes.OPENID, OidcScopes.PROFILE, OidcScopes.EMAIL))); } @Test public void requestWhenConfigurationRequestAndClientRegistrationEnabledThenConfigurationResponseIncludesRegistrationEndpoint() throws Exception { this.spring.register(AuthorizationServerConfigurationWithClientRegistrationEnabled.class).autowire(); this.mvc.perform(get(ISSUER.concat(DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI))) .andExpect(status().is2xxSuccessful()) .andExpectAll(defaultConfigurationMatchers(ISSUER)) .andExpect(jsonPath("$.registration_endpoint") .value(ISSUER.concat(this.authorizationServerSettings.getOidcClientRegistrationEndpoint()))); } @Test public void requestWhenConfigurationRequestAndDeviceCodeGrantEnabledThenConfigurationResponseIncludesDeviceAuthorizationEndpoint() throws Exception { this.spring.register(AuthorizationServerConfigurationWithDeviceCodeGrantEnabled.class).autowire(); this.mvc.perform(get(ISSUER.concat(DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI))) .andExpect(status().is2xxSuccessful()) .andExpectAll(defaultConfigurationMatchers(ISSUER)) .andExpect(jsonPath("$.device_authorization_endpoint") .value(ISSUER.concat(this.authorizationServerSettings.getDeviceAuthorizationEndpoint()))) .andExpect(jsonPath("$.grant_types_supported[4]").value(AuthorizationGrantType.DEVICE_CODE.getValue())); } @Test public void requestWhenConfigurationRequestAndPushedAuthorizationRequestEnabledThenConfigurationResponseIncludesPushedAuthorizationRequestEndpoint() throws Exception { this.spring.register(AuthorizationServerConfigurationWithPushedAuthorizationRequestEnabled.class).autowire(); this.mvc.perform(get(ISSUER.concat(DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI))) .andExpect(status().is2xxSuccessful()) .andExpectAll(defaultConfigurationMatchers(ISSUER)) .andExpect(jsonPath("$.pushed_authorization_request_endpoint") .value(ISSUER.concat(this.authorizationServerSettings.getPushedAuthorizationRequestEndpoint()))); } private ResultMatcher[] defaultConfigurationMatchers(String issuer) { // @formatter:off return new ResultMatcher[] { jsonPath("issuer").value(issuer), jsonPath("authorization_endpoint").value(issuer.concat(this.authorizationServerSettings.getAuthorizationEndpoint())), jsonPath("token_endpoint").value(issuer.concat(this.authorizationServerSettings.getTokenEndpoint())), jsonPath("$.token_endpoint_auth_methods_supported[0]").value(ClientAuthenticationMethod.CLIENT_SECRET_BASIC.getValue()), jsonPath("$.token_endpoint_auth_methods_supported[1]").value(ClientAuthenticationMethod.CLIENT_SECRET_POST.getValue()), jsonPath("$.token_endpoint_auth_methods_supported[2]").value(ClientAuthenticationMethod.CLIENT_SECRET_JWT.getValue()), jsonPath("$.token_endpoint_auth_methods_supported[3]").value(ClientAuthenticationMethod.PRIVATE_KEY_JWT.getValue()), jsonPath("jwks_uri").value(issuer.concat(this.authorizationServerSettings.getJwkSetEndpoint())), jsonPath("userinfo_endpoint").value(issuer.concat(this.authorizationServerSettings.getOidcUserInfoEndpoint())), jsonPath("end_session_endpoint").value(issuer.concat(this.authorizationServerSettings.getOidcLogoutEndpoint())), jsonPath("response_types_supported").value(OAuth2AuthorizationResponseType.CODE.getValue()), jsonPath("$.grant_types_supported[0]").value(AuthorizationGrantType.AUTHORIZATION_CODE.getValue()), jsonPath("$.grant_types_supported[1]").value(AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()), jsonPath("$.grant_types_supported[2]").value(AuthorizationGrantType.REFRESH_TOKEN.getValue()), jsonPath("$.grant_types_supported[3]").value(AuthorizationGrantType.TOKEN_EXCHANGE.getValue()), jsonPath("revocation_endpoint").value(issuer.concat(this.authorizationServerSettings.getTokenRevocationEndpoint())), jsonPath("$.revocation_endpoint_auth_methods_supported[0]").value(ClientAuthenticationMethod.CLIENT_SECRET_BASIC.getValue()), jsonPath("$.revocation_endpoint_auth_methods_supported[1]").value(ClientAuthenticationMethod.CLIENT_SECRET_POST.getValue()), jsonPath("$.revocation_endpoint_auth_methods_supported[2]").value(ClientAuthenticationMethod.CLIENT_SECRET_JWT.getValue()), jsonPath("$.revocation_endpoint_auth_methods_supported[3]").value(ClientAuthenticationMethod.PRIVATE_KEY_JWT.getValue()), jsonPath("introspection_endpoint").value(issuer.concat(this.authorizationServerSettings.getTokenIntrospectionEndpoint())), jsonPath("$.introspection_endpoint_auth_methods_supported[0]").value(ClientAuthenticationMethod.CLIENT_SECRET_BASIC.getValue()), jsonPath("$.introspection_endpoint_auth_methods_supported[1]").value(ClientAuthenticationMethod.CLIENT_SECRET_POST.getValue()), jsonPath("$.introspection_endpoint_auth_methods_supported[2]").value(ClientAuthenticationMethod.CLIENT_SECRET_JWT.getValue()), jsonPath("$.introspection_endpoint_auth_methods_supported[3]").value(ClientAuthenticationMethod.PRIVATE_KEY_JWT.getValue()), jsonPath("$.code_challenge_methods_supported[0]").value("S256"), jsonPath("subject_types_supported").value("public"), jsonPath("id_token_signing_alg_values_supported").value(SignatureAlgorithm.RS256.getName()), jsonPath("scopes_supported").value(OidcScopes.OPENID) }; // @formatter:on } @Test public void loadContextWhenIssuerNotValidUrlThenThrowException() { assertThatExceptionOfType(BeanCreationException.class).isThrownBy( () -> this.spring.register(AuthorizationServerConfigurationWithInvalidIssuerUrl.class).autowire()); } @Test public void loadContextWhenIssuerNotValidUriThenThrowException() { assertThatExceptionOfType(BeanCreationException.class).isThrownBy( () -> this.spring.register(AuthorizationServerConfigurationWithInvalidIssuerUri.class).autowire()); } @Test public void loadContextWhenIssuerWithQueryThenThrowException() { assertThatExceptionOfType(BeanCreationException.class) .isThrownBy(() -> this.spring.register(AuthorizationServerConfigurationWithIssuerQuery.class).autowire()); } @Test public void loadContextWhenIssuerWithFragmentThenThrowException() { assertThatExceptionOfType(BeanCreationException.class).isThrownBy( () -> this.spring.register(AuthorizationServerConfigurationWithIssuerFragment.class).autowire()); } @Test public void loadContextWhenIssuerWithQueryAndFragmentThenThrowException() { assertThatExceptionOfType(BeanCreationException.class) .isThrownBy(() -> this.spring.register(AuthorizationServerConfigurationWithIssuerQueryAndFragment.class) .autowire()); } @Test public void loadContextWhenIssuerWithEmptyQueryThenThrowException() { assertThatExceptionOfType(BeanCreationException.class).isThrownBy( () -> this.spring.register(AuthorizationServerConfigurationWithIssuerEmptyQuery.class).autowire()); } @Test public void loadContextWhenIssuerWithEmptyFragmentThenThrowException() { assertThatExceptionOfType(BeanCreationException.class).isThrownBy( () -> this.spring.register(AuthorizationServerConfigurationWithIssuerEmptyFragment.class).autowire()); } @EnableWebSecurity @Configuration(proxyBeanMethods = false) static class AuthorizationServerConfiguration { @Bean SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { // @formatter:off http .oauth2AuthorizationServer((authorizationServer) -> authorizationServer .oidc(Customizer.withDefaults()) // Enable OpenID Connect 1.0 ); // @formatter:on return http.build(); } @Bean RegisteredClientRepository registeredClientRepository() { RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); return new InMemoryRegisteredClientRepository(registeredClient); } @Bean JWKSource jwkSource() { return new ImmutableJWKSet<>(new JWKSet(TestJwks.DEFAULT_RSA_JWK)); } @Bean JwtDecoder jwtDecoder(JWKSource jwkSource) { return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource); } @Bean AuthorizationServerSettings authorizationServerSettings() { return AuthorizationServerSettings.builder().issuer(ISSUER).build(); } } @EnableWebSecurity @Configuration(proxyBeanMethods = false) static class AuthorizationServerConfigurationWithMultipleIssuersAllowed extends AuthorizationServerConfiguration { @Bean AuthorizationServerSettings authorizationServerSettings() { return AuthorizationServerSettings.builder().multipleIssuersAllowed(true).build(); } } @EnableWebSecurity @Configuration(proxyBeanMethods = false) static class AuthorizationServerConfigurationWithProviderConfigurationCustomizer extends AuthorizationServerConfiguration { // @formatter:off @Bean SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { http .oauth2AuthorizationServer((authorizationServer) -> authorizationServer .oidc((oidc) -> oidc.providerConfigurationEndpoint((providerConfigurationEndpoint) -> providerConfigurationEndpoint .providerConfigurationCustomizer(providerConfigurationCustomizer()))) ) .authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated() ); return http.build(); } // @formatter:on private Consumer providerConfigurationCustomizer() { return (providerConfiguration) -> providerConfiguration.scope(OidcScopes.PROFILE).scope(OidcScopes.EMAIL); } } @EnableWebSecurity @Configuration(proxyBeanMethods = false) static class AuthorizationServerConfigurationWithClientRegistrationEnabled extends AuthorizationServerConfiguration { // @formatter:off @Bean SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { http .oauth2AuthorizationServer((authorizationServer) -> authorizationServer .oidc((oidc) -> oidc.clientRegistrationEndpoint(Customizer.withDefaults()) ) ); return http.build(); } // @formatter:on } @EnableWebSecurity @Configuration(proxyBeanMethods = false) static class AuthorizationServerConfigurationWithDeviceCodeGrantEnabled extends AuthorizationServerConfiguration { // @formatter:off @Bean SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { http .oauth2AuthorizationServer((authorizationServer) -> authorizationServer .deviceAuthorizationEndpoint(Customizer.withDefaults()) .oidc(Customizer.withDefaults()) ); return http.build(); } // @formatter:on } @EnableWebSecurity @Configuration(proxyBeanMethods = false) static class AuthorizationServerConfigurationWithPushedAuthorizationRequestEnabled extends AuthorizationServerConfiguration { // @formatter:off @Bean SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { http .oauth2AuthorizationServer((authorizationServer) -> authorizationServer .pushedAuthorizationRequestEndpoint(Customizer.withDefaults()) .oidc(Customizer.withDefaults()) ); return http.build(); } // @formatter:on } @EnableWebSecurity @Configuration(proxyBeanMethods = false) static class AuthorizationServerConfigurationWithInvalidIssuerUrl extends AuthorizationServerConfiguration { @Bean AuthorizationServerSettings authorizationServerSettings() { return AuthorizationServerSettings.builder().issuer("urn:example").build(); } } @EnableWebSecurity @Configuration(proxyBeanMethods = false) static class AuthorizationServerConfigurationWithInvalidIssuerUri extends AuthorizationServerConfiguration { @Bean AuthorizationServerSettings authorizationServerSettings() { return AuthorizationServerSettings.builder().issuer("https://not a valid uri").build(); } } @EnableWebSecurity @Configuration(proxyBeanMethods = false) static class AuthorizationServerConfigurationWithIssuerQuery extends AuthorizationServerConfiguration { @Bean AuthorizationServerSettings authorizationServerSettings() { return AuthorizationServerSettings.builder().issuer(ISSUER + "?param=value").build(); } } @EnableWebSecurity @Configuration(proxyBeanMethods = false) static class AuthorizationServerConfigurationWithIssuerFragment extends AuthorizationServerConfiguration { @Bean AuthorizationServerSettings authorizationServerSettings() { return AuthorizationServerSettings.builder().issuer(ISSUER + "#fragment").build(); } } @EnableWebSecurity @Configuration(proxyBeanMethods = false) static class AuthorizationServerConfigurationWithIssuerQueryAndFragment extends AuthorizationServerConfiguration { @Bean AuthorizationServerSettings authorizationServerSettings() { return AuthorizationServerSettings.builder().issuer(ISSUER + "?param=value#fragment").build(); } } @EnableWebSecurity @Configuration(proxyBeanMethods = false) static class AuthorizationServerConfigurationWithIssuerEmptyQuery extends AuthorizationServerConfiguration { @Bean AuthorizationServerSettings authorizationServerSettings() { return AuthorizationServerSettings.builder().issuer(ISSUER + "?").build(); } } @EnableWebSecurity @Configuration(proxyBeanMethods = false) static class AuthorizationServerConfigurationWithIssuerEmptyFragment extends AuthorizationServerConfiguration { @Bean AuthorizationServerSettings authorizationServerSettings() { return AuthorizationServerSettings.builder().issuer(ISSUER + "#").build(); } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OidcTests.java ================================================ /* * 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 org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.security.Principal; import java.util.Base64; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import com.nimbusds.jose.jwk.JWKSet; import com.nimbusds.jose.jwk.source.JWKSource; import com.nimbusds.jose.proc.SecurityContext; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.jdbc.core.JdbcOperations; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; import org.springframework.lang.Nullable; import org.springframework.mock.http.client.MockClientHttpResponse; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.mock.web.MockHttpSession; import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.session.SessionRegistry; import org.springframework.security.core.session.SessionRegistryImpl; import org.springframework.security.crypto.password.NoOpPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.core.OAuth2RefreshToken; import org.springframework.security.oauth2.core.OAuth2Token; import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType; import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; import org.springframework.security.oauth2.core.endpoint.PkceParameterNames; import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter; import org.springframework.security.oauth2.core.oidc.OidcScopes; import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames; import org.springframework.security.oauth2.jose.TestJwks; import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.security.oauth2.jwt.JwtDecoder; import org.springframework.security.oauth2.jwt.NimbusJwtEncoder; import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService; import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationCode; import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations; import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository; import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository.RegisteredClientParametersMapper; import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; import org.springframework.security.oauth2.server.authorization.token.DelegatingOAuth2TokenGenerator; import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext; import org.springframework.security.oauth2.server.authorization.token.JwtGenerator; import org.springframework.security.oauth2.server.authorization.token.OAuth2RefreshTokenGenerator; import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenContext; import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer; import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator; import org.springframework.security.web.SecurityFilterChain; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.util.StringUtils; import org.springframework.web.util.UriComponents; import org.springframework.web.util.UriComponentsBuilder; import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.CoreMatchers.containsString; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Integration tests for OpenID Connect 1.0. * * @author Daniel Garnier-Moiroux * @author Joe Grandja */ @ExtendWith(SpringTestContextExtension.class) public class OidcTests { private static final String DEFAULT_AUTHORIZATION_ENDPOINT_URI = "/oauth2/authorize"; private static final String DEFAULT_TOKEN_ENDPOINT_URI = "/oauth2/token"; private static final String DEFAULT_OIDC_LOGOUT_ENDPOINT_URI = "/connect/logout"; // See RFC 7636: Appendix B. Example for the S256 code_challenge_method // https://tools.ietf.org/html/rfc7636#appendix-B private static final String S256_CODE_VERIFIER = "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"; private static final String S256_CODE_CHALLENGE = "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM"; private static final String AUTHORITIES_CLAIM = "authorities"; private static final OAuth2TokenType AUTHORIZATION_CODE_TOKEN_TYPE = new OAuth2TokenType(OAuth2ParameterNames.CODE); private static EmbeddedDatabase db; private static JWKSource jwkSource; private static HttpMessageConverter accessTokenHttpResponseConverter = new OAuth2AccessTokenResponseHttpMessageConverter(); private static SessionRegistry sessionRegistry; public final SpringTestContext spring = new SpringTestContext(this); @Autowired private MockMvc mvc; @Autowired private JdbcOperations jdbcOperations; @Autowired private RegisteredClientRepository registeredClientRepository; @Autowired private OAuth2AuthorizationService authorizationService; @Autowired private JwtDecoder jwtDecoder; @Autowired(required = false) private OAuth2TokenGenerator tokenGenerator; @BeforeAll public static void init() { JWKSet jwkSet = new JWKSet(TestJwks.DEFAULT_RSA_JWK); jwkSource = (jwkSelector, securityContext) -> jwkSelector.select(jwkSet); db = new EmbeddedDatabaseBuilder().generateUniqueName(true) .setType(EmbeddedDatabaseType.HSQL) .setScriptEncoding("UTF-8") .addScript("org/springframework/security/oauth2/server/authorization/oauth2-authorization-schema.sql") .addScript( "org/springframework/security/oauth2/server/authorization/client/oauth2-registered-client-schema.sql") .build(); sessionRegistry = spy(new SessionRegistryImpl()); } @AfterEach public void tearDown() { if (this.jdbcOperations != null) { this.jdbcOperations.update("truncate table oauth2_authorization"); this.jdbcOperations.update("truncate table oauth2_registered_client"); } } @AfterAll public static void destroy() { db.shutdown(); } @Test public void requestWhenAuthenticationRequestThenTokenResponseIncludesIdToken() throws Exception { this.spring.register(AuthorizationServerConfiguration.class).autowire(); RegisteredClient registeredClient = TestRegisteredClients.registeredClient().scope(OidcScopes.OPENID).build(); this.registeredClientRepository.save(registeredClient); MultiValueMap authorizationRequestParameters = getAuthorizationRequestParameters( registeredClient); MvcResult mvcResult = this.mvc .perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI).queryParams(authorizationRequestParameters) .with(user("user").roles("A", "B"))) .andExpect(status().is3xxRedirection()) .andReturn(); String redirectedUrl = mvcResult.getResponse().getRedirectedUrl(); String expectedRedirectUri = authorizationRequestParameters.getFirst(OAuth2ParameterNames.REDIRECT_URI); assertThat(redirectedUrl).matches(expectedRedirectUri + "\\?code=.{15,}&state=state"); String authorizationCode = extractParameterFromRedirectUri(redirectedUrl, "code"); OAuth2Authorization authorization = this.authorizationService.findByToken(authorizationCode, AUTHORIZATION_CODE_TOKEN_TYPE); mvcResult = this.mvc .perform(post(DEFAULT_TOKEN_ENDPOINT_URI).params(getTokenRequestParameters(registeredClient, authorization)) .header(HttpHeaders.AUTHORIZATION, "Basic " + encodeBasicAuth(registeredClient.getClientId(), registeredClient.getClientSecret()))) .andExpect(status().isOk()) .andExpect(header().string(HttpHeaders.CACHE_CONTROL, containsString("no-store"))) .andExpect(header().string(HttpHeaders.PRAGMA, containsString("no-cache"))) .andExpect(jsonPath("$.access_token").isNotEmpty()) .andExpect(jsonPath("$.token_type").isNotEmpty()) .andExpect(jsonPath("$.expires_in").isNotEmpty()) .andExpect(jsonPath("$.refresh_token").isNotEmpty()) .andExpect(jsonPath("$.scope").isNotEmpty()) .andExpect(jsonPath("$.id_token").isNotEmpty()) .andReturn(); MockHttpServletResponse servletResponse = mvcResult.getResponse(); MockClientHttpResponse httpResponse = new MockClientHttpResponse(servletResponse.getContentAsByteArray(), HttpStatus.valueOf(servletResponse.getStatus())); OAuth2AccessTokenResponse accessTokenResponse = accessTokenHttpResponseConverter .read(OAuth2AccessTokenResponse.class, httpResponse); Jwt idToken = this.jwtDecoder .decode((String) accessTokenResponse.getAdditionalParameters().get(OidcParameterNames.ID_TOKEN)); // Assert user authorities was propagated as claim in ID Token List authoritiesClaim = idToken.getClaim(AUTHORITIES_CLAIM); Authentication principal = authorization.getAttribute(Principal.class.getName()); Set userAuthorities = new HashSet<>(); for (GrantedAuthority authority : principal.getAuthorities()) { userAuthorities.add(authority.getAuthority()); } assertThat(authoritiesClaim).containsExactlyInAnyOrderElementsOf(userAuthorities); // Assert sid claim was added in ID Token assertThat(idToken.getClaim("sid")).isNotNull(); } // gh-1224 @Test public void requestWhenRefreshTokenRequestThenIdTokenContainsSidClaim() throws Exception { this.spring.register(AuthorizationServerConfiguration.class).autowire(); RegisteredClient registeredClient = TestRegisteredClients.registeredClient().scope(OidcScopes.OPENID).build(); this.registeredClientRepository.save(registeredClient); MultiValueMap authorizationRequestParameters = getAuthorizationRequestParameters( registeredClient); MvcResult mvcResult = this.mvc .perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI).queryParams(authorizationRequestParameters) .with(user("user").roles("A", "B"))) .andExpect(status().is3xxRedirection()) .andReturn(); String redirectedUrl = mvcResult.getResponse().getRedirectedUrl(); String expectedRedirectUri = authorizationRequestParameters.getFirst(OAuth2ParameterNames.REDIRECT_URI); assertThat(redirectedUrl).matches(expectedRedirectUri + "\\?code=.{15,}&state=state"); String authorizationCode = extractParameterFromRedirectUri(redirectedUrl, "code"); OAuth2Authorization authorization = this.authorizationService.findByToken(authorizationCode, AUTHORIZATION_CODE_TOKEN_TYPE); mvcResult = this.mvc .perform(post(DEFAULT_TOKEN_ENDPOINT_URI).params(getTokenRequestParameters(registeredClient, authorization)) .header(HttpHeaders.AUTHORIZATION, "Basic " + encodeBasicAuth(registeredClient.getClientId(), registeredClient.getClientSecret()))) .andExpect(status().isOk()) .andReturn(); MockHttpServletResponse servletResponse = mvcResult.getResponse(); MockClientHttpResponse httpResponse = new MockClientHttpResponse(servletResponse.getContentAsByteArray(), HttpStatus.valueOf(servletResponse.getStatus())); OAuth2AccessTokenResponse accessTokenResponse = accessTokenHttpResponseConverter .read(OAuth2AccessTokenResponse.class, httpResponse); Jwt idToken = this.jwtDecoder .decode((String) accessTokenResponse.getAdditionalParameters().get(OidcParameterNames.ID_TOKEN)); String sidClaim = idToken.getClaim("sid"); assertThat(sidClaim).isNotNull(); // Refresh access token mvcResult = this.mvc .perform(post(DEFAULT_TOKEN_ENDPOINT_URI) .param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.REFRESH_TOKEN.getValue()) .param(OAuth2ParameterNames.REFRESH_TOKEN, accessTokenResponse.getRefreshToken().getTokenValue()) .header(HttpHeaders.AUTHORIZATION, "Basic " + encodeBasicAuth(registeredClient.getClientId(), registeredClient.getClientSecret()))) .andExpect(status().isOk()) .andReturn(); servletResponse = mvcResult.getResponse(); httpResponse = new MockClientHttpResponse(servletResponse.getContentAsByteArray(), HttpStatus.valueOf(servletResponse.getStatus())); accessTokenResponse = accessTokenHttpResponseConverter.read(OAuth2AccessTokenResponse.class, httpResponse); idToken = this.jwtDecoder .decode((String) accessTokenResponse.getAdditionalParameters().get(OidcParameterNames.ID_TOKEN)); assertThat(idToken.getClaim("sid")).isEqualTo(sidClaim); } @Test public void requestWhenLogoutRequestThenLogout() throws Exception { this.spring.register(AuthorizationServerConfiguration.class).autowire(); RegisteredClient registeredClient = TestRegisteredClients.registeredClient().scope(OidcScopes.OPENID).build(); this.registeredClientRepository.save(registeredClient); String issuer = "https://example.com:8443/issuer1"; // Login MultiValueMap authorizationRequestParameters = getAuthorizationRequestParameters( registeredClient); MvcResult mvcResult = this.mvc .perform(get(issuer.concat(DEFAULT_AUTHORIZATION_ENDPOINT_URI)).queryParams(authorizationRequestParameters) .with(user("user"))) .andExpect(status().is3xxRedirection()) .andReturn(); MockHttpSession session = (MockHttpSession) mvcResult.getRequest().getSession(); assertThat(session.isNew()).isTrue(); String redirectedUrl = mvcResult.getResponse().getRedirectedUrl(); String authorizationCode = extractParameterFromRedirectUri(redirectedUrl, "code"); OAuth2Authorization authorization = this.authorizationService.findByToken(authorizationCode, AUTHORIZATION_CODE_TOKEN_TYPE); // Get ID Token mvcResult = this.mvc .perform(post(issuer.concat(DEFAULT_TOKEN_ENDPOINT_URI)) .params(getTokenRequestParameters(registeredClient, authorization)) .header(HttpHeaders.AUTHORIZATION, "Basic " + encodeBasicAuth(registeredClient.getClientId(), registeredClient.getClientSecret()))) .andExpect(status().isOk()) .andReturn(); MockHttpServletResponse servletResponse = mvcResult.getResponse(); MockClientHttpResponse httpResponse = new MockClientHttpResponse(servletResponse.getContentAsByteArray(), HttpStatus.valueOf(servletResponse.getStatus())); OAuth2AccessTokenResponse accessTokenResponse = accessTokenHttpResponseConverter .read(OAuth2AccessTokenResponse.class, httpResponse); String idToken = (String) accessTokenResponse.getAdditionalParameters().get(OidcParameterNames.ID_TOKEN); // Logout mvcResult = this.mvc .perform(post(issuer.concat(DEFAULT_OIDC_LOGOUT_ENDPOINT_URI)).param("id_token_hint", idToken) .session(session)) .andExpect(status().is3xxRedirection()) .andReturn(); redirectedUrl = mvcResult.getResponse().getRedirectedUrl(); assertThat(redirectedUrl).matches("/"); assertThat(session.isInvalid()).isTrue(); } @Test public void requestWhenLogoutRequestWithOtherUsersIdTokenThenNotLogout() throws Exception { this.spring.register(AuthorizationServerConfiguration.class).autowire(); // Login user1 RegisteredClient registeredClient1 = TestRegisteredClients.registeredClient().scope(OidcScopes.OPENID).build(); this.registeredClientRepository.save(registeredClient1); MultiValueMap authorizationRequestParameters = getAuthorizationRequestParameters( registeredClient1); MvcResult mvcResult = this.mvc .perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI).queryParams(authorizationRequestParameters) .with(user("user1"))) .andExpect(status().is3xxRedirection()) .andReturn(); MockHttpSession user1Session = (MockHttpSession) mvcResult.getRequest().getSession(); assertThat(user1Session.isNew()).isTrue(); String redirectedUrl = mvcResult.getResponse().getRedirectedUrl(); String authorizationCode = extractParameterFromRedirectUri(redirectedUrl, "code"); OAuth2Authorization user1Authorization = this.authorizationService.findByToken(authorizationCode, AUTHORIZATION_CODE_TOKEN_TYPE); mvcResult = this.mvc .perform(post(DEFAULT_TOKEN_ENDPOINT_URI) .params(getTokenRequestParameters(registeredClient1, user1Authorization)) .header(HttpHeaders.AUTHORIZATION, "Basic " + encodeBasicAuth(registeredClient1.getClientId(), registeredClient1.getClientSecret()))) .andExpect(status().isOk()) .andReturn(); MockHttpServletResponse servletResponse = mvcResult.getResponse(); MockClientHttpResponse httpResponse = new MockClientHttpResponse(servletResponse.getContentAsByteArray(), HttpStatus.valueOf(servletResponse.getStatus())); OAuth2AccessTokenResponse accessTokenResponse = accessTokenHttpResponseConverter .read(OAuth2AccessTokenResponse.class, httpResponse); String user1IdToken = (String) accessTokenResponse.getAdditionalParameters().get(OidcParameterNames.ID_TOKEN); // Login user2 RegisteredClient registeredClient2 = TestRegisteredClients.registeredClient2().scope(OidcScopes.OPENID).build(); this.registeredClientRepository.save(registeredClient2); authorizationRequestParameters = getAuthorizationRequestParameters(registeredClient2); mvcResult = this.mvc .perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI).queryParams(authorizationRequestParameters) .with(user("user2"))) .andExpect(status().is3xxRedirection()) .andReturn(); MockHttpSession user2Session = (MockHttpSession) mvcResult.getRequest().getSession(); assertThat(user2Session.isNew()).isTrue(); redirectedUrl = mvcResult.getResponse().getRedirectedUrl(); authorizationCode = extractParameterFromRedirectUri(redirectedUrl, "code"); OAuth2Authorization user2Authorization = this.authorizationService.findByToken(authorizationCode, AUTHORIZATION_CODE_TOKEN_TYPE); mvcResult = this.mvc .perform(post(DEFAULT_TOKEN_ENDPOINT_URI) .params(getTokenRequestParameters(registeredClient2, user2Authorization)) .header(HttpHeaders.AUTHORIZATION, "Basic " + encodeBasicAuth(registeredClient2.getClientId(), registeredClient2.getClientSecret()))) .andExpect(status().isOk()) .andReturn(); servletResponse = mvcResult.getResponse(); httpResponse = new MockClientHttpResponse(servletResponse.getContentAsByteArray(), HttpStatus.valueOf(servletResponse.getStatus())); accessTokenResponse = accessTokenHttpResponseConverter.read(OAuth2AccessTokenResponse.class, httpResponse); String user2IdToken = (String) accessTokenResponse.getAdditionalParameters().get(OidcParameterNames.ID_TOKEN); // Attempt to log out user1 using user2's ID Token mvcResult = this.mvc .perform(post(DEFAULT_OIDC_LOGOUT_ENDPOINT_URI).param("id_token_hint", user2IdToken).session(user1Session)) .andExpect(status().isBadRequest()) .andExpect(status().reason("[invalid_token] OpenID Connect 1.0 Logout Request Parameter: sub")) .andReturn(); assertThat(user1Session.isInvalid()).isFalse(); } @Test public void requestWhenCustomTokenGeneratorThenUsed() throws Exception { this.spring.register(AuthorizationServerConfigurationWithTokenGenerator.class).autowire(); RegisteredClient registeredClient = TestRegisteredClients.registeredClient().scope(OidcScopes.OPENID).build(); this.registeredClientRepository.save(registeredClient); OAuth2Authorization authorization = createAuthorization(registeredClient); this.authorizationService.save(authorization); this.mvc .perform(post(DEFAULT_TOKEN_ENDPOINT_URI).params(getTokenRequestParameters(registeredClient, authorization)) .header(HttpHeaders.AUTHORIZATION, "Basic " + encodeBasicAuth(registeredClient.getClientId(), registeredClient.getClientSecret()))) .andExpect(status().isOk()); verify(this.tokenGenerator, times(3)).generate(any()); } // gh-1422 @Test public void requestWhenAuthenticationRequestWithOfflineAccessScopeThenTokenResponseIncludesRefreshToken() throws Exception { this.spring.register(AuthorizationServerConfigurationWithCustomRefreshTokenGenerator.class).autowire(); RegisteredClient registeredClient = TestRegisteredClients.registeredClient() .scope(OidcScopes.OPENID) .scope("offline_access") .build(); this.registeredClientRepository.save(registeredClient); MultiValueMap authorizationRequestParameters = getAuthorizationRequestParameters( registeredClient); MvcResult mvcResult = this.mvc .perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI).queryParams(authorizationRequestParameters) .with(user("user"))) .andExpect(status().is3xxRedirection()) .andReturn(); String redirectedUrl = mvcResult.getResponse().getRedirectedUrl(); String expectedRedirectUri = authorizationRequestParameters.getFirst(OAuth2ParameterNames.REDIRECT_URI); assertThat(redirectedUrl).matches(expectedRedirectUri + "\\?code=.{15,}&state=state"); String authorizationCode = extractParameterFromRedirectUri(redirectedUrl, "code"); OAuth2Authorization authorization = this.authorizationService.findByToken(authorizationCode, AUTHORIZATION_CODE_TOKEN_TYPE); this.mvc .perform(post(DEFAULT_TOKEN_ENDPOINT_URI).params(getTokenRequestParameters(registeredClient, authorization)) .header(HttpHeaders.AUTHORIZATION, "Basic " + encodeBasicAuth(registeredClient.getClientId(), registeredClient.getClientSecret()))) .andExpect(status().isOk()) .andExpect(header().string(HttpHeaders.CACHE_CONTROL, containsString("no-store"))) .andExpect(header().string(HttpHeaders.PRAGMA, containsString("no-cache"))) .andExpect(jsonPath("$.access_token").isNotEmpty()) .andExpect(jsonPath("$.token_type").isNotEmpty()) .andExpect(jsonPath("$.expires_in").isNotEmpty()) .andExpect(jsonPath("$.refresh_token").isNotEmpty()) .andExpect(jsonPath("$.scope").isNotEmpty()) .andExpect(jsonPath("$.id_token").isNotEmpty()) .andReturn(); } // gh-1422 @Test public void requestWhenAuthenticationRequestWithoutOfflineAccessScopeThenTokenResponseDoesNotIncludeRefreshToken() throws Exception { this.spring.register(AuthorizationServerConfigurationWithCustomRefreshTokenGenerator.class).autowire(); RegisteredClient registeredClient = TestRegisteredClients.registeredClient().scope(OidcScopes.OPENID).build(); this.registeredClientRepository.save(registeredClient); MultiValueMap authorizationRequestParameters = getAuthorizationRequestParameters( registeredClient); MvcResult mvcResult = this.mvc .perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI).queryParams(authorizationRequestParameters) .with(user("user"))) .andExpect(status().is3xxRedirection()) .andReturn(); String redirectedUrl = mvcResult.getResponse().getRedirectedUrl(); String expectedRedirectUri = authorizationRequestParameters.getFirst(OAuth2ParameterNames.REDIRECT_URI); assertThat(redirectedUrl).matches(expectedRedirectUri + "\\?code=.{15,}&state=state"); String authorizationCode = extractParameterFromRedirectUri(redirectedUrl, "code"); OAuth2Authorization authorization = this.authorizationService.findByToken(authorizationCode, AUTHORIZATION_CODE_TOKEN_TYPE); this.mvc .perform(post(DEFAULT_TOKEN_ENDPOINT_URI).params(getTokenRequestParameters(registeredClient, authorization)) .header(HttpHeaders.AUTHORIZATION, "Basic " + encodeBasicAuth(registeredClient.getClientId(), registeredClient.getClientSecret()))) .andExpect(status().isOk()) .andExpect(header().string(HttpHeaders.CACHE_CONTROL, containsString("no-store"))) .andExpect(header().string(HttpHeaders.PRAGMA, containsString("no-cache"))) .andExpect(jsonPath("$.access_token").isNotEmpty()) .andExpect(jsonPath("$.token_type").isNotEmpty()) .andExpect(jsonPath("$.expires_in").isNotEmpty()) .andExpect(jsonPath("$.refresh_token").doesNotExist()) .andExpect(jsonPath("$.scope").isNotEmpty()) .andExpect(jsonPath("$.id_token").isNotEmpty()) .andReturn(); } private static OAuth2Authorization createAuthorization(RegisteredClient registeredClient) { Map additionalParameters = new HashMap<>(); additionalParameters.put(PkceParameterNames.CODE_CHALLENGE, S256_CODE_CHALLENGE); additionalParameters.put(PkceParameterNames.CODE_CHALLENGE_METHOD, "S256"); return TestOAuth2Authorizations.authorization(registeredClient, additionalParameters).build(); } private static MultiValueMap getAuthorizationRequestParameters(RegisteredClient registeredClient) { MultiValueMap parameters = new LinkedMultiValueMap<>(); parameters.set(OAuth2ParameterNames.RESPONSE_TYPE, OAuth2AuthorizationResponseType.CODE.getValue()); parameters.set(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()); parameters.set(OAuth2ParameterNames.REDIRECT_URI, registeredClient.getRedirectUris().iterator().next()); parameters.set(OAuth2ParameterNames.SCOPE, StringUtils.collectionToDelimitedString(registeredClient.getScopes(), " ")); parameters.set(OAuth2ParameterNames.STATE, "state"); parameters.set(PkceParameterNames.CODE_CHALLENGE, S256_CODE_CHALLENGE); parameters.set(PkceParameterNames.CODE_CHALLENGE_METHOD, "S256"); return parameters; } private static MultiValueMap getTokenRequestParameters(RegisteredClient registeredClient, OAuth2Authorization authorization) { MultiValueMap parameters = new LinkedMultiValueMap<>(); parameters.set(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.AUTHORIZATION_CODE.getValue()); parameters.set(OAuth2ParameterNames.CODE, authorization.getToken(OAuth2AuthorizationCode.class).getToken().getTokenValue()); parameters.set(OAuth2ParameterNames.REDIRECT_URI, registeredClient.getRedirectUris().iterator().next()); parameters.set(PkceParameterNames.CODE_VERIFIER, S256_CODE_VERIFIER); return parameters; } private static String encodeBasicAuth(String clientId, String secret) throws Exception { clientId = URLEncoder.encode(clientId, StandardCharsets.UTF_8.name()); secret = URLEncoder.encode(secret, StandardCharsets.UTF_8.name()); String credentialsString = clientId + ":" + secret; byte[] encodedBytes = Base64.getEncoder().encode(credentialsString.getBytes(StandardCharsets.UTF_8)); return new String(encodedBytes, StandardCharsets.UTF_8); } private String extractParameterFromRedirectUri(String redirectUri, String param) throws UnsupportedEncodingException { String locationHeader = URLDecoder.decode(redirectUri, StandardCharsets.UTF_8.name()); UriComponents uriComponents = UriComponentsBuilder.fromUriString(locationHeader).build(); return uriComponents.getQueryParams().getFirst(param); } @EnableWebSecurity @Configuration(proxyBeanMethods = false) static class AuthorizationServerConfiguration { @Bean SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { // @formatter:off http .oauth2AuthorizationServer((authorizationServer) -> authorizationServer .oidc(Customizer.withDefaults()) // Enable OpenID Connect 1.0 ); // @formatter:on return http.build(); } @Bean OAuth2AuthorizationService authorizationService(JdbcOperations jdbcOperations, RegisteredClientRepository registeredClientRepository) { return new JdbcOAuth2AuthorizationService(jdbcOperations, registeredClientRepository); } @Bean @SuppressWarnings("removal") RegisteredClientRepository registeredClientRepository(JdbcOperations jdbcOperations) { JdbcRegisteredClientRepository jdbcRegisteredClientRepository = new JdbcRegisteredClientRepository( jdbcOperations); RegisteredClientParametersMapper registeredClientParametersMapper = new RegisteredClientParametersMapper(); jdbcRegisteredClientRepository.setRegisteredClientParametersMapper(registeredClientParametersMapper); return jdbcRegisteredClientRepository; } @Bean JdbcOperations jdbcOperations() { return new JdbcTemplate(db); } @Bean JWKSource jwkSource() { return jwkSource; } @Bean JwtDecoder jwtDecoder(JWKSource jwkSource) { return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource); } @Bean OAuth2TokenCustomizer jwtCustomizer() { return (context) -> { if (context.getTokenType().getValue().equals(OidcParameterNames.ID_TOKEN)) { Authentication principal = context.getPrincipal(); Set authorities = new HashSet<>(); for (GrantedAuthority authority : principal.getAuthorities()) { authorities.add(authority.getAuthority()); } context.getClaims().claim(AUTHORITIES_CLAIM, authorities); } }; } @Bean AuthorizationServerSettings authorizationServerSettings() { return AuthorizationServerSettings.builder().multipleIssuersAllowed(true).build(); } @Bean PasswordEncoder passwordEncoder() { return NoOpPasswordEncoder.getInstance(); } @Bean SessionRegistry sessionRegistry() { return sessionRegistry; } } @EnableWebSecurity @Configuration static class AuthorizationServerConfigurationWithTokenGenerator extends AuthorizationServerConfiguration { // @formatter:off @Bean SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { http .oauth2AuthorizationServer((authorizationServer) -> authorizationServer .tokenGenerator(tokenGenerator()) .oidc(Customizer.withDefaults()) ) .authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated() ); return http.build(); } // @formatter:on @Bean OAuth2TokenGenerator tokenGenerator() { JwtGenerator jwtGenerator = new JwtGenerator(new NimbusJwtEncoder(jwkSource())); jwtGenerator.setJwtCustomizer(jwtCustomizer()); OAuth2RefreshTokenGenerator refreshTokenGenerator = new OAuth2RefreshTokenGenerator(); OAuth2TokenGenerator delegatingTokenGenerator = new DelegatingOAuth2TokenGenerator( jwtGenerator, refreshTokenGenerator); return spy(new OAuth2TokenGenerator() { @Override public OAuth2Token generate(OAuth2TokenContext context) { return delegatingTokenGenerator.generate(context); } }); } } @EnableWebSecurity @Configuration static class AuthorizationServerConfigurationWithCustomRefreshTokenGenerator extends AuthorizationServerConfiguration { // @formatter:off @Bean SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { http .oauth2AuthorizationServer((authorizationServer) -> authorizationServer .tokenGenerator(tokenGenerator()) .oidc(Customizer.withDefaults()) ) .authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated() ); return http.build(); } // @formatter:on @Bean OAuth2TokenGenerator tokenGenerator() { JwtGenerator jwtGenerator = new JwtGenerator(new NimbusJwtEncoder(jwkSource())); jwtGenerator.setJwtCustomizer(jwtCustomizer()); OAuth2TokenGenerator refreshTokenGenerator = new CustomRefreshTokenGenerator(); return new DelegatingOAuth2TokenGenerator(jwtGenerator, refreshTokenGenerator); } private static final class CustomRefreshTokenGenerator implements OAuth2TokenGenerator { private final OAuth2RefreshTokenGenerator delegate = new OAuth2RefreshTokenGenerator(); @Nullable @Override public OAuth2RefreshToken generate(OAuth2TokenContext context) { if (context.getAuthorizedScopes().contains(OidcScopes.OPENID) && !context.getAuthorizedScopes().contains("offline_access")) { return null; } return this.delegate.generate(context); } } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OidcUserInfoTests.java ================================================ /* * 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 org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization; import java.time.Instant; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.function.Consumer; import java.util.function.Function; import com.nimbusds.jose.jwk.JWKSet; import com.nimbusds.jose.jwk.source.ImmutableJWKSet; import com.nimbusds.jose.jwk.source.JWKSource; import com.nimbusds.jose.proc.SecurityContext; import jakarta.servlet.http.HttpServletResponse; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.oauth2.core.OAuth2AccessToken; import org.springframework.security.oauth2.core.oidc.OidcIdToken; import org.springframework.security.oauth2.core.oidc.OidcScopes; import org.springframework.security.oauth2.core.oidc.OidcUserInfo; import org.springframework.security.oauth2.jose.TestJwks; import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm; import org.springframework.security.oauth2.jwt.JwsHeader; import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.security.oauth2.jwt.JwtClaimsSet; import org.springframework.security.oauth2.jwt.JwtDecoder; import org.springframework.security.oauth2.jwt.JwtEncoder; import org.springframework.security.oauth2.jwt.JwtEncoderParameters; import org.springframework.security.oauth2.jwt.NimbusJwtEncoder; import org.springframework.security.oauth2.server.authorization.InMemoryOAuth2AuthorizationService; import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations; import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository; import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcUserInfoAuthenticationContext; import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcUserInfoAuthenticationProvider; import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcUserInfoAuthenticationToken; import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.AuthenticationConverter; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.security.web.context.HttpSessionSecurityContextRepository; import org.springframework.security.web.context.SecurityContextRepository; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.ResultMatcher; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.willAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Integration tests for the OpenID Connect 1.0 UserInfo endpoint. * * @author Steve Riesenberg */ @ExtendWith(SpringTestContextExtension.class) public class OidcUserInfoTests { private static final String DEFAULT_OIDC_USER_INFO_ENDPOINT_URI = "/userinfo"; private static SecurityContextRepository securityContextRepository; public final SpringTestContext spring = new SpringTestContext(this); @Autowired private MockMvc mvc; @Autowired private JwtEncoder jwtEncoder; @Autowired private JwtDecoder jwtDecoder; @Autowired private OAuth2AuthorizationService authorizationService; private static AuthenticationConverter authenticationConverter; private static Consumer> authenticationConvertersConsumer; private static AuthenticationProvider authenticationProvider; private static Consumer> authenticationProvidersConsumer; private static AuthenticationSuccessHandler authenticationSuccessHandler; private static AuthenticationFailureHandler authenticationFailureHandler; private static Function userInfoMapper; @BeforeAll public static void init() { securityContextRepository = spy(new HttpSessionSecurityContextRepository()); authenticationConverter = mock(AuthenticationConverter.class); authenticationConvertersConsumer = mock(Consumer.class); authenticationProvider = mock(AuthenticationProvider.class); authenticationProvidersConsumer = mock(Consumer.class); authenticationSuccessHandler = mock(AuthenticationSuccessHandler.class); authenticationFailureHandler = mock(AuthenticationFailureHandler.class); userInfoMapper = mock(Function.class); } @BeforeEach public void setup() { reset(securityContextRepository); reset(authenticationConverter); reset(authenticationConvertersConsumer); reset(authenticationProvider); reset(authenticationProvidersConsumer); reset(authenticationSuccessHandler); reset(authenticationFailureHandler); reset(userInfoMapper); } @Test public void requestWhenUserInfoRequestGetThenUserInfoResponse() throws Exception { this.spring.register(AuthorizationServerConfiguration.class).autowire(); OAuth2Authorization authorization = createAuthorization(); this.authorizationService.save(authorization); OAuth2AccessToken accessToken = authorization.getAccessToken().getToken(); // @formatter:off this.mvc.perform(get(DEFAULT_OIDC_USER_INFO_ENDPOINT_URI) .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken.getTokenValue())) .andExpect(status().is2xxSuccessful()) .andExpectAll(userInfoResponse()); // @formatter:on } @Test public void requestWhenUserInfoRequestPostThenUserInfoResponse() throws Exception { this.spring.register(AuthorizationServerConfiguration.class).autowire(); OAuth2Authorization authorization = createAuthorization(); this.authorizationService.save(authorization); OAuth2AccessToken accessToken = authorization.getAccessToken().getToken(); // @formatter:off this.mvc.perform(post(DEFAULT_OIDC_USER_INFO_ENDPOINT_URI) .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken.getTokenValue())) .andExpect(status().is2xxSuccessful()) .andExpectAll(userInfoResponse()); // @formatter:on } @Test public void requestWhenUserInfoRequestIncludesIssuerPathThenUserInfoResponse() throws Exception { this.spring.register(AuthorizationServerConfiguration.class).autowire(); OAuth2Authorization authorization = createAuthorization(); this.authorizationService.save(authorization); String issuer = "https://example.com:8443/issuer1"; OAuth2AccessToken accessToken = authorization.getAccessToken().getToken(); // @formatter:off this.mvc.perform(get(issuer.concat(DEFAULT_OIDC_USER_INFO_ENDPOINT_URI)) .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken.getTokenValue())) .andExpect(status().is2xxSuccessful()) .andExpectAll(userInfoResponse()); // @formatter:on } @Test public void requestWhenUserInfoEndpointCustomizedThenUsed() throws Exception { this.spring.register(CustomUserInfoConfiguration.class).autowire(); OAuth2Authorization authorization = createAuthorization(); this.authorizationService.save(authorization); given(userInfoMapper.apply(any())).willReturn(createUserInfo()); OAuth2AccessToken accessToken = authorization.getAccessToken().getToken(); // @formatter:off this.mvc.perform(get(DEFAULT_OIDC_USER_INFO_ENDPOINT_URI) .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken.getTokenValue())) .andExpect(status().is2xxSuccessful()); // @formatter:on verify(userInfoMapper).apply(any()); verify(authenticationConverter).convert(any()); verify(authenticationSuccessHandler).onAuthenticationSuccess(any(), any(), any()); verifyNoInteractions(authenticationFailureHandler); ArgumentCaptor> authenticationProvidersCaptor = ArgumentCaptor .forClass(List.class); verify(authenticationProvidersConsumer).accept(authenticationProvidersCaptor.capture()); List authenticationProviders = authenticationProvidersCaptor.getValue(); assertThat(authenticationProviders).hasSize(2) .allMatch((provider) -> provider == authenticationProvider || provider instanceof OidcUserInfoAuthenticationProvider); ArgumentCaptor> authenticationConvertersCaptor = ArgumentCaptor .forClass(List.class); verify(authenticationConvertersConsumer).accept(authenticationConvertersCaptor.capture()); List authenticationConverters = authenticationConvertersCaptor.getValue(); assertThat(authenticationConverters).hasSize(2).allMatch(AuthenticationConverter.class::isInstance); } @Test public void requestWhenUserInfoEndpointCustomizedWithAuthenticationProviderThenUsed() throws Exception { this.spring.register(CustomUserInfoConfiguration.class).autowire(); OAuth2Authorization authorization = createAuthorization(); this.authorizationService.save(authorization); given(authenticationProvider.supports(eq(OidcUserInfoAuthenticationToken.class))).willReturn(true); String tokenValue = authorization.getAccessToken().getToken().getTokenValue(); Jwt jwt = this.jwtDecoder.decode(tokenValue); OidcUserInfoAuthenticationToken oidcUserInfoAuthentication = new OidcUserInfoAuthenticationToken( new JwtAuthenticationToken(jwt), createUserInfo()); given(authenticationProvider.authenticate(any())).willReturn(oidcUserInfoAuthentication); OAuth2AccessToken accessToken = authorization.getAccessToken().getToken(); // @formatter:off this.mvc.perform(get(DEFAULT_OIDC_USER_INFO_ENDPOINT_URI) .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken.getTokenValue())) .andExpect(status().is2xxSuccessful()); // @formatter:on verify(authenticationSuccessHandler).onAuthenticationSuccess(any(), any(), any()); verify(authenticationProvider).authenticate(any()); verifyNoInteractions(authenticationFailureHandler); verifyNoInteractions(userInfoMapper); } @Test public void requestWhenUserInfoEndpointCustomizedWithAuthenticationFailureHandlerThenUsed() throws Exception { this.spring.register(CustomUserInfoConfiguration.class).autowire(); given(userInfoMapper.apply(any())).willReturn(createUserInfo()); willAnswer((invocation) -> { HttpServletResponse response = invocation.getArgument(1); response.setStatus(HttpStatus.UNAUTHORIZED.value()); response.getWriter().write("unauthorized"); return null; }).given(authenticationFailureHandler).onAuthenticationFailure(any(), any(), any()); OAuth2AccessToken accessToken = createAuthorization().getAccessToken().getToken(); // @formatter:off this.mvc.perform(get(DEFAULT_OIDC_USER_INFO_ENDPOINT_URI) .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken.getTokenValue())) .andExpect(status().is4xxClientError()); // @formatter:on verify(authenticationFailureHandler).onAuthenticationFailure(any(), any(), any()); verifyNoInteractions(authenticationSuccessHandler); verifyNoInteractions(userInfoMapper); } // gh-482 @Test public void requestWhenUserInfoRequestThenBearerTokenAuthenticationNotPersisted() throws Exception { this.spring.register(AuthorizationServerConfigurationWithSecurityContextRepository.class).autowire(); OAuth2Authorization authorization = createAuthorization(); this.authorizationService.save(authorization); OAuth2AccessToken accessToken = authorization.getAccessToken().getToken(); // @formatter:off MvcResult mvcResult = this.mvc.perform(get(DEFAULT_OIDC_USER_INFO_ENDPOINT_URI) .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken.getTokenValue())) .andExpect(status().is2xxSuccessful()) .andExpectAll(userInfoResponse()) .andReturn(); // @formatter:on org.springframework.security.core.context.SecurityContext securityContext = securityContextRepository .loadDeferredContext(mvcResult.getRequest()) .get(); assertThat(securityContext.getAuthentication()).isNull(); } private static ResultMatcher[] userInfoResponse() { // @formatter:off return new ResultMatcher[] { jsonPath("sub").value("user1"), jsonPath("name").value("First Last"), jsonPath("given_name").value("First"), jsonPath("family_name").value("Last"), jsonPath("middle_name").value("Middle"), jsonPath("nickname").value("User"), jsonPath("preferred_username").value("user"), jsonPath("profile").value("https://example.com/user1"), jsonPath("picture").value("https://example.com/user1.jpg"), jsonPath("website").value("https://example.com"), jsonPath("email").value("user1@example.com"), jsonPath("email_verified").value("true"), jsonPath("gender").value("female"), jsonPath("birthdate").value("1970-01-01"), jsonPath("zoneinfo").value("Europe/Paris"), jsonPath("locale").value("en-US"), jsonPath("phone_number").value("+1 (604) 555-1234;ext=5678"), jsonPath("phone_number_verified").value("false"), jsonPath("address.formatted").value("Champ de Mars\n5 Av. Anatole France\n75007 Paris\nFrance"), jsonPath("updated_at").value("1970-01-01T00:00:00Z") }; // @formatter:on } private OAuth2Authorization createAuthorization() { JwsHeader headers = JwsHeader.with(SignatureAlgorithm.RS256).build(); // @formatter:off JwtClaimsSet claimSet = JwtClaimsSet.builder() .claims((claims) -> claims.putAll(createUserInfo().getClaims())) .build(); // @formatter:on Jwt jwt = this.jwtEncoder.encode(JwtEncoderParameters.from(headers, claimSet)); Instant now = Instant.now(); Set scopes = new HashSet<>(Arrays.asList(OidcScopes.OPENID, OidcScopes.ADDRESS, OidcScopes.EMAIL, OidcScopes.PHONE, OidcScopes.PROFILE)); OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, jwt.getTokenValue(), now, now.plusSeconds(300), scopes); OidcIdToken idToken = OidcIdToken.withTokenValue("id-token") .claims((claims) -> claims.putAll(createUserInfo().getClaims())) .build(); return TestOAuth2Authorizations.authorization().accessToken(accessToken).token(idToken).build(); } private static OidcUserInfo createUserInfo() { // @formatter:off return OidcUserInfo.builder() .subject("user1") .name("First Last") .givenName("First") .familyName("Last") .middleName("Middle") .nickname("User") .preferredUsername("user") .profile("https://example.com/user1") .picture("https://example.com/user1.jpg") .website("https://example.com") .email("user1@example.com") .emailVerified(true) .gender("female") .birthdate("1970-01-01") .zoneinfo("Europe/Paris") .locale("en-US") .phoneNumber("+1 (604) 555-1234;ext=5678") .phoneNumberVerified(false) .claim("address", Collections.singletonMap("formatted", "Champ de Mars\n5 Av. Anatole France\n75007 Paris\nFrance")) .updatedAt("1970-01-01T00:00:00Z") .build(); // @formatter:on } @EnableWebSecurity @Configuration(proxyBeanMethods = false) static class CustomUserInfoConfiguration extends AuthorizationServerConfiguration { @Bean @Override SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { // @formatter:off http .oauth2AuthorizationServer((authorizationServer) -> authorizationServer .oidc((oidc) -> oidc .userInfoEndpoint((userInfo) -> userInfo .userInfoRequestConverter(authenticationConverter) .userInfoRequestConverters(authenticationConvertersConsumer) .authenticationProvider(authenticationProvider) .authenticationProviders(authenticationProvidersConsumer) .userInfoResponseHandler(authenticationSuccessHandler) .errorResponseHandler(authenticationFailureHandler) .userInfoMapper(userInfoMapper))) ) .authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated() ); // @formatter:on return http.build(); } } @EnableWebSecurity @Configuration(proxyBeanMethods = false) static class AuthorizationServerConfigurationWithSecurityContextRepository extends AuthorizationServerConfiguration { @Bean @Override SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { // @formatter:off http .oauth2AuthorizationServer((authorizationServer) -> authorizationServer .oidc(Customizer.withDefaults()) ) .authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated() ) .securityContext((securityContext) -> securityContext.securityContextRepository(securityContextRepository)); // @formatter:on return http.build(); } } @EnableWebSecurity @Configuration(proxyBeanMethods = false) static class AuthorizationServerConfiguration { @Bean SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { // @formatter:off http .oauth2AuthorizationServer((authorizationServer) -> authorizationServer .oidc(Customizer.withDefaults()) ) .authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated() ); // @formatter:on return http.build(); } @Bean RegisteredClientRepository registeredClientRepository() { RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); return new InMemoryRegisteredClientRepository(registeredClient); } @Bean OAuth2AuthorizationService authorizationService() { return new InMemoryOAuth2AuthorizationService(); } @Bean JWKSource jwkSource() { return new ImmutableJWKSet<>(new JWKSet(TestJwks.DEFAULT_RSA_JWK)); } @Bean JwtDecoder jwtDecoder(JWKSource jwkSource) { return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource); } @Bean JwtEncoder jwtEncoder(JWKSource jwkSource) { return new NimbusJwtEncoder(jwkSource); } @Bean AuthorizationServerSettings authorizationServerSettings() { return AuthorizationServerSettings.builder().multipleIssuersAllowed(true).build(); } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/DPoPAuthenticationConfigurerTests.java ================================================ /* * 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 org.springframework.security.config.annotation.web.configurers.oauth2.server.resource; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.interfaces.ECPrivateKey; import java.security.interfaces.ECPublicKey; import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; import java.time.Instant; import java.time.temporal.ChronoUnit; import java.util.Base64; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.UUID; import com.nimbusds.jose.jwk.ECKey; import com.nimbusds.jose.jwk.JWK; import com.nimbusds.jose.jwk.JWKSet; import com.nimbusds.jose.jwk.RSAKey; import com.nimbusds.jose.jwk.source.JWKSource; import com.nimbusds.jose.proc.SecurityContext; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; import org.springframework.security.oauth2.jose.TestJwks; import org.springframework.security.oauth2.jose.TestKeys; import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm; import org.springframework.security.oauth2.jwt.JwsHeader; import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.security.oauth2.jwt.JwtClaimsSet; import org.springframework.security.oauth2.jwt.JwtEncoderParameters; import org.springframework.security.oauth2.jwt.NimbusJwtDecoder; import org.springframework.security.oauth2.jwt.NimbusJwtEncoder; import org.springframework.security.web.SecurityFilterChain; import org.springframework.test.web.servlet.MockMvc; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Tests for {@link DPoPAuthenticationConfigurer}. * * @author Joe Grandja */ @ExtendWith(SpringTestContextExtension.class) public class DPoPAuthenticationConfigurerTests { private static final RSAPublicKey PROVIDER_RSA_PUBLIC_KEY = TestKeys.DEFAULT_PUBLIC_KEY; private static final RSAPrivateKey PROVIDER_RSA_PRIVATE_KEY = TestKeys.DEFAULT_PRIVATE_KEY; private static final ECPublicKey CLIENT_EC_PUBLIC_KEY = (ECPublicKey) TestKeys.DEFAULT_EC_KEY_PAIR.getPublic(); private static final ECPrivateKey CLIENT_EC_PRIVATE_KEY = (ECPrivateKey) TestKeys.DEFAULT_EC_KEY_PAIR.getPrivate(); private static final ECKey CLIENT_EC_KEY = TestJwks.jwk(CLIENT_EC_PUBLIC_KEY, CLIENT_EC_PRIVATE_KEY).build(); private static NimbusJwtEncoder providerJwtEncoder; private static NimbusJwtEncoder clientJwtEncoder; public final SpringTestContext spring = new SpringTestContext(this); @Autowired private MockMvc mvc; @BeforeAll public static void init() { RSAKey providerRsaKey = TestJwks.jwk(PROVIDER_RSA_PUBLIC_KEY, PROVIDER_RSA_PRIVATE_KEY).build(); JWKSource providerJwkSource = (jwkSelector, securityContext) -> jwkSelector .select(new JWKSet(providerRsaKey)); providerJwtEncoder = new NimbusJwtEncoder(providerJwkSource); JWKSource clientJwkSource = (jwkSelector, securityContext) -> jwkSelector .select(new JWKSet(CLIENT_EC_KEY)); clientJwtEncoder = new NimbusJwtEncoder(clientJwkSource); } @Test public void requestWhenDPoPAndBearerAuthenticationThenUnauthorized() throws Exception { this.spring.register(SecurityConfig.class, ResourceEndpoints.class).autowire(); Set scope = Collections.singleton("resource1.read"); String accessToken = generateAccessToken(scope, CLIENT_EC_KEY); String dPoPProof = generateDPoPProof(HttpMethod.GET.name(), "http://localhost/resource1", accessToken); // @formatter:off this.mvc.perform(get("/resource1") .header(HttpHeaders.AUTHORIZATION, "DPoP " + accessToken) .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken) .header("DPoP", dPoPProof)) .andExpect(status().isUnauthorized()) .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, "DPoP error=\"invalid_request\", error_description=\"Found multiple Authorization headers.\", algs=\"RS256 RS384 RS512 PS256 PS384 PS512 ES256 ES384 ES512\"")); // @formatter:on } @Test public void requestWhenDPoPAccessTokenMalformedThenUnauthorized() throws Exception { this.spring.register(SecurityConfig.class, ResourceEndpoints.class).autowire(); Set scope = Collections.singleton("resource1.read"); String accessToken = generateAccessToken(scope, CLIENT_EC_KEY); String dPoPProof = generateDPoPProof(HttpMethod.GET.name(), "http://localhost/resource1", accessToken); // @formatter:off this.mvc.perform(get("/resource1") .header(HttpHeaders.AUTHORIZATION, "DPoP " + accessToken + " m a l f o r m e d ") .header("DPoP", dPoPProof)) .andExpect(status().isUnauthorized()) .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, "DPoP error=\"invalid_token\", error_description=\"DPoP access token is malformed.\", algs=\"RS256 RS384 RS512 PS256 PS384 PS512 ES256 ES384 ES512\"")); // @formatter:on } @Test public void requestWhenMultipleDPoPProofsThenUnauthorized() throws Exception { this.spring.register(SecurityConfig.class, ResourceEndpoints.class).autowire(); Set scope = Collections.singleton("resource1.read"); String accessToken = generateAccessToken(scope, CLIENT_EC_KEY); String dPoPProof = generateDPoPProof(HttpMethod.GET.name(), "http://localhost/resource1", accessToken); // @formatter:off this.mvc.perform(get("/resource1") .header(HttpHeaders.AUTHORIZATION, "DPoP " + accessToken) .header("DPoP", dPoPProof) .header("DPoP", dPoPProof)) .andExpect(status().isUnauthorized()) .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, "DPoP error=\"invalid_request\", error_description=\"DPoP proof is missing or invalid.\", algs=\"RS256 RS384 RS512 PS256 PS384 PS512 ES256 ES384 ES512\"")); // @formatter:on } @Test public void requestWhenDPoPAuthenticationValidThenAccessed() throws Exception { this.spring.register(SecurityConfig.class, ResourceEndpoints.class).autowire(); Set scope = Collections.singleton("resource1.read"); String accessToken = generateAccessToken(scope, CLIENT_EC_KEY); String dPoPProof = generateDPoPProof(HttpMethod.GET.name(), "http://localhost/resource1", accessToken); // @formatter:off this.mvc.perform(get("/resource1") .header(HttpHeaders.AUTHORIZATION, "DPoP " + accessToken) .header("DPoP", dPoPProof)) .andExpect(status().isOk()) .andExpect(content().string("resource1")); // @formatter:on } private static String generateAccessToken(Set scope, JWK jwk) { Map jktClaim = null; if (jwk != null) { try { String sha256Thumbprint = jwk.toPublicJWK().computeThumbprint().toString(); jktClaim = new HashMap<>(); jktClaim.put("jkt", sha256Thumbprint); } catch (Exception ignored) { } } JwsHeader jwsHeader = JwsHeader.with(SignatureAlgorithm.RS256).build(); Instant issuedAt = Instant.now(); Instant expiresAt = issuedAt.plus(30, ChronoUnit.MINUTES); // @formatter:off JwtClaimsSet.Builder claimsBuilder = JwtClaimsSet.builder() .issuer("https://provider.com") .subject("subject") .issuedAt(issuedAt) .expiresAt(expiresAt) .id(UUID.randomUUID().toString()) .claim(OAuth2ParameterNames.SCOPE, scope); if (jktClaim != null) { claimsBuilder.claim("cnf", jktClaim); // Bind client public key } // @formatter:on Jwt jwt = providerJwtEncoder.encode(JwtEncoderParameters.from(jwsHeader, claimsBuilder.build())); return jwt.getTokenValue(); } private static String generateDPoPProof(String method, String resourceUri, String accessToken) throws Exception { // @formatter:off Map publicJwk = CLIENT_EC_KEY.toPublicJWK().toJSONObject(); JwsHeader jwsHeader = JwsHeader.with(SignatureAlgorithm.ES256) .type("dpop+jwt") .jwk(publicJwk) .build(); JwtClaimsSet claims = JwtClaimsSet.builder() .issuedAt(Instant.now()) .claim("htm", method) .claim("htu", resourceUri) .claim("ath", computeSHA256(accessToken)) .id(UUID.randomUUID().toString()) .build(); // @formatter:on Jwt jwt = clientJwtEncoder.encode(JwtEncoderParameters.from(jwsHeader, claims)); return jwt.getTokenValue(); } private static String computeSHA256(String value) throws Exception { MessageDigest md = MessageDigest.getInstance("SHA-256"); byte[] digest = md.digest(value.getBytes(StandardCharsets.UTF_8)); return Base64.getUrlEncoder().withoutPadding().encodeToString(digest); } @Configuration @EnableWebSecurity @EnableWebMvc static class SecurityConfig { @Bean SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((authorize) -> authorize .requestMatchers("/resource1").hasAnyAuthority("SCOPE_resource1.read", "SCOPE_resource1.write") .requestMatchers("/resource2").hasAnyAuthority("SCOPE_resource2.read", "SCOPE_resource2.write") .anyRequest().authenticated() ) .oauth2ResourceServer((oauth2) -> oauth2 .jwt(Customizer.withDefaults())); // @formatter:on return http.build(); } @Bean NimbusJwtDecoder jwtDecoder() { return NimbusJwtDecoder.withPublicKey(PROVIDER_RSA_PUBLIC_KEY).build(); } } @RestController static class ResourceEndpoints { @RequestMapping(value = "/resource1", method = { RequestMethod.GET, RequestMethod.POST }) String resource1() { return "resource1"; } @RequestMapping(value = "/resource2", method = { RequestMethod.GET, RequestMethod.POST }) String resource2() { return "resource2"; } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ProtectedResourceMetadataTests.java ================================================ /* * 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 org.springframework.security.config.annotation.web.configurers.oauth2.server.resource; import java.util.function.Consumer; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.oauth2.jose.TestKeys; import org.springframework.security.oauth2.jwt.JwtDecoder; import org.springframework.security.oauth2.jwt.NimbusJwtDecoder; import org.springframework.security.oauth2.server.resource.OAuth2ProtectedResourceMetadata; import org.springframework.security.oauth2.server.resource.OAuth2ProtectedResourceMetadataClaimNames; import org.springframework.security.web.SecurityFilterChain; import org.springframework.test.web.servlet.MockMvc; import static org.hamcrest.Matchers.hasItem; import static org.hamcrest.Matchers.hasSize; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Integration tests for OAuth 2.0 Protected Resource Metadata Requests. * * @author Joe Grandja */ @ExtendWith(SpringTestContextExtension.class) public class OAuth2ProtectedResourceMetadataTests { private static final String DEFAULT_OAUTH2_PROTECTED_RESOURCE_METADATA_ENDPOINT_URI = "/.well-known/oauth-protected-resource"; private static final String RESOURCE = "https://resource.com:8443"; private static final String ISSUER_1 = "https://provider1.com"; private static final String ISSUER_2 = "https://provider2.com"; public final SpringTestContext spring = new SpringTestContext(this); @Autowired private MockMvc mvc; @Test public void requestWhenProtectedResourceMetadataRequestThenReturnMetadataResponse() throws Exception { this.spring.register(ResourceServerConfiguration.class).autowire(); this.mvc.perform(get(RESOURCE.concat(DEFAULT_OAUTH2_PROTECTED_RESOURCE_METADATA_ENDPOINT_URI))) .andExpect(status().is2xxSuccessful()) .andExpect(jsonPath(OAuth2ProtectedResourceMetadataClaimNames.RESOURCE).value(RESOURCE)) .andExpect(jsonPath(OAuth2ProtectedResourceMetadataClaimNames.BEARER_METHODS_SUPPORTED).isArray()) .andExpect(jsonPath(OAuth2ProtectedResourceMetadataClaimNames.BEARER_METHODS_SUPPORTED).value(hasSize(1))) .andExpect(jsonPath(OAuth2ProtectedResourceMetadataClaimNames.BEARER_METHODS_SUPPORTED) .value(hasItem("header"))) .andExpect(jsonPath(OAuth2ProtectedResourceMetadataClaimNames.TLS_CLIENT_CERTIFICATE_BOUND_ACCESS_TOKENS) .value(true)) .andReturn(); } @Test public void requestWhenProtectedResourceMetadataRequestIncludesResourcePathThenMetadataResponseHasResourcePath() throws Exception { this.spring.register(ResourceServerConfiguration.class).autowire(); String host = RESOURCE; String resourcePath = "/resource1"; String resource = host.concat(resourcePath); this.mvc.perform(get(host.concat(DEFAULT_OAUTH2_PROTECTED_RESOURCE_METADATA_ENDPOINT_URI).concat(resourcePath))) .andExpect(status().is2xxSuccessful()) .andExpect(jsonPath(OAuth2ProtectedResourceMetadataClaimNames.RESOURCE).value(resource)) .andReturn(); resourcePath = "/path1/resource2"; resource = host.concat(resourcePath); this.mvc.perform(get(host.concat(DEFAULT_OAUTH2_PROTECTED_RESOURCE_METADATA_ENDPOINT_URI).concat(resourcePath))) .andExpect(status().is2xxSuccessful()) .andExpect(jsonPath(OAuth2ProtectedResourceMetadataClaimNames.RESOURCE).value(resource)) .andReturn(); resourcePath = "/path1/path2/resource3"; resource = host.concat(resourcePath); this.mvc.perform(get(host.concat(DEFAULT_OAUTH2_PROTECTED_RESOURCE_METADATA_ENDPOINT_URI).concat(resourcePath))) .andExpect(status().is2xxSuccessful()) .andExpect(jsonPath(OAuth2ProtectedResourceMetadataClaimNames.RESOURCE).value(resource)) .andReturn(); } @Test public void requestWhenProtectedResourceMetadataRequestAndMetadataCustomizerSetThenReturnCustomMetadataResponse() throws Exception { this.spring.register(ResourceServerConfigurationWithMetadataCustomizer.class).autowire(); this.mvc.perform(get(RESOURCE.concat(DEFAULT_OAUTH2_PROTECTED_RESOURCE_METADATA_ENDPOINT_URI))) .andExpect(status().is2xxSuccessful()) .andExpect(jsonPath(OAuth2ProtectedResourceMetadataClaimNames.RESOURCE).value(RESOURCE)) .andExpect(jsonPath(OAuth2ProtectedResourceMetadataClaimNames.AUTHORIZATION_SERVERS).isArray()) .andExpect(jsonPath(OAuth2ProtectedResourceMetadataClaimNames.AUTHORIZATION_SERVERS).value(hasSize(2))) .andExpect( jsonPath(OAuth2ProtectedResourceMetadataClaimNames.AUTHORIZATION_SERVERS).value(hasItem(ISSUER_1))) .andExpect( jsonPath(OAuth2ProtectedResourceMetadataClaimNames.AUTHORIZATION_SERVERS).value(hasItem(ISSUER_2))) .andExpect(jsonPath(OAuth2ProtectedResourceMetadataClaimNames.SCOPES_SUPPORTED).isArray()) .andExpect(jsonPath(OAuth2ProtectedResourceMetadataClaimNames.SCOPES_SUPPORTED).value(hasSize(2))) .andExpect(jsonPath(OAuth2ProtectedResourceMetadataClaimNames.SCOPES_SUPPORTED).value(hasItem("scope1"))) .andExpect(jsonPath(OAuth2ProtectedResourceMetadataClaimNames.SCOPES_SUPPORTED).value(hasItem("scope2"))) .andExpect(jsonPath(OAuth2ProtectedResourceMetadataClaimNames.BEARER_METHODS_SUPPORTED).isArray()) .andExpect(jsonPath(OAuth2ProtectedResourceMetadataClaimNames.BEARER_METHODS_SUPPORTED).value(hasSize(1))) .andExpect(jsonPath(OAuth2ProtectedResourceMetadataClaimNames.BEARER_METHODS_SUPPORTED) .value(hasItem("header"))) .andExpect(jsonPath(OAuth2ProtectedResourceMetadataClaimNames.RESOURCE_NAME).value("resourceName")) .andExpect(jsonPath(OAuth2ProtectedResourceMetadataClaimNames.TLS_CLIENT_CERTIFICATE_BOUND_ACCESS_TOKENS) .value(true)) .andReturn(); } @EnableWebSecurity @Configuration(proxyBeanMethods = false) static class ResourceServerConfiguration { @Bean SecurityFilterChain securityFilterChain(HttpSecurity http) { // @formatter:off http .authorizeHttpRequests((authorize) -> authorize .anyRequest().authenticated() ) .oauth2ResourceServer((oauth2) -> oauth2 .jwt(Customizer.withDefaults()) ); // @formatter:on return http.build(); } @Bean JwtDecoder jwtDecoder() { return NimbusJwtDecoder.withPublicKey(TestKeys.DEFAULT_PUBLIC_KEY).build(); } } @EnableWebSecurity @Configuration(proxyBeanMethods = false) static class ResourceServerConfigurationWithMetadataCustomizer extends ResourceServerConfiguration { @Bean SecurityFilterChain securityFilterChain(HttpSecurity http) { // @formatter:off http .authorizeHttpRequests((authorize) -> authorize .anyRequest().authenticated() ) .oauth2ResourceServer((oauth2) -> oauth2 .jwt(Customizer.withDefaults()) .protectedResourceMetadata((metadata) -> metadata.protectedResourceMetadataCustomizer(protectedResourceMetadataCustomizer()) ) ); // @formatter:on return http.build(); } private Consumer protectedResourceMetadataCustomizer() { return (protectedResourceMetadata) -> protectedResourceMetadata.authorizationServer(ISSUER_1) .authorizationServer(ISSUER_2) .scope("scope1") .scope("scope2") .resourceName("resourceName"); } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurerTests.java ================================================ /* * 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 org.springframework.security.config.annotation.web.configurers.oauth2.server.resource; import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; import java.security.KeyFactory; import java.security.interfaces.RSAPublicKey; import java.security.spec.X509EncodedKeySpec; import java.time.Clock; import java.time.Duration; import java.time.Instant; import java.time.ZoneId; import java.util.Base64; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.stream.Collectors; import com.nimbusds.jose.JWSAlgorithm; import com.nimbusds.jose.JWSHeader; import com.nimbusds.jose.JWSObject; import com.nimbusds.jose.Payload; import com.nimbusds.jose.crypto.RSASSASigner; import com.nimbusds.jose.jwk.JWKSet; import com.nimbusds.jose.jwk.RSAKey; import com.nimbusds.jose.util.JSONObjectUtils; import jakarta.annotation.PreDestroy; import jakarta.servlet.http.HttpServletRequest; import net.minidev.json.JSONObject; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; import org.hamcrest.core.AllOf; import org.hamcrest.core.StringContains; import org.hamcrest.core.StringEndsWith; import org.hamcrest.core.StringStartsWith; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.verification.VerificationMode; import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.NoUniqueBeanDefinitionException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.context.ApplicationContext; import org.springframework.context.EnvironmentAware; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.support.GenericApplicationContext; import org.springframework.core.ParameterizedTypeReference; import org.springframework.core.convert.converter.Converter; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.Environment; import org.springframework.core.env.PropertySource; import org.springframework.core.io.ClassPathResource; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.RequestEntity; import org.springframework.http.ResponseEntity; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.authentication.AuthenticationDetailsSource; import org.springframework.security.authentication.AuthenticationEventPublisher; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationManagerResolver; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.AuthenticationServiceException; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.config.Customizer; import org.springframework.security.config.ObjectPostProcessor; import org.springframework.security.config.annotation.SecurityContextChangedListenerConfig; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.context.SecurityContextChangedListener; import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository; import org.springframework.security.oauth2.client.registration.TestClientRegistrations; import org.springframework.security.oauth2.core.DefaultOAuth2AuthenticatedPrincipal; import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal; import org.springframework.security.oauth2.core.OAuth2AuthenticationException; import org.springframework.security.oauth2.core.OAuth2Error; import org.springframework.security.oauth2.core.OAuth2TokenValidator; import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult; import org.springframework.security.oauth2.core.TestOAuth2AccessTokens; import org.springframework.security.oauth2.jose.TestKeys; import org.springframework.security.oauth2.jwt.BadJwtException; import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.security.oauth2.jwt.JwtClaimNames; import org.springframework.security.oauth2.jwt.JwtDecoder; import org.springframework.security.oauth2.jwt.JwtTimestampValidator; import org.springframework.security.oauth2.jwt.NimbusJwtDecoder; import org.springframework.security.oauth2.jwt.TestJwts; import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthentication; import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter; import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; import org.springframework.security.oauth2.server.resource.authentication.JwtIssuerAuthenticationManagerResolver; import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenAuthenticationConverter; import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector; import org.springframework.security.oauth2.server.resource.introspection.SpringOpaqueTokenIntrospector; import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationEntryPoint; import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver; import org.springframework.security.oauth2.server.resource.web.DefaultBearerTokenResolver; import org.springframework.security.oauth2.server.resource.web.access.BearerTokenAccessDeniedHandler; import org.springframework.security.oauth2.server.resource.web.authentication.BearerTokenAuthenticationConverter; import org.springframework.security.oauth2.server.resource.web.authentication.BearerTokenAuthenticationFilter; import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.access.AccessDeniedHandler; import org.springframework.security.web.access.AccessDeniedHandlerImpl; import org.springframework.security.web.authentication.AuthenticationConverter; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.ResultMatcher; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; import org.springframework.test.web.servlet.request.RequestPostProcessor; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestOperations; import org.springframework.web.context.support.GenericWebApplicationContext; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.startsWith; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.springframework.security.config.Customizer.withDefaults; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Tests for {@link OAuth2ResourceServerConfigurer} * * @author Josh Cummings * @author Evgeniy Cheban */ @ExtendWith(SpringTestContextExtension.class) public class OAuth2ResourceServerConfigurerTests { private static final String JWT_TOKEN = "token"; private static final String JWT_SUBJECT = "mock-test-subject"; private static final Map JWT_CLAIMS = Collections.singletonMap(JwtClaimNames.SUB, JWT_SUBJECT); private static final Jwt JWT = TestJwts.jwt().build(); private static final String JWK_SET_URI = "https://mock.org"; private static final JwtAuthenticationToken JWT_AUTHENTICATION_TOKEN = new JwtAuthenticationToken(JWT, Collections.emptyList()); private static final String INTROSPECTION_URI = "https://idp.example.com"; private static final String CLIENT_ID = "client-id"; private static final String CLIENT_SECRET = "client-secret"; private static final BearerTokenAuthentication INTROSPECTION_AUTHENTICATION_TOKEN = new BearerTokenAuthentication( new DefaultOAuth2AuthenticatedPrincipal(JWT_CLAIMS, Collections.emptyList()), TestOAuth2AccessTokens.noScopes(), Collections.emptyList()); @Autowired(required = false) MockMvc mvc; @Autowired(required = false) MockWebServer web; public final SpringTestContext spring = new SpringTestContext(this); @Test public void getWhenUsingDefaultsWithValidBearerTokenThenAcceptsRequest() throws Exception { this.spring.register(RestOperationsConfig.class, DefaultConfig.class, BasicController.class).autowire(); mockJwksRestOperations(jwks("Default")); String token = this.token("ValidNoScopes"); // @formatter:off this.mvc.perform(get("/").with(bearerToken(token))) .andExpect(status().isOk()) .andExpect(content().string("ok")); // @formatter:on } @Test public void getWhenCustomSecurityContextHolderStrategyThenUses() throws Exception { this.spring .register(RestOperationsConfig.class, DefaultConfig.class, BasicController.class, SecurityContextChangedListenerConfig.class) .autowire(); mockJwksRestOperations(jwks("Default")); String token = this.token("ValidNoScopes"); // @formatter:off this.mvc.perform(get("/").with(bearerToken(token))) .andExpect(status().isOk()) .andExpect(content().string("ok")); // @formatter:on verifyBean(SecurityContextHolderStrategy.class, atLeastOnce()).getContext(); } @Test public void getWhenSecurityContextHolderStrategyThenUses() throws Exception { this.spring .register(RestOperationsConfig.class, DefaultConfig.class, SecurityContextChangedListenerConfig.class, BasicController.class) .autowire(); mockJwksRestOperations(jwks("Default")); String token = this.token("ValidNoScopes"); // @formatter:off this.mvc.perform(get("/").with(bearerToken(token))) .andExpect(status().isOk()) .andExpect(content().string("ok")); // @formatter:on verifyBean(SecurityContextChangedListener.class, atLeastOnce()).securityContextChanged(any()); } @Test public void getWhenUsingDefaultsInLambdaWithValidBearerTokenThenAcceptsRequest() throws Exception { this.spring.register(RestOperationsConfig.class, DefaultInLambdaConfig.class, BasicController.class).autowire(); mockJwksRestOperations(jwks("Default")); String token = this.token("ValidNoScopes"); // @formatter:off this.mvc.perform(get("/").with(bearerToken(token))) .andExpect(status().isOk()) .andExpect(content().string("ok")); // @formatter:on } @Test public void getWhenUsingJwkSetUriThenAcceptsRequest() throws Exception { this.spring.register(WebServerConfig.class, JwkSetUriConfig.class, BasicController.class).autowire(); mockWebServer(jwks("Default")); String token = this.token("ValidNoScopes"); // @formatter:off this.mvc.perform(get("/").with(bearerToken(token))) .andExpect(status().isOk()) .andExpect(content().string("ok")); // @formatter:on } @Test public void getWhenUsingJwkSetUriInLambdaThenAcceptsRequest() throws Exception { this.spring.register(WebServerConfig.class, JwkSetUriInLambdaConfig.class, BasicController.class).autowire(); mockWebServer(jwks("Default")); String token = this.token("ValidNoScopes"); // @formatter:off this.mvc.perform(get("/").with(bearerToken(token))) .andExpect(status().isOk()) .andExpect(content().string("ok")); // @formatter:on } @Test public void getWhenUsingDefaultsWithExpiredBearerTokenThenInvalidToken() throws Exception { this.spring.register(RestOperationsConfig.class, DefaultConfig.class, BasicController.class).autowire(); mockJwksRestOperations(jwks("Default")); String token = this.token("Expired"); // @formatter:off this.mvc.perform(get("/").with(bearerToken(token))) .andExpect(status().isUnauthorized()) .andExpect(invalidTokenHeader("An error occurred while attempting to decode the Jwt")); // @formatter:on } @Test public void getWhenUsingDefaultsWithBadJwkEndpointThen500() throws Exception { this.spring.register(RestOperationsConfig.class, DefaultConfig.class).autowire(); mockRestOperations("malformed"); String token = this.token("ValidNoScopes"); // @formatter:off assertThatExceptionOfType(AuthenticationServiceException.class) .isThrownBy(() -> this.mvc.perform(get("/").with(bearerToken(token)))); // @formatter:on } @Test public void getWhenUsingDefaultsWithUnavailableJwkEndpointThen500() throws Exception { this.spring.register(WebServerConfig.class, JwkSetUriConfig.class).autowire(); this.web.shutdown(); String token = this.token("ValidNoScopes"); // @formatter:off assertThatExceptionOfType(AuthenticationServiceException.class) .isThrownBy(() -> this.mvc.perform(get("/").with(bearerToken(token)))); // @formatter:on } @Test public void getWhenUsingDefaultsWithMalformedBearerTokenThenInvalidToken() throws Exception { this.spring.register(JwkSetUriConfig.class).autowire(); // @formatter:off this.mvc.perform(get("/").with(bearerToken("an\"invalid\"token"))) .andExpect(status().isUnauthorized()) .andExpect(invalidTokenHeader("Bearer token is malformed")); // @formatter:on } @Test public void getWhenUsingDefaultsWithMalformedPayloadThenInvalidToken() throws Exception { this.spring.register(RestOperationsConfig.class, DefaultConfig.class).autowire(); mockJwksRestOperations(jwks("Default")); String token = this.token("MalformedPayload"); // @formatter:off this.mvc.perform(get("/").with(bearerToken(token))) .andExpect(status().isUnauthorized()) .andExpect(invalidTokenHeader("An error occurred while attempting to decode the Jwt: Malformed payload")); // @formatter:on } @Test public void getWhenUsingDefaultsWithUnsignedBearerTokenThenInvalidToken() throws Exception { this.spring.register(JwkSetUriConfig.class).autowire(); String token = this.token("Unsigned"); // @formatter:off this.mvc.perform(get("/").with(bearerToken(token))) .andExpect(status().isUnauthorized()) .andExpect(invalidTokenHeader("Unsupported algorithm of none")); // @formatter:on } @Test public void getWhenUsingDefaultsWithBearerTokenBeforeNotBeforeThenInvalidToken() throws Exception { this.spring.register(RestOperationsConfig.class, DefaultConfig.class).autowire(); this.mockJwksRestOperations(jwks("Default")); String token = this.token("TooEarly"); // @formatter:off this.mvc.perform(get("/").with(bearerToken(token))) .andExpect(status().isUnauthorized()) .andExpect(invalidTokenHeader("An error occurred while attempting to decode the Jwt")); // @formatter:on } @Test public void getWhenUsingDefaultsWithBearerTokenInTwoPlacesThenInvalidRequest() throws Exception { this.spring.register(JwkSetUriConfig.class).autowire(); // @formatter:off this.mvc.perform(get("/").with(bearerToken("token")).with(bearerToken("token").asParam())) .andExpect(status().isBadRequest()) .andExpect(invalidRequestHeader("Found multiple bearer tokens in the request")); // @formatter:on } @Test public void getWhenUsingDefaultsWithBearerTokenInTwoParametersThenInvalidRequest() throws Exception { this.spring.register(JwkSetUriConfig.class).autowire(); MultiValueMap params = new LinkedMultiValueMap<>(); params.add("access_token", "token1"); params.add("access_token", "token2"); // @formatter:off this.mvc.perform(get("/").params(params)) .andExpect(status().isBadRequest()) .andExpect(invalidRequestHeader("Found multiple bearer tokens in the request")); // @formatter:on } @Test public void postWhenUsingDefaultsWithBearerTokenAsFormParameterThenIgnoresToken() throws Exception { this.spring.register(JwkSetUriConfig.class).autowire(); // engage csrf // @formatter:off this.mvc.perform(post("/").header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE).with(bearerToken("token").asParam())) .andExpect(status().isForbidden()) .andExpect(header().doesNotExist(HttpHeaders.WWW_AUTHENTICATE)); // @formatter:on } @Test public void postWhenCsrfDisabledWithBearerTokenAsFormParameterThenIgnoresToken() throws Exception { this.spring.register(CsrfDisabledConfig.class).autowire(); // @formatter:off this.mvc.perform(post("/").header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE).with(bearerToken("token").asParam())) .andExpect(status().isUnauthorized()) .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, "Bearer resource_metadata=\"http://localhost/.well-known/oauth-protected-resource\"")); // @formatter:on } // gh-8031 @Test public void getWhenAnonymousDisabledThenAllows() throws Exception { this.spring.register(RestOperationsConfig.class, AnonymousDisabledConfig.class).autowire(); mockJwksRestOperations(jwks("Default")); String token = token("ValidNoScopes"); // @formatter:off this.mvc.perform(get("/authenticated").with(bearerToken(token))) .andExpect(status().isNotFound()); // @formatter:on } @Test public void getWhenUsingDefaultsWithNoBearerTokenThenUnauthorized() throws Exception { this.spring.register(JwkSetUriConfig.class).autowire(); // @formatter:off this.mvc.perform(get("/")) .andExpect(status().isUnauthorized()) .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, "Bearer resource_metadata=\"http://localhost/.well-known/oauth-protected-resource\"")); // @formatter:on } @Test public void getWhenUsingDefaultsWithSufficientlyScopedBearerTokenThenAcceptsRequest() throws Exception { this.spring.register(RestOperationsConfig.class, DefaultConfig.class, BasicController.class).autowire(); mockJwksRestOperations(jwks("Default")); String token = this.token("ValidMessageReadScope"); // @formatter:off this.mvc.perform(get("/requires-read-scope").with(bearerToken(token))) .andExpect(status().isOk()) .andExpect(content().string("[SCOPE_message:read]")); // @formatter:on } @Test public void getWhenUsingDefaultsWithInsufficientScopeThenInsufficientScopeError() throws Exception { this.spring.register(RestOperationsConfig.class, DefaultConfig.class, BasicController.class).autowire(); mockJwksRestOperations(jwks("Default")); String token = this.token("ValidNoScopes"); // @formatter:off this.mvc.perform(get("/requires-read-scope").with(bearerToken(token))) .andExpect(status().isForbidden()) .andExpect(insufficientScopeHeader()); // @formatter:on } @Test public void getWhenUsingDefaultsWithInsufficientScpThenInsufficientScopeError() throws Exception { this.spring.register(RestOperationsConfig.class, DefaultConfig.class, BasicController.class).autowire(); mockJwksRestOperations(jwks("Default")); String token = this.token("ValidMessageWriteScp"); // @formatter:off this.mvc.perform(get("/requires-read-scope").with(bearerToken(token))) .andExpect(status().isForbidden()) .andExpect(insufficientScopeHeader()); // @formatter:on } @Test public void getWhenUsingDefaultsAndAuthorizationServerHasNoMatchingKeyThenInvalidToken() throws Exception { this.spring.register(RestOperationsConfig.class, DefaultConfig.class).autowire(); mockJwksRestOperations(jwks("Empty")); String token = this.token("ValidNoScopes"); // @formatter:off this.mvc.perform(get("/").with(bearerToken(token))) .andExpect(status().isUnauthorized()) .andExpect(invalidTokenHeader("An error occurred while attempting to decode the Jwt")); // @formatter:on } @Test public void getWhenUsingDefaultsAndAuthorizationServerHasMultipleMatchingKeysThenOk() throws Exception { this.spring.register(RestOperationsConfig.class, DefaultConfig.class, BasicController.class).autowire(); mockJwksRestOperations(jwks("TwoKeys")); String token = this.token("ValidNoScopes"); // @formatter:off this.mvc.perform(get("/authenticated").with(bearerToken(token))) .andExpect(status().isOk()) .andExpect(content().string("test-subject")); // @formatter:on } @Test public void getWhenUsingDefaultsAndKeyMatchesByKidThenOk() throws Exception { this.spring.register(RestOperationsConfig.class, DefaultConfig.class, BasicController.class).autowire(); mockJwksRestOperations(jwks("TwoKeys")); String token = this.token("Kid"); // @formatter:off this.mvc.perform(get("/authenticated").with(bearerToken(token))) .andExpect(status().isOk()) .andExpect(content().string("test-subject")); // @formatter:on } @Test public void getWhenUsingMethodSecurityWithValidBearerTokenThenAcceptsRequest() throws Exception { this.spring.register(RestOperationsConfig.class, MethodSecurityConfig.class, BasicController.class).autowire(); mockJwksRestOperations(jwks("Default")); String token = this.token("ValidMessageReadScope"); // @formatter:off this.mvc.perform(get("/ms-requires-read-scope").with(bearerToken(token))) .andExpect(status().isOk()) .andExpect(content().string("[SCOPE_message:read]")); // @formatter:on } @Test public void getWhenUsingMethodSecurityWithValidBearerTokenHavingScpAttributeThenAcceptsRequest() throws Exception { this.spring.register(RestOperationsConfig.class, MethodSecurityConfig.class, BasicController.class).autowire(); mockJwksRestOperations(jwks("Default")); String token = this.token("ValidMessageReadScp"); // @formatter:off this.mvc.perform(get("/ms-requires-read-scope").with(bearerToken(token))) .andExpect(status().isOk()) .andExpect(content().string("[SCOPE_message:read]")); // @formatter:on } @Test public void getWhenUsingMethodSecurityWithInsufficientScopeThenInsufficientScopeError() throws Exception { this.spring.register(RestOperationsConfig.class, MethodSecurityConfig.class, BasicController.class).autowire(); mockJwksRestOperations(jwks("Default")); String token = this.token("ValidNoScopes"); // @formatter:off this.mvc.perform(get("/ms-requires-read-scope").with(bearerToken(token))) .andExpect(status().isForbidden()) .andExpect(insufficientScopeHeader()); // @formatter:on } @Test public void getWhenUsingMethodSecurityWithInsufficientScpThenInsufficientScopeError() throws Exception { this.spring.register(RestOperationsConfig.class, MethodSecurityConfig.class, BasicController.class).autowire(); mockJwksRestOperations(jwks("Default")); String token = this.token("ValidMessageWriteScp"); // @formatter:off this.mvc.perform(get("/ms-requires-read-scope").with(bearerToken(token))) .andExpect(status().isForbidden()) .andExpect(insufficientScopeHeader()); // @formatter:on } @Test public void getWhenUsingMethodSecurityWithDenyAllThenInsufficientScopeError() throws Exception { this.spring.register(RestOperationsConfig.class, MethodSecurityConfig.class, BasicController.class).autowire(); mockJwksRestOperations(jwks("Default")); String token = this.token("ValidMessageReadScope"); // @formatter:off this.mvc.perform(get("/ms-deny").with(bearerToken(token))) .andExpect(status().isForbidden()) .andExpect(insufficientScopeHeader()); // @formatter:on } @Test public void postWhenUsingDefaultsWithValidBearerTokenAndNoCsrfTokenThenOk() throws Exception { this.spring.register(RestOperationsConfig.class, DefaultConfig.class, BasicController.class).autowire(); mockJwksRestOperations(jwks("Default")); String token = this.token("ValidNoScopes"); // @formatter:off this.mvc.perform(post("/authenticated").header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE).with(bearerToken(token))) .andExpect(status().isOk()) .andExpect(content().string("test-subject")); // @formatter:on } @Test public void postWhenUsingDefaultsWithNoBearerTokenThenCsrfDenies() throws Exception { this.spring.register(JwkSetUriConfig.class).autowire(); // @formatter:off this.mvc.perform(post("/authenticated")) .andExpect(status().isForbidden()) .andExpect(header().doesNotExist(HttpHeaders.WWW_AUTHENTICATE)); // @formatter:on } @Test public void postWhenUsingDefaultsWithExpiredBearerTokenAndNoCsrfThenInvalidToken() throws Exception { this.spring.register(RestOperationsConfig.class, DefaultConfig.class).autowire(); mockJwksRestOperations(jwks("Default")); String token = this.token("Expired"); // @formatter:off this.mvc.perform(post("/authenticated").header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE).with(bearerToken(token))) .andExpect(status().isUnauthorized()) .andExpect(invalidTokenHeader("An error occurred while attempting to decode the Jwt")); // @formatter:on } @Test public void requestWhenDefaultConfiguredThenSessionIsNotCreated() throws Exception { this.spring.register(RestOperationsConfig.class, DefaultConfig.class, BasicController.class).autowire(); mockJwksRestOperations(jwks("Default")); String token = this.token("ValidNoScopes"); // @formatter:off MvcResult result = this.mvc.perform(get("/").with(bearerToken(token))) .andExpect(status().isOk()) .andReturn(); // @formatter:on assertThat(result.getRequest().getSession(false)).isNull(); } @Test public void requestWhenIntrospectionConfiguredThenSessionIsNotCreated() throws Exception { this.spring.register(RestOperationsConfig.class, OpaqueTokenConfig.class, BasicController.class).autowire(); mockJsonRestOperations(json("Active")); // @formatter:off MvcResult result = this.mvc.perform(get("/authenticated").with(bearerToken("token"))) .andExpect(status().isOk()) .andExpect(content().string("test-subject")) .andReturn(); // @formatter:on assertThat(result.getRequest().getSession(false)).isNull(); } @Test public void requestWhenUsingDefaultsAndNoBearerTokenThenSessionIsCreated() throws Exception { this.spring.register(JwkSetUriConfig.class, BasicController.class).autowire(); // @formatter:off MvcResult result = this.mvc.perform(get("/")) .andExpect(status().isUnauthorized()) .andReturn(); // @formatter:on assertThat(result.getRequest().getSession(false)).isNotNull(); } @Test public void requestWhenSessionManagementConfiguredThenUserConfigurationOverrides() throws Exception { this.spring.register(RestOperationsConfig.class, AlwaysSessionCreationConfig.class, BasicController.class) .autowire(); mockJwksRestOperations(jwks("Default")); String token = this.token("ValidNoScopes"); // @formatter:off MvcResult result = this.mvc.perform(get("/").with(bearerToken(token))) .andExpect(status().isOk()) .andReturn(); // @formatter:on assertThat(result.getRequest().getSession(false)).isNotNull(); } @Test public void requestWhenBearerTokenResolverAllowsRequestBodyThenEitherHeaderOrRequestBodyIsAccepted() throws Exception { this.spring.register(AllowBearerTokenInRequestBodyConfig.class, JwtDecoderConfig.class, BasicController.class) .autowire(); JwtDecoder decoder = this.spring.getContext().getBean(JwtDecoder.class); given(decoder.decode(anyString())).willReturn(JWT); // @formatter:off this.mvc.perform(get("/authenticated").with(bearerToken(JWT_TOKEN))) .andExpect(status().isOk()) .andExpect(content().string(JWT_SUBJECT)); this.mvc.perform(post("/authenticated").header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE).param("access_token", JWT_TOKEN)) .andExpect(status().isOk()) .andExpect(content().string(JWT_SUBJECT)); // @formatter:on } @Test public void requestWhenBearerTokenResolverAllowsQueryParameterThenEitherHeaderOrQueryParameterIsAccepted() throws Exception { this.spring .register(AllowBearerTokenAsQueryParameterConfig.class, JwtDecoderConfig.class, BasicController.class) .autowire(); JwtDecoder decoder = this.spring.getContext().getBean(JwtDecoder.class); given(decoder.decode(anyString())).willReturn(JWT); // @formatter:off this.mvc.perform(get("/authenticated").with(bearerToken(JWT_TOKEN))) .andExpect(status().isOk()) .andExpect(content().string(JWT_SUBJECT)); this.mvc.perform(get("/authenticated").param("access_token", JWT_TOKEN)) .andExpect(status().isOk()) .andExpect(content().string(JWT_SUBJECT)); // @formatter:on } @Test public void requestWhenBearerTokenResolverAllowsRequestBodyAndRequestContainsTwoTokensThenInvalidRequest() throws Exception { this.spring.register(AllowBearerTokenInRequestBodyConfig.class, JwtDecoderConfig.class, BasicController.class) .autowire(); JwtDecoder decoder = this.spring.getContext().getBean(JwtDecoder.class); given(decoder.decode(anyString())).willReturn(JWT); // @formatter:off MockHttpServletRequestBuilder request = post("/authenticated") .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE) .param("access_token", JWT_TOKEN) .with(bearerToken(JWT_TOKEN)) .with(csrf()); this.mvc.perform(request) .andExpect(status().isBadRequest()) .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, containsString("invalid_request"))); // @formatter:on } @Test public void requestWhenBearerTokenResolverAllowsQueryParameterAndRequestContainsTwoTokensThenInvalidRequest() throws Exception { this.spring .register(AllowBearerTokenAsQueryParameterConfig.class, JwtDecoderConfig.class, BasicController.class) .autowire(); JwtDecoder decoder = this.spring.getContext().getBean(JwtDecoder.class); given(decoder.decode(anyString())).willReturn(JWT); // @formatter:off MockHttpServletRequestBuilder request = get("/authenticated") .with(bearerToken(JWT_TOKEN)) .param("access_token", JWT_TOKEN); this.mvc.perform(request) .andExpect(status().isBadRequest()) .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, containsString("invalid_request"))); // @formatter:on } @Test public void getBearerTokenResolverWhenDuplicateResolverBeansAndAnotherOnTheDslThenTheDslOneIsUsed() { BearerTokenResolver resolverBean = mock(BearerTokenResolver.class); BearerTokenResolver resolver = mock(BearerTokenResolver.class); GenericWebApplicationContext context = new GenericWebApplicationContext(); context.registerBean("resolverOne", BearerTokenResolver.class, () -> resolverBean); context.registerBean("resolverTwo", BearerTokenResolver.class, () -> resolverBean); this.spring.context(context).autowire(); OAuth2ResourceServerConfigurer oauth2 = new OAuth2ResourceServerConfigurer(context); oauth2.bearerTokenResolver(resolver); assertThat(oauth2.getBearerTokenResolver()).isEqualTo(resolver); } @Test public void getBearerTokenResolverWhenDuplicateResolverBeansThenWiringException() { assertThatExceptionOfType(BeanCreationException.class) .isThrownBy(() -> this.spring.register(MultipleBearerTokenResolverBeansConfig.class, JwtDecoderConfig.class) .autowire()) .withRootCauseInstanceOf(NoUniqueBeanDefinitionException.class); } @Test public void getBearerTokenResolverWhenResolverBeanAndAnotherOnTheDslThenTheDslOneIsUsed() { BearerTokenResolver resolver = mock(BearerTokenResolver.class); BearerTokenResolver resolverBean = mock(BearerTokenResolver.class); GenericWebApplicationContext context = new GenericWebApplicationContext(); context.registerBean(BearerTokenResolver.class, () -> resolverBean); this.spring.context(context).autowire(); OAuth2ResourceServerConfigurer oauth2 = new OAuth2ResourceServerConfigurer(context); oauth2.bearerTokenResolver(resolver); assertThat(oauth2.getBearerTokenResolver()).isEqualTo(resolver); } @Test public void requestWhenCustomAuthenticationDetailsSourceThenUsed() throws Exception { this.spring.register(CustomAuthenticationDetailsSource.class, JwtDecoderConfig.class, BasicController.class) .autowire(); JwtDecoder decoder = this.spring.getContext().getBean(JwtDecoder.class); given(decoder.decode(anyString())).willReturn(JWT); this.mvc.perform(get("/authenticated").with(bearerToken(JWT_TOKEN))) .andExpect(status().isOk()) .andExpect(content().string(JWT_SUBJECT)); verifyBean(AuthenticationDetailsSource.class).buildDetails(any()); } @Test public void requestWhenCustomJwtDecoderWiredOnDslThenUsed() throws Exception { this.spring.register(CustomJwtDecoderOnDsl.class, BasicController.class).autowire(); CustomJwtDecoderOnDsl config = this.spring.getContext().getBean(CustomJwtDecoderOnDsl.class); JwtDecoder decoder = config.decoder(); given(decoder.decode(anyString())).willReturn(JWT); // @formatter:off this.mvc.perform(get("/authenticated").with(bearerToken(JWT_TOKEN))) .andExpect(status().isOk()) .andExpect(content().string(JWT_SUBJECT)); // @formatter:on } @Test public void requestWhenCustomJwtDecoderInLambdaOnDslThenUsed() throws Exception { this.spring.register(CustomJwtDecoderInLambdaOnDsl.class, BasicController.class).autowire(); CustomJwtDecoderInLambdaOnDsl config = this.spring.getContext().getBean(CustomJwtDecoderInLambdaOnDsl.class); JwtDecoder decoder = config.decoder(); given(decoder.decode(anyString())).willReturn(JWT); // @formatter:off this.mvc.perform(get("/authenticated").with(bearerToken(JWT_TOKEN))) .andExpect(status().isOk()) .andExpect(content().string(JWT_SUBJECT)); // @formatter:on } @Test public void requestWhenCustomJwtDecoderExposedAsBeanThenUsed() throws Exception { this.spring.register(CustomJwtDecoderAsBean.class, BasicController.class).autowire(); JwtDecoder decoder = this.spring.getContext().getBean(JwtDecoder.class); given(decoder.decode(anyString())).willReturn(JWT); // @formatter:off this.mvc.perform(get("/authenticated").with(bearerToken(JWT_TOKEN))) .andExpect(status().isOk()) .andExpect(content().string(JWT_SUBJECT)); // @formatter:on } @Test public void getJwtDecoderWhenConfiguredWithDecoderAndJwkSetUriThenLastOneWins() { ApplicationContext context = mock(ApplicationContext.class); JwtDecoder decoder = mock(JwtDecoder.class); new OAuth2ResourceServerConfigurer(context).jwt((jwt) -> { jwt.jwkSetUri(JWK_SET_URI); jwt.decoder(decoder); assertThat(jwt.getJwtDecoder()).isEqualTo(decoder); }); new OAuth2ResourceServerConfigurer(context).jwt((jwt) -> { jwt.decoder(decoder).jwkSetUri(JWK_SET_URI); assertThat(jwt.getJwtDecoder()).isInstanceOf(NimbusJwtDecoder.class); }); } @Test public void getJwtDecoderWhenConflictingJwtDecodersThenTheDslWiredOneTakesPrecedence() { JwtDecoder decoderBean = mock(JwtDecoder.class); JwtDecoder decoder = mock(JwtDecoder.class); ApplicationContext context = mock(ApplicationContext.class); given(context.getBean(JwtDecoder.class)).willReturn(decoderBean); new OAuth2ResourceServerConfigurer(context).jwt((jwt) -> { jwt.decoder(decoder); assertThat(jwt.getJwtDecoder()).isEqualTo(decoder); }); } @Test public void getJwtDecoderWhenContextHasBeanAndUserConfiguresJwkSetUriThenJwkSetUriTakesPrecedence() { JwtDecoder decoder = mock(JwtDecoder.class); ApplicationContext context = mock(ApplicationContext.class); given(context.getBean(JwtDecoder.class)).willReturn(decoder); new OAuth2ResourceServerConfigurer(context).jwt((jwt) -> { jwt.jwkSetUri(JWK_SET_URI); assertThat(jwt.getJwtDecoder()).isNotEqualTo(decoder); assertThat(jwt.getJwtDecoder()).isInstanceOf(NimbusJwtDecoder.class); }); } @Test public void getJwtDecoderWhenTwoJwtDecoderBeansAndAnotherWiredOnDslThenDslWiredOneTakesPrecedence() { JwtDecoder decoderBean = mock(JwtDecoder.class); JwtDecoder decoder = mock(JwtDecoder.class); GenericWebApplicationContext context = new GenericWebApplicationContext(); context.registerBean("decoderOne", JwtDecoder.class, () -> decoderBean); context.registerBean("decoderTwo", JwtDecoder.class, () -> decoderBean); this.spring.context(context).autowire(); new OAuth2ResourceServerConfigurer(context).jwt((jwt) -> { jwt.decoder(decoder); assertThat(jwt.getJwtDecoder()).isEqualTo(decoder); }); } @Test public void getJwtDecoderWhenTwoJwtDecoderBeansThenThrowsException() { JwtDecoder decoder = mock(JwtDecoder.class); GenericWebApplicationContext context = new GenericWebApplicationContext(); context.registerBean("decoderOne", JwtDecoder.class, () -> decoder); context.registerBean("decoderTwo", JwtDecoder.class, () -> decoder); this.spring.context(context).autowire(); new OAuth2ResourceServerConfigurer(context) .jwt((jwt) -> assertThatExceptionOfType(NoUniqueBeanDefinitionException.class) .isThrownBy(jwt::getJwtDecoder)); } @Test public void requestWhenRealmNameConfiguredThenUsesOnUnauthenticated() throws Exception { this.spring.register(RealmNameConfiguredOnEntryPoint.class, JwtDecoderConfig.class).autowire(); JwtDecoder decoder = this.spring.getContext().getBean(JwtDecoder.class); given(decoder.decode(anyString())).willThrow(BadJwtException.class); // @formatter:off this.mvc.perform(get("/authenticated").with(bearerToken("invalid_token"))) .andExpect(status().isUnauthorized()) .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, startsWith("Bearer realm=\"myRealm\""))); // @formatter:on } @Test public void requestWhenRealmNameConfiguredThenUsesOnAccessDenied() throws Exception { this.spring.register(RealmNameConfiguredOnAccessDeniedHandler.class, JwtDecoderConfig.class).autowire(); JwtDecoder decoder = this.spring.getContext().getBean(JwtDecoder.class); given(decoder.decode(anyString())).willReturn(JWT); // @formatter:off this.mvc.perform(get("/authenticated").with(bearerToken("insufficiently_scoped"))) .andExpect(status().isForbidden()) .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, startsWith("Bearer realm=\"myRealm\""))); // @formatter:on } @Test public void authenticationEntryPointWhenGivenNullThenThrowsException() { ApplicationContext context = mock(ApplicationContext.class); OAuth2ResourceServerConfigurer configurer = new OAuth2ResourceServerConfigurer(context); assertThatIllegalArgumentException().isThrownBy(() -> configurer.authenticationEntryPoint(null)); } @Test public void accessDeniedHandlerWhenGivenNullThenThrowsException() { ApplicationContext context = mock(ApplicationContext.class); OAuth2ResourceServerConfigurer configurer = new OAuth2ResourceServerConfigurer(context); assertThatIllegalArgumentException().isThrownBy(() -> configurer.accessDeniedHandler(null)); } @Test public void requestWhenCustomJwtValidatorFailsThenCorrespondingErrorMessage() throws Exception { this.spring.register(RestOperationsConfig.class, CustomJwtValidatorConfig.class).autowire(); mockJwksRestOperations(jwks("Default")); String token = this.token("ValidNoScopes"); OAuth2TokenValidator jwtValidator = this.spring.getContext() .getBean(CustomJwtValidatorConfig.class) .getJwtValidator(); OAuth2Error error = new OAuth2Error("custom-error", "custom-description", "custom-uri"); given(jwtValidator.validate(any(Jwt.class))).willReturn(OAuth2TokenValidatorResult.failure(error)); // @formatter:off this.mvc.perform(get("/").with(bearerToken(token))) .andExpect(status().isUnauthorized()) .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, containsString("custom-description"))); // @formatter:on } @Test public void requestWhenClockSkewSetThenTimestampWindowRelaxedAccordingly() throws Exception { this.spring.register(RestOperationsConfig.class, UnexpiredJwtClockSkewConfig.class, BasicController.class) .autowire(); mockJwksRestOperations(jwks("Default")); String token = this.token("ExpiresAt4687177990"); // @formatter:off this.mvc.perform(get("/").with(bearerToken(token))) .andExpect(status().isOk()); // @formatter:on } @Test public void requestWhenClockSkewSetButJwtStillTooLateThenReportsExpired() throws Exception { this.spring.register(RestOperationsConfig.class, ExpiredJwtClockSkewConfig.class, BasicController.class) .autowire(); mockJwksRestOperations(jwks("Default")); String token = this.token("ExpiresAt4687177990"); // @formatter:off this.mvc.perform(get("/").with(bearerToken(token))) .andExpect(status().isUnauthorized()) .andExpect(invalidTokenHeader("Jwt expired at")); // @formatter:on } @Test public void requestWhenJwtAuthenticationConverterConfiguredOnDslThenIsUsed() throws Exception { this.spring .register(JwtDecoderConfig.class, JwtAuthenticationConverterConfiguredOnDsl.class, BasicController.class) .autowire(); Converter jwtAuthenticationConverter = this.spring.getContext() .getBean(JwtAuthenticationConverterConfiguredOnDsl.class) .getJwtAuthenticationConverter(); given(jwtAuthenticationConverter.convert(JWT)).willReturn(JWT_AUTHENTICATION_TOKEN); JwtDecoder jwtDecoder = this.spring.getContext().getBean(JwtDecoder.class); given(jwtDecoder.decode(anyString())).willReturn(JWT); // @formatter:off this.mvc.perform(get("/").with(bearerToken(JWT_TOKEN))) .andExpect(status().isOk()); // @formatter:on verify(jwtAuthenticationConverter).convert(JWT); } @Test public void requestWhenJwtAuthenticationConverterCustomizedAuthoritiesThenThoseAuthoritiesArePropagated() throws Exception { this.spring.register(JwtDecoderConfig.class, CustomAuthorityMappingConfig.class, BasicController.class) .autowire(); JwtDecoder decoder = this.spring.getContext().getBean(JwtDecoder.class); given(decoder.decode(JWT_TOKEN)).willReturn(JWT); // @formatter:off this.mvc.perform(get("/requires-read-scope").with(bearerToken(JWT_TOKEN))) .andExpect(status().isOk()); // @formatter:on } @Test public void requestWhenUsingPublicKeyAndValidTokenThenAuthenticates() throws Exception { this.spring.register(SingleKeyConfig.class, BasicController.class).autowire(); String token = this.token("ValidNoScopes"); // @formatter:off this.mvc.perform(get("/").with(bearerToken(token))) .andExpect(status().isOk()); // @formatter:on } @Test public void requestWhenUsingPublicKeyAndSignatureFailsThenReturnsInvalidToken() throws Exception { this.spring.register(SingleKeyConfig.class).autowire(); String token = this.token("WrongSignature"); // @formatter:off this.mvc.perform(get("/").with(bearerToken(token))) .andExpect(invalidTokenHeader("signature")); // @formatter:on } @Test public void requestWhenUsingPublicKeyAlgorithmDoesNotMatchThenReturnsInvalidToken() throws Exception { this.spring.register(SingleKeyConfig.class).autowire(); String token = this.token("WrongAlgorithm"); // @formatter:off this.mvc.perform(get("/").with(bearerToken(token))) .andExpect(invalidTokenHeader("algorithm")); // @formatter:on } // gh-7793 @Test public void requestWhenUsingCustomAuthenticationEventPublisherThenUses() throws Exception { this.spring.register(CustomAuthenticationEventPublisher.class).autowire(); given(bean(JwtDecoder.class).decode(anyString())).willThrow(new BadJwtException("problem")); this.mvc.perform(get("/").with(bearerToken("token"))); verifyBean(AuthenticationEventPublisher.class) .publishAuthenticationFailure(any(OAuth2AuthenticationException.class), any(Authentication.class)); } @Test public void getWhenCustomJwtAuthenticationManagerThenUsed() throws Exception { this.spring.register(JwtAuthenticationManagerConfig.class, BasicController.class).autowire(); given(bean(AuthenticationProvider.class).authenticate(any(Authentication.class))) .willReturn(JWT_AUTHENTICATION_TOKEN); // @formatter:off this.mvc.perform(get("/authenticated").with(bearerToken("token"))) .andExpect(status().isOk()) .andExpect(content().string("mock-test-subject")); // @formatter:on verifyBean(AuthenticationProvider.class).authenticate(any(Authentication.class)); } @Test public void getWhenDefaultAndCustomJwtAuthenticationManagerThenCustomUsed() throws Exception { this.spring.register(DefaultAndJwtAuthenticationManagerConfig.class, BasicController.class).autowire(); DefaultAndJwtAuthenticationManagerConfig config = this.spring.getContext() .getBean(DefaultAndJwtAuthenticationManagerConfig.class); AuthenticationManager defaultAuthenticationManager = config.defaultAuthenticationManager(); AuthenticationManager jwtAuthenticationManager = config.jwtAuthenticationManager(); given(defaultAuthenticationManager.authenticate(any())) .willThrow(new RuntimeException("should not interact with default auth manager")); given(jwtAuthenticationManager.authenticate(any())).willReturn(JWT_AUTHENTICATION_TOKEN); // @formatter:off this.mvc.perform(get("/authenticated").with(bearerToken("token"))) .andExpect(status().isOk()) .andExpect(content().string("mock-test-subject")); // @formatter:on verify(jwtAuthenticationManager).authenticate(any(Authentication.class)); } @Test public void getWhenIntrospectingThenOk() throws Exception { this.spring.register(RestOperationsConfig.class, OpaqueTokenConfig.class, BasicController.class).autowire(); mockJsonRestOperations(json("Active")); // @formatter:off this.mvc.perform(get("/authenticated").with(bearerToken("token"))) .andExpect(status().isOk()) .andExpect(content().string("test-subject")); // @formatter:on } @Test public void getWhenOpaqueTokenInLambdaAndIntrospectingThenOk() throws Exception { this.spring.register(RestOperationsConfig.class, OpaqueTokenInLambdaConfig.class, BasicController.class) .autowire(); mockJsonRestOperations(json("Active")); // @formatter:off this.mvc.perform(get("/authenticated").with(bearerToken("token"))) .andExpect(status().isOk()) .andExpect(content().string("test-subject")); // @formatter:on } @Test public void getWhenIntrospectionFailsThenUnauthorized() throws Exception { this.spring.register(RestOperationsConfig.class, OpaqueTokenConfig.class).autowire(); mockJsonRestOperations(json("Inactive")); // @formatter:off this.mvc.perform(get("/").with(bearerToken("token"))) .andExpect(status().isUnauthorized()) .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, containsString("Provided token isn't active"))); // @formatter:on } @Test public void getWhenIntrospectionLacksScopeThenForbidden() throws Exception { this.spring.register(RestOperationsConfig.class, OpaqueTokenConfig.class).autowire(); mockJsonRestOperations(json("ActiveNoScopes")); // @formatter:off this.mvc.perform(get("/requires-read-scope").with(bearerToken("token"))) .andExpect(status().isForbidden()) .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, containsString("scope"))); // @formatter:on } @Test public void getWhenCustomIntrospectionAuthenticationManagerThenUsed() throws Exception { this.spring.register(OpaqueTokenAuthenticationManagerConfig.class, BasicController.class).autowire(); given(bean(AuthenticationProvider.class).authenticate(any(Authentication.class))) .willReturn(INTROSPECTION_AUTHENTICATION_TOKEN); // @formatter:off this.mvc.perform(get("/authenticated").with(bearerToken("token"))) .andExpect(status().isOk()) .andExpect(content().string("mock-test-subject")); // @formatter:on verifyBean(AuthenticationProvider.class).authenticate(any(Authentication.class)); } @Test public void getWhenDefaultAndCustomIntrospectionAuthenticationManagerThenCustomUsed() throws Exception { this.spring.register(DefaultAndOpaqueTokenAuthenticationManagerConfig.class, BasicController.class).autowire(); DefaultAndOpaqueTokenAuthenticationManagerConfig config = this.spring.getContext() .getBean(DefaultAndOpaqueTokenAuthenticationManagerConfig.class); AuthenticationManager defaultAuthenticationManager = config.defaultAuthenticationManager(); AuthenticationManager opaqueTokenAuthenticationManager = config.opaqueTokenAuthenticationManager(); given(defaultAuthenticationManager.authenticate(any())) .willThrow(new RuntimeException("should not interact with default auth manager")); given(opaqueTokenAuthenticationManager.authenticate(any())).willReturn(INTROSPECTION_AUTHENTICATION_TOKEN); // @formatter:off this.mvc.perform(get("/authenticated").with(bearerToken("token"))) .andExpect(status().isOk()) .andExpect(content().string("mock-test-subject")); // @formatter:on verify(opaqueTokenAuthenticationManager).authenticate(any(Authentication.class)); } @Test public void getWhenCustomIntrospectionAuthenticationManagerInLambdaThenUsed() throws Exception { this.spring.register(OpaqueTokenAuthenticationManagerInLambdaConfig.class, BasicController.class).autowire(); given(bean(AuthenticationProvider.class).authenticate(any(Authentication.class))) .willReturn(INTROSPECTION_AUTHENTICATION_TOKEN); // @formatter:off this.mvc.perform(get("/authenticated").with(bearerToken("token"))) .andExpect(status().isOk()) .andExpect(content().string("mock-test-subject")); // @formatter:on verifyBean(AuthenticationProvider.class).authenticate(any(Authentication.class)); } @Test public void configureWhenOnlyIntrospectionUrlThenException() { assertThatExceptionOfType(BeanCreationException.class) .isThrownBy(() -> this.spring.register(OpaqueTokenHalfConfiguredConfig.class).autowire()); } @Test public void getIntrospectionClientWhenConfiguredWithClientAndIntrospectionUriThenLastOneWins() { ApplicationContext context = mock(ApplicationContext.class); OpaqueTokenIntrospector client = mock(OpaqueTokenIntrospector.class); new OAuth2ResourceServerConfigurer(context).opaqueToken((opaqueToken) -> { opaqueToken.introspectionUri(INTROSPECTION_URI); opaqueToken.introspectionClientCredentials(CLIENT_ID, CLIENT_SECRET); opaqueToken.introspector(client); assertThat(opaqueToken.getIntrospector()).isEqualTo(client); }); new OAuth2ResourceServerConfigurer(context).opaqueToken((opaqueToken) -> { opaqueToken.introspector(client); opaqueToken.introspectionUri(INTROSPECTION_URI); opaqueToken.introspectionClientCredentials(CLIENT_ID, CLIENT_SECRET); assertThat(opaqueToken.getIntrospector()).isNotSameAs(client); }); } @Test public void getIntrospectionClientWhenDslAndBeanWiredThenDslTakesPrecedence() { GenericApplicationContext context = new GenericApplicationContext(); registerMockBean(context, "introspectionClientOne", OpaqueTokenIntrospector.class); registerMockBean(context, "introspectionClientTwo", OpaqueTokenIntrospector.class); new OAuth2ResourceServerConfigurer(context).opaqueToken((opaqueToken) -> { opaqueToken.introspectionUri(INTROSPECTION_URI); opaqueToken.introspectionClientCredentials(CLIENT_ID, CLIENT_SECRET); assertThat(opaqueToken.getIntrospector()).isNotNull(); }); } @Test public void requestWhenBasicAndResourceServerEntryPointsThenMatchedByRequest() throws Exception { this.spring.register(BasicAndResourceServerConfig.class, JwtDecoderConfig.class).autowire(); JwtDecoder decoder = this.spring.getContext().getBean(JwtDecoder.class); given(decoder.decode(anyString())).willThrow(BadJwtException.class); // @formatter:off this.mvc.perform(get("/authenticated").with(httpBasic("some", "user"))) .andExpect(status().isUnauthorized()) .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, startsWith("Basic"))); this.mvc.perform(get("/authenticated")) .andExpect(status().isUnauthorized()) .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, startsWith("Basic"))); this.mvc.perform(get("/authenticated").with(bearerToken("invalid_token"))) .andExpect(status().isUnauthorized()) .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, startsWith("Bearer"))); // @formatter:on } @Test public void requestWhenFormLoginAndResourceServerEntryPointsThenSessionCreatedByRequest() throws Exception { this.spring.register(FormAndResourceServerConfig.class, JwtDecoderConfig.class).autowire(); JwtDecoder decoder = this.spring.getContext().getBean(JwtDecoder.class); given(decoder.decode(anyString())).willThrow(BadJwtException.class); // @formatter:off MvcResult result = this.mvc.perform(get("/authenticated") .header("Accept", "text/html")) .andExpect(status().isFound()) .andExpect(redirectedUrl("/login")) .andReturn(); // @formatter:on assertThat(result.getRequest().getSession(false)).isNotNull(); // @formatter:off result = this.mvc.perform(get("/authenticated").with(bearerToken("token"))) .andExpect(status().isUnauthorized()) .andReturn(); // @formatter:on assertThat(result.getRequest().getSession(false)).isNull(); } @Test public void unauthenticatedRequestWhenFormOAuth2LoginAndResourceServerThenNegotiates() throws Exception { this.spring.register(OAuth2LoginAndResourceServerConfig.class, JwtDecoderConfig.class).autowire(); this.mvc.perform(get("/any").header("X-Requested-With", "XMLHttpRequest")).andExpect(status().isUnauthorized()); this.mvc.perform(get("/any").header("Accept", "application/json")).andExpect(status().isUnauthorized()); this.mvc.perform(get("/any").header("Accept", "text/html")).andExpect(status().is3xxRedirection()); this.mvc.perform(get("/any").header("Accept", "image/jpg")).andExpect(status().is3xxRedirection()); } @Test public void requestWhenDefaultAndResourceServerAccessDeniedHandlersThenMatchedByRequest() throws Exception { this.spring .register(ExceptionHandlingAndResourceServerWithAccessDeniedHandlerConfig.class, JwtDecoderConfig.class) .autowire(); JwtDecoder decoder = this.spring.getContext().getBean(JwtDecoder.class); given(decoder.decode(anyString())).willReturn(JWT); // @formatter:off this.mvc.perform(get("/authenticated").with(httpBasic("basic-user", "basic-password"))) .andExpect(status().isForbidden()) .andExpect(header().doesNotExist(HttpHeaders.WWW_AUTHENTICATE)); this.mvc.perform(get("/authenticated").with(bearerToken("insufficiently_scoped"))) .andExpect(status().isForbidden()) .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, startsWith("Bearer"))); // @formatter:on } @Test public void getWhenAlsoUsingHttpBasicThenCorrectProviderEngages() throws Exception { this.spring.register(RestOperationsConfig.class, BasicAndResourceServerConfig.class, BasicController.class) .autowire(); mockJwksRestOperations(jwks("Default")); String token = this.token("ValidNoScopes"); // @formatter:off this.mvc.perform(get("/authenticated").with(bearerToken(token))) .andExpect(status().isOk()) .andExpect(content().string("test-subject")); this.mvc.perform(get("/authenticated").with(httpBasic("basic-user", "basic-password"))) .andExpect(status().isOk()) .andExpect(content().string("basic-user")); // @formatter:on } @Test public void getAuthenticationManagerWhenConfiguredAuthenticationManagerThenTakesPrecedence() { ApplicationContext context = mock(ApplicationContext.class); OAuth2ResourceServerConfigurer oauth2ResourceServer = new OAuth2ResourceServerConfigurer<>( context); AuthenticationManager authenticationManager = mock(AuthenticationManager.class); oauth2ResourceServer .jwt((jwt) -> jwt.authenticationManager(authenticationManager).decoder(mock(JwtDecoder.class))); assertThat(oauth2ResourceServer.getAuthenticationManager(null)).isSameAs(authenticationManager); oauth2ResourceServer = new OAuth2ResourceServerConfigurer<>(context); oauth2ResourceServer.opaqueToken((opaqueToken) -> opaqueToken.authenticationManager(authenticationManager) .introspector(mock(OpaqueTokenIntrospector.class))); assertThat(oauth2ResourceServer.getAuthenticationManager(null)).isSameAs(authenticationManager); } @Test public void getWhenMultipleIssuersThenUsesIssuerClaimToDifferentiate() throws Exception { this.spring.register(WebServerConfig.class, MultipleIssuersConfig.class, BasicController.class).autowire(); MockWebServer server = this.spring.getContext().getBean(MockWebServer.class); String metadata = "{\n" + " \"issuer\": \"%s\", \n" + " \"jwks_uri\": \"%s/.well-known/jwks.json\" \n" + "}"; String jwkSet = jwkSet(); String issuerOne = server.url("/issuerOne").toString(); String issuerTwo = server.url("/issuerTwo").toString(); String issuerThree = server.url("/issuerThree").toString(); String jwtOne = jwtFromIssuer(issuerOne); String jwtTwo = jwtFromIssuer(issuerTwo); String jwtThree = jwtFromIssuer(issuerThree); mockWebServer(String.format(metadata, issuerOne, issuerOne)); mockWebServer(jwkSet); // @formatter:off this.mvc.perform(get("/authenticated").with(bearerToken(jwtOne))) .andExpect(status().isOk()) .andExpect(content().string("test-subject")); // @formatter:on mockWebServer(String.format(metadata, issuerTwo, issuerTwo)); mockWebServer(jwkSet); // @formatter:off this.mvc.perform(get("/authenticated").with(bearerToken(jwtTwo))) .andExpect(status().isOk()) .andExpect(content().string("test-subject")); // @formatter:on mockWebServer(String.format(metadata, issuerThree, issuerThree)); mockWebServer(jwkSet); // @formatter:off this.mvc.perform(get("/authenticated").with(bearerToken(jwtThree))) .andExpect(status().isUnauthorized()) .andExpect(invalidTokenHeader("Invalid issuer")); // @formatter:on } @Test public void configuredWhenMissingJwtAuthenticationProviderThenWiringException() { assertThatExceptionOfType(BeanCreationException.class) .isThrownBy(() -> this.spring.register(JwtlessConfig.class).autowire()) .withMessageContaining("neither was found"); } @Test public void configureWhenMissingJwkSetUriThenWiringException() { assertThatExceptionOfType(BeanCreationException.class) .isThrownBy(() -> this.spring.register(JwtHalfConfiguredConfig.class).autowire()) .withMessageContaining("No qualifying bean of type"); } @Test public void configureWhenUsingBothJwtAndOpaqueThenWiringException() { assertThatExceptionOfType(BeanCreationException.class) .isThrownBy(() -> this.spring.register(OpaqueAndJwtConfig.class).autowire()) .withMessageContaining("Spring Security only supports JWTs or Opaque Tokens"); } @Test public void configureWhenUsingBothAuthenticationManagerResolverAndOpaqueThenWiringException() { assertThatExceptionOfType(BeanCreationException.class) .isThrownBy(() -> this.spring.register(AuthenticationManagerResolverPlusOtherConfig.class).autowire()) .withMessageContaining("authenticationManagerResolver"); } @Test public void getJwtAuthenticationConverterWhenNoConverterSpecifiedThenTheDefaultIsUsed() { ApplicationContext context = this.spring.context(new GenericWebApplicationContext()).getContext(); new OAuth2ResourceServerConfigurer(context) .jwt((jwt) -> assertThat(jwt.getJwtAuthenticationConverter()) .isInstanceOf(JwtAuthenticationConverter.class)); } @Test public void getJwtAuthenticationConverterWhenConverterBeanSpecified() { JwtAuthenticationConverter converterBean = new JwtAuthenticationConverter(); GenericWebApplicationContext context = new GenericWebApplicationContext(); context.registerBean(JwtAuthenticationConverter.class, () -> converterBean); this.spring.context(context).autowire(); new OAuth2ResourceServerConfigurer(context) .jwt((jwt) -> assertThat(jwt.getJwtAuthenticationConverter()).isEqualTo(converterBean)); } @Test public void getJwtAuthenticationConverterWhenConverterBeanAndAnotherOnTheDslThenTheDslOneIsUsed() { JwtAuthenticationConverter converter = new JwtAuthenticationConverter(); JwtAuthenticationConverter converterBean = new JwtAuthenticationConverter(); GenericWebApplicationContext context = new GenericWebApplicationContext(); context.registerBean(JwtAuthenticationConverter.class, () -> converterBean); this.spring.context(context).autowire(); new OAuth2ResourceServerConfigurer(context).jwt((jwt) -> { jwt.jwtAuthenticationConverter(converter); assertThat(jwt.getJwtAuthenticationConverter()).isEqualTo(converter); }); } @Test public void getJwtAuthenticationConverterWhenDuplicateConverterBeansAndAnotherOnTheDslThenTheDslOneIsUsed() { JwtAuthenticationConverter converter = new JwtAuthenticationConverter(); JwtAuthenticationConverter converterBean = new JwtAuthenticationConverter(); GenericWebApplicationContext context = new GenericWebApplicationContext(); context.registerBean("converterOne", JwtAuthenticationConverter.class, () -> converterBean); context.registerBean("converterTwo", JwtAuthenticationConverter.class, () -> converterBean); this.spring.context(context).autowire(); new OAuth2ResourceServerConfigurer(context).jwt((jwt) -> { jwt.jwtAuthenticationConverter(converter); assertThat(jwt.getJwtAuthenticationConverter()).isEqualTo(converter); }); } @Test public void getJwtAuthenticationConverterWhenDuplicateConverterBeansThenThrowsException() { JwtAuthenticationConverter converterBean = new JwtAuthenticationConverter(); GenericWebApplicationContext context = new GenericWebApplicationContext(); context.registerBean("converterOne", JwtAuthenticationConverter.class, () -> converterBean); context.registerBean("converterTwo", JwtAuthenticationConverter.class, () -> converterBean); this.spring.context(context).autowire(); new OAuth2ResourceServerConfigurer(context) .jwt((jwt) -> assertThatExceptionOfType(NoUniqueBeanDefinitionException.class) .isThrownBy(jwt::getJwtAuthenticationConverter)); } @Test public void getWhenCustomAuthenticationConverterThenUsed() throws Exception { this.spring .register(RestOperationsConfig.class, OpaqueTokenAuthenticationConverterConfig.class, BasicController.class) .autowire(); OpaqueTokenAuthenticationConverter authenticationConverter = bean(OpaqueTokenAuthenticationConverter.class); given(authenticationConverter.convert(anyString(), any(OAuth2AuthenticatedPrincipal.class))) .willReturn(new TestingAuthenticationToken("jdoe", null, Collections.emptyList())); mockJsonRestOperations(json("Active")); // @formatter:off this.mvc.perform(get("/authenticated").with(bearerToken("token"))) .andExpect(status().isOk()) .andExpect(content().string("jdoe")); // @formatter:on verify(authenticationConverter).convert(any(), any()); } @Test public void getAuthenticationConverterWhenDuplicateConverterBeansAndAnotherOnTheDslThenTheDslOneIsUsed() { AuthenticationConverter converter = mock(AuthenticationConverter.class); AuthenticationConverter converterBean = mock(AuthenticationConverter.class); GenericWebApplicationContext context = new GenericWebApplicationContext(); context.registerBean("converterOne", AuthenticationConverter.class, () -> converterBean); context.registerBean("converterTwo", AuthenticationConverter.class, () -> converterBean); this.spring.context(context).autowire(); OAuth2ResourceServerConfigurer oauth2 = new OAuth2ResourceServerConfigurer(context); oauth2.authenticationConverter(converter); assertThat(oauth2.getAuthenticationConverter()).isEqualTo(converter); } @Test public void getAuthenticationConverterWhenConverterBeanAndAnotherOnTheDslThenTheDslOneIsUsed() { AuthenticationConverter converter = mock(AuthenticationConverter.class); AuthenticationConverter converterBean = mock(AuthenticationConverter.class); GenericWebApplicationContext context = new GenericWebApplicationContext(); context.registerBean(AuthenticationConverter.class, () -> converterBean); this.spring.context(context).autowire(); OAuth2ResourceServerConfigurer oauth2 = new OAuth2ResourceServerConfigurer(context); oauth2.authenticationConverter(converter); assertThat(oauth2.getAuthenticationConverter()).isEqualTo(converter); } @Test public void getAuthenticationConverterWhenDuplicateConverterBeansThenWiringException() { assertThatExceptionOfType(BeanCreationException.class) .isThrownBy( () -> this.spring.register(MultipleAuthenticationConverterBeansConfig.class, JwtDecoderConfig.class) .autowire()) .withRootCauseInstanceOf(NoUniqueBeanDefinitionException.class); } @Test public void getAuthenticationConverterWhenNoConverterSpecifiedThenTheDefaultIsUsed() { ApplicationContext context = this.spring.context(new GenericWebApplicationContext()).getContext(); OAuth2ResourceServerConfigurer oauth2 = new OAuth2ResourceServerConfigurer(context); assertThat(oauth2.getAuthenticationConverter()).isInstanceOf(BearerTokenAuthenticationConverter.class); } private static void registerMockBean(GenericApplicationContext context, String name, Class clazz) { context.registerBean(name, clazz, () -> mock(clazz)); } private static BearerTokenRequestPostProcessor bearerToken(String token) { return new BearerTokenRequestPostProcessor(token); } private static ResultMatcher invalidRequestHeader(String message) { return header().string(HttpHeaders.WWW_AUTHENTICATE, AllOf.allOf(new StringStartsWith("Bearer " + "error=\"invalid_request\", " + "error_description=\""), new StringContains(message), new StringContains(", " + "error_uri=\"https://tools.ietf.org/html/rfc6750#section-3.1\""), new StringEndsWith( ", " + "resource_metadata=\"http://localhost/.well-known/oauth-protected-resource\""))); } private static ResultMatcher invalidTokenHeader(String message) { return header().string(HttpHeaders.WWW_AUTHENTICATE, AllOf.allOf(new StringStartsWith("Bearer " + "error=\"invalid_token\", " + "error_description=\""), new StringContains(message), new StringContains(", " + "error_uri=\"https://tools.ietf.org/html/rfc6750#section-3.1\""), new StringEndsWith( ", " + "resource_metadata=\"http://localhost/.well-known/oauth-protected-resource\""))); } private static ResultMatcher insufficientScopeHeader() { return header().string(HttpHeaders.WWW_AUTHENTICATE, "Bearer " + "error=\"insufficient_scope\"" + ", error_description=\"The request requires higher privileges than provided by the access token.\"" + ", error_uri=\"https://tools.ietf.org/html/rfc6750#section-3.1\""); } private String jwkSet() { return new JWKSet(new RSAKey.Builder(TestKeys.DEFAULT_PUBLIC_KEY).keyID("1").build()).toString(); } private String jwtFromIssuer(String issuer) throws Exception { Map claims = new HashMap<>(); claims.put(JwtClaimNames.ISS, issuer); claims.put(JwtClaimNames.SUB, "test-subject"); claims.put("scope", "message:read"); JWSObject jws = new JWSObject(new JWSHeader.Builder(JWSAlgorithm.RS256).keyID("1").build(), new Payload(new JSONObject(claims))); jws.sign(new RSASSASigner(TestKeys.DEFAULT_PRIVATE_KEY)); return jws.serialize(); } private void mockWebServer(String response) { this.web.enqueue(new MockResponse().setResponseCode(200) .setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) .setBody(response)); } private void mockRestOperations(String response) { RestOperations rest = this.spring.getContext().getBean(RestOperations.class); HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); ResponseEntity entity = new ResponseEntity<>(response, headers, HttpStatus.OK); given(rest.exchange(any(RequestEntity.class), eq(String.class))).willReturn(entity); } private void mockJwksRestOperations(String response) { RestOperations rest = this.spring.getContext().getBean(RestOperations.class); HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); ResponseEntity entity = new ResponseEntity<>(response, headers, HttpStatus.OK); given(rest.exchange(any(RequestEntity.class), eq(String.class))).willReturn(entity); } private void mockJsonRestOperations(String response) { try { RestOperations rest = this.spring.getContext().getBean(RestOperations.class); HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); ResponseEntity> entity = new ResponseEntity<>(JSONObjectUtils.parse(response), headers, HttpStatus.OK); given(rest.exchange(any(RequestEntity.class), eq(new ParameterizedTypeReference>() { }))).willReturn(entity); } catch (Exception ex) { throw new IllegalArgumentException(ex); } } private T bean(Class beanClass) { return this.spring.getContext().getBean(beanClass); } private T verifyBean(Class beanClass) { return verify(this.spring.getContext().getBean(beanClass)); } private T verifyBean(Class beanClass, VerificationMode mode) { return verify(this.spring.getContext().getBean(beanClass), mode); } private String json(String name) throws IOException { return resource(name + ".json"); } private String jwks(String name) throws IOException { return resource(name + ".jwks"); } private String token(String name) throws IOException { return resource(name + ".token"); } private String resource(String suffix) throws IOException { String name = this.getClass().getSimpleName() + "-" + suffix; ClassPathResource resource = new ClassPathResource(name, this.getClass()); try (BufferedReader reader = new BufferedReader(new FileReader(resource.getFile()))) { return reader.lines().collect(Collectors.joining()); } } @Configuration @EnableWebSecurity @EnableWebMvc static class DefaultConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((requests) -> requests .requestMatchers("/requires-read-scope").hasAuthority("SCOPE_message:read") .anyRequest().authenticated()) .oauth2ResourceServer((server) -> server .jwt(Customizer.withDefaults())); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity @EnableWebMvc static class DefaultInLambdaConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((authorize) -> authorize .requestMatchers("/requires-read-scope").hasAuthority("SCOPE_message:read") .anyRequest().authenticated() ) .oauth2ResourceServer((oauth2) -> oauth2 .jwt(withDefaults()) ); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity @EnableWebMvc static class JwkSetUriConfig { @Value("${mockwebserver.url:https://example.org}") String jwkSetUri; @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off DefaultBearerTokenResolver defaultBearerTokenResolver = new DefaultBearerTokenResolver(); defaultBearerTokenResolver.setAllowUriQueryParameter(true); http .authorizeHttpRequests((requests) -> requests .requestMatchers("/requires-read-scope").hasAuthority("SCOPE_message:read") .anyRequest().authenticated()) .oauth2ResourceServer((server) -> server .bearerTokenResolver(defaultBearerTokenResolver) .jwt((jwt) -> jwt.jwkSetUri(this.jwkSetUri))); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity @EnableWebMvc static class JwkSetUriInLambdaConfig { @Value("${mockwebserver.url:https://example.org}") String jwkSetUri; @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((authorize) -> authorize .requestMatchers("/requires-read-scope").hasAuthority("SCOPE_message:read") .anyRequest().authenticated() ) .oauth2ResourceServer((oauth2) -> oauth2 .jwt((jwt) -> jwt .jwkSetUri(this.jwkSetUri) ) ); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity @EnableWebMvc static class CsrfDisabledConfig { @Value("${mockwebserver.url:https://example.org}") String jwkSetUri; @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((requests) -> requests .requestMatchers("/requires-read-scope").hasAuthority("SCOPE_message:read") .anyRequest().authenticated()) .csrf((csrf) -> csrf.disable()) .oauth2ResourceServer((server) -> server .jwt((jwt) -> jwt.jwkSetUri(this.jwkSetUri))); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class AnonymousDisabledConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((requests) -> requests .anyRequest().authenticated()) .anonymous((anonymous) -> anonymous.disable()) .oauth2ResourceServer((server) -> server .jwt(Customizer.withDefaults())); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) static class MethodSecurityConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((requests) -> requests .anyRequest().authenticated()) .oauth2ResourceServer((server) -> server .jwt(Customizer.withDefaults())); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class JwtlessConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((requests) -> requests .anyRequest().authenticated()) .oauth2ResourceServer(withDefaults()); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class RealmNameConfiguredOnEntryPoint { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((requests) -> requests .anyRequest().authenticated()) .oauth2ResourceServer((server) -> server .authenticationEntryPoint(authenticationEntryPoint()) .jwt(Customizer.withDefaults())); return http.build(); // @formatter:on } AuthenticationEntryPoint authenticationEntryPoint() { BearerTokenAuthenticationEntryPoint entryPoint = new BearerTokenAuthenticationEntryPoint(); entryPoint.setRealmName("myRealm"); return entryPoint; } } @Configuration @EnableWebSecurity static class RealmNameConfiguredOnAccessDeniedHandler { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((requests) -> requests .anyRequest().denyAll()) .oauth2ResourceServer((server) -> server .accessDeniedHandler(accessDeniedHandler()) .jwt(Customizer.withDefaults())); return http.build(); // @formatter:on } AccessDeniedHandler accessDeniedHandler() { BearerTokenAccessDeniedHandler accessDeniedHandler = new BearerTokenAccessDeniedHandler(); accessDeniedHandler.setRealmName("myRealm"); return accessDeniedHandler; } } @Configuration @EnableWebSecurity static class ExceptionHandlingAndResourceServerWithAccessDeniedHandlerConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((requests) -> requests .anyRequest().denyAll()) .exceptionHandling((handling) -> handling .defaultAccessDeniedHandlerFor(new AccessDeniedHandlerImpl(), (request) -> false)) .httpBasic(withDefaults()) .oauth2ResourceServer((server) -> server .jwt(Customizer.withDefaults())); return http.build(); // @formatter:on } @Bean UserDetailsService userDetailsService() { return new InMemoryUserDetailsManager( // @formatter:off org.springframework.security.core.userdetails.User.withDefaultPasswordEncoder() .username("basic-user") .password("basic-password") .roles("USER") .build()); // @formatter:on } } @Configuration @EnableWebSecurity static class JwtAuthenticationConverterConfiguredOnDsl { private final Converter jwtAuthenticationConverter = mock(Converter.class); @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((requests) -> requests .anyRequest().authenticated()) .oauth2ResourceServer((server) -> server .jwt((jwt) -> jwt .jwtAuthenticationConverter(getJwtAuthenticationConverter()))); return http.build(); // @formatter:on } Converter getJwtAuthenticationConverter() { return this.jwtAuthenticationConverter; } } @Configuration @EnableWebSecurity @EnableWebMvc static class CustomAuthorityMappingConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((requests) -> requests .requestMatchers("/requires-read-scope").hasAuthority("message:read")) .oauth2ResourceServer((server) -> server .jwt((jwt) -> jwt .jwtAuthenticationConverter(getJwtAuthenticationConverter()))); return http.build(); // @formatter:on } Converter getJwtAuthenticationConverter() { JwtAuthenticationConverter converter = new JwtAuthenticationConverter(); converter.setJwtGrantedAuthoritiesConverter( (jwt) -> Collections.singletonList(new SimpleGrantedAuthority("message:read"))); return converter; } } @Configuration @EnableWebSecurity static class BasicAndResourceServerConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((requests) -> requests .anyRequest().authenticated()) .httpBasic(withDefaults()) .oauth2ResourceServer((server) -> server .jwt(Customizer.withDefaults())); return http.build(); // @formatter:on } @Bean UserDetailsService userDetailsService() { return new InMemoryUserDetailsManager( // @formatter:off org.springframework.security.core.userdetails.User.withDefaultPasswordEncoder() .username("basic-user") .password("basic-password") .roles("USER") .build()); // @formatter:on } } @Configuration @EnableWebSecurity static class FormAndResourceServerConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((requests) -> requests .anyRequest().authenticated()) .formLogin(withDefaults()) .oauth2ResourceServer((server) -> server .jwt(Customizer.withDefaults())); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class OAuth2LoginAndResourceServerConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((authz) -> authz .anyRequest().authenticated() ) .oauth2Login(withDefaults()) .oauth2ResourceServer((oauth2) -> oauth2.jwt(withDefaults())); return http.build(); // @formatter:on } @Bean ClientRegistrationRepository clients() { ClientRegistration registration = TestClientRegistrations.clientRegistration().build(); return new InMemoryClientRegistrationRepository(registration); } } @Configuration @EnableWebSecurity static class JwtHalfConfiguredConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((requests) -> requests .anyRequest().authenticated()) .oauth2ResourceServer((server) -> server .jwt(Customizer.withDefaults())); return http.build(); // missing key configuration, e.g. jwkSetUri // @formatter:on } } @Configuration @EnableWebSecurity static class AlwaysSessionCreationConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .sessionManagement((management) -> management .sessionCreationPolicy(SessionCreationPolicy.ALWAYS)) .oauth2ResourceServer((server) -> server .jwt(Customizer.withDefaults())); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class AllowBearerTokenInRequestBodyConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((requests) -> requests .anyRequest().authenticated()) .oauth2ResourceServer((server) -> server .bearerTokenResolver(allowRequestBody()) .jwt(Customizer.withDefaults())); return http.build(); // @formatter:on } private BearerTokenResolver allowRequestBody() { DefaultBearerTokenResolver resolver = new DefaultBearerTokenResolver(); resolver.setAllowFormEncodedBodyParameter(true); return resolver; } } @Configuration @EnableWebSecurity static class AllowBearerTokenAsQueryParameterConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((requests) -> requests .anyRequest().authenticated()) .oauth2ResourceServer((server) -> server .jwt(Customizer.withDefaults())); return http.build(); // @formatter:on } @Bean BearerTokenResolver allowQueryParameter() { DefaultBearerTokenResolver resolver = new DefaultBearerTokenResolver(); resolver.setAllowUriQueryParameter(true); return resolver; } } @Configuration @EnableWebSecurity static class MultipleBearerTokenResolverBeansConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((requests) -> requests .anyRequest().authenticated()) .oauth2ResourceServer((server) -> server .jwt(Customizer.withDefaults())); return http.build(); // @formatter:on } @Bean BearerTokenResolver resolverOne() { DefaultBearerTokenResolver resolver = new DefaultBearerTokenResolver(); resolver.setAllowUriQueryParameter(true); return resolver; } @Bean BearerTokenResolver resolverTwo() { DefaultBearerTokenResolver resolver = new DefaultBearerTokenResolver(); resolver.setAllowFormEncodedBodyParameter(true); return resolver; } } @Configuration @EnableWebSecurity static class CustomAuthenticationDetailsSource { AuthenticationDetailsSource authenticationDetailsSource = mock( AuthenticationDetailsSource.class); @Bean SecurityFilterChain web(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((authorize) -> authorize .anyRequest().authenticated() ) .oauth2ResourceServer((oauth2) -> oauth2 .jwt(withDefaults()) .withObjectPostProcessor(new ObjectPostProcessor() { @Override public BearerTokenAuthenticationFilter postProcess(BearerTokenAuthenticationFilter object) { object.setAuthenticationDetailsSource(CustomAuthenticationDetailsSource.this.authenticationDetailsSource); return object; } }) ); return http.build(); } @Bean AuthenticationDetailsSource authenticationDetailsSource() { return this.authenticationDetailsSource; } } @Configuration @EnableWebSecurity static class CustomJwtDecoderOnDsl { JwtDecoder decoder = mock(JwtDecoder.class); @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((requests) -> requests .anyRequest().authenticated()) .oauth2ResourceServer((server) -> server .jwt((jwt) -> jwt.decoder(decoder()))); return http.build(); // @formatter:on } JwtDecoder decoder() { return this.decoder; } } @Configuration @EnableWebSecurity static class CustomJwtDecoderInLambdaOnDsl { JwtDecoder decoder = mock(JwtDecoder.class); @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((authorize) -> authorize .anyRequest().authenticated() ) .oauth2ResourceServer((oauth2) -> oauth2 .jwt((jwt) -> jwt .decoder(decoder()) ) ); return http.build(); // @formatter:on } JwtDecoder decoder() { return this.decoder; } } @Configuration @EnableWebSecurity static class CustomJwtDecoderAsBean { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((requests) -> requests .anyRequest().authenticated()) .oauth2ResourceServer((server) -> server .jwt(Customizer.withDefaults())); return http.build(); // @formatter:on } @Bean JwtDecoder decoder() { return mock(JwtDecoder.class); } } @Configuration @EnableWebSecurity static class JwtAuthenticationManagerConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((requests) -> requests .anyRequest().authenticated()) .oauth2ResourceServer((server) -> server .jwt((jwt) -> jwt .authenticationManager(authenticationProvider()::authenticate))); return http.build(); // @formatter:on } @Bean AuthenticationProvider authenticationProvider() { return mock(AuthenticationProvider.class); } } @Configuration @EnableWebSecurity static class DefaultAndJwtAuthenticationManagerConfig { AuthenticationManager defaultAuthenticationManager = mock(AuthenticationManager.class); AuthenticationManager jwtAuthenticationManager = mock(AuthenticationManager.class); @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authenticationManager(this.defaultAuthenticationManager) .authorizeHttpRequests((authz) -> authz .anyRequest().authenticated() ) .oauth2ResourceServer((oauth2) -> oauth2 .jwt((jwt) -> jwt .authenticationManager(this.jwtAuthenticationManager) ) ); return http.build(); // @formatter:on } AuthenticationManager defaultAuthenticationManager() { return this.defaultAuthenticationManager; } AuthenticationManager jwtAuthenticationManager() { return this.jwtAuthenticationManager; } } @Configuration @EnableWebSecurity static class CustomJwtValidatorConfig { @Autowired NimbusJwtDecoder jwtDecoder; private final OAuth2TokenValidator jwtValidator = mock(OAuth2TokenValidator.class); @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { this.jwtDecoder.setJwtValidator(this.jwtValidator); // @formatter:off http .oauth2ResourceServer((server) -> server .jwt(Customizer.withDefaults())); return http.build(); // @formatter:on } OAuth2TokenValidator getJwtValidator() { return this.jwtValidator; } } @Configuration @EnableWebSecurity static class UnexpiredJwtClockSkewConfig { @Autowired NimbusJwtDecoder jwtDecoder; @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { Clock nearlyAnHourFromTokenExpiry = Clock.fixed(Instant.ofEpochMilli(4687181540000L), ZoneId.systemDefault()); JwtTimestampValidator jwtValidator = new JwtTimestampValidator(Duration.ofHours(1)); jwtValidator.setClock(nearlyAnHourFromTokenExpiry); this.jwtDecoder.setJwtValidator(jwtValidator); // @formatter:off http .oauth2ResourceServer((server) -> server .jwt(Customizer.withDefaults())); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class ExpiredJwtClockSkewConfig { @Autowired NimbusJwtDecoder jwtDecoder; @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { Clock justOverOneHourAfterExpiry = Clock.fixed(Instant.ofEpochMilli(4687181595000L), ZoneId.systemDefault()); JwtTimestampValidator jwtValidator = new JwtTimestampValidator(Duration.ofHours(1)); jwtValidator.setClock(justOverOneHourAfterExpiry); this.jwtDecoder.setJwtValidator(jwtValidator); // @formatter:off http .oauth2ResourceServer((server) -> server .jwt(Customizer.withDefaults())); return http.build(); } } @Configuration @EnableWebSecurity static class SingleKeyConfig { byte[] spec = Base64.getDecoder().decode( "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoXJ8OyOv/eRnce4akdan" + "R4KYRfnC2zLV4uYNQpcFn6oHL0dj7D6kxQmsXoYgJV8ZVDn71KGmuLvolxsDncc2" + "UrhyMBY6DVQVgMSVYaPCTgW76iYEKGgzTEw5IBRQL9w3SRJWd3VJTZZQjkXef48O" + "cz06PGF3lhbz4t5UEZtdF4rIe7u+977QwHuh7yRPBQ3sII+cVoOUMgaXB9SHcGF2" + "iZCtPzL/IffDUcfhLQteGebhW8A6eUHgpD5A1PQ+JCw/G7UOzZAjjDjtNM2eqm8j" + "+Ms/gqnm4MiCZ4E+9pDN77CAAPVN7kuX6ejs9KBXpk01z48i9fORYk9u7rAkh1Hu" + "QwIDAQAB"); @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((requests) -> requests .anyRequest().authenticated()) .oauth2ResourceServer((server) -> server .jwt(Customizer.withDefaults())); return http.build(); // @formatter:on } @Bean JwtDecoder decoder() throws Exception { RSAPublicKey publicKey = (RSAPublicKey) KeyFactory.getInstance("RSA") .generatePublic(new X509EncodedKeySpec(this.spec)); return NimbusJwtDecoder.withPublicKey(publicKey).build(); } } @Configuration @EnableWebSecurity static class CustomAuthenticationEventPublisher { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((requests) -> requests .anyRequest().authenticated()) .oauth2ResourceServer((server) -> server .jwt(Customizer.withDefaults())); return http.build(); // @formatter:on } @Bean JwtDecoder jwtDecoder() { return mock(JwtDecoder.class); } @Bean AuthenticationEventPublisher authenticationEventPublisher() { return mock(AuthenticationEventPublisher.class); } } @Configuration @EnableWebSecurity @EnableWebMvc static class OpaqueTokenConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((requests) -> requests .requestMatchers("/requires-read-scope").hasAuthority("SCOPE_message:read") .anyRequest().authenticated()) .oauth2ResourceServer((server) -> server .opaqueToken(Customizer.withDefaults())); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity @EnableWebMvc static class OpaqueTokenInLambdaConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((authorize) -> authorize .requestMatchers("/requires-read-scope").hasAuthority("SCOPE_message:read") .anyRequest().authenticated() ) .oauth2ResourceServer((oauth2) -> oauth2 .opaqueToken(withDefaults()) ); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class OpaqueTokenAuthenticationManagerConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((requests) -> requests .anyRequest().authenticated()) .oauth2ResourceServer((server) -> server .opaqueToken((opaqueToken) -> opaqueToken .authenticationManager(authenticationProvider()::authenticate))); return http.build(); // @formatter:on } @Bean AuthenticationProvider authenticationProvider() { return mock(AuthenticationProvider.class); } } @Configuration @EnableWebSecurity static class OpaqueTokenAuthenticationManagerInLambdaConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((authorize) -> authorize .anyRequest().authenticated() ) .oauth2ResourceServer((oauth2) -> oauth2 .opaqueToken((opaqueToken) -> opaqueToken .authenticationManager(authenticationProvider()::authenticate) ) ); return http.build(); // @formatter:on } @Bean AuthenticationProvider authenticationProvider() { return mock(AuthenticationProvider.class); } } @Configuration @EnableWebSecurity static class DefaultAndOpaqueTokenAuthenticationManagerConfig { AuthenticationManager defaultAuthenticationManager = mock(AuthenticationManager.class); AuthenticationManager opaqueTokenAuthenticationManager = mock(AuthenticationManager.class); @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authenticationManager(this.defaultAuthenticationManager) .authorizeHttpRequests((authz) -> authz .anyRequest().authenticated() ) .oauth2ResourceServer((oauth2) -> oauth2 .opaqueToken((opaque) -> opaque .authenticationManager(this.opaqueTokenAuthenticationManager) ) ); return http.build(); // @formatter:on } AuthenticationManager defaultAuthenticationManager() { return this.defaultAuthenticationManager; } AuthenticationManager opaqueTokenAuthenticationManager() { return this.opaqueTokenAuthenticationManager; } } @Configuration @EnableWebSecurity static class OpaqueAndJwtConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .oauth2ResourceServer((server) -> server .jwt(Customizer.withDefaults()) .opaqueToken(Customizer.withDefaults())); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class OpaqueTokenHalfConfiguredConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((requests) -> requests .anyRequest().authenticated()) .oauth2ResourceServer((server) -> server .opaqueToken((opaqueToken) -> opaqueToken .introspectionUri("https://idp.example.com"))); return http.build(); // missing credentials // @formatter:on } } @Configuration @EnableWebSecurity static class MultipleAuthenticationConverterBeansConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((requests) -> requests .anyRequest().authenticated()) .oauth2ResourceServer((server) -> server .jwt(Customizer.withDefaults())); return http.build(); // @formatter:on } @Bean AuthenticationConverter authenticationConverterOne() { DefaultBearerTokenResolver resolver = new DefaultBearerTokenResolver(); resolver.setAllowUriQueryParameter(true); BearerTokenAuthenticationConverter authenticationConverter = new BearerTokenAuthenticationConverter(); authenticationConverter.setBearerTokenResolver(resolver); return authenticationConverter; } @Bean AuthenticationConverter authenticationConverterTwo() { DefaultBearerTokenResolver resolver = new DefaultBearerTokenResolver(); resolver.setAllowUriQueryParameter(true); BearerTokenAuthenticationConverter authenticationConverter = new BearerTokenAuthenticationConverter(); authenticationConverter.setBearerTokenResolver(resolver); return authenticationConverter; } } @Configuration @EnableWebSecurity static class MultipleIssuersConfig { @Autowired MockWebServer web; @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { String issuerOne = this.web.url("/issuerOne").toString(); String issuerTwo = this.web.url("/issuerTwo").toString(); JwtIssuerAuthenticationManagerResolver authenticationManagerResolver = JwtIssuerAuthenticationManagerResolver .fromTrustedIssuers(issuerOne, issuerTwo); // @formatter:off http .oauth2ResourceServer((server) -> server .authenticationManagerResolver(authenticationManagerResolver)) .anonymous(AbstractHttpConfigurer::disable); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity static class AuthenticationManagerResolverPlusOtherConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((requests) -> requests .anyRequest().authenticated()) .oauth2ResourceServer((server) -> server .authenticationManagerResolver(mock(AuthenticationManagerResolver.class)) .opaqueToken(Customizer.withDefaults())); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity @EnableWebMvc static class OpaqueTokenAuthenticationConverterConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((requests) -> requests .requestMatchers("/requires-read-scope").hasAuthority("SCOPE_message:read") .anyRequest().authenticated()) .oauth2ResourceServer((server) -> server .opaqueToken((opaqueToken) -> opaqueToken .authenticationConverter(authenticationConverter()))); return http.build(); // @formatter:on } @Bean OpaqueTokenAuthenticationConverter authenticationConverter() { return mock(OpaqueTokenAuthenticationConverter.class); } } @Configuration static class JwtDecoderConfig { @Bean JwtDecoder jwtDecoder() { return mock(JwtDecoder.class); } } @RestController static class BasicController { @GetMapping("/") String get() { return "ok"; } @PostMapping("/post") String post() { return "post"; } @RequestMapping(value = "/authenticated", method = { RequestMethod.GET, RequestMethod.POST }) String authenticated(Authentication authentication) { return authentication.getName(); } @GetMapping("/requires-read-scope") String requiresReadScope(JwtAuthenticationToken token) { return token.getAuthorities() .stream() .filter((ga) -> ga.getAuthority().startsWith("SCOPE_")) .map(GrantedAuthority::getAuthority) .collect(Collectors.toList()) .toString(); } @GetMapping("/ms-requires-read-scope") @PreAuthorize("hasAuthority('SCOPE_message:read')") String msRequiresReadScope(JwtAuthenticationToken token) { return requiresReadScope(token); } @GetMapping("/ms-deny") @PreAuthorize("denyAll") String deny() { return "hmm, that's odd"; } } @Configuration static class WebServerConfig implements BeanPostProcessor, EnvironmentAware { private final MockWebServer server = new MockWebServer(); @PreDestroy void shutdown() throws IOException { this.server.shutdown(); } @Override public void setEnvironment(Environment environment) { if (environment instanceof ConfigurableEnvironment) { ((ConfigurableEnvironment) environment).getPropertySources() .addFirst(new MockWebServerPropertySource()); } } @Bean MockWebServer web() { return this.server; } private class MockWebServerPropertySource extends PropertySource { MockWebServerPropertySource() { super("mockwebserver"); } @Override public Object getProperty(String name) { if ("mockwebserver.url".equals(name)) { return WebServerConfig.this.server.url("/.well-known/jwks.json").toString(); } else { return null; } } } } @Configuration static class RestOperationsConfig { RestOperations rest = mock(RestOperations.class); @Bean RestOperations rest() { return this.rest; } @Bean NimbusJwtDecoder jwtDecoder() { return NimbusJwtDecoder.withJwkSetUri("https://example.org/.well-known/jwks.json") .restOperations(this.rest) .build(); } @Bean OpaqueTokenIntrospector tokenIntrospectionClient() { return new SpringOpaqueTokenIntrospector("https://example.org/introspect", this.rest); } } private static class BearerTokenRequestPostProcessor implements RequestPostProcessor { private boolean asRequestParameter; private String token; BearerTokenRequestPostProcessor(String token) { this.token = token; } BearerTokenRequestPostProcessor asParam() { this.asRequestParameter = true; return this; } @Override public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) { if (this.asRequestParameter) { request.setParameter("access_token", this.token); } else { request.addHeader("Authorization", "Bearer " + this.token); } return request; } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/annotation/web/configurers/ott/OneTimeTokenLoginConfigurerTests.java ================================================ /* * 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 org.springframework.security.config.annotation.web.configurers.ott; import java.io.IOException; import java.time.Duration; import java.time.Instant; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.hamcrest.Matchers; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.security.authentication.ott.DefaultOneTimeToken; import org.springframework.security.authentication.ott.GenerateOneTimeTokenRequest; import org.springframework.security.authentication.ott.OneTimeToken; import org.springframework.security.authentication.ott.OneTimeTokenService; import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.core.userdetails.PasswordEncodedUser; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; import org.springframework.security.web.authentication.ott.GenerateOneTimeTokenRequestResolver; import org.springframework.security.web.authentication.ott.OneTimeTokenGenerationSuccessHandler; import org.springframework.security.web.authentication.ott.RedirectOneTimeTokenGenerationSuccessHandler; import org.springframework.security.web.csrf.CsrfToken; import org.springframework.security.web.csrf.DefaultCsrfToken; import org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository; import org.springframework.test.web.servlet.MockMvc; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatException; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated; import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.unauthenticated; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @ExtendWith(SpringTestContextExtension.class) public class OneTimeTokenLoginConfigurerTests { public SpringTestContext spring = new SpringTestContext(this); @Autowired(required = false) MockMvc mvc; @Autowired(required = false) private GenerateOneTimeTokenRequestResolver resolver; @Autowired(required = false) private OneTimeTokenService tokenService; @Autowired(required = false) private OneTimeTokenGenerationSuccessHandler tokenGenerationSuccessHandler; @Test void oneTimeTokenWhenCorrectTokenThenCanAuthenticate() throws Exception { this.spring.register(OneTimeTokenDefaultConfig.class).autowire(); this.mvc.perform(post("/ott/generate").param("username", "user").with(csrf())) .andExpectAll(status().isFound(), redirectedUrl("/login/ott")); String token = getLastToken().getTokenValue(); this.mvc.perform(post("/login/ott").param("token", token).with(csrf())) .andExpectAll(status().isFound(), redirectedUrl("/"), authenticated()); } @Test void oneTimeTokenWhenDifferentAuthenticationUrlsThenCanAuthenticate() throws Exception { this.spring.register(OneTimeTokenDifferentUrlsConfig.class).autowire(); this.mvc.perform(post("/generateurl").param("username", "user").with(csrf())) .andExpectAll(status().isFound(), redirectedUrl("/redirected")); String token = getLastToken().getTokenValue(); this.mvc.perform(post("/loginprocessingurl").param("token", token).with(csrf())) .andExpectAll(status().isFound(), redirectedUrl("/authenticated"), authenticated()); } @Test void oneTimeTokenWhenCorrectTokenUsedTwiceThenSecondTimeFails() throws Exception { this.spring.register(OneTimeTokenDefaultConfig.class).autowire(); this.mvc.perform(post("/ott/generate").param("username", "user").with(csrf())) .andExpectAll(status().isFound(), redirectedUrl("/login/ott")); String token = getLastToken().getTokenValue(); this.mvc.perform(post("/login/ott").param("token", token).with(csrf())) .andExpectAll(status().isFound(), redirectedUrl("/"), authenticated()); this.mvc.perform(post("/login/ott").param("token", token).with(csrf())) .andExpectAll(status().isFound(), redirectedUrl("/login?error"), unauthenticated()); } @Test void oneTimeTokenWhenWrongTokenThenAuthenticationFail() throws Exception { this.spring.register(OneTimeTokenDefaultConfig.class).autowire(); this.mvc.perform(post("/ott/generate").param("username", "user").with(csrf())) .andExpectAll(status().isFound(), redirectedUrl("/login/ott")); String token = "wrong"; this.mvc.perform(post("/login/ott").param("token", token).with(csrf())) .andExpectAll(status().isFound(), redirectedUrl("/login?error"), unauthenticated()); } @Test void oneTimeTokenWhenConfiguredThenServesCss() throws Exception { this.spring.register(OneTimeTokenDefaultConfig.class).autowire(); this.mvc.perform(get("/default-ui.css")) .andExpect(status().isOk()) .andExpect(content().string(Matchers.containsString("body {"))); } @Test void oneTimeTokenWhenConfiguredThenRendersRequestTokenForm() throws Exception { this.spring.register(OneTimeTokenDefaultConfig.class).autowire(); CsrfToken csrfToken = new DefaultCsrfToken("X-CSRF-TOKEN", "_csrf", "BaseSpringSpec_CSRFTOKEN"); String csrfAttributeName = HttpSessionCsrfTokenRepository.class.getName().concat(".CSRF_TOKEN"); //@formatter:off this.mvc.perform(get("/login").sessionAttr(csrfAttributeName, csrfToken)) .andExpect((result) -> { CsrfToken token = (CsrfToken) result.getRequest().getAttribute(CsrfToken.class.getName()); assertThat(result.getResponse().getContentAsString()).isEqualTo( """ Please sign in
""".formatted(token.getToken(), token.getToken())); }); //@formatter:on } @Test void oneTimeTokenWhenLoginPageConfiguredThenRedirects() throws Exception { this.spring.register(OneTimeTokenLoginPageConfig.class).autowire(); this.mvc.perform(get("/login")).andExpect(status().isFound()).andExpect(redirectedUrl("/custom-login")); } @Test void oneTimeTokenWhenNoTokenGenerationSuccessHandlerThenException() { assertThatException() .isThrownBy(() -> this.spring.register(OneTimeTokenNoGeneratedOttHandlerConfig.class).autowire()) .havingRootCause() .isInstanceOf(IllegalStateException.class) .withMessage(""" A OneTimeTokenGenerationSuccessHandler is required to enable oneTimeTokenLogin(). Please provide it as a bean or pass it to the oneTimeTokenLogin() DSL. """); } @Test void oneTimeTokenWhenCustomTokenExpirationTimeSetThenAuthenticate() throws Exception { this.spring.register(OneTimeTokenConfigWithCustomImpls.class).autowire(); GenerateOneTimeTokenRequest expectedGenerateRequest = new GenerateOneTimeTokenRequest("username-123", Duration.ofMinutes(10)); OneTimeToken ott = new DefaultOneTimeToken("token-123", expectedGenerateRequest.getUsername(), Instant.now().plus(expectedGenerateRequest.getExpiresIn())); given(this.resolver.resolve(any())).willReturn(expectedGenerateRequest); given(this.tokenService.generate(expectedGenerateRequest)).willReturn(ott); this.mvc.perform(post("/ott/generate").param("username", "user").with(csrf())); verify(this.resolver).resolve(any()); verify(this.tokenService).generate(expectedGenerateRequest); verify(this.tokenGenerationSuccessHandler).handle(any(), any(), eq(ott)); } private OneTimeToken getLastToken() { OneTimeToken lastToken = this.spring.getContext() .getBean(TestOneTimeTokenGenerationSuccessHandler.class).lastToken; return lastToken; } @Configuration(proxyBeanMethods = false) @EnableWebSecurity @Import(UserDetailsServiceConfig.class) static class OneTimeTokenConfigWithCustomImpls { @Bean SecurityFilterChain securityFilterChain(HttpSecurity http, GenerateOneTimeTokenRequestResolver ottRequestResolver, OneTimeTokenService ottTokenService, OneTimeTokenGenerationSuccessHandler ottSuccessHandler) throws Exception { // @formatter:off http .authorizeHttpRequests((authorize) -> authorize .anyRequest().authenticated() ) .oneTimeTokenLogin((ott) -> ott .generateRequestResolver(ottRequestResolver) .tokenService(ottTokenService) .tokenGenerationSuccessHandler(ottSuccessHandler) ); // @formatter:on return http.build(); } @Bean GenerateOneTimeTokenRequestResolver generateOneTimeTokenRequestResolver() { return mock(GenerateOneTimeTokenRequestResolver.class); } @Bean OneTimeTokenService ottService() { return mock(OneTimeTokenService.class); } @Bean OneTimeTokenGenerationSuccessHandler ottSuccessHandler() { return mock(OneTimeTokenGenerationSuccessHandler.class); } } @Configuration(proxyBeanMethods = false) @EnableWebSecurity @Import(UserDetailsServiceConfig.class) static class OneTimeTokenDefaultConfig { @Bean SecurityFilterChain securityFilterChain(HttpSecurity http, OneTimeTokenGenerationSuccessHandler ottSuccessHandler) throws Exception { // @formatter:off http .authorizeHttpRequests((authorize) -> authorize .anyRequest().authenticated() ) .oneTimeTokenLogin((ott) -> ott .tokenGenerationSuccessHandler(ottSuccessHandler) ); // @formatter:on return http.build(); } @Bean TestOneTimeTokenGenerationSuccessHandler ottSuccessHandler() { return new TestOneTimeTokenGenerationSuccessHandler(); } } @Configuration(proxyBeanMethods = false) @EnableWebSecurity @Import(UserDetailsServiceConfig.class) static class OneTimeTokenLoginPageConfig { @Bean SecurityFilterChain securityFilterChain(HttpSecurity http, OneTimeTokenGenerationSuccessHandler ottSuccessHandler) throws Exception { // @formatter:off http .authorizeHttpRequests((authorize) -> authorize .anyRequest().authenticated() ) .oneTimeTokenLogin((ott) -> ott .tokenGenerationSuccessHandler(ottSuccessHandler) .loginPage("/custom-login") ); // @formatter:on return http.build(); } @Bean TestOneTimeTokenGenerationSuccessHandler ottSuccessHandler() { return new TestOneTimeTokenGenerationSuccessHandler(); } } @Configuration(proxyBeanMethods = false) @EnableWebSecurity @Import(UserDetailsServiceConfig.class) static class OneTimeTokenDifferentUrlsConfig { @Bean SecurityFilterChain securityFilterChain(HttpSecurity http, OneTimeTokenGenerationSuccessHandler ottSuccessHandler) throws Exception { // @formatter:off http .authorizeHttpRequests((authorize) -> authorize .anyRequest().authenticated() ) .oneTimeTokenLogin((ott) -> ott .tokenGeneratingUrl("/generateurl") .tokenGenerationSuccessHandler(ottSuccessHandler) .loginProcessingUrl("/loginprocessingurl") .successHandler(new SimpleUrlAuthenticationSuccessHandler("/authenticated")) ); // @formatter:on return http.build(); } @Bean TestOneTimeTokenGenerationSuccessHandler ottSuccessHandler() { return new TestOneTimeTokenGenerationSuccessHandler("/redirected"); } } @Configuration(proxyBeanMethods = false) @EnableWebSecurity @Import(UserDetailsServiceConfig.class) static class OneTimeTokenNoGeneratedOttHandlerConfig { @Bean SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((authorize) -> authorize .anyRequest().authenticated() ) .oneTimeTokenLogin(Customizer.withDefaults()); // @formatter:on return http.build(); } } static class TestOneTimeTokenGenerationSuccessHandler implements OneTimeTokenGenerationSuccessHandler { private OneTimeToken lastToken; private final OneTimeTokenGenerationSuccessHandler delegate; TestOneTimeTokenGenerationSuccessHandler() { this.delegate = new RedirectOneTimeTokenGenerationSuccessHandler("/login/ott"); } TestOneTimeTokenGenerationSuccessHandler(String redirectUrl) { this.delegate = new RedirectOneTimeTokenGenerationSuccessHandler(redirectUrl); } @Override public void handle(HttpServletRequest request, HttpServletResponse response, OneTimeToken oneTimeToken) throws IOException, ServletException { this.lastToken = oneTimeToken; this.delegate.handle(request, response, oneTimeToken); } } @Configuration(proxyBeanMethods = false) static class UserDetailsServiceConfig { @Bean UserDetailsService userDetailsService() { return new InMemoryUserDetailsManager(PasswordEncodedUser.user(), PasswordEncodedUser.admin()); } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LoginConfigurerTests.java ================================================ /* * 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 org.springframework.security.config.annotation.web.configurers.saml2; import java.io.IOException; import java.net.URLDecoder; import java.nio.charset.StandardCharsets; import java.util.Base64; import java.util.Collections; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.instancio.internal.util.ReflectionUtils; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport; import org.opensaml.core.xml.io.Marshaller; import org.opensaml.saml.saml2.core.Assertion; import org.opensaml.saml.saml2.core.Response; import org.w3c.dom.Element; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.http.MediaType; import org.springframework.mock.web.MockFilterChain; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.mock.web.MockHttpSession; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.AuthenticationServiceException; import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.SecurityContextChangedListenerConfig; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.context.SecurityContextChangedListener; import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.security.saml2.core.OpenSamlInitializationService; import org.springframework.security.saml2.core.Saml2ErrorCodes; import org.springframework.security.saml2.core.Saml2Utils; import org.springframework.security.saml2.core.TestSaml2X509Credentials; import org.springframework.security.saml2.provider.service.authentication.AbstractSaml2AuthenticationRequest; import org.springframework.security.saml2.provider.service.authentication.OpenSaml5AuthenticationProvider; import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal; import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication; import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException; import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationToken; import org.springframework.security.saml2.provider.service.authentication.TestOpenSamlObjects; import org.springframework.security.saml2.provider.service.registration.InMemoryRelyingPartyRegistrationRepository; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository; import org.springframework.security.saml2.provider.service.registration.TestRelyingPartyRegistrations; import org.springframework.security.saml2.provider.service.web.DefaultRelyingPartyRegistrationResolver; import org.springframework.security.saml2.provider.service.web.RelyingPartyRegistrationResolver; import org.springframework.security.saml2.provider.service.web.Saml2AuthenticationRequestRepository; import org.springframework.security.saml2.provider.service.web.Saml2AuthenticationTokenConverter; import org.springframework.security.saml2.provider.service.web.authentication.OpenSaml5AuthenticationRequestResolver; import org.springframework.security.saml2.provider.service.web.authentication.Saml2AuthenticationRequestResolver; import org.springframework.security.web.FilterChainProxy; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.AuthenticationConverter; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.security.web.context.HttpRequestResponseHolder; import org.springframework.security.web.context.HttpSessionSecurityContextRepository; import org.springframework.security.web.context.SecurityContextRepository; import org.springframework.security.web.servlet.TestMockHttpServletRequests; import org.springframework.test.util.ReflectionTestUtils; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.util.UriComponents; import org.springframework.web.util.UriComponentsBuilder; import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.Matchers.startsWith; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.springframework.security.config.Customizer.withDefaults; import static org.springframework.security.config.annotation.SecurityContextChangedListenerArgumentMatchers.setAuthentication; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Tests for different Java configuration for {@link Saml2LoginConfigurer} */ @ExtendWith(SpringTestContextExtension.class) public class Saml2LoginConfigurerTests { static { OpenSamlInitializationService.initialize(); } private static final RelyingPartyRegistration registration = TestRelyingPartyRegistrations.noCredentials() .signingX509Credentials((c) -> c.add(TestSaml2X509Credentials.assertingPartySigningCredential())) .assertingPartyMetadata((party) -> party .verificationX509Credentials((c) -> c.add(TestSaml2X509Credentials.relyingPartyVerifyingCredential()))) .build(); private static String SIGNED_RESPONSE; private static final AuthenticationConverter AUTHENTICATION_CONVERTER = mock(AuthenticationConverter.class); @Autowired private ConfigurableApplicationContext context; @Autowired private FilterChainProxy springSecurityFilterChain; @Autowired private RelyingPartyRegistrationRepository repository; @Autowired SecurityContextRepository securityContextRepository; public final SpringTestContext spring = new SpringTestContext(this); @Autowired(required = false) MockMvc mvc; private MockHttpServletRequest request; private MockHttpServletResponse response; private MockFilterChain filterChain; @BeforeAll static void createResponse() throws Exception { String destination = registration.getAssertionConsumerServiceLocation(); String assertingPartyEntityId = registration.getAssertingPartyMetadata().getEntityId(); String relyingPartyEntityId = registration.getEntityId(); Response response = TestOpenSamlObjects.response(destination, assertingPartyEntityId); Assertion assertion = TestOpenSamlObjects.assertion("test@saml.user", assertingPartyEntityId, relyingPartyEntityId, destination); response.getAssertions().add(assertion); Response signed = TestOpenSamlObjects.signed(response, registration.getSigningX509Credentials().iterator().next(), relyingPartyEntityId); Marshaller marshaller = XMLObjectProviderRegistrySupport.getMarshallerFactory().getMarshaller(signed); Element element = marshaller.marshall(signed); Class clazz = ReflectionUtils.loadClass("net.shibboleth.utilities.java.support.xml.SerializeSupport"); if (clazz == null) { clazz = ReflectionUtils.loadClass("net.shibboleth.shared.xml.SerializeSupport"); } String serialized = ReflectionTestUtils.invokeMethod(clazz, "nodeToString", element); SIGNED_RESPONSE = Saml2Utils.samlEncode(serialized.getBytes(StandardCharsets.UTF_8)); } @BeforeEach public void setup() { this.request = TestMockHttpServletRequests.post("/login/saml2/sso/test-rp").build(); this.response = new MockHttpServletResponse(); this.filterChain = new MockFilterChain(); } @AfterEach public void cleanup() { if (this.context != null) { this.context.close(); } } @Test public void saml2LoginWhenDefaultsThenSaml2AuthenticatedPrincipal() throws Exception { this.spring.register(Saml2LoginConfig.class, ResourceController.class).autowire(); // @formatter:off MockHttpSession session = (MockHttpSession) this.mvc .perform(post("/login/saml2/sso/registration-id") .param("SAMLResponse", SIGNED_RESPONSE)) .andExpect(redirectedUrl("/")).andReturn().getRequest().getSession(false); this.mvc.perform(get("/").session(session)) .andExpect(content().string("test@saml.user")); // @formatter:on } @Test public void saml2LoginWhenCustomSecurityContextHolderStrategyThenUses() throws Exception { this.spring .register(Saml2LoginConfig.class, SecurityContextChangedListenerConfig.class, ResourceController.class) .autowire(); // @formatter:off MockHttpSession session = (MockHttpSession) this.mvc .perform(post("/login/saml2/sso/registration-id") .param("SAMLResponse", SIGNED_RESPONSE)) .andExpect(redirectedUrl("/")).andReturn().getRequest().getSession(false); this.mvc.perform(get("/").session(session)) .andExpect(content().string("test@saml.user")); // @formatter:on SecurityContextHolderStrategy strategy = this.spring.getContext().getBean(SecurityContextHolderStrategy.class); verify(strategy, atLeastOnce()).getContext(); SecurityContextChangedListener listener = this.spring.getContext() .getBean(SecurityContextChangedListener.class); verify(listener, times(2)).securityContextChanged(setAuthentication(Saml2Authentication.class)); } @Test public void saml2LoginWhenConfiguringAuthenticationManagerThenTheManagerIsUsed() throws Exception { // setup application context this.spring.register(Saml2LoginConfigWithCustomAuthenticationManager.class).autowire(); performSaml2Login("ROLE_AUTH_MANAGER"); } @Test public void saml2LoginWhenDefaultAndSamlAuthenticationManagerThenSamlManagerIsUsed() throws Exception { this.spring.register(Saml2LoginConfigWithDefaultAndCustomAuthenticationManager.class).autowire(); performSaml2Login("ROLE_AUTH_MANAGER"); } @Test public void authenticationRequestWhenAuthenticationRequestResolverBeanThenUses() throws Exception { this.spring.register(CustomAuthenticationRequestResolverBean.class).autowire(); MvcResult result = this.mvc.perform(get("/saml2/authenticate/registration-id")).andReturn(); UriComponents components = UriComponentsBuilder.fromUriString(result.getResponse().getRedirectedUrl()).build(); String samlRequest = components.getQueryParams().getFirst("SAMLRequest"); String decoded = URLDecoder.decode(samlRequest, "UTF-8"); String inflated = Saml2Utils.samlInflate(Saml2Utils.samlDecode(decoded)); assertThat(inflated).contains("ForceAuthn=\"true\""); } @Test public void authenticationRequestWhenAuthenticationRequestResolverDslThenUses() throws Exception { this.spring.register(CustomAuthenticationRequestResolverDsl.class).autowire(); MvcResult result = this.mvc.perform(get("/saml2/authenticate/registration-id")).andReturn(); UriComponents components = UriComponentsBuilder.fromUriString(result.getResponse().getRedirectedUrl()).build(); String samlRequest = components.getQueryParams().getFirst("SAMLRequest"); String decoded = URLDecoder.decode(samlRequest, "UTF-8"); String inflated = Saml2Utils.samlInflate(Saml2Utils.samlDecode(decoded)); assertThat(inflated).contains("ForceAuthn=\"true\""); } @Test public void authenticateWhenCustomAuthenticationConverterThenUses() throws Exception { this.spring.register(CustomAuthenticationConverter.class).autowire(); RelyingPartyRegistration relyingPartyRegistration = this.repository.findByRegistrationId("registration-id"); String response = new String(Saml2Utils.samlDecode(SIGNED_RESPONSE)); given(CustomAuthenticationConverter.authenticationConverter.convert(any(HttpServletRequest.class))) .willReturn(new Saml2AuthenticationToken(relyingPartyRegistration, response)); // @formatter:off MockHttpServletRequestBuilder request = post("/login/saml2/sso/" + relyingPartyRegistration.getRegistrationId()) .param("SAMLResponse", SIGNED_RESPONSE); // @formatter:on this.mvc.perform(request).andExpect(redirectedUrl("/")); verify(CustomAuthenticationConverter.authenticationConverter).convert(any(HttpServletRequest.class)); } @Test public void authenticateWhenCustomAuthenticationConverterBeanThenUses() throws Exception { this.spring.register(CustomAuthenticationConverterBean.class).autowire(); Saml2AuthenticationTokenConverter authenticationConverter = this.spring.getContext() .getBean(Saml2AuthenticationTokenConverter.class); RelyingPartyRegistration relyingPartyRegistration = this.repository.findByRegistrationId("registration-id"); String response = new String(Saml2Utils.samlDecode(SIGNED_RESPONSE)); given(authenticationConverter.convert(any(HttpServletRequest.class))) .willReturn(new Saml2AuthenticationToken(relyingPartyRegistration, response)); // @formatter:off MockHttpServletRequestBuilder request = post("/login/saml2/sso/" + relyingPartyRegistration.getRegistrationId()) .param("SAMLResponse", SIGNED_RESPONSE); // @formatter:on this.mvc.perform(request).andExpect(redirectedUrl("/")); verify(authenticationConverter).convert(any(HttpServletRequest.class)); } @Test public void authenticateWithInvalidDeflatedSAMLResponseThenFailureHandlerUses() throws Exception { this.spring.register(CustomAuthenticationFailureHandler.class).autowire(); byte[] invalidDeflated = "invalid".getBytes(); String encoded = Saml2Utils.samlEncode(invalidDeflated); MockHttpServletRequestBuilder request = get("/login/saml2/sso/registration-id").queryParam("SAMLResponse", encoded); this.mvc.perform(request); ArgumentCaptor captor = ArgumentCaptor .forClass(Saml2AuthenticationException.class); verify(CustomAuthenticationFailureHandler.authenticationFailureHandler) .onAuthenticationFailure(any(HttpServletRequest.class), any(HttpServletResponse.class), captor.capture()); Saml2AuthenticationException exception = captor.getValue(); assertThat(exception.getSaml2Error().getErrorCode()).isEqualTo(Saml2ErrorCodes.INVALID_RESPONSE); assertThat(exception.getSaml2Error().getDescription()).isEqualTo("Unable to inflate string"); assertThat(exception).hasRootCauseInstanceOf(IOException.class); } @Test public void authenticationRequestWhenCustomAuthnRequestRepositoryThenUses() throws Exception { this.spring.register(CustomAuthenticationRequestRepository.class).autowire(); MockHttpServletRequestBuilder request = get("/saml2/authenticate/registration-id"); this.mvc.perform(request).andExpect(status().isFound()); Saml2AuthenticationRequestRepository repository = this.spring.getContext() .getBean(Saml2AuthenticationRequestRepository.class); verify(repository).saveAuthenticationRequest(any(AbstractSaml2AuthenticationRequest.class), any(HttpServletRequest.class), any(HttpServletResponse.class)); } @Test public void authenticateWhenCustomAuthnRequestRepositoryThenUses() throws Exception { this.spring.register(CustomAuthenticationRequestRepository.class).autowire(); MockHttpServletRequestBuilder request = post("/login/saml2/sso/registration-id").param("SAMLResponse", SIGNED_RESPONSE); Saml2AuthenticationRequestRepository repository = this.spring.getContext() .getBean(Saml2AuthenticationRequestRepository.class); this.mvc.perform(request); verify(repository).loadAuthenticationRequest(any(HttpServletRequest.class)); verify(repository).removeAuthenticationRequest(any(HttpServletRequest.class), any(HttpServletResponse.class)); } @Test public void authenticationRequestWhenCustomAuthenticationRequestUriRepositoryThenUses() throws Exception { this.spring.register(CustomAuthenticationRequestUriCustomAuthenticationConverter.class).autowire(); MockHttpServletRequestBuilder request = get("/custom/auth/registration-id"); this.mvc.perform(request).andExpect(status().isFound()); Saml2AuthenticationRequestRepository repository = this.spring.getContext() .getBean(Saml2AuthenticationRequestRepository.class); verify(repository).saveAuthenticationRequest(any(AbstractSaml2AuthenticationRequest.class), any(HttpServletRequest.class), any(HttpServletResponse.class)); } @Test public void authenticationRequestWhenCustomAuthenticationRequestPathRepositoryThenUses() throws Exception { this.spring.register(CustomAuthenticationRequestUriQuery.class).autowire(); MockHttpServletRequestBuilder request = get("/custom/auth/sso"); this.mvc.perform(request) .andExpect(status().isFound()) .andExpect(redirectedUrl("/custom/auth/sso?entityId=registration-id")); request.queryParam("entityId", registration.getRegistrationId()); MvcResult result = this.mvc.perform(request).andExpect(status().isFound()).andReturn(); String redirectedUrl = result.getResponse().getRedirectedUrl(); assertThat(redirectedUrl).startsWith(registration.getAssertingPartyMetadata().getSingleSignOnServiceLocation()); } @Test public void saml2LoginWhenLoginProcessingUrlWithoutRegistrationIdAndDefaultAuthenticationConverterThenAutowires() throws Exception { this.spring.register(CustomLoginProcessingUrlDefaultAuthenticationConverter.class).autowire(); } @Test public void authenticateWhenCustomLoginProcessingUrlAndCustomAuthenticationConverterThenAuthenticate() throws Exception { this.spring.register(CustomLoginProcessingUrlCustomAuthenticationConverter.class).autowire(); RelyingPartyRegistration relyingPartyRegistration = this.repository.findByRegistrationId("registration-id"); String response = new String(Saml2Utils.samlDecode(SIGNED_RESPONSE)); given(AUTHENTICATION_CONVERTER.convert(any(HttpServletRequest.class))) .willReturn(new Saml2AuthenticationToken(relyingPartyRegistration, response)); // @formatter:off MockHttpServletRequestBuilder request = post("/my/custom/url").param("SAMLResponse", SIGNED_RESPONSE); // @formatter:on this.mvc.perform(request).andExpect(redirectedUrl("/")); verify(AUTHENTICATION_CONVERTER).convert(any(HttpServletRequest.class)); } @Test public void authenticateWhenCustomLoginProcessingUrlAndSaml2AuthenticationTokenConverterBeanThenAuthenticate() throws Exception { this.spring.register(CustomLoginProcessingUrlSaml2AuthenticationTokenConverterBean.class).autowire(); Saml2AuthenticationTokenConverter authenticationConverter = this.spring.getContext() .getBean(Saml2AuthenticationTokenConverter.class); RelyingPartyRegistration relyingPartyRegistration = this.repository.findByRegistrationId("registration-id"); String response = new String(Saml2Utils.samlDecode(SIGNED_RESPONSE)); given(authenticationConverter.convert(any(HttpServletRequest.class))) .willReturn(new Saml2AuthenticationToken(relyingPartyRegistration, response)); // @formatter:off MockHttpServletRequestBuilder request = post("/my/custom/url").param("SAMLResponse", SIGNED_RESPONSE); // @formatter:on this.mvc.perform(request).andExpect(redirectedUrl("/")); verify(authenticationConverter).convert(any(HttpServletRequest.class)); } // gh-11657 @Test public void getFaviconWhenDefaultConfigurationThenDoesNotSaveAuthnRequest() throws Exception { this.spring.register(Saml2LoginConfig.class).autowire(); this.mvc.perform(get("/favicon.ico").accept(MediaType.TEXT_HTML)) .andExpect(status().isFound()) .andExpect(redirectedUrl("/login")); this.mvc.perform(get("/").accept(MediaType.TEXT_HTML)) .andExpect(status().isFound()) .andExpect(header().string("Location", startsWith("/saml2/authenticate"))); } @Test public void saml2LoginWhenCustomAuthenticationProviderThenUses() throws Exception { this.spring.register(CustomAuthenticationProviderConfig.class).autowire(); AuthenticationProvider provider = this.spring.getContext().getBean(AuthenticationProvider.class); this.mvc.perform(post("/login/saml2/sso/registration-id").param("SAMLResponse", SIGNED_RESPONSE)) .andExpect(status().isFound()); verify(provider).authenticate(any()); } private void performSaml2Login(String expected) throws IOException, ServletException { // setup authentication parameters this.request.setRequestURI("/login/saml2/sso/registration-id"); this.request.setParameter("SAMLResponse", Base64.getEncoder().encodeToString("saml2-xml-response-object".getBytes())); // perform test this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain); // assertions Authentication authentication = this.securityContextRepository .loadContext(new HttpRequestResponseHolder(this.request, this.response)) .getAuthentication(); assertThat(authentication).as("Expected a valid authentication object.").isNotNull(); assertThat(authentication.getAuthorities()).hasSize(1); assertThat(authentication.getAuthorities()).first() .isInstanceOf(SimpleGrantedAuthority.class) .hasToString(expected); } private static AuthenticationManager getAuthenticationManagerMock(String role) { return new AuthenticationManager() { @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { if (!supports(authentication.getClass())) { throw new AuthenticationServiceException("not supported"); } return new Saml2Authentication(() -> "auth principal", "saml2 response", Collections.singletonList(new SimpleGrantedAuthority(role))); } public boolean supports(Class authentication) { return authentication.isAssignableFrom(Saml2AuthenticationToken.class); } }; } @Configuration @EnableWebSecurity @EnableWebMvc @Import(Saml2LoginConfigBeans.class) static class Saml2LoginConfig { @Bean SecurityFilterChain web(HttpSecurity http) throws Exception { http.authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated()) .saml2Login(Customizer.withDefaults()); return http.build(); } } @Configuration @EnableWebSecurity @Import(Saml2LoginConfigBeans.class) static class Saml2LoginConfigWithCustomAuthenticationManager { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http.saml2Login((login) -> login.authenticationManager(getAuthenticationManagerMock("ROLE_AUTH_MANAGER"))); return http.build(); } } @Configuration @EnableWebSecurity @Import(Saml2LoginConfigBeans.class) static class Saml2LoginConfigWithDefaultAndCustomAuthenticationManager { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authenticationManager(getAuthenticationManagerMock("DEFAULT_AUTH_MANAGER")) .saml2Login((saml) -> saml .authenticationManager(getAuthenticationManagerMock("ROLE_AUTH_MANAGER")) ); return http.build(); // @formatter:on } } @Configuration @EnableWebSecurity @Import(Saml2LoginConfigBeans.class) static class CustomAuthenticationFailureHandler { static final AuthenticationFailureHandler authenticationFailureHandler = mock( AuthenticationFailureHandler.class); @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http.authorizeHttpRequests((authz) -> authz.anyRequest().authenticated()) .saml2Login((saml2) -> saml2.failureHandler(authenticationFailureHandler)); return http.build(); } } @Configuration @EnableWebSecurity @Import(Saml2LoginConfigBeans.class) static class CustomAuthenticationRequestResolverBean { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((authz) -> authz .anyRequest().authenticated() ) .saml2Login(Customizer.withDefaults()); // @formatter:on return http.build(); } @Bean Saml2AuthenticationRequestResolver authenticationRequestResolver( RelyingPartyRegistrationRepository registrations) { RelyingPartyRegistrationResolver registrationResolver = new DefaultRelyingPartyRegistrationResolver( registrations); OpenSaml5AuthenticationRequestResolver delegate = new OpenSaml5AuthenticationRequestResolver( registrationResolver); delegate.setAuthnRequestCustomizer((parameters) -> parameters.getAuthnRequest().setForceAuthn(true)); return delegate; } } @Configuration @EnableWebSecurity @Import(Saml2LoginConfigBeans.class) static class CustomAuthenticationRequestResolverDsl { @Bean SecurityFilterChain filterChain(HttpSecurity http, RelyingPartyRegistrationRepository registrations) throws Exception { // @formatter:off http .authorizeHttpRequests((authz) -> authz .anyRequest().authenticated() ) .saml2Login((saml2) -> saml2 .authenticationRequestResolver(authenticationRequestResolver(registrations)) ); // @formatter:on return http.build(); } Saml2AuthenticationRequestResolver authenticationRequestResolver( RelyingPartyRegistrationRepository registrations) { RelyingPartyRegistrationResolver registrationResolver = new DefaultRelyingPartyRegistrationResolver( registrations); OpenSaml5AuthenticationRequestResolver delegate = new OpenSaml5AuthenticationRequestResolver( registrationResolver); delegate.setAuthnRequestCustomizer((parameters) -> parameters.getAuthnRequest().setForceAuthn(true)); return delegate; } } @Configuration @EnableWebSecurity @Import(Saml2LoginConfigBeans.class) static class CustomAuthenticationConverter { static final AuthenticationConverter authenticationConverter = mock(AuthenticationConverter.class); @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http.authorizeHttpRequests((authz) -> authz.anyRequest().authenticated()) .saml2Login((saml2) -> saml2.authenticationConverter(authenticationConverter)); return http.build(); } } @Configuration @EnableWebSecurity @Import(Saml2LoginConfigBeans.class) static class CustomAuthenticationConverterBean { private final Saml2AuthenticationTokenConverter authenticationConverter = mock( Saml2AuthenticationTokenConverter.class); @Bean SecurityFilterChain app(HttpSecurity http) throws Exception { http.authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated()) .saml2Login(Customizer.withDefaults()); return http.build(); } @Bean Saml2AuthenticationTokenConverter authenticationConverter() { return this.authenticationConverter; } } @Configuration @EnableWebSecurity @Import(Saml2LoginConfigBeans.class) static class CustomAuthenticationRequestRepository { private final Saml2AuthenticationRequestRepository repository = mock( Saml2AuthenticationRequestRepository.class); @Bean SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http.authorizeHttpRequests((authz) -> authz.anyRequest().authenticated()); http.saml2Login(withDefaults()); return http.build(); } @Bean Saml2AuthenticationRequestRepository authenticationRequestRepository() { return this.repository; } } @Configuration @EnableWebSecurity @Import(Saml2LoginConfigBeans.class) static class CustomLoginProcessingUrlDefaultAuthenticationConverter { @Bean SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((authz) -> authz.anyRequest().authenticated()) .saml2Login((saml2) -> saml2.loginProcessingUrl("/my/custom/url")); // @formatter:on return http.build(); } } @Configuration @EnableWebSecurity @Import(Saml2LoginConfigBeans.class) static class CustomAuthenticationRequestUriCustomAuthenticationConverter { private final Saml2AuthenticationRequestRepository repository = mock( Saml2AuthenticationRequestRepository.class); @Bean Saml2AuthenticationRequestRepository authenticationRequestRepository() { return this.repository; } @Bean SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((authz) -> authz.anyRequest().authenticated()) .saml2Login((saml2) -> saml2.authenticationRequestUri("/custom/auth/{registrationId}")); // @formatter:on return http.build(); } } @Configuration @EnableWebSecurity @Import(Saml2LoginConfigBeans.class) static class CustomAuthenticationRequestUriQuery { @Bean SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated()) .saml2Login((saml2) -> saml2.authenticationRequestUriQuery("/custom/auth/sso?entityId={registrationId}")); // @formatter:on return http.build(); } } @Configuration @EnableWebSecurity @Import(Saml2LoginConfigBeans.class) static class CustomLoginProcessingUrlCustomAuthenticationConverter { @Bean SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((authz) -> authz.anyRequest().authenticated()) .saml2Login((saml2) -> saml2 .loginProcessingUrl("/my/custom/url") .authenticationConverter(AUTHENTICATION_CONVERTER) ); // @formatter:on return http.build(); } } @Configuration @EnableWebSecurity @Import(Saml2LoginConfigBeans.class) static class CustomLoginProcessingUrlSaml2AuthenticationTokenConverterBean { private final Saml2AuthenticationTokenConverter authenticationConverter = mock( Saml2AuthenticationTokenConverter.class); @Bean SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((authz) -> authz.anyRequest().authenticated()) .saml2Login((saml2) -> saml2.loginProcessingUrl("/my/custom/url")); // @formatter:on return http.build(); } @Bean Saml2AuthenticationTokenConverter authenticationTokenConverter() { return this.authenticationConverter; } } @Configuration @EnableWebSecurity @EnableWebMvc @Import(Saml2LoginConfigBeans.class) static class CustomAuthenticationProviderConfig { private final AuthenticationProvider provider = spy(new OpenSaml5AuthenticationProvider()); @Bean SecurityFilterChain web(HttpSecurity http) throws Exception { http.authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated()) .saml2Login(Customizer.withDefaults()); return http.build(); } @Bean AuthenticationProvider provider() { return this.provider; } } static class Saml2LoginConfigBeans { @Bean SecurityContextRepository securityContextRepository() { return new HttpSessionSecurityContextRepository(); } @Bean RelyingPartyRegistrationRepository relyingPartyRegistrationRepository() { return spy(new InMemoryRelyingPartyRegistrationRepository(registration)); } } @RestController static class ResourceController { @GetMapping("/") String user(@AuthenticationPrincipal Saml2AuthenticatedPrincipal principal) { return principal.getName(); } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LogoutConfigurerTests.java ================================================ /* * 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 org.springframework.security.config.annotation.web.configurers.saml2; import java.nio.charset.StandardCharsets; import java.time.Instant; import java.util.Collection; import java.util.Collections; import java.util.Map; import java.util.function.Consumer; import java.util.function.Function; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.opensaml.saml.saml2.core.LogoutRequest; import org.opensaml.xmlsec.signature.support.SignatureConstants; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.http.HttpMethod; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.mock.web.MockHttpSession; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.config.ObjectPostProcessor; import org.springframework.security.config.annotation.SecurityContextChangedListenerConfig; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.security.saml2.core.Saml2Utils; import org.springframework.security.saml2.core.Saml2X509Credential; import org.springframework.security.saml2.core.TestSaml2X509Credentials; import org.springframework.security.saml2.provider.service.authentication.DefaultSaml2AuthenticatedPrincipal; import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication; import org.springframework.security.saml2.provider.service.authentication.TestOpenSamlObjects; import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutRequest; import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutRequestValidator; import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutResponse; import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutResponseValidator; import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutValidatorResult; import org.springframework.security.saml2.provider.service.registration.InMemoryRelyingPartyRegistrationRepository; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository; import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding; import org.springframework.security.saml2.provider.service.registration.TestRelyingPartyRegistrations; import org.springframework.security.saml2.provider.service.web.authentication.logout.HttpSessionLogoutRequestRepository; import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutRequestFilter; import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutRequestRepository; import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutRequestResolver; import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutResponseFilter; import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutResponseResolver; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.logout.LogoutFilter; import org.springframework.security.web.authentication.logout.LogoutHandler; import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; import org.springframework.security.web.servlet.TestMockHttpServletRequests; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.request.RequestPostProcessor; import org.springframework.web.util.UriComponentsBuilder; import org.springframework.web.util.UriUtils; import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.Matchers.containsString; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.mock; import static org.mockito.BDDMockito.verify; import static org.mockito.BDDMockito.verifyNoInteractions; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.spy; import static org.springframework.security.config.Customizer.withDefaults; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; import static org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher.pathPattern; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Tests for different Java configuration for {@link Saml2LogoutConfigurer} */ @ExtendWith(SpringTestContextExtension.class) public class Saml2LogoutConfigurerTests { @Autowired private ConfigurableApplicationContext context; @Autowired private RelyingPartyRegistrationRepository repository; private final Saml2LogoutRequestRepository logoutRequestRepository = new HttpSessionLogoutRequestRepository(); public final SpringTestContext spring = new SpringTestContext(this); @Autowired(required = false) MockMvc mvc; private Saml2Authentication user; String apLogoutRequest = "nZFBa4MwGIb/iuQeE2NTXFDLQAaC26Hrdtgt1dQFNMnyxdH9+zlboeyww275SN7nzcOX787jEH0qD9qaAiUxRZEyre206Qv0cnjAGdqVOchxYE40trdT2KuPSUGI5qQBcbkq0OSNsBI0CCNHBSK04vn+sREspsJ5G2xrBxRVc1AbGZa29xAcCEK8i9VZjm5QsfU9GZYWsoCJv5ShqK4K1Ow5p5LyU4aP6XaLN3cpw9mGctydjrxNaZt1XM5vASZVGwjShAIxyhJMU8z4gSWCM8GSmDH+hqLX1Xv+JLpaiiXsb+3+lpMAyv8IoVI6rEzQ4QvrLie3uBX+NMfr6l/waT6t0AumvI6/FlN+Aw=="; String apLogoutRequestSigAlg = SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256; String apLogoutRequestRelayState = "33591874-b123-4f2c-ab0d-2d0d84aa8b56"; String apLogoutRequestSignature = "oKqdzrmn2YAqXcwkow2lzRXr5PNHm0s/gWsRnaZYhC+Oq5ekK5uIKQYvtmNR94HJjDe1VRs+vVQCYivgdoTzBV2ZlffTXZmYsCsY9q4jbCWR6R5CbhU73/MkKQsPcyVvMhNYxnDYapIlxDsfoZNTboDEz3GM+HRoGRfl9emCXY0lPRYwqC4kpu7oMDBkafR0A09jPIxFuNpqlLPwUxL9m+DGkvDK3mFDN1xJcgZaK73HcuJe7Qh4huOrKNFetwc5EvqfiwgiWF6sfq9A+rZBfCIYo10NNLY7fNQAR2IqwcKtawHgTGWbeshRyFrwVYMR64EnClfxUHsHKf5kiZ2dlw=="; String apLogoutResponse = "fZHRa4MwEMb/Fcl7jEadGqplrAwK3Uvb9WFvZ4ydoInk4uj++1nXbmWMvhwcd9/3Jb9bLE99530oi63RBQn9gHhKS1O3+liQ1/0zzciyXCD0HR/ExhzN6LYKB6NReZNUo/ieFWS0WhjAFoWGXqFwUuweXzaC+4EYrHFGmo54K4Wu1eDmuHfnBhSM2cFXJ+iHTvnGHlk3x7DZmNlLGvHWq4Jstk0GUSjjiIZJI2lcpQnNeRLTAOo4fwCeQg3Trr6+cm/OqmnWVHECVGWQ0jgCSatsKvXUxhFvZF7xSYU4qrVGB9oVhAc8pEFEebLnkeBc8NyPePpGvMOV1/Q3cqEjZrG9hXKfCSAqe+ZAShio0q51n7StF+zW7gf9zoEb8U/7ZGrlHaAb1f0onLfFbpRSIRJWXkJ+bdm/Fy6/AA=="; String apLogoutResponseSigAlg = SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256; String apLogoutResponseRelayState = "8f63887a-ec7e-4149-b6a0-dd730017f315"; String apLogoutResponseSignature = "h2fDqSIBfmnkRHKDMY4IxkCXcI0w98ydNsnPmv1b7GTZCWLbJ+oxaP2yZNPw7wOWXTv86cTPwKLjx5halKy5C+hhWnT0haKhuMcUvHlsgAMBbJKLV+1afzL4O77cvAQJmMNRK7ugXGNV5PTEnd1U4voy134OgdD5XycYiFVRZOwP5H84eJ9xxlvqQwqDvZTcgiF/ZS4ioZgzgnIFcbagZQ12LWNh26OMaUpIW04kCeO6t2dUsxOL6nZWvNrX/Zx1sORIpu4doDUa1RYC8YnjZeQEzDqUVC/dBO/mbVJ/hbF9tD0jBUx7YIgoXpqsWK4TcCsvmlmhrJXvGxDyoAWu2Q=="; String rpLogoutRequest = "nZFBa4MwGIb/iuQeY6NlGtQykIHgdui6HXaLmrqAJlm+OLp/v0wrlB122CXkI3mfNw/JD5dpDD6FBalVgXZhhAKhOt1LNRTo5fSAU3Qoc+DTSA1r9KBndxQfswAX+KQCth4VaLaKaQ4SmOKTAOY69nz/2DAaRsxY7XSnRxRUPigVd0vbu3MGGCHchOLCJzOKUNuBjEsLWcDErmUoqKsCNcc+yc5tsudYpPwOJzHvcJv6pfdjEtNzl7XU3wWYRa3AceUKRCO6w1GM6f5EY0Ypo1lIk+gNBa+bt38kulqyJWxv7f6W4wDC/gih0hoslJPuC8s+J7e4Df7k43X1L/jsdxt0xZTX8dfHlN8="; String rpLogoutRequestId = "LRd49fb45a-e8a7-43ac-b8ac-d8a7432fc9b2"; String rpLogoutRequestRelayState = "8f63887a-ec7e-4149-b6a0-dd730017f315"; String rpLogoutRequestSignature = "h2fDqSIBfmnkRHKDMY4IxkCXcI0w98ydNsnPmv1b7GTZCWLbJ+oxaP2yZNPw7wOWXTv86cTPwKLjx5halKy5C+hhWnT0haKhuMcUvHlsgAMBbJKLV+1afzL4O77cvAQJmMNRK7ugXGNV5PTEnd1U4voy134OgdD5XycYiFVRZOwP5H84eJ9xxlvqQwqDvZTcgiF/ZS4ioZgzgnIFcbagZQ12LWNh26OMaUpIW04kCeO6t2dUsxOL6nZWvNrX/Zx1sORIpu4doDUa1RYC8YnjZeQEzDqUVC/dBO/mbVJ/hbF9tD0jBUx7YIgoXpqsWK4TcCsvmlmhrJXvGxDyoAWu2Q=="; private MockHttpServletRequest request; private MockHttpServletResponse response; @BeforeEach public void setup() { DefaultSaml2AuthenticatedPrincipal principal = new DefaultSaml2AuthenticatedPrincipal("user", Collections.emptyMap()); principal.setRelyingPartyRegistrationId("registration-id"); this.user = new Saml2Authentication(principal, "response", AuthorityUtils.createAuthorityList("ROLE_USER")); this.request = TestMockHttpServletRequests.post("/login/saml2/sso/test-rp").build(); this.response = new MockHttpServletResponse(); } @AfterEach public void cleanup() { if (this.context != null) { this.context.close(); } } @Test public void logoutWhenDefaultsAndNotSaml2LoginThenDefaultLogout() throws Exception { this.spring.register(Saml2LogoutDefaultsConfig.class).autowire(); TestingAuthenticationToken user = new TestingAuthenticationToken("user", "password"); MvcResult result = this.mvc.perform(post("/logout").with(authentication(user)).with(csrf())) .andExpect(status().isFound()) .andReturn(); String location = result.getResponse().getHeader("Location"); LogoutHandler logoutHandler = this.spring.getContext().getBean(LogoutHandler.class); assertThat(location).isEqualTo("/login?logout"); verify(logoutHandler).logout(any(), any(), any()); } @Test public void saml2LogoutWhenDefaultsThenLogsOutAndSendsLogoutRequest() throws Exception { this.spring.register(Saml2LogoutDefaultsConfig.class).autowire(); MvcResult result = this.mvc.perform(post("/logout").with(authentication(this.user)).with(csrf())) .andExpect(status().isFound()) .andReturn(); String location = result.getResponse().getHeader("Location"); LogoutHandler logoutHandler = this.spring.getContext().getBean(LogoutHandler.class); assertThat(location).startsWith("https://ap.example.org/logout/saml2/request"); verify(logoutHandler).logout(any(), any(), any()); } @Test public void saml2LogoutWhenUnauthenticatedThenEntryPoint() throws Exception { this.spring.register(Saml2LogoutDefaultsConfig.class).autowire(); this.mvc.perform(post("/logout").with(csrf())) .andExpect(status().isFound()) .andExpect(redirectedUrl("/login?logout")); } @Test public void saml2LogoutWhenMissingCsrfThen403() throws Exception { this.spring.register(Saml2LogoutDefaultsConfig.class).autowire(); this.mvc.perform(post("/logout").with(authentication(this.user))).andExpect(status().isForbidden()); verifyNoInteractions(getBean(LogoutHandler.class)); } @Test public void saml2LogoutWhenGetThenDefaultLogoutPage() throws Exception { this.spring.register(Saml2LogoutDefaultsConfig.class).autowire(); MvcResult result = this.mvc.perform(get("/logout").with(authentication(this.user))) .andExpect(status().isOk()) .andReturn(); assertThat(result.getResponse().getContentAsString()).contains("Are you sure you want to log out?"); verifyNoInteractions(getBean(LogoutHandler.class)); } @Test public void saml2LogoutWhenPutOrDeleteThen404() throws Exception { this.spring.register(Saml2LogoutDefaultsConfig.class).autowire(); this.mvc.perform(put("/logout").with(authentication(this.user)).with(csrf())).andExpect(status().isNotFound()); this.mvc.perform(delete("/logout").with(authentication(this.user)).with(csrf())) .andExpect(status().isNotFound()); verifyNoInteractions(this.spring.getContext().getBean(LogoutHandler.class)); } @Test public void saml2LogoutWhenNoRegistrationThen401() throws Exception { this.spring.register(Saml2LogoutDefaultsConfig.class).autowire(); DefaultSaml2AuthenticatedPrincipal principal = new DefaultSaml2AuthenticatedPrincipal("user", Collections.emptyMap()); principal.setRelyingPartyRegistrationId("wrong"); Saml2Authentication authentication = new Saml2Authentication(principal, "response", AuthorityUtils.createAuthorityList("ROLE_USER")); this.mvc.perform(post("/logout").with(authentication(authentication)).with(csrf())) .andExpect(status().isUnauthorized()); } @Test public void saml2LogoutWhenCsrfDisabledAndNoAuthenticationThenFinalRedirect() throws Exception { this.spring.register(Saml2LogoutCsrfDisabledConfig.class).autowire(); this.mvc.perform(post("/logout")); LogoutSuccessHandler logoutSuccessHandler = this.spring.getContext().getBean(LogoutSuccessHandler.class); verify(logoutSuccessHandler).onLogoutSuccess(any(), any(), any()); } @Test public void saml2LogoutWhenCustomLogoutRequestResolverThenUses() throws Exception { this.spring.register(Saml2LogoutComponentsConfig.class).autowire(); RelyingPartyRegistration registration = this.repository.findByRegistrationId("registration-id"); Saml2LogoutRequest logoutRequest = Saml2LogoutRequest.withRelyingPartyRegistration(registration) .samlRequest(this.rpLogoutRequest) .id(this.rpLogoutRequestId) .relayState(this.rpLogoutRequestRelayState) .parameters((params) -> params.put("Signature", this.rpLogoutRequestSignature)) .build(); given(getBean(Saml2LogoutRequestResolver.class).resolve(any(), any())).willReturn(logoutRequest); this.mvc.perform(post("/logout").with(authentication(this.user)).with(csrf())); verify(getBean(Saml2LogoutRequestResolver.class)).resolve(any(), any()); } @Test public void saml2LogoutRequestWhenDefaultsThenLogsOutAndSendsLogoutResponse() throws Exception { this.spring.register(Saml2LogoutDefaultsConfig.class).autowire(); DefaultSaml2AuthenticatedPrincipal principal = new DefaultSaml2AuthenticatedPrincipal("user", Collections.emptyMap()); principal.setRelyingPartyRegistrationId("get"); Saml2Authentication user = new Saml2Authentication(principal, "response", AuthorityUtils.createAuthorityList("ROLE_USER")); MvcResult result = this.mvc .perform(get("/logout/saml2/slo").param("SAMLRequest", this.apLogoutRequest) .param("RelayState", this.apLogoutRequestRelayState) .param("SigAlg", this.apLogoutRequestSigAlg) .param("Signature", this.apLogoutRequestSignature) .with(samlQueryString()) .with(authentication(user))) .andExpect(status().isFound()) .andReturn(); String location = result.getResponse().getHeader("Location"); assertThat(location).startsWith("https://ap.example.org/logout/saml2/response"); verify(getBean(LogoutHandler.class)).logout(any(), any(), any()); } @Test public void saml2LogoutRequestWhenCustomSecurityContextHolderStrategyThenUses() throws Exception { this.spring.register(Saml2LogoutDefaultsConfig.class, SecurityContextChangedListenerConfig.class).autowire(); DefaultSaml2AuthenticatedPrincipal principal = new DefaultSaml2AuthenticatedPrincipal("user", Collections.emptyMap()); principal.setRelyingPartyRegistrationId("get"); Saml2Authentication user = new Saml2Authentication(principal, "response", AuthorityUtils.createAuthorityList("ROLE_USER")); MvcResult result = this.mvc .perform(get("/logout/saml2/slo").param("SAMLRequest", this.apLogoutRequest) .param("RelayState", this.apLogoutRequestRelayState) .param("SigAlg", this.apLogoutRequestSigAlg) .param("Signature", this.apLogoutRequestSignature) .with(samlQueryString()) .with(authentication(user))) .andExpect(status().isFound()) .andReturn(); String location = result.getResponse().getHeader("Location"); assertThat(location).startsWith("https://ap.example.org/logout/saml2/response"); verify(getBean(LogoutHandler.class)).logout(any(), any(), any()); verify(getBean(SecurityContextHolderStrategy.class), atLeastOnce()).getContext(); } // gh-11235 @Test public void saml2LogoutRequestWhenLowercaseEncodingThenLogsOutAndSendsLogoutResponse() throws Exception { this.spring.register(Saml2LogoutDefaultsConfig.class).autowire(); String apLogoutRequest = "nZFNa4QwEIb/iuQeP6K7dYO6FKQg2B622x56G3WwgiY2E8v239fqCksPPfSWIXmfNw+THC9D73yi\r\n" + "oU6rlAWuzxxUtW461abs5fzAY3bMEoKhF6Msdasne8KPCck6c1KRXK9SNhklNVBHUsGAJG0tn+8f\r\n" + "SylcX45GW13rnjn5HOwU2KXt3dqRpOeZ0cULDGOPrjat1y8t3gL2zFrGnCJPWXkKcR8KCHY8xmrP\r\n" + "Iz868OpOVLwO4wohggagmd8STVgosqBsyoQvBPd3XITnIJaRL8PYjcThjTmvm/f8SXa1lEvY3Nr9\r\n" + "LQdEaH6EWAYjR2U7+8W7JvFucRv8aY4X+b/g03zaoCsmu46/FpN9Aw=="; String apLogoutRequestRelayState = "d118dbd5-3853-4268-b3e5-c40fc033fa2f"; String apLogoutRequestSignature = "VZ7rWa5u3hIX60fAQs/gBQZWDP2BAIlCMMrNrTHafoKKj0uXWnuITYLuL8NdsWmyQN0+fqWW4X05+BqiLpL80jHLmQR5RVqqL1EtVv1SpPUna938lgz2sOliuYmfQNj4Bmd+Z5G1K6QhbVrtfb7TQHURjUafzfRm8+jGz3dPjVBrn/rD/umfGoSn6RuWngugcMNL4U0A+JcEh1NSfSYNVz7y+MqlW1UhX2kF86rm97ERCrxay7Gh/bI2f3fJPJ1r+EyLjzrDUkqw5cva3rVlFgEQouMVu35lUJn7SFompW8oTxkI23oc/t+AGZqaBupNITNdjyGCBpfukZ69EZrj8g=="; DefaultSaml2AuthenticatedPrincipal principal = new DefaultSaml2AuthenticatedPrincipal("user", Collections.emptyMap()); principal.setRelyingPartyRegistrationId("get"); Saml2Authentication user = new Saml2Authentication(principal, "response", AuthorityUtils.createAuthorityList("ROLE_USER")); MvcResult result = this.mvc .perform(get("/logout/saml2/slo").param("SAMLRequest", apLogoutRequest) .param("RelayState", apLogoutRequestRelayState) .param("SigAlg", this.apLogoutRequestSigAlg) .param("Signature", apLogoutRequestSignature) .with(new SamlQueryStringRequestPostProcessor(true)) .with(authentication(user))) .andExpect(status().isFound()) .andReturn(); String location = result.getResponse().getHeader("Location"); assertThat(location).startsWith("https://ap.example.org/logout/saml2/response"); verify(getBean(LogoutHandler.class)).logout(any(), any(), any()); } // gh-12346 @Test public void saml2LogoutRequestWhenLowercaseEncodingAndDifferentQueryParamOrderThenLogsOutAndSendsLogoutResponse() throws Exception { this.spring.register(Saml2LogoutDefaultsConfig.class).autowire(); String apLogoutRequest = "nZFNa4QwEIb/iuQeP6K7dYO6FKQg2B622x56G3WwgiY2E8v239fqCksPPfSWIXmfNw+THC9D73yi\r\n" + "oU6rlAWuzxxUtW461abs5fzAY3bMEoKhF6Msdasne8KPCck6c1KRXK9SNhklNVBHUsGAJG0tn+8f\r\n" + "SylcX45GW13rnjn5HOwU2KXt3dqRpOeZ0cULDGOPrjat1y8t3gL2zFrGnCJPWXkKcR8KCHY8xmrP\r\n" + "Iz868OpOVLwO4wohggagmd8STVgosqBsyoQvBPd3XITnIJaRL8PYjcThjTmvm/f8SXa1lEvY3Nr9\r\n" + "LQdEaH6EWAYjR2U7+8W7JvFucRv8aY4X+b/g03zaoCsmu46/FpN9Aw=="; String apLogoutRequestRelayState = "d118dbd5-3853-4268-b3e5-c40fc033fa2f"; String apLogoutRequestSignature = "VZ7rWa5u3hIX60fAQs/gBQZWDP2BAIlCMMrNrTHafoKKj0uXWnuITYLuL8NdsWmyQN0+fqWW4X05+BqiLpL80jHLmQR5RVqqL1EtVv1SpPUna938lgz2sOliuYmfQNj4Bmd+Z5G1K6QhbVrtfb7TQHURjUafzfRm8+jGz3dPjVBrn/rD/umfGoSn6RuWngugcMNL4U0A+JcEh1NSfSYNVz7y+MqlW1UhX2kF86rm97ERCrxay7Gh/bI2f3fJPJ1r+EyLjzrDUkqw5cva3rVlFgEQouMVu35lUJn7SFompW8oTxkI23oc/t+AGZqaBupNITNdjyGCBpfukZ69EZrj8g=="; DefaultSaml2AuthenticatedPrincipal principal = new DefaultSaml2AuthenticatedPrincipal("user", Collections.emptyMap()); principal.setRelyingPartyRegistrationId("get"); Saml2Authentication user = new Saml2Authentication(principal, "response", AuthorityUtils.createAuthorityList("ROLE_USER")); MvcResult result = this.mvc .perform(get("/logout/saml2/slo").param("SAMLRequest", apLogoutRequest) .param("SigAlg", this.apLogoutRequestSigAlg) .param("RelayState", apLogoutRequestRelayState) .param("Signature", apLogoutRequestSignature) .with(new SamlQueryStringRequestPostProcessor(true)) .with(authentication(user))) .andExpect(status().isFound()) .andReturn(); String location = result.getResponse().getHeader("Location"); assertThat(location).startsWith("https://ap.example.org/logout/saml2/response"); verify(getBean(LogoutHandler.class)).logout(any(), any(), any()); } @Test public void saml2LogoutRequestWhenNoRegistrationThen400() throws Exception { this.spring.register(Saml2LogoutDefaultsConfig.class).autowire(); DefaultSaml2AuthenticatedPrincipal principal = new DefaultSaml2AuthenticatedPrincipal("user", Collections.emptyMap()); principal.setRelyingPartyRegistrationId("wrong"); Saml2Authentication user = new Saml2Authentication(principal, "response", AuthorityUtils.createAuthorityList("ROLE_USER")); this.mvc .perform(get("/logout/saml2/slo").param("SAMLRequest", this.apLogoutRequest) .param("RelayState", this.apLogoutRequestRelayState) .param("SigAlg", this.apLogoutRequestSigAlg) .param("Signature", this.apLogoutRequestSignature) .with(authentication(user))) .andExpect(status().isBadRequest()); verifyNoInteractions(getBean(LogoutHandler.class)); } @Test public void saml2LogoutRequestWhenInvalidSamlRequestThen302Redirect() throws Exception { this.spring.register(Saml2LogoutDefaultsConfig.class).autowire(); this.mvc .perform(get("/logout/saml2/slo").param("SAMLRequest", this.apLogoutRequest) .param("RelayState", this.apLogoutRequestRelayState) .param("SigAlg", this.apLogoutRequestSigAlg) .with(authentication(this.user))) .andExpect(status().isFound()); verifyNoInteractions(getBean(LogoutHandler.class)); } @Test public void saml2LogoutRequestWhenCustomLogoutRequestHandlerThenUses() throws Exception { this.spring.register(Saml2LogoutComponentsConfig.class).autowire(); RelyingPartyRegistration registration = this.repository.findByRegistrationId("registration-id"); LogoutRequest logoutRequest = TestOpenSamlObjects.assertingPartyLogoutRequest(registration); logoutRequest.setIssueInstant(Instant.now()); given(getBean(Saml2LogoutRequestValidator.class).validate(any())) .willReturn(Saml2LogoutValidatorResult.success()); Saml2LogoutResponse logoutResponse = Saml2LogoutResponse.withRelyingPartyRegistration(registration) .samlResponse("samlResponse") .build(); given(getBean(Saml2LogoutResponseResolver.class).resolve(any(), any())).willReturn(logoutResponse); this.mvc.perform(post("/logout/saml2/slo").param("SAMLRequest", "samlRequest").with(authentication(this.user))) .andReturn(); verify(getBean(Saml2LogoutRequestValidator.class)).validate(any()); verify(getBean(Saml2LogoutResponseResolver.class)).resolve(any(), any()); } @Test public void saml2LogoutResponseWhenDefaultsThenRedirects() throws Exception { this.spring.register(Saml2LogoutDefaultsConfig.class).autowire(); RelyingPartyRegistration registration = this.repository.findByRegistrationId("get"); Saml2LogoutRequest logoutRequest = Saml2LogoutRequest.withRelyingPartyRegistration(registration) .samlRequest(this.rpLogoutRequest) .id(this.rpLogoutRequestId) .relayState(this.rpLogoutRequestRelayState) .parameters((params) -> params.put("Signature", this.rpLogoutRequestSignature)) .build(); this.logoutRequestRepository.saveLogoutRequest(logoutRequest, this.request, this.response); this.request.setParameter("RelayState", logoutRequest.getRelayState()); assertThat(this.logoutRequestRepository.loadLogoutRequest(this.request)).isNotNull(); this.mvc .perform(get("/logout/saml2/slo").session(((MockHttpSession) this.request.getSession())) .param("SAMLResponse", this.apLogoutResponse) .param("RelayState", this.apLogoutResponseRelayState) .param("SigAlg", this.apLogoutResponseSigAlg) .param("Signature", this.apLogoutResponseSignature) .with(samlQueryString())) .andExpect(status().isFound()) .andExpect(redirectedUrl("/login?logout")); verifyNoInteractions(getBean(LogoutHandler.class)); assertThat(this.logoutRequestRepository.loadLogoutRequest(this.request)).isNull(); } @Test public void saml2LogoutResponseWhenInvalidSamlResponseThen401() throws Exception { this.spring.register(Saml2LogoutDefaultsConfig.class).autowire(); RelyingPartyRegistration registration = this.repository.findByRegistrationId("registration-id"); Saml2LogoutRequest logoutRequest = Saml2LogoutRequest.withRelyingPartyRegistration(registration) .samlRequest(this.rpLogoutRequest) .id(this.rpLogoutRequestId) .relayState(this.rpLogoutRequestRelayState) .parameters((params) -> params.put("Signature", this.rpLogoutRequestSignature)) .build(); this.logoutRequestRepository.saveLogoutRequest(logoutRequest, this.request, this.response); String deflatedApLogoutResponse = Saml2Utils.samlEncode( Saml2Utils.samlInflate(Saml2Utils.samlDecode(this.apLogoutResponse)).getBytes(StandardCharsets.UTF_8)); this.mvc .perform(post("/logout/saml2/slo").session((MockHttpSession) this.request.getSession()) .param("SAMLResponse", deflatedApLogoutResponse) .param("RelayState", this.rpLogoutRequestRelayState) .param("SigAlg", this.apLogoutRequestSigAlg) .param("Signature", this.apLogoutResponseSignature) .with(samlQueryString())) .andExpect(status().reason(containsString("invalid_signature"))) .andExpect(status().isUnauthorized()); verifyNoInteractions(getBean(LogoutHandler.class)); } @Test public void saml2LogoutResponseWhenCustomLogoutResponseHandlerThenUses() throws Exception { this.spring.register(Saml2LogoutComponentsConfig.class).autowire(); RelyingPartyRegistration registration = this.repository.findByRegistrationId("get"); Saml2LogoutRequest logoutRequest = Saml2LogoutRequest.withRelyingPartyRegistration(registration) .samlRequest(this.rpLogoutRequest) .id(this.rpLogoutRequestId) .relayState(this.rpLogoutRequestRelayState) .parameters((params) -> params.put("Signature", this.rpLogoutRequestSignature)) .build(); given(getBean(Saml2LogoutRequestRepository.class).removeLogoutRequest(any(), any())).willReturn(logoutRequest); given(getBean(Saml2LogoutResponseValidator.class).validate(any())) .willReturn(Saml2LogoutValidatorResult.success()); this.mvc.perform(get("/logout/saml2/slo").param("SAMLResponse", "samlResponse")).andReturn(); verify(getBean(Saml2LogoutResponseValidator.class)).validate(any()); } // gh-11363 @Test public void saml2LogoutWhenCustomLogoutRequestRepositoryThenUses() throws Exception { this.spring.register(Saml2LogoutComponentsConfig.class).autowire(); RelyingPartyRegistration registration = this.repository.findByRegistrationId("registration-id"); Saml2LogoutRequest logoutRequest = Saml2LogoutRequest.withRelyingPartyRegistration(registration) .samlRequest(this.rpLogoutRequest) .id(this.rpLogoutRequestId) .relayState(this.rpLogoutRequestRelayState) .parameters((params) -> params.put("Signature", this.rpLogoutRequestSignature)) .build(); given(getBean(Saml2LogoutRequestResolver.class).resolve(any(), any())).willReturn(logoutRequest); this.mvc.perform(post("/logout").with(authentication(this.user)).with(csrf())); verify(getBean(Saml2LogoutRequestRepository.class)).saveLogoutRequest(eq(logoutRequest), any(), any()); } @Test public void saml2LogoutWhenLogoutGetThenLogsOutAndSendsLogoutRequest() throws Exception { this.spring.register(Saml2LogoutWithHttpGet.class).autowire(); MvcResult result = this.mvc.perform(get("/logout").with(authentication(this.user))) .andExpect(status().isFound()) .andReturn(); String location = result.getResponse().getHeader("Location"); LogoutHandler logoutHandler = this.spring.getContext().getBean(LogoutHandler.class); assertThat(location).startsWith("https://ap.example.org/logout/saml2/request"); verify(logoutHandler).logout(any(), any(), any()); } @Test public void saml2LogoutWhenSaml2LogoutRequestFilterPostProcessedThenUses() { Saml2DefaultsWithObjectPostProcessorConfig.objectPostProcessor = spy(ReflectingObjectPostProcessor.class); this.spring.register(Saml2DefaultsWithObjectPostProcessorConfig.class).autowire(); verify(Saml2DefaultsWithObjectPostProcessorConfig.objectPostProcessor) .postProcess(any(Saml2LogoutRequestFilter.class)); } @Test public void saml2LogoutWhenSaml2LogoutResponseFilterPostProcessedThenUses() { Saml2DefaultsWithObjectPostProcessorConfig.objectPostProcessor = spy(ReflectingObjectPostProcessor.class); this.spring.register(Saml2DefaultsWithObjectPostProcessorConfig.class).autowire(); verify(Saml2DefaultsWithObjectPostProcessorConfig.objectPostProcessor) .postProcess(any(Saml2LogoutResponseFilter.class)); } @Test public void saml2LogoutWhenLogoutFilterPostProcessedThenUses() { Saml2DefaultsWithObjectPostProcessorConfig.objectPostProcessor = spy(ReflectingObjectPostProcessor.class); this.spring.register(Saml2DefaultsWithObjectPostProcessorConfig.class).autowire(); verify(Saml2DefaultsWithObjectPostProcessorConfig.objectPostProcessor, atLeastOnce()) .postProcess(any(LogoutFilter.class)); } private T getBean(Class clazz) { return this.spring.getContext().getBean(clazz); } private SamlQueryStringRequestPostProcessor samlQueryString() { return new SamlQueryStringRequestPostProcessor(); } @Configuration @EnableWebSecurity @Import(Saml2LoginConfigBeans.class) static class Saml2LogoutDefaultsConfig { LogoutHandler mockLogoutHandler = mock(LogoutHandler.class); @Bean SecurityFilterChain web(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated()) .logout((logout) -> logout.addLogoutHandler(this.mockLogoutHandler)) .saml2Login(withDefaults()) .saml2Logout(withDefaults()); return http.build(); // @formatter:on } @Bean LogoutHandler logoutHandler() { return this.mockLogoutHandler; } } @Configuration @EnableWebSecurity @Import(Saml2LoginConfigBeans.class) static class Saml2LogoutCsrfDisabledConfig { LogoutSuccessHandler mockLogoutSuccessHandler = mock(LogoutSuccessHandler.class); @Bean SecurityFilterChain web(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated()) .logout((logout) -> logout.logoutSuccessHandler(this.mockLogoutSuccessHandler)) .saml2Login(withDefaults()) .saml2Logout(withDefaults()) .csrf((csrf) -> csrf.disable()); return http.build(); // @formatter:on } @Bean LogoutSuccessHandler logoutSuccessHandler() { return this.mockLogoutSuccessHandler; } } @Configuration @EnableWebSecurity @Import(Saml2LoginConfigBeans.class) static class Saml2LogoutWithHttpGet { LogoutHandler mockLogoutHandler = mock(LogoutHandler.class); @Bean SecurityFilterChain web(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated()) .logout((logout) -> logout.addLogoutHandler(this.mockLogoutHandler)) .saml2Login(withDefaults()) .saml2Logout((saml2) -> saml2.addObjectPostProcessor(new ObjectPostProcessor() { @Override public O postProcess(O filter) { filter.setLogoutRequestMatcher(pathPattern(HttpMethod.GET, "/logout")); return filter; } })); return http.build(); // @formatter:on } @Bean LogoutHandler logoutHandler() { return this.mockLogoutHandler; } } @Configuration @EnableWebSecurity @Import(Saml2LoginConfigBeans.class) static class Saml2DefaultsWithObjectPostProcessorConfig { static ObjectPostProcessor objectPostProcessor; @Bean SecurityFilterChain web(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated()) .saml2Login(withDefaults()) .saml2Logout(withDefaults()); return http.build(); // @formatter:on } @Bean static ObjectPostProcessor objectPostProcessor() { return objectPostProcessor; } } @Configuration @EnableWebSecurity @Import(Saml2LoginConfigBeans.class) static class Saml2LogoutComponentsConfig { Saml2LogoutRequestRepository logoutRequestRepository = mock(Saml2LogoutRequestRepository.class); Saml2LogoutRequestValidator logoutRequestValidator = mock(Saml2LogoutRequestValidator.class); Saml2LogoutRequestResolver logoutRequestResolver = mock(Saml2LogoutRequestResolver.class); Saml2LogoutResponseValidator logoutResponseValidator = mock(Saml2LogoutResponseValidator.class); Saml2LogoutResponseResolver logoutResponseResolver = mock(Saml2LogoutResponseResolver.class); @Bean SecurityFilterChain web(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated()) .saml2Login(withDefaults()) .saml2Logout((logout) -> logout .logoutRequest((request) -> request .logoutRequestRepository(this.logoutRequestRepository) .logoutRequestValidator(this.logoutRequestValidator) .logoutRequestResolver(this.logoutRequestResolver) ) .logoutResponse((response) -> response .logoutResponseValidator(this.logoutResponseValidator) .logoutResponseResolver(this.logoutResponseResolver) ) ); return http.build(); // @formatter:on } @Bean Saml2LogoutRequestRepository logoutRequestRepository() { return this.logoutRequestRepository; } @Bean Saml2LogoutRequestValidator logoutRequestAuthenticator() { return this.logoutRequestValidator; } @Bean Saml2LogoutRequestResolver logoutRequestResolver() { return this.logoutRequestResolver; } @Bean Saml2LogoutResponseValidator logoutResponseAuthenticator() { return this.logoutResponseValidator; } @Bean Saml2LogoutResponseResolver logoutResponseResolver() { return this.logoutResponseResolver; } } static class Saml2LoginConfigBeans { @Bean RelyingPartyRegistrationRepository relyingPartyRegistrationRepository() { Saml2X509Credential signing = TestSaml2X509Credentials.assertingPartySigningCredential(); Saml2X509Credential verification = TestSaml2X509Credentials.relyingPartyVerifyingCredential(); RelyingPartyRegistration.Builder withCreds = TestRelyingPartyRegistrations.noCredentials() .signingX509Credentials(credential(signing)) .assertingPartyMetadata((party) -> party.verificationX509Credentials(credential(verification))); RelyingPartyRegistration post = withCreds.build(); RelyingPartyRegistration get = withCreds.registrationId("get") .singleLogoutServiceBinding(Saml2MessageBinding.REDIRECT) .build(); RelyingPartyRegistration ap = withCreds.registrationId("ap") .entityId("ap-entity-id") .assertingPartyMetadata( (party) -> party.singleLogoutServiceLocation("https://rp.example.org/logout/saml2/request") .singleLogoutServiceResponseLocation("https://rp.example.org/logout/saml2/response")) .build(); return new InMemoryRelyingPartyRegistrationRepository(ap, get, post); } private Consumer> credential(Saml2X509Credential credential) { return (credentials) -> credentials.add(credential); } } static class ReflectingObjectPostProcessor implements ObjectPostProcessor { @Override public O postProcess(O object) { return object; } } static class SamlQueryStringRequestPostProcessor implements RequestPostProcessor { private Function urlEncodingPostProcessor = Function.identity(); SamlQueryStringRequestPostProcessor() { this(false); } SamlQueryStringRequestPostProcessor(boolean lowercased) { if (lowercased) { Pattern encoding = Pattern.compile("%\\d[A-Fa-f]"); this.urlEncodingPostProcessor = (encoded) -> { Matcher m = encoding.matcher(encoded); while (m.find()) { String found = m.group(0); encoded = encoded.replace(found, found.toLowerCase()); } return encoded; }; } } @Override public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) { UriComponentsBuilder builder = UriComponentsBuilder.newInstance(); for (Map.Entry entries : request.getParameterMap().entrySet()) { builder.queryParam(entries.getKey(), UriUtils.encode(entries.getValue()[0], StandardCharsets.ISO_8859_1)); } String queryString = this.urlEncodingPostProcessor.apply(builder.build(true).toUriString().substring(1)); request.setQueryString(queryString); return request; } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2MetadataConfigurerTests.java ================================================ /* * 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 org.springframework.security.config.annotation.web.configurers.saml2; import com.google.common.net.HttpHeaders; import jakarta.servlet.http.HttpServletRequest; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.saml2.provider.service.metadata.OpenSaml5MetadataResolver; import org.springframework.security.saml2.provider.service.metadata.RequestMatcherMetadataResponseResolver; import org.springframework.security.saml2.provider.service.metadata.Saml2MetadataResponse; import org.springframework.security.saml2.provider.service.metadata.Saml2MetadataResponseResolver; import org.springframework.security.saml2.provider.service.registration.InMemoryRelyingPartyRegistrationRepository; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository; import org.springframework.security.saml2.provider.service.registration.TestRelyingPartyRegistrations; import org.springframework.security.web.SecurityFilterChain; import org.springframework.test.web.servlet.MockMvc; import static org.hamcrest.Matchers.containsString; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Tests for {@link Saml2MetadataConfigurer} */ @ExtendWith(SpringTestContextExtension.class) public class Saml2MetadataConfigurerTests { static RelyingPartyRegistration registration = TestRelyingPartyRegistrations.relyingPartyRegistration().build(); public final SpringTestContext spring = new SpringTestContext(this); @Autowired(required = false) MockMvc mvc; @Test void saml2MetadataRegistrationIdWhenDefaultsThenReturnsMetadata() throws Exception { this.spring.register(DefaultConfig.class).autowire(); String filename = "saml-" + registration.getRegistrationId() + "-metadata.xml"; this.mvc.perform(get("/saml2/metadata/" + registration.getRegistrationId())) .andExpect(status().isOk()) .andExpect(header().string(HttpHeaders.CONTENT_DISPOSITION, containsString(filename))) .andExpect(content().string(containsString("md:EntityDescriptor"))); } @Test void saml2MetadataRegistrationIdWhenWrongIdThenUnauthorized() throws Exception { this.spring.register(DefaultConfig.class).autowire(); this.mvc.perform(get("/saml2/metadata/" + registration.getRegistrationId() + "wrong")) .andExpect(status().isUnauthorized()); } @Test void saml2MetadataWhenDefaultsThenReturnsMetadata() throws Exception { this.spring.register(DefaultConfig.class).autowire(); this.mvc.perform(get("/saml2/metadata")) .andExpect(status().isOk()) .andExpect(header().string(HttpHeaders.CONTENT_DISPOSITION, containsString("-metadata.xml"))) .andExpect(content().string(containsString("md:EntityDescriptor"))); } @Test void saml2MetadataWhenMetadataResponseResolverThenUses() throws Exception { this.spring.register(DefaultConfig.class, MetadataResponseResolverConfig.class).autowire(); Saml2MetadataResponseResolver metadataResponseResolver = this.spring.getContext() .getBean(Saml2MetadataResponseResolver.class); given(metadataResponseResolver.resolve(any(HttpServletRequest.class))) .willReturn(new Saml2MetadataResponse("metadata", "filename")); this.mvc.perform(get("/saml2/metadata")) .andExpect(status().isOk()) .andExpect(header().string(HttpHeaders.CONTENT_DISPOSITION, containsString("filename"))) .andExpect(content().string(containsString("metadata"))); verify(metadataResponseResolver).resolve(any(HttpServletRequest.class)); } @Test void saml2MetadataWhenMetadataResponseResolverDslThenUses() throws Exception { this.spring.register(MetadataResponseResolverDslConfig.class).autowire(); this.mvc.perform(get("/saml2/metadata")) .andExpect(status().isOk()) .andExpect(header().string(HttpHeaders.CONTENT_DISPOSITION, containsString("filename"))) .andExpect(content().string(containsString("metadata"))); } @Test void saml2MetadataWhenMetadataUrlThenUses() throws Exception { this.spring.register(MetadataUrlConfig.class).autowire(); String filename = "saml-" + registration.getRegistrationId() + "-metadata.xml"; this.mvc.perform(get("/saml/metadata")) .andExpect(status().isOk()) .andExpect(header().string(HttpHeaders.CONTENT_DISPOSITION, containsString(filename))) .andExpect(content().string(containsString("md:EntityDescriptor"))); this.mvc.perform(get("/saml2/metadata")).andExpect(status().isForbidden()); } @EnableWebSecurity @Configuration @Import(RelyingPartyRegistrationConfig.class) static class DefaultConfig { @Bean SecurityFilterChain filters(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated()) .saml2Metadata(Customizer.withDefaults()); return http.build(); // @formatter:on } } @EnableWebSecurity @Configuration @Import(RelyingPartyRegistrationConfig.class) static class MetadataUrlConfig { @Bean SecurityFilterChain filters(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated()) .saml2Metadata((saml2) -> saml2.metadataUrl("/saml/metadata")); return http.build(); // @formatter:on } // should ignore @Bean Saml2MetadataResponseResolver metadataResponseResolver(RelyingPartyRegistrationRepository registrations) { return new RequestMatcherMetadataResponseResolver(registrations, new OpenSaml5MetadataResolver()); } } @EnableWebSecurity @Configuration @Import(RelyingPartyRegistrationConfig.class) static class MetadataResponseResolverDslConfig { Saml2MetadataResponseResolver metadataResponseResolver = mock(Saml2MetadataResponseResolver.class); { given(this.metadataResponseResolver.resolve(any(HttpServletRequest.class))) .willReturn(new Saml2MetadataResponse("metadata", "filename")); } @Bean SecurityFilterChain filters(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated()) .saml2Metadata((saml2) -> saml2.metadataResponseResolver(this.metadataResponseResolver)); return http.build(); // @formatter:on } } @Configuration static class MetadataResponseResolverConfig { Saml2MetadataResponseResolver metadataResponseResolver = mock(Saml2MetadataResponseResolver.class); @Bean Saml2MetadataResponseResolver metadataResponseResolver() { return this.metadataResponseResolver; } } @Configuration static class RelyingPartyRegistrationConfig { RelyingPartyRegistrationRepository registrations = new InMemoryRelyingPartyRegistrationRepository(registration); @Bean RelyingPartyRegistrationRepository registrations() { return this.registrations; } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/annotation/web/configurers/saml2/TestSaml2Credentials.java ================================================ /* * 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 org.springframework.security.config.annotation.web.configurers.saml2; import java.io.ByteArrayInputStream; import java.nio.charset.StandardCharsets; import java.security.PrivateKey; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import org.springframework.security.converter.RsaKeyConverters; import org.springframework.security.saml2.core.Saml2X509Credential; /** * Preconfigured SAML credentials for SAML integration tests. */ public final class TestSaml2Credentials { private TestSaml2Credentials() { } static Saml2X509Credential verificationCertificate() { // @formatter:off String certificate = "-----BEGIN CERTIFICATE-----\n" + "MIIEEzCCAvugAwIBAgIJAIc1qzLrv+5nMA0GCSqGSIb3DQEBCwUAMIGfMQswCQYD\n" + "VQQGEwJVUzELMAkGA1UECAwCQ08xFDASBgNVBAcMC0Nhc3RsZSBSb2NrMRwwGgYD\n" + "VQQKDBNTYW1sIFRlc3RpbmcgU2VydmVyMQswCQYDVQQLDAJJVDEgMB4GA1UEAwwX\n" + "c2ltcGxlc2FtbHBocC5jZmFwcHMuaW8xIDAeBgkqhkiG9w0BCQEWEWZoYW5pa0Bw\n" + "aXZvdGFsLmlvMB4XDTE1MDIyMzIyNDUwM1oXDTI1MDIyMjIyNDUwM1owgZ8xCzAJ\n" + "BgNVBAYTAlVTMQswCQYDVQQIDAJDTzEUMBIGA1UEBwwLQ2FzdGxlIFJvY2sxHDAa\n" + "BgNVBAoME1NhbWwgVGVzdGluZyBTZXJ2ZXIxCzAJBgNVBAsMAklUMSAwHgYDVQQD\n" + "DBdzaW1wbGVzYW1scGhwLmNmYXBwcy5pbzEgMB4GCSqGSIb3DQEJARYRZmhhbmlr\n" + "QHBpdm90YWwuaW8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC4cn62\n" + "E1xLqpN34PmbrKBbkOXFjzWgJ9b+pXuaRft6A339uuIQeoeH5qeSKRVTl32L0gdz\n" + "2ZivLwZXW+cqvftVW1tvEHvzJFyxeTW3fCUeCQsebLnA2qRa07RkxTo6Nf244mWW\n" + "RDodcoHEfDUSbxfTZ6IExSojSIU2RnD6WllYWFdD1GFpBJOmQB8rAc8wJIBdHFdQ\n" + "nX8Ttl7hZ6rtgqEYMzYVMuJ2F2r1HSU1zSAvwpdYP6rRGFRJEfdA9mm3WKfNLSc5\n" + "cljz0X/TXy0vVlAV95l9qcfFzPmrkNIst9FZSwpvB49LyAVke04FQPPwLgVH4gph\n" + "iJH3jvZ7I+J5lS8VAgMBAAGjUDBOMB0GA1UdDgQWBBTTyP6Cc5HlBJ5+ucVCwGc5\n" + "ogKNGzAfBgNVHSMEGDAWgBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAMBgNVHRMEBTAD\n" + "AQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAvMS4EQeP/ipV4jOG5lO6/tYCb/iJeAduO\n" + "nRhkJk0DbX329lDLZhTTL/x/w/9muCVcvLrzEp6PN+VWfw5E5FWtZN0yhGtP9R+v\n" + "ZnrV+oc2zGD+no1/ySFOe3EiJCO5dehxKjYEmBRv5sU/LZFKZpozKN/BMEa6CqLu\n" + "xbzb7ykxVr7EVFXwltPxzE9TmL9OACNNyF5eJHWMRMllarUvkcXlh4pux4ks9e6z\n" + "V9DQBy2zds9f1I3qxg0eX6JnGrXi/ZiCT+lJgVe3ZFXiejiLAiKB04sXW3ti0LW3\n" + "lx13Y1YlQ4/tlpgTgfIJxKV6nyPiLoK0nywbMd+vpAirDt2Oc+hk\n" + "-----END CERTIFICATE-----"; // @formatter:on return new Saml2X509Credential(x509Certificate(certificate), Saml2X509Credential.Saml2X509CredentialType.VERIFICATION); } static X509Certificate x509Certificate(String source) { try { final CertificateFactory factory = CertificateFactory.getInstance("X.509"); return (X509Certificate) factory .generateCertificate(new ByteArrayInputStream(source.getBytes(StandardCharsets.UTF_8))); } catch (Exception ex) { throw new IllegalArgumentException(ex); } } static Saml2X509Credential signingCredential() { // @formatter:off String key = "-----BEGIN PRIVATE KEY-----\n" + "MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBANG7v8QjQGU3MwQE\n" + "VUBxvH6Uuiy/MhZT7TV0ZNjyAF2ExA1gpn3aUxx6jYK5UnrpxRRE/KbeLucYbOhK\n" + "cDECt77Rggz5TStrOta0BQTvfluRyoQtmQ5Nkt6Vqg7O2ZapFt7k64Sal7AftzH6\n" + "Q2BxWN1y04bLdDrH4jipqRj/2qEFAgMBAAECgYEAj4ExY1jjdN3iEDuOwXuRB+Nn\n" + "x7pC4TgntE2huzdKvLJdGvIouTArce8A6JM5NlTBvm69mMepvAHgcsiMH1zGr5J5\n" + "wJz23mGOyhM1veON41/DJTVG+cxq4soUZhdYy3bpOuXGMAaJ8QLMbQQoivllNihd\n" + "vwH0rNSK8LTYWWPZYIECQQDxct+TFX1VsQ1eo41K0T4fu2rWUaxlvjUGhK6HxTmY\n" + "8OMJptunGRJL1CUjIb45Uz7SP8TPz5FwhXWsLfS182kRAkEA3l+Qd9C9gdpUh1uX\n" + "oPSNIxn5hFUrSTW1EwP9QH9vhwb5Vr8Jrd5ei678WYDLjUcx648RjkjhU9jSMzIx\n" + "EGvYtQJBAMm/i9NR7IVyyNIgZUpz5q4LI21rl1r4gUQuD8vA36zM81i4ROeuCly0\n" + "KkfdxR4PUfnKcQCX11YnHjk9uTFj75ECQEFY/gBnxDjzqyF35hAzrYIiMPQVfznt\n" + "YX/sDTE2AdVBVGaMj1Cb51bPHnNC6Q5kXKQnj/YrLqRQND09Q7ParX0CQQC5NxZr\n" + "9jKqhHj8yQD6PlXTsY4Occ7DH6/IoDenfdEVD5qlet0zmd50HatN2Jiqm5ubN7CM\n" + "INrtuLp4YHbgk1mi\n" + "-----END PRIVATE KEY-----"; // @formatter:on // @formatter:off String certificate = "-----BEGIN CERTIFICATE-----\n" + "MIICgTCCAeoCCQCuVzyqFgMSyDANBgkqhkiG9w0BAQsFADCBhDELMAkGA1UEBhMC\n" + "VVMxEzARBgNVBAgMCldhc2hpbmd0b24xEjAQBgNVBAcMCVZhbmNvdXZlcjEdMBsG\n" + "A1UECgwUU3ByaW5nIFNlY3VyaXR5IFNBTUwxCzAJBgNVBAsMAnNwMSAwHgYDVQQD\n" + "DBdzcC5zcHJpbmcuc2VjdXJpdHkuc2FtbDAeFw0xODA1MTQxNDMwNDRaFw0yODA1\n" + "MTExNDMwNDRaMIGEMQswCQYDVQQGEwJVUzETMBEGA1UECAwKV2FzaGluZ3RvbjES\n" + "MBAGA1UEBwwJVmFuY291dmVyMR0wGwYDVQQKDBRTcHJpbmcgU2VjdXJpdHkgU0FN\n" + "TDELMAkGA1UECwwCc3AxIDAeBgNVBAMMF3NwLnNwcmluZy5zZWN1cml0eS5zYW1s\n" + "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDRu7/EI0BlNzMEBFVAcbx+lLos\n" + "vzIWU+01dGTY8gBdhMQNYKZ92lMceo2CuVJ66cUURPym3i7nGGzoSnAxAre+0YIM\n" + "+U0razrWtAUE735bkcqELZkOTZLelaoOztmWqRbe5OuEmpewH7cx+kNgcVjdctOG\n" + "y3Q6x+I4qakY/9qhBQIDAQABMA0GCSqGSIb3DQEBCwUAA4GBAAeViTvHOyQopWEi\n" + "XOfI2Z9eukwrSknDwq/zscR0YxwwqDBMt/QdAODfSwAfnciiYLkmEjlozWRtOeN+\n" + "qK7UFgP1bRl5qksrYX5S0z2iGJh0GvonLUt3e20Ssfl5tTEDDnAEUMLfBkyaxEHD\n" + "RZ/nbTJ7VTeZOSyRoVn5XHhpuJ0B\n" + "-----END CERTIFICATE-----"; // @formatter:on PrivateKey pk = RsaKeyConverters.pkcs8().convert(new ByteArrayInputStream(key.getBytes())); X509Certificate cert = x509Certificate(certificate); return new Saml2X509Credential(pk, cert, Saml2X509Credential.Saml2X509CredentialType.SIGNING, Saml2X509Credential.Saml2X509CredentialType.DECRYPTION); } } ================================================ FILE: config/src/test/java/org/springframework/security/config/annotation/web/reactive/EnableWebFluxSecurityTests.java ================================================ /* * 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 org.springframework.security.config.annotation.web.reactive; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.nio.charset.StandardCharsets; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import reactor.core.publisher.Mono; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.core.Ordered; import org.springframework.core.annotation.AliasFor; import org.springframework.core.annotation.Order; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DefaultDataBufferFactory; import org.springframework.http.MediaType; import org.springframework.mock.web.MockServletContext; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.config.users.ReactiveAuthenticationTestConfiguration; import org.springframework.security.config.web.server.ServerHttpSecurity; import org.springframework.security.core.Authentication; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.core.context.ReactiveSecurityContextHolder; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextImpl; import org.springframework.security.core.userdetails.MapReactiveUserDetailsService; import org.springframework.security.core.userdetails.ReactiveUserDetailsService; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners; import org.springframework.security.test.context.support.WithMockUser; import org.springframework.security.test.web.reactive.server.WebTestClientBuilder; import org.springframework.security.web.reactive.result.method.annotation.AuthenticationPrincipalArgumentResolver; import org.springframework.security.web.reactive.result.view.CsrfRequestDataValueProcessor; import org.springframework.security.web.server.SecurityWebFilterChain; import org.springframework.security.web.server.WebFilterChainProxy; import org.springframework.security.web.server.context.WebSessionServerSecurityContextRepository; import org.springframework.security.web.server.util.matcher.PathPatternParserServerWebExchangeMatcher; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.reactive.server.FluxExchangeResult; import org.springframework.test.web.reactive.server.WebTestClient; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; import org.springframework.web.reactive.config.DelegatingWebFluxConfiguration; import org.springframework.web.reactive.config.EnableWebFlux; import org.springframework.web.reactive.function.BodyInserters; import org.springframework.web.reactive.result.view.AbstractView; import org.springframework.web.server.WebFilter; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.csrf; /** * @author Rob Winch * @since 5.0 */ @ExtendWith({ SpringExtension.class, SpringTestContextExtension.class }) @SecurityTestExecutionListeners public class EnableWebFluxSecurityTests { public final SpringTestContext spring = new SpringTestContext(this); @Autowired WebFilterChainProxy springSecurityFilterChain; @Test public void defaultRequiresAuthentication() { this.spring.register(Config.class).autowire(); // @formatter:off WebTestClient client = WebTestClientBuilder .bindToWebFilters(this.springSecurityFilterChain) .build(); client.get() .uri("/") .exchange() .expectStatus().isUnauthorized() .expectBody().isEmpty(); // @formatter:on } // gh-4831 @Test public void defaultMediaAllThenUnAuthorized() { this.spring.register(Config.class).autowire(); // @formatter:off WebTestClient client = WebTestClientBuilder .bindToWebFilters(this.springSecurityFilterChain) .build(); client.get() .uri("/") .accept(MediaType.ALL) .exchange() .expectStatus().isUnauthorized() .expectBody().isEmpty(); // @formatter:on } @Test public void authenticateWhenBasicThenNoSession() { this.spring.register(Config.class).autowire(); // @formatter:off WebTestClient client = WebTestClientBuilder .bindToWebFilters(this.springSecurityFilterChain) .build(); FluxExchangeResult result = client.get() .headers((headers) -> headers.setBasicAuth("user", "password")) .exchange() .expectStatus().isOk() .returnResult(String.class); // @formatter:on result.assertWithDiagnostics(() -> assertThat(result.getResponseCookies().isEmpty())); } @Test public void defaultPopulatesReactorContext() { this.spring.register(Config.class).autowire(); Authentication currentPrincipal = new TestingAuthenticationToken("user", "password", "ROLE_USER"); WebSessionServerSecurityContextRepository contextRepository = new WebSessionServerSecurityContextRepository(); SecurityContext context = new SecurityContextImpl(currentPrincipal); // @formatter:off WebFilter contextRepositoryWebFilter = (exchange, chain) -> contextRepository.save(exchange, context) .switchIfEmpty(chain.filter(exchange)) .flatMap((e) -> chain.filter(exchange)); WebTestClient client = WebTestClientBuilder .bindToWebFilters(contextRepositoryWebFilter, this.springSecurityFilterChain, writePrincipalWebFilter()) .build(); client.get() .uri("/") .exchange() .expectStatus().isOk() .expectBody(String.class).consumeWith((result) -> assertThat(result.getResponseBody()).isEqualTo(currentPrincipal.getName())); // @formatter:on } private WebFilter writePrincipalWebFilter() { // @formatter:off return (exchange, chain) -> ReactiveSecurityContextHolder.getContext() .map(SecurityContext::getAuthentication) .flatMap((principal) -> exchange.getResponse() .writeWith(Mono.just(toDataBuffer(principal.getName()))) ); // @formatter:on } @Test public void defaultPopulatesReactorContextWhenAuthenticating() { this.spring.register(Config.class).autowire(); // @formatter:off WebTestClient client = WebTestClientBuilder .bindToWebFilters(this.springSecurityFilterChain, writePrincipalWebFilter()) .build(); client.get() .uri("/") .headers((headers) -> headers.setBasicAuth("user", "password")) .exchange() .expectStatus().isOk() .expectBody(String.class).consumeWith((result) -> assertThat(result.getResponseBody()).isEqualTo("user")); // @formatter:on } @Test public void requestDataValueProcessor() { this.spring.register(Config.class).autowire(); ConfigurableApplicationContext context = this.spring.getContext(); CsrfRequestDataValueProcessor rdvp = context.getBean(AbstractView.REQUEST_DATA_VALUE_PROCESSOR_BEAN_NAME, CsrfRequestDataValueProcessor.class); assertThat(rdvp).isNotNull(); } @Test public void passwordEncoderBeanIsUsed() { this.spring.register(CustomPasswordEncoderConfig.class).autowire(); // @formatter:off WebTestClient client = WebTestClientBuilder .bindToWebFilters(this.springSecurityFilterChain, writePrincipalWebFilter()) .build(); client.get().uri("/").headers((headers) -> headers.setBasicAuth("user", "password")) .exchange().expectStatus().isOk() .expectBody(String.class) .consumeWith((result) -> assertThat(result.getResponseBody()).isEqualTo("user")); // @formatter:on } @Test public void passwordUpdateManagerUsed() { this.spring.register(MapReactiveUserDetailsServiceConfig.class).autowire(); // @formatter:off WebTestClient client = WebTestClientBuilder .bindToWebFilters(this.springSecurityFilterChain) .build(); client.get() .uri("/") .headers((h) -> h.setBasicAuth("user", "password")) .exchange() .expectStatus().isOk(); // @formatter:on ReactiveUserDetailsService users = this.spring.getContext().getBean(ReactiveUserDetailsService.class); assertThat(users.findByUsername("user").block().getPassword()).startsWith("{bcrypt}"); } @Test public void formLoginWorks() { this.spring.register(Config.class).autowire(); // @formatter:off WebTestClient client = WebTestClientBuilder .bindToWebFilters(this.springSecurityFilterChain, writePrincipalWebFilter()) .build(); // @formatter:on MultiValueMap data = new LinkedMultiValueMap<>(); data.add("username", "user"); data.add("password", "password"); // @formatter:off client.mutateWith(csrf()) .post() .uri("/login") .body(BodyInserters.fromFormData(data)) .exchange() .expectStatus().is3xxRedirection() .expectHeader().valueMatches("Location", "/"); // @formatter:on } @Test public void multiWorks() { this.spring.register(MultiSecurityHttpConfig.class).autowire(); // @formatter:off WebTestClient client = WebTestClientBuilder .bindToWebFilters(this.springSecurityFilterChain) .build(); client.get() .uri("/api/test") .exchange() .expectStatus().isUnauthorized() .expectBody().isEmpty(); client.get() .uri("/test") .exchange() .expectStatus().isOk(); // @formatter:on } @Test @WithMockUser public void authenticationPrincipalArgumentResolverWhenSpelThenWorks() { this.spring.register(AuthenticationPrincipalConfig.class).autowire(); // @formatter:off WebTestClient client = WebTestClient .bindToApplicationContext(this.spring.getContext()) .build(); client.get() .uri("/spel") .exchange() .expectStatus().isOk() .expectBody(String.class).isEqualTo("user"); // @formatter:on } private static DataBuffer toDataBuffer(String body) { DataBuffer buffer = new DefaultDataBufferFactory().allocateBuffer(); buffer.write(body.getBytes(StandardCharsets.UTF_8)); return buffer; } @Test public void enableWebFluxSecurityWhenNoConfigurationAnnotationThenBeanProxyingEnabled() { this.spring.register(BeanProxyEnabledByDefaultConfig.class).autowire(); Child childBean = this.spring.getContext().getBean(Child.class); Parent parentBean = this.spring.getContext().getBean(Parent.class); assertThat(parentBean.getChild()).isSameAs(childBean); } @Test public void enableWebFluxSecurityWhenProxyBeanMethodsFalseThenBeanProxyingDisabled() { this.spring.register(BeanProxyDisabledConfig.class).autowire(); Child childBean = this.spring.getContext().getBean(Child.class); Parent parentBean = this.spring.getContext().getBean(Parent.class); assertThat(parentBean.getChild()).isNotSameAs(childBean); } @Test // gh-8596 public void resolveAuthenticationPrincipalArgumentResolverFirstDoesNotCauseBeanCurrentlyInCreationException() { this.spring .register(EnableWebFluxSecurityConfiguration.class, ReactiveAuthenticationTestConfiguration.class, DelegatingWebFluxConfiguration.class) .autowire(); } @Test // gh-10076 public void webFluxConfigurationSupportAndServerHttpSecurityConfigurationDoNotCauseCircularReference() { AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext(); context.setAllowCircularReferences(false); context.register(EnableWebFluxSecurityConfiguration.class, ReactiveAuthenticationTestConfiguration.class, DelegatingWebFluxConfiguration.class); context.setServletContext(new MockServletContext()); context.refresh(); } @Configuration @EnableWebFluxSecurity @Import(ReactiveAuthenticationTestConfiguration.class) static class Config { } @Configuration @EnableWebFluxSecurity static class CustomPasswordEncoderConfig { @Bean ReactiveUserDetailsService userDetailsService(PasswordEncoder encoder) { return new MapReactiveUserDetailsService( User.withUsername("user").password(encoder.encode("password")).roles("USER").build()); } @Bean static PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } } @Configuration @EnableWebFluxSecurity static class MapReactiveUserDetailsServiceConfig { @Bean MapReactiveUserDetailsService userDetailsService() { // @formatter:off return new MapReactiveUserDetailsService(User.withUsername("user") .password("{noop}password") .roles("USER") .build() // @formatter:on ); } } @Configuration @EnableWebFluxSecurity @Import(ReactiveAuthenticationTestConfiguration.class) static class MultiSecurityHttpConfig { @Order(Ordered.HIGHEST_PRECEDENCE) @Bean SecurityWebFilterChain apiHttpSecurity(ServerHttpSecurity http) { http.securityMatcher(new PathPatternParserServerWebExchangeMatcher("/api/**")) .authorizeExchange((authorize) -> authorize.anyExchange().denyAll()); return http.build(); } @Bean SecurityWebFilterChain httpSecurity(ServerHttpSecurity http) { return http.build(); } } @Configuration @EnableWebFluxSecurity @EnableWebFlux @Import(ReactiveAuthenticationTestConfiguration.class) static class AuthenticationPrincipalConfig { @Bean PrincipalBean principalBean() { return new PrincipalBean(); } static class PrincipalBean { public String username(UserDetails user) { return user.getUsername(); } } @Target({ ElementType.PARAMETER }) @Retention(RetentionPolicy.RUNTIME) @AuthenticationPrincipal @interface Property { @AliasFor(attribute = "expression", annotation = AuthenticationPrincipal.class) String value() default "id"; } interface UsernameResolver { String username(@Property("@principalBean.username(#this)") String username); } @RestController static class AuthenticationPrincipalResolver implements UsernameResolver { @Override @GetMapping("/spel") public String username(String username) { return username; } } } @Configuration @EnableWebFluxSecurity @Import(ReactiveAuthenticationTestConfiguration.class) static class BeanProxyEnabledByDefaultConfig { @Bean Child child() { return new Child(); } @Bean Parent parent() { return new Parent(child()); } } @Configuration(proxyBeanMethods = false) @EnableWebFluxSecurity @Import(ReactiveAuthenticationTestConfiguration.class) static class BeanProxyDisabledConfig { @Bean Child child() { return new Child(); } @Bean Parent parent() { return new Parent(child()); } } static class Parent { private Child child; Parent(Child child) { this.child = child; } Child getChild() { return this.child; } } static class Child { Child() { } } @EnableWebFluxSecurity @Configuration(proxyBeanMethods = false) static class EnableWebFluxSecurityConfiguration { /** * It is necessary to Autowire AuthenticationPrincipalArgumentResolver because it * triggers eager loading of AuthenticationPrincipalArgumentResolver bean which * causes BeanCurrentlyInCreationException */ @Autowired AuthenticationPrincipalArgumentResolver resolver; } } ================================================ FILE: config/src/test/java/org/springframework/security/config/annotation/web/reactive/ReactiveOAuth2AuthorizedClientManagerConfigurationTests.java ================================================ /* * 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 org.springframework.security.config.annotation.web.reactive; import java.time.Duration; import java.time.Instant; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; import reactor.core.publisher.Mono; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.mock.http.server.reactive.MockServerHttpRequest; import org.springframework.mock.web.server.MockServerWebExchange; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.config.oauth2.client.CommonOAuth2Provider; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.oauth2.client.AuthorizationCodeReactiveOAuth2AuthorizedClientProvider; import org.springframework.security.oauth2.client.ClientAuthorizationRequiredException; import org.springframework.security.oauth2.client.ClientCredentialsReactiveOAuth2AuthorizedClientProvider; import org.springframework.security.oauth2.client.JwtBearerReactiveOAuth2AuthorizedClientProvider; import org.springframework.security.oauth2.client.OAuth2AuthorizationContext; import org.springframework.security.oauth2.client.OAuth2AuthorizeRequest; import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientManager; import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientService; import org.springframework.security.oauth2.client.RefreshTokenReactiveOAuth2AuthorizedClientProvider; import org.springframework.security.oauth2.client.TokenExchangeReactiveOAuth2AuthorizedClientProvider; import org.springframework.security.oauth2.client.endpoint.AbstractOAuth2AuthorizationGrantRequest; import org.springframework.security.oauth2.client.endpoint.JwtBearerGrantRequest; import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest; import org.springframework.security.oauth2.client.endpoint.OAuth2ClientCredentialsGrantRequest; import org.springframework.security.oauth2.client.endpoint.OAuth2RefreshTokenGrantRequest; import org.springframework.security.oauth2.client.endpoint.ReactiveOAuth2AccessTokenResponseClient; import org.springframework.security.oauth2.client.endpoint.TokenExchangeGrantRequest; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.InMemoryReactiveClientRegistrationRepository; import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository; import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository; import org.springframework.security.oauth2.client.web.server.WebSessionServerOAuth2AuthorizedClientRepository; import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.core.ClientAuthenticationMethod; import org.springframework.security.oauth2.core.OAuth2AccessToken; import org.springframework.security.oauth2.core.OAuth2AuthorizationException; import org.springframework.security.oauth2.core.OAuth2Error; import org.springframework.security.oauth2.core.TestOAuth2RefreshTokens; import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; import org.springframework.security.oauth2.core.endpoint.TestOAuth2AccessTokenResponses; import org.springframework.security.oauth2.jwt.JoseHeaderNames; import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.security.oauth2.jwt.JwtClaimNames; import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; import org.springframework.web.server.ServerWebExchange; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; /** * Tests for * {@link ReactiveOAuth2ClientConfiguration.ReactiveOAuth2AuthorizedClientManagerConfiguration}. * * @author Steve Riesenberg */ public class ReactiveOAuth2AuthorizedClientManagerConfigurationTests { private static ReactiveOAuth2AccessTokenResponseClient MOCK_RESPONSE_CLIENT; public final SpringTestContext spring = new SpringTestContext(this); @Autowired private ReactiveOAuth2AuthorizedClientManager authorizedClientManager; @Autowired private ReactiveClientRegistrationRepository clientRegistrationRepository; @Autowired(required = false) private ServerOAuth2AuthorizedClientRepository authorizedClientRepository; @Autowired(required = false) private ReactiveOAuth2AuthorizedClientService authorizedClientService; @Autowired(required = false) private AuthorizationCodeReactiveOAuth2AuthorizedClientProvider authorizationCodeAuthorizedClientProvider; private MockServerWebExchange exchange; @BeforeEach @SuppressWarnings("unchecked") public void setUp() { MOCK_RESPONSE_CLIENT = mock(ReactiveOAuth2AccessTokenResponseClient.class); MockServerHttpRequest request = MockServerHttpRequest.get("/").build(); this.exchange = MockServerWebExchange.builder(request).build(); } @Test public void loadContextWhenOAuth2ClientEnabledThenConfigured() { this.spring.register(MinimalOAuth2ClientConfig.class).autowire(); assertThat(this.authorizedClientManager).isNotNull(); } @Test public void authorizeWhenAuthorizationCodeAuthorizedClientProviderBeanThenUsed() { this.spring.register(CustomAuthorizedClientProvidersConfig.class).autowire(); TestingAuthenticationToken authentication = new TestingAuthenticationToken("user", null, "ROLE_USER"); // @formatter:off OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest .withClientRegistrationId("google") .principal(authentication) .attribute(ServerWebExchange.class.getName(), this.exchange) .build(); assertThatExceptionOfType(ClientAuthorizationRequiredException.class) .isThrownBy(() -> this.authorizedClientManager.authorize(authorizeRequest).block()) .extracting(OAuth2AuthorizationException::getError) .extracting(OAuth2Error::getErrorCode) .isEqualTo("client_authorization_required"); // @formatter:on verify(this.authorizationCodeAuthorizedClientProvider).authorize(any(OAuth2AuthorizationContext.class)); } @Test public void authorizeWhenAuthorizedClientServiceBeanThenUsed() { this.spring.register(CustomAuthorizedClientServiceConfig.class).autowire(); TestingAuthenticationToken authentication = new TestingAuthenticationToken("user", null, "ROLE_USER"); // @formatter:off OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest .withClientRegistrationId("google") .principal(authentication) .attribute(ServerWebExchange.class.getName(), this.exchange) .build(); assertThatExceptionOfType(ClientAuthorizationRequiredException.class) .isThrownBy(() -> this.authorizedClientManager.authorize(authorizeRequest).block()) .extracting(OAuth2AuthorizationException::getError) .extracting(OAuth2Error::getErrorCode) .isEqualTo("client_authorization_required"); // @formatter:on verify(this.authorizedClientService).loadAuthorizedClient(authorizeRequest.getClientRegistrationId(), authentication.getName()); } @Test public void authorizeWhenRefreshTokenAccessTokenResponseClientBeanThenUsed() { this.spring.register(CustomAccessTokenResponseClientsConfig.class).autowire(); testRefreshTokenGrant(); } @Test public void authorizeWhenRefreshTokenAuthorizedClientProviderBeanThenUsed() { this.spring.register(CustomAuthorizedClientProvidersConfig.class).autowire(); testRefreshTokenGrant(); } private void testRefreshTokenGrant() { OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build(); given(MOCK_RESPONSE_CLIENT.getTokenResponse(any(OAuth2RefreshTokenGrantRequest.class))) .willReturn(Mono.just(accessTokenResponse)); TestingAuthenticationToken authentication = new TestingAuthenticationToken("user", null, "ROLE_USER"); ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId("google") .block(); assertThat(clientRegistration).isNotNull(); OAuth2AuthorizedClient existingAuthorizedClient = new OAuth2AuthorizedClient(clientRegistration, authentication.getName(), getExpiredAccessToken(), TestOAuth2RefreshTokens.refreshToken()); this.authorizedClientRepository.saveAuthorizedClient(existingAuthorizedClient, authentication, this.exchange) .block(); // @formatter:off OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest .withClientRegistrationId(clientRegistration.getRegistrationId()) .principal(authentication) .attribute(ServerWebExchange.class.getName(), this.exchange) .build(); // @formatter:on OAuth2AuthorizedClient authorizedClient = this.authorizedClientManager.authorize(authorizeRequest).block(); assertThat(authorizedClient).isNotNull(); ArgumentCaptor grantRequestCaptor = ArgumentCaptor .forClass(OAuth2RefreshTokenGrantRequest.class); verify(MOCK_RESPONSE_CLIENT).getTokenResponse(grantRequestCaptor.capture()); OAuth2RefreshTokenGrantRequest grantRequest = grantRequestCaptor.getValue(); assertThat(grantRequest.getClientRegistration().getRegistrationId()) .isEqualTo(clientRegistration.getRegistrationId()); assertThat(grantRequest.getGrantType()).isEqualTo(AuthorizationGrantType.REFRESH_TOKEN); assertThat(grantRequest.getAccessToken()).isEqualTo(existingAuthorizedClient.getAccessToken()); assertThat(grantRequest.getRefreshToken()).isEqualTo(existingAuthorizedClient.getRefreshToken()); } @Test public void authorizeWhenClientCredentialsAccessTokenResponseClientBeanThenUsed() { this.spring.register(CustomAccessTokenResponseClientsConfig.class).autowire(); testClientCredentialsGrant(); } @Test public void authorizeWhenClientCredentialsAuthorizedClientProviderBeanThenUsed() { this.spring.register(CustomAuthorizedClientProvidersConfig.class).autowire(); testClientCredentialsGrant(); } private void testClientCredentialsGrant() { OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build(); given(MOCK_RESPONSE_CLIENT.getTokenResponse(any(OAuth2ClientCredentialsGrantRequest.class))) .willReturn(Mono.just(accessTokenResponse)); TestingAuthenticationToken authentication = new TestingAuthenticationToken("user", null, "ROLE_USER"); ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId("github") .block(); assertThat(clientRegistration).isNotNull(); // @formatter:off OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest .withClientRegistrationId(clientRegistration.getRegistrationId()) .principal(authentication) .attribute(ServerWebExchange.class.getName(), this.exchange) .build(); // @formatter:on OAuth2AuthorizedClient authorizedClient = this.authorizedClientManager.authorize(authorizeRequest).block(); assertThat(authorizedClient).isNotNull(); ArgumentCaptor grantRequestCaptor = ArgumentCaptor .forClass(OAuth2ClientCredentialsGrantRequest.class); verify(MOCK_RESPONSE_CLIENT).getTokenResponse(grantRequestCaptor.capture()); OAuth2ClientCredentialsGrantRequest grantRequest = grantRequestCaptor.getValue(); assertThat(grantRequest.getClientRegistration().getRegistrationId()) .isEqualTo(clientRegistration.getRegistrationId()); assertThat(grantRequest.getGrantType()).isEqualTo(AuthorizationGrantType.CLIENT_CREDENTIALS); } @Test public void authorizeWhenJwtBearerAccessTokenResponseClientBeanThenUsed() { this.spring.register(CustomAccessTokenResponseClientsConfig.class).autowire(); testJwtBearerGrant(); } @Test public void authorizeWhenJwtBearerAuthorizedClientProviderBeanThenUsed() { this.spring.register(CustomAuthorizedClientProvidersConfig.class).autowire(); testJwtBearerGrant(); } private void testJwtBearerGrant() { OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build(); given(MOCK_RESPONSE_CLIENT.getTokenResponse(any(JwtBearerGrantRequest.class))) .willReturn(Mono.just(accessTokenResponse)); JwtAuthenticationToken authentication = new JwtAuthenticationToken(getJwt()); ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId("okta").block(); assertThat(clientRegistration).isNotNull(); // @formatter:off OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest .withClientRegistrationId(clientRegistration.getRegistrationId()) .principal(authentication) .attribute(ServerWebExchange.class.getName(), this.exchange) .build(); // @formatter:on OAuth2AuthorizedClient authorizedClient = this.authorizedClientManager.authorize(authorizeRequest).block(); assertThat(authorizedClient).isNotNull(); ArgumentCaptor grantRequestCaptor = ArgumentCaptor.forClass(JwtBearerGrantRequest.class); verify(MOCK_RESPONSE_CLIENT).getTokenResponse(grantRequestCaptor.capture()); JwtBearerGrantRequest grantRequest = grantRequestCaptor.getValue(); assertThat(grantRequest.getClientRegistration().getRegistrationId()) .isEqualTo(clientRegistration.getRegistrationId()); assertThat(grantRequest.getGrantType()).isEqualTo(AuthorizationGrantType.JWT_BEARER); assertThat(grantRequest.getJwt().getSubject()).isEqualTo("user"); } @Test public void authorizeWhenTokenExchangeAccessTokenResponseClientBeanThenUsed() { this.spring.register(CustomAccessTokenResponseClientsConfig.class).autowire(); testTokenExchangeGrant(); } @Test public void authorizeWhenTokenExchangeAuthorizedClientProviderBeanThenUsed() { this.spring.register(CustomAuthorizedClientProvidersConfig.class).autowire(); testTokenExchangeGrant(); } private void testTokenExchangeGrant() { OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build(); given(MOCK_RESPONSE_CLIENT.getTokenResponse(any(TokenExchangeGrantRequest.class))) .willReturn(Mono.just(accessTokenResponse)); JwtAuthenticationToken authentication = new JwtAuthenticationToken(getJwt()); ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId("auth0").block(); assertThat(clientRegistration).isNotNull(); // @formatter:off OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest .withClientRegistrationId(clientRegistration.getRegistrationId()) .principal(authentication) .attribute(ServerWebExchange.class.getName(), this.exchange) .build(); // @formatter:on OAuth2AuthorizedClient authorizedClient = this.authorizedClientManager.authorize(authorizeRequest).block(); assertThat(authorizedClient).isNotNull(); ArgumentCaptor grantRequestCaptor = ArgumentCaptor .forClass(TokenExchangeGrantRequest.class); verify(MOCK_RESPONSE_CLIENT).getTokenResponse(grantRequestCaptor.capture()); TokenExchangeGrantRequest grantRequest = grantRequestCaptor.getValue(); assertThat(grantRequest.getClientRegistration().getRegistrationId()) .isEqualTo(clientRegistration.getRegistrationId()); assertThat(grantRequest.getGrantType()).isEqualTo(AuthorizationGrantType.TOKEN_EXCHANGE); assertThat(grantRequest.getSubjectToken()).isEqualTo(authentication.getToken()); } private static OAuth2AccessToken getExpiredAccessToken() { Instant expiresAt = Instant.now().minusSeconds(60); Instant issuedAt = expiresAt.minus(Duration.ofDays(1)); return new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, "scopes", issuedAt, expiresAt, new HashSet<>(Arrays.asList("read", "write"))); } private static Jwt getJwt() { Instant issuedAt = Instant.now(); return new Jwt("token", issuedAt, issuedAt.plusSeconds(300), Collections.singletonMap(JoseHeaderNames.ALG, "RS256"), Collections.singletonMap(JwtClaimNames.SUB, "user")); } @Configuration @EnableWebFluxSecurity static class MinimalOAuth2ClientConfig extends OAuth2ClientBaseConfig { @Bean ServerOAuth2AuthorizedClientRepository authorizedClientRepository() { return new WebSessionServerOAuth2AuthorizedClientRepository(); } } @Configuration @EnableWebFluxSecurity static class CustomAuthorizedClientServiceConfig extends OAuth2ClientBaseConfig { @Bean ReactiveOAuth2AuthorizedClientService authorizedClientService() { ReactiveOAuth2AuthorizedClientService authorizedClientService = mock( ReactiveOAuth2AuthorizedClientService.class); given(authorizedClientService.loadAuthorizedClient(anyString(), anyString())).willReturn(Mono.empty()); return authorizedClientService; } } @Configuration @EnableWebFluxSecurity static class CustomAccessTokenResponseClientsConfig extends MinimalOAuth2ClientConfig { @Bean ReactiveOAuth2AccessTokenResponseClient authorizationCodeAccessTokenResponseClient() { return new MockAccessTokenResponseClient<>(); } @Bean ReactiveOAuth2AccessTokenResponseClient refreshTokenTokenAccessResponseClient() { return new MockAccessTokenResponseClient<>(); } @Bean ReactiveOAuth2AccessTokenResponseClient clientCredentialsAccessTokenResponseClient() { return new MockAccessTokenResponseClient<>(); } @Bean ReactiveOAuth2AccessTokenResponseClient jwtBearerAccessTokenResponseClient() { return new MockAccessTokenResponseClient<>(); } @Bean ReactiveOAuth2AccessTokenResponseClient tokenExchangeAccessTokenResponseClient() { return new MockAccessTokenResponseClient<>(); } } @Configuration @EnableWebFluxSecurity static class CustomAuthorizedClientProvidersConfig extends MinimalOAuth2ClientConfig { @Bean AuthorizationCodeReactiveOAuth2AuthorizedClientProvider authorizationCode() { return spy(new AuthorizationCodeReactiveOAuth2AuthorizedClientProvider()); } @Bean RefreshTokenReactiveOAuth2AuthorizedClientProvider refreshToken() { RefreshTokenReactiveOAuth2AuthorizedClientProvider authorizedClientProvider = new RefreshTokenReactiveOAuth2AuthorizedClientProvider(); authorizedClientProvider.setAccessTokenResponseClient(new MockAccessTokenResponseClient<>()); return authorizedClientProvider; } @Bean ClientCredentialsReactiveOAuth2AuthorizedClientProvider clientCredentials() { ClientCredentialsReactiveOAuth2AuthorizedClientProvider authorizedClientProvider = new ClientCredentialsReactiveOAuth2AuthorizedClientProvider(); authorizedClientProvider.setAccessTokenResponseClient(new MockAccessTokenResponseClient<>()); return authorizedClientProvider; } @Bean JwtBearerReactiveOAuth2AuthorizedClientProvider jwtBearer() { JwtBearerReactiveOAuth2AuthorizedClientProvider authorizedClientProvider = new JwtBearerReactiveOAuth2AuthorizedClientProvider(); authorizedClientProvider.setAccessTokenResponseClient(new MockAccessTokenResponseClient<>()); return authorizedClientProvider; } @Bean TokenExchangeReactiveOAuth2AuthorizedClientProvider tokenExchange() { TokenExchangeReactiveOAuth2AuthorizedClientProvider authorizedClientProvider = new TokenExchangeReactiveOAuth2AuthorizedClientProvider(); authorizedClientProvider.setAccessTokenResponseClient(new MockAccessTokenResponseClient<>()); return authorizedClientProvider; } } abstract static class OAuth2ClientBaseConfig { @Bean ReactiveClientRegistrationRepository clientRegistrationRepository() { // @formatter:off return new InMemoryReactiveClientRegistrationRepository( CommonOAuth2Provider.GOOGLE.getBuilder("google") .clientId("google-client-id") .clientSecret("google-client-secret") .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) .build(), CommonOAuth2Provider.GITHUB.getBuilder("github") .clientId("github-client-id") .clientSecret("github-client-secret") .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS) .build(), CommonOAuth2Provider.OKTA.getBuilder("okta") .clientId("okta-client-id") .clientSecret("okta-client-secret") .authorizationGrantType(AuthorizationGrantType.JWT_BEARER) .build(), ClientRegistration.withRegistrationId("auth0") .clientName("Auth0") .clientId("auth0-client-id") .clientSecret("auth0-client-secret") .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) .authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE) .scope("user.read", "user.write") .build()); // @formatter:on } } private static class MockAccessTokenResponseClient implements ReactiveOAuth2AccessTokenResponseClient { @Override public Mono getTokenResponse(T grantRequest) { return MOCK_RESPONSE_CLIENT.getTokenResponse(grantRequest); } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/annotation/web/reactive/ReactiveOAuth2ClientImportSelectorTests.java ================================================ /* * 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 org.springframework.security.config.annotation.web.reactive; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import reactor.core.publisher.Mono; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.config.web.server.ServerHttpSecurity; import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientManager; import org.springframework.security.oauth2.client.annotation.RegisteredOAuth2AuthorizedClient; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository; import org.springframework.security.oauth2.client.registration.TestClientRegistrations; import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository; import org.springframework.security.oauth2.core.TestOAuth2AccessTokens; import org.springframework.security.web.server.SecurityWebFilterChain; import org.springframework.test.web.reactive.server.WebTestClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.reactive.config.EnableWebFlux; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; /** * Tests for {@link ReactiveOAuth2ClientImportSelector}. * * @author Alavudin Kuttikkattil */ @ExtendWith(SpringTestContextExtension.class) public class ReactiveOAuth2ClientImportSelectorTests { public final SpringTestContext spring = new SpringTestContext(this); WebTestClient client; @Autowired public void setApplicationContext(ApplicationContext context) { // @formatter:off this.client = WebTestClient .bindToApplicationContext(context) .build(); // @formatter:on } @Test public void requestWhenAuthorizedClientManagerConfiguredThenUsed() { String clientRegistrationId = "client"; String principalName = "user"; ReactiveClientRegistrationRepository clientRegistrationRepository = mock( ReactiveClientRegistrationRepository.class); ServerOAuth2AuthorizedClientRepository authorizedClientRepository = mock( ServerOAuth2AuthorizedClientRepository.class); ReactiveOAuth2AuthorizedClientManager authorizedClientManager = mock( ReactiveOAuth2AuthorizedClientManager.class); ClientRegistration clientRegistration = TestClientRegistrations.clientCredentials() .registrationId(clientRegistrationId) .build(); OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(clientRegistration, principalName, TestOAuth2AccessTokens.noScopes()); given(authorizedClientManager.authorize(any())).willReturn(Mono.just(authorizedClient)); OAuth2AuthorizedClientManagerRegisteredConfig.CLIENT_REGISTRATION_REPOSITORY = clientRegistrationRepository; OAuth2AuthorizedClientManagerRegisteredConfig.AUTHORIZED_CLIENT_REPOSITORY = authorizedClientRepository; OAuth2AuthorizedClientManagerRegisteredConfig.AUTHORIZED_CLIENT_MANAGER = authorizedClientManager; this.spring.register(OAuth2AuthorizedClientManagerRegisteredConfig.class).autowire(); // @formatter:off this.client .get() .uri("http://localhost/authorized-client") .headers((headers) -> headers.setBasicAuth("user", "password")).exchange().expectStatus().isOk() .expectBody(String.class).isEqualTo("resolved"); // @formatter:on verify(authorizedClientManager).authorize(any()); verifyNoInteractions(clientRegistrationRepository); verifyNoInteractions(authorizedClientRepository); } @Test public void requestWhenAuthorizedClientManagerNotConfigureThenUseDefaultAuthorizedClientManager() { String clientRegistrationId = "client"; String principalName = "user"; ReactiveClientRegistrationRepository clientRegistrationRepository = mock( ReactiveClientRegistrationRepository.class); ServerOAuth2AuthorizedClientRepository authorizedClientRepository = mock( ServerOAuth2AuthorizedClientRepository.class); ClientRegistration clientRegistration = TestClientRegistrations.clientCredentials() .registrationId(clientRegistrationId) .build(); OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(clientRegistration, principalName, TestOAuth2AccessTokens.noScopes()); OAuth2AuthorizedClientManagerRegisteredConfig.CLIENT_REGISTRATION_REPOSITORY = clientRegistrationRepository; OAuth2AuthorizedClientManagerRegisteredConfig.AUTHORIZED_CLIENT_REPOSITORY = authorizedClientRepository; OAuth2AuthorizedClientManagerRegisteredConfig.AUTHORIZED_CLIENT_MANAGER = null; given(authorizedClientRepository.loadAuthorizedClient(any(), any(), any())) .willReturn(Mono.just(authorizedClient)); this.spring.register(OAuth2AuthorizedClientManagerRegisteredConfig.class).autowire(); // @formatter:off this.client .get() .uri("http://localhost/authorized-client") .headers((headers) -> headers.setBasicAuth("user", "password")).exchange().expectStatus().isOk() .expectBody(String.class).isEqualTo("resolved"); // @formatter:on } @Configuration @EnableWebFlux @EnableWebFluxSecurity static class OAuth2AuthorizedClientManagerRegisteredConfig { static ReactiveClientRegistrationRepository CLIENT_REGISTRATION_REPOSITORY; static ServerOAuth2AuthorizedClientRepository AUTHORIZED_CLIENT_REPOSITORY; static ReactiveOAuth2AuthorizedClientManager AUTHORIZED_CLIENT_MANAGER; @Bean SecurityWebFilterChain springSecurity(ServerHttpSecurity http) { return http.build(); } @Bean ReactiveClientRegistrationRepository clientRegistrationRepository() { return CLIENT_REGISTRATION_REPOSITORY; } @Bean ServerOAuth2AuthorizedClientRepository authorizedClientRepository() { return AUTHORIZED_CLIENT_REPOSITORY; } @Bean ReactiveOAuth2AuthorizedClientManager authorizedClientManager() { return AUTHORIZED_CLIENT_MANAGER; } @RestController class Controller { @GetMapping("/authorized-client") String authorizedClient( @RegisteredOAuth2AuthorizedClient("client1") OAuth2AuthorizedClient authorizedClient) { return (authorizedClient != null) ? "resolved" : "not-resolved"; } } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/annotation/web/reactive/ServerHttpSecurityConfigurationBuilder.java ================================================ /* * 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 org.springframework.security.config.annotation.web.reactive; import org.springframework.security.authentication.ReactiveAuthenticationManager; import org.springframework.security.authentication.UserDetailsRepositoryReactiveAuthenticationManager; import org.springframework.security.config.users.ReactiveAuthenticationTestConfiguration; import org.springframework.security.config.web.server.ServerHttpSecurity; import org.springframework.security.core.userdetails.ReactiveUserDetailsService; /** * @author Rob Winch * @since 5.0 */ public final class ServerHttpSecurityConfigurationBuilder { private ServerHttpSecurityConfigurationBuilder() { } public static ServerHttpSecurity http() { return new ServerHttpSecurityConfiguration().httpSecurity(); } public static ServerHttpSecurity httpWithDefaultAuthentication() { ReactiveUserDetailsService reactiveUserDetailsService = ReactiveAuthenticationTestConfiguration .userDetailsService(); ReactiveAuthenticationManager authenticationManager = new UserDetailsRepositoryReactiveAuthenticationManager( reactiveUserDetailsService); return http().authenticationManager(authenticationManager); } } ================================================ FILE: config/src/test/java/org/springframework/security/config/annotation/web/reactive/ServerHttpSecurityConfigurationTests.java ================================================ /* * 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 org.springframework.security.config.annotation.web.reactive; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.net.URI; import java.util.Iterator; import io.micrometer.observation.Observation; import io.micrometer.observation.ObservationHandler; import io.micrometer.observation.ObservationRegistry; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; import org.mockito.InOrder; import org.mockito.Mockito; import reactor.core.publisher.Mono; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.messaging.rsocket.annotation.support.RSocketMessageHandler; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.authentication.password.CompromisedPasswordDecision; import org.springframework.security.authentication.password.CompromisedPasswordException; import org.springframework.security.authentication.password.ReactiveCompromisedPasswordChecker; import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.rsocket.EnableRSocketSecurity; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.config.users.ReactiveAuthenticationTestConfiguration; import org.springframework.security.config.web.server.ServerHttpSecurity; import org.springframework.security.config.web.server.ServerHttpSecurity.AuthorizeExchangeSpec; import org.springframework.security.core.Authentication; import org.springframework.security.core.annotation.AnnotationTemplateExpressionDefaults; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.core.annotation.CurrentSecurityContext; import org.springframework.security.core.userdetails.MapReactiveUserDetailsService; import org.springframework.security.core.userdetails.PasswordEncodedUser; import org.springframework.security.core.userdetails.ReactiveUserDetailsService; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.web.server.DefaultServerRedirectStrategy; import org.springframework.security.web.server.SecurityWebFilterChain; import org.springframework.test.web.reactive.server.WebTestClient; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.reactive.config.EnableWebFlux; import org.springframework.web.reactive.function.BodyInserters; import org.springframework.web.server.adapter.WebHttpHandlerBuilder; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.springframework.security.config.Customizer.withDefaults; import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.csrf; import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.mockAuthentication; import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.springSecurity; /** * Tests for {@link ServerHttpSecurityConfiguration}. * * @author Eleftheria Stein */ @ExtendWith(SpringTestContextExtension.class) public class ServerHttpSecurityConfigurationTests { public final SpringTestContext spring = new SpringTestContext(this); WebTestClient webClient; @Autowired void setup(ApplicationContext context) { if (!context.containsBean(WebHttpHandlerBuilder.WEB_HANDLER_BEAN_NAME)) { return; } this.webClient = WebTestClient.bindToApplicationContext(context) .apply(springSecurity()) .configureClient() .build(); } @Test public void loadConfigWhenReactiveUserAuthenticationServiceConfiguredThenServerHttpSecurityExists() { this.spring .register(ServerHttpSecurityConfiguration.class, ReactiveAuthenticationTestConfiguration.class, WebFluxSecurityConfiguration.class) .autowire(); ServerHttpSecurity serverHttpSecurity = this.spring.getContext().getBean(ServerHttpSecurity.class); assertThat(serverHttpSecurity).isNotNull(); } @Test public void loadConfigWhenOnlyReactiveUserDetailsServiceConfiguredThenServerHttpSecurityExists() { this.spring .register(ServerHttpSecurityConfiguration.class, ReactiveUserDetailsServiceOnlyTestConfiguration.class, WebFluxSecurityConfiguration.class) .autowire(); ServerHttpSecurity serverHttpSecurity = this.spring.getContext().getBean(ServerHttpSecurity.class); assertThat(serverHttpSecurity).isNotNull(); } @Test public void loadConfigWhenProxyingEnabledAndSubclassThenServerHttpSecurityExists() { this.spring .register(SubclassConfig.class, ReactiveAuthenticationTestConfiguration.class, WebFluxSecurityConfiguration.class) .autowire(); ServerHttpSecurity serverHttpSecurity = this.spring.getContext().getBean(ServerHttpSecurity.class); assertThat(serverHttpSecurity).isNotNull(); } @Test void loginWhenCompromisePasswordCheckerConfiguredAndPasswordCompromisedThenUnauthorized() { this.spring.register(FormLoginConfig.class, UserDetailsConfig.class, CompromisedPasswordCheckerConfig.class) .autowire(); MultiValueMap data = new LinkedMultiValueMap<>(); data.add("username", "user"); data.add("password", "password"); // @formatter:off this.webClient.mutateWith(csrf()) .post() .uri("/login") .body(BodyInserters.fromFormData(data)) .exchange() .expectStatus().is3xxRedirection() .expectHeader().location("/login?error"); // @formatter:on } @Test void loginWhenCompromisePasswordCheckerConfiguredAndPasswordNotCompromisedThenUnauthorized() { this.spring.register(FormLoginConfig.class, UserDetailsConfig.class, CompromisedPasswordCheckerConfig.class) .autowire(); MultiValueMap data = new LinkedMultiValueMap<>(); data.add("username", "admin"); data.add("password", "password2"); // @formatter:off this.webClient.mutateWith(csrf()) .post() .uri("/login") .body(BodyInserters.fromFormData(data)) .exchange() .expectStatus().is3xxRedirection() .expectHeader().location("/"); // @formatter:on } @Test void loginWhenCompromisedPasswordAndRedirectIfPasswordExceptionThenRedirectedToResetPassword() { this.spring .register(FormLoginRedirectToResetPasswordConfig.class, UserDetailsConfig.class, CompromisedPasswordCheckerConfig.class) .autowire(); MultiValueMap data = new LinkedMultiValueMap<>(); data.add("username", "user"); data.add("password", "password"); // @formatter:off this.webClient.mutateWith(csrf()) .post() .uri("/login") .body(BodyInserters.fromFormData(data)) .exchange() .expectStatus().is3xxRedirection() .expectHeader().location("/reset-password"); // @formatter:on } @Test public void metaAnnotationWhenTemplateDefaultsBeanThenResolvesExpression() throws Exception { this.spring.register(MetaAnnotationPlaceholderConfig.class).autowire(); Authentication user = new TestingAuthenticationToken("user", "password", "ROLE_USER"); this.webClient.mutateWith(mockAuthentication(user)) .get() .uri("/hi") .exchange() .expectStatus() .isOk() .expectBody(String.class) .isEqualTo("Hi, Stranger!"); Authentication harold = new TestingAuthenticationToken("harold", "password", "ROLE_USER"); this.webClient.mutateWith(mockAuthentication(harold)) .get() .uri("/hi") .exchange() .expectBody(String.class) .isEqualTo("Hi, Harold!"); } @Test public void resoleMetaAnnotationWhenTemplateDefaultsBeanThenResolvesExpression() throws Exception { this.spring.register(MetaAnnotationPlaceholderConfig.class).autowire(); Authentication user = new TestingAuthenticationToken("user", "password", "ROLE_USER"); this.webClient.mutateWith(mockAuthentication(user)) .get() .uri("/hello") .exchange() .expectStatus() .isOk() .expectBody(String.class) .isEqualTo("user"); Authentication harold = new TestingAuthenticationToken("harold", "password", "ROLE_USER"); this.webClient.mutateWith(mockAuthentication(harold)) .get() .uri("/hello") .exchange() .expectBody(String.class) .isEqualTo("harold"); } @Test public void getWhenUsingObservationRegistryThenObservesRequest() { this.spring.register(ObservationRegistryConfig.class).autowire(); // @formatter:off this.webClient .get() .uri("/hello") .headers((headers) -> headers.setBasicAuth("user", "password")) .exchange() .expectStatus() .isNotFound(); // @formatter:on ObservationHandler handler = this.spring.getContext().getBean(ObservationHandler.class); ArgumentCaptor captor = ArgumentCaptor.forClass(Observation.Context.class); verify(handler, times(6)).onStart(captor.capture()); Iterator contexts = captor.getAllValues().iterator(); assertThat(contexts.next().getContextualName()).isEqualTo("http get"); assertThat(contexts.next().getContextualName()).isEqualTo("security filterchain before"); assertThat(contexts.next().getName()).isEqualTo("spring.security.authentications"); assertThat(contexts.next().getName()).isEqualTo("spring.security.authorizations"); assertThat(contexts.next().getName()).isEqualTo("spring.security.http.secured.requests"); assertThat(contexts.next().getContextualName()).isEqualTo("security filterchain after"); } // gh-16161 @Test public void getWhenUsingRSocketThenObservesRequest() { this.spring.register(ObservationRegistryConfig.class, RSocketSecurityConfig.class).autowire(); // @formatter:off this.webClient .get() .uri("/hello") .headers((headers) -> headers.setBasicAuth("user", "password")) .exchange() .expectStatus() .isNotFound(); // @formatter:on ObservationHandler handler = this.spring.getContext().getBean(ObservationHandler.class); ArgumentCaptor captor = ArgumentCaptor.forClass(Observation.Context.class); verify(handler, times(6)).onStart(captor.capture()); Iterator contexts = captor.getAllValues().iterator(); assertThat(contexts.next().getContextualName()).isEqualTo("http get"); assertThat(contexts.next().getContextualName()).isEqualTo("security filterchain before"); assertThat(contexts.next().getName()).isEqualTo("spring.security.authentications"); assertThat(contexts.next().getName()).isEqualTo("spring.security.authorizations"); assertThat(contexts.next().getName()).isEqualTo("spring.security.http.secured.requests"); assertThat(contexts.next().getContextualName()).isEqualTo("security filterchain after"); } @Test void authorizeExchangeCustomizerBean() { this.spring.register(AuthorizeExchangeCustomizerBeanConfig.class).autowire(); Customizer authzCustomizer = this.spring.getContext().getBean("authz", Customizer.class); ArgumentCaptor arg0 = ArgumentCaptor.forClass(AuthorizeExchangeSpec.class); verify(authzCustomizer).customize(arg0.capture()); } @Test void multiAuthorizeExchangeCustomizerBean() { this.spring.register(MultiAuthorizeExchangeCustomizerBeanConfig.class).autowire(); Customizer authzCustomizer = this.spring.getContext().getBean("authz", Customizer.class); ArgumentCaptor arg0 = ArgumentCaptor.forClass(AuthorizeExchangeSpec.class); verify(authzCustomizer).customize(arg0.capture()); } @Test void serverHttpSecurityCustomizerBean() { this.spring.register(ServerHttpSecurityCustomizerConfig.class).autowire(); Customizer httpSecurityCustomizer = this.spring.getContext() .getBean("httpSecurityCustomizer", Customizer.class); ArgumentCaptor arg0 = ArgumentCaptor.forClass(ServerHttpSecurity.class); verify(httpSecurityCustomizer).customize(arg0.capture()); } @Test void multiServerHttpSecurityCustomizerBean() { this.spring.register(MultiServerHttpSecurityCustomizerConfig.class).autowire(); Customizer httpSecurityCustomizer = this.spring.getContext() .getBean("httpSecurityCustomizer", Customizer.class); Customizer httpSecurityCustomizer0 = this.spring.getContext() .getBean("httpSecurityCustomizer0", Customizer.class); InOrder inOrder = Mockito.inOrder(httpSecurityCustomizer0, httpSecurityCustomizer); ArgumentCaptor arg0 = ArgumentCaptor.forClass(ServerHttpSecurity.class); inOrder.verify(httpSecurityCustomizer0).customize(arg0.capture()); inOrder.verify(httpSecurityCustomizer).customize(arg0.capture()); } @Configuration static class SubclassConfig extends ServerHttpSecurityConfiguration { } @Configuration(proxyBeanMethods = false) @EnableWebFlux @EnableWebFluxSecurity static class FormLoginConfig { @Bean SecurityWebFilterChain filterChain(ServerHttpSecurity http) { // @formatter:off http .authorizeExchange((authorize) -> authorize .anyExchange().authenticated() ) .formLogin(Customizer.withDefaults()); // @formatter:on return http.build(); } } @Configuration(proxyBeanMethods = false) @EnableWebFlux @EnableWebFluxSecurity static class FormLoginRedirectToResetPasswordConfig { @Bean SecurityWebFilterChain filterChain(ServerHttpSecurity http) { // @formatter:off http .authorizeExchange((authorize) -> authorize .anyExchange().authenticated() ) .formLogin((form) -> form .authenticationFailureHandler((webFilterExchange, exception) -> { String redirectUrl = "/login?error"; if (exception instanceof CompromisedPasswordException) { redirectUrl = "/reset-password"; } return new DefaultServerRedirectStrategy().sendRedirect(webFilterExchange.getExchange(), URI.create(redirectUrl)); }) ); // @formatter:on return http.build(); } } @Configuration(proxyBeanMethods = false) static class UserDetailsConfig { @Bean MapReactiveUserDetailsService userDetailsService() { // @formatter:off UserDetails user = PasswordEncodedUser.user(); UserDetails admin = User.withDefaultPasswordEncoder() .username("admin") .password("password2") .roles("USER", "ADMIN") .build(); // @formatter:on return new MapReactiveUserDetailsService(user, admin); } } @Configuration(proxyBeanMethods = false) static class CompromisedPasswordCheckerConfig { @Bean TestReactivePasswordChecker compromisedPasswordChecker() { return new TestReactivePasswordChecker(); } } static class TestReactivePasswordChecker implements ReactiveCompromisedPasswordChecker { @Override public Mono check(String password) { if ("password".equals(password)) { return Mono.just(new CompromisedPasswordDecision(true)); } return Mono.just(new CompromisedPasswordDecision(false)); } } @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.PARAMETER) @AuthenticationPrincipal(expression = "#this.equals('{value}')") @interface IsUser { String value() default "user"; } @Target({ ElementType.PARAMETER }) @Retention(RetentionPolicy.RUNTIME) @CurrentSecurityContext(expression = "authentication.{property}") @interface CurrentAuthenticationProperty { String property(); } @RestController static class TestController { @GetMapping("/hi") String ifUser(@IsUser("harold") boolean isHarold) { if (isHarold) { return "Hi, Harold!"; } else { return "Hi, Stranger!"; } } @GetMapping("/hello") String getCurrentAuthenticationProperty( @CurrentAuthenticationProperty(property = "principal") String principal) { return principal; } } @Configuration @EnableWebFlux @EnableWebFluxSecurity static class MetaAnnotationPlaceholderConfig { @Bean SecurityWebFilterChain filterChain(ServerHttpSecurity http) { // @formatter:off http .authorizeExchange((authorize) -> authorize.anyExchange().authenticated()) .httpBasic(Customizer.withDefaults()); // @formatter:on return http.build(); } @Bean ReactiveUserDetailsService userDetailsService() { return new MapReactiveUserDetailsService( User.withUsername("user").password("password").authorities("app").build()); } @Bean TestController testController() { return new TestController(); } @Bean AnnotationTemplateExpressionDefaults templateExpressionDefaults() { return new AnnotationTemplateExpressionDefaults(); } } @Configuration @EnableWebFlux @EnableWebFluxSecurity static class ObservationRegistryConfig { private ObservationHandler handler = mock(ObservationHandler.class); @Bean SecurityWebFilterChain app(ServerHttpSecurity http) throws Exception { http.httpBasic(withDefaults()).authorizeExchange((authorize) -> authorize.anyExchange().authenticated()); return http.build(); } @Bean ReactiveUserDetailsService userDetailsService() { return new MapReactiveUserDetailsService( User.withDefaultPasswordEncoder().username("user").password("password").authorities("app").build()); } @Bean ObservationHandler observationHandler() { return this.handler; } @Bean ObservationRegistry observationRegistry() { given(this.handler.supportsContext(any())).willReturn(true); ObservationRegistry registry = ObservationRegistry.create(); registry.observationConfig().observationHandler(this.handler); return registry; } } @EnableRSocketSecurity static class RSocketSecurityConfig { @Bean RSocketMessageHandler messageHandler() { return new RSocketMessageHandler(); } } @Configuration(proxyBeanMethods = false) @EnableWebFlux @EnableWebFluxSecurity @Import(UserDetailsConfig.class) static class AuthorizeExchangeCustomizerBeanConfig { @Bean SecurityWebFilterChain filterChain(ServerHttpSecurity http) { return http.build(); } @Bean static Customizer authz() { return mock(Customizer.class); } } @Configuration(proxyBeanMethods = false) @Import(AuthorizeExchangeCustomizerBeanConfig.class) static class MultiAuthorizeExchangeCustomizerBeanConfig { @Bean @Order(Ordered.HIGHEST_PRECEDENCE) Customizer authz0() { return mock(Customizer.class); } } @Configuration(proxyBeanMethods = false) @EnableWebFlux @EnableWebFluxSecurity @Import(UserDetailsConfig.class) static class ServerHttpSecurityCustomizerConfig { @Bean SecurityWebFilterChain filterChain(ServerHttpSecurity http) { return http.build(); } @Bean static Customizer httpSecurityCustomizer() { return mock(Customizer.class); } } @Configuration(proxyBeanMethods = false) @Import(ServerHttpSecurityCustomizerConfig.class) static class MultiServerHttpSecurityCustomizerConfig { @Bean @Order(Ordered.HIGHEST_PRECEDENCE) static Customizer httpSecurityCustomizer0() { return mock(Customizer.class); } } @Configuration(proxyBeanMethods = false) static class ReactiveUserDetailsServiceOnlyTestConfiguration { @Bean static ReactiveUserDetailsService userDetailsService() { return (username) -> Mono.just(PasswordEncodedUser.user()); } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/annotation/web/reactive/WebFluxSecurityConfigurationTests.java ================================================ /* * 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 org.springframework.security.config.annotation.web.reactive; import java.util.Collections; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import reactor.core.publisher.Mono; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpStatus; import org.springframework.lang.NonNull; import org.springframework.mock.http.server.reactive.MockServerHttpRequest; import org.springframework.mock.web.server.MockServerWebExchange; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.config.users.ReactiveAuthenticationTestConfiguration; import org.springframework.security.web.server.WebFilterChainProxy; import org.springframework.security.web.server.firewall.HttpStatusExchangeRejectedHandler; import org.springframework.security.web.server.firewall.ServerExchangeRejectedHandler; import org.springframework.security.web.server.firewall.ServerWebExchangeFirewall; import org.springframework.web.server.handler.DefaultWebFilterChain; import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link WebFluxSecurityConfiguration}. * * @author Eleftheria Stein */ @ExtendWith(SpringTestContextExtension.class) public class WebFluxSecurityConfigurationTests { public final SpringTestContext spring = new SpringTestContext(this); @Test public void loadConfigWhenReactiveUserDetailsServiceConfiguredThenWebFilterChainProxyExists() { this.spring .register(ServerHttpSecurityConfiguration.class, ReactiveAuthenticationTestConfiguration.class, WebFluxSecurityConfiguration.class) .autowire(); WebFilterChainProxy webFilterChainProxy = this.spring.getContext().getBean(WebFilterChainProxy.class); assertThat(webFilterChainProxy).isNotNull(); } @Test void loadConfigWhenDefaultThenFirewalled() throws Exception { this.spring .register(ServerHttpSecurityConfiguration.class, ReactiveAuthenticationTestConfiguration.class, WebFluxSecurityConfiguration.class) .autowire(); WebFilterChainProxy webFilterChainProxy = this.spring.getContext().getBean(WebFilterChainProxy.class); MockServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/;/").build()); DefaultWebFilterChain chain = emptyChain(); webFilterChainProxy.filter(exchange, chain).block(); assertThat(exchange.getResponse().getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST); } @Test void loadConfigWhenCustomRejectedHandler() throws Exception { this.spring .register(ServerHttpSecurityConfiguration.class, ReactiveAuthenticationTestConfiguration.class, WebFluxSecurityConfiguration.class, CustomServerExchangeRejectedHandlerConfig.class) .autowire(); WebFilterChainProxy webFilterChainProxy = this.spring.getContext().getBean(WebFilterChainProxy.class); MockServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/;/").build()); DefaultWebFilterChain chain = emptyChain(); webFilterChainProxy.filter(exchange, chain).block(); assertThat(exchange.getResponse().getStatusCode()) .isEqualTo(CustomServerExchangeRejectedHandlerConfig.EXPECTED_STATUS); } @Test void loadConfigWhenFirewallBeanThenCustomized() throws Exception { this.spring .register(ServerHttpSecurityConfiguration.class, ReactiveAuthenticationTestConfiguration.class, WebFluxSecurityConfiguration.class, NoOpFirewallConfig.class) .autowire(); WebFilterChainProxy webFilterChainProxy = this.spring.getContext().getBean(WebFilterChainProxy.class); MockServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/;/").build()); DefaultWebFilterChain chain = emptyChain(); webFilterChainProxy.filter(exchange, chain).block(); assertThat(exchange.getResponse().getStatusCode()).isNotEqualTo(HttpStatus.BAD_REQUEST); } @Test public void loadConfigWhenBeanProxyingEnabledAndSubclassThenWebFilterChainProxyExists() { this.spring .register(ServerHttpSecurityConfiguration.class, ReactiveAuthenticationTestConfiguration.class, WebFluxSecurityConfigurationTests.SubclassConfig.class) .autowire(); WebFilterChainProxy webFilterChainProxy = this.spring.getContext().getBean(WebFilterChainProxy.class); assertThat(webFilterChainProxy).isNotNull(); } private static @NonNull DefaultWebFilterChain emptyChain() { return new DefaultWebFilterChain((webExchange) -> Mono.empty(), Collections.emptyList()); } @Configuration static class NoOpFirewallConfig { @Bean ServerWebExchangeFirewall noOpFirewall() { return ServerWebExchangeFirewall.INSECURE_NOOP; } } @Configuration static class CustomServerExchangeRejectedHandlerConfig { static HttpStatus EXPECTED_STATUS = HttpStatus.I_AM_A_TEAPOT; @Bean ServerExchangeRejectedHandler rejectedHandler() { return new HttpStatusExchangeRejectedHandler(EXPECTED_STATUS); } } @Configuration static class SubclassConfig extends WebFluxSecurityConfiguration { } } ================================================ FILE: config/src/test/java/org/springframework/security/config/annotation/web/socket/SyncExecutorSubscribableChannelPostProcessor.java ================================================ /* * 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 org.springframework.security.config.annotation.web.socket; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.messaging.support.ExecutorSubscribableChannel; /** * @author Rob Winch */ public class SyncExecutorSubscribableChannelPostProcessor implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { if (bean instanceof ExecutorSubscribableChannel original) { ExecutorSubscribableChannel channel = new ExecutorSubscribableChannel(); channel.setInterceptors(original.getInterceptors()); return channel; } return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { return bean; } } ================================================ FILE: config/src/test/java/org/springframework/security/config/annotation/web/socket/TestDeferredCsrfToken.java ================================================ /* * 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 org.springframework.security.config.annotation.web.socket; import org.springframework.security.web.csrf.CsrfToken; import org.springframework.security.web.csrf.DeferredCsrfToken; /** * @author Steve Riesenberg */ final class TestDeferredCsrfToken implements DeferredCsrfToken { private final CsrfToken csrfToken; TestDeferredCsrfToken(CsrfToken csrfToken) { this.csrfToken = csrfToken; } @Override public CsrfToken get() { return this.csrfToken; } @Override public boolean isGenerated() { return false; } } ================================================ FILE: config/src/test/java/org/springframework/security/config/annotation/web/socket/WebSocketMessageBrokerSecurityConfigurationDocTests.java ================================================ /* * 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 org.springframework.security.config.annotation.web.socket; import java.util.HashMap; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.messaging.Message; import org.springframework.messaging.MessageChannel; import org.springframework.messaging.MessageDeliveryException; import org.springframework.messaging.handler.annotation.MessageMapping; import org.springframework.messaging.simp.SimpMessageHeaderAccessor; import org.springframework.messaging.simp.SimpMessageType; import org.springframework.messaging.simp.config.MessageBrokerRegistry; import org.springframework.messaging.support.GenericMessage; import org.springframework.mock.web.MockServletConfig; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.authorization.AuthorizationManager; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.messaging.access.intercept.MessageMatcherDelegatingAuthorizationManager; import org.springframework.security.web.csrf.CsrfToken; import org.springframework.security.web.csrf.DefaultCsrfToken; import org.springframework.stereotype.Controller; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; import org.springframework.web.socket.config.annotation.StompEndpointRegistry; import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; public class WebSocketMessageBrokerSecurityConfigurationDocTests { AnnotationConfigWebApplicationContext context; TestingAuthenticationToken messageUser; CsrfToken token; String sessionAttr; @BeforeEach public void setup() { this.token = new DefaultCsrfToken("header", "param", "token"); this.sessionAttr = "sessionAttr"; this.messageUser = new TestingAuthenticationToken("user", "pass", "ROLE_USER"); } @AfterEach public void cleanup() { if (this.context != null) { this.context.close(); } } @Test public void securityMappings() { loadConfig(WebSocketSecurityConfig.class); clientInboundChannel().send(message("/user/queue/errors", SimpMessageType.SUBSCRIBE)); assertThatExceptionOfType(MessageDeliveryException.class) .isThrownBy(() -> clientInboundChannel().send(message("/denyAll", SimpMessageType.MESSAGE))) .withCauseInstanceOf(AccessDeniedException.class); } private void loadConfig(Class... configs) { this.context = new AnnotationConfigWebApplicationContext(); this.context.register(configs); this.context.register(WebSocketConfig.class, SyncExecutorConfig.class); this.context.setServletConfig(new MockServletConfig()); this.context.refresh(); } private MessageChannel clientInboundChannel() { return this.context.getBean("clientInboundChannel", MessageChannel.class); } private Message message(String destination, SimpMessageType type) { SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.create(type); return message(headers, destination); } private Message message(SimpMessageHeaderAccessor headers, String destination) { headers.setSessionId("123"); headers.setSessionAttributes(new HashMap<>()); if (destination != null) { headers.setDestination(destination); } if (this.messageUser != null) { headers.setUser(this.messageUser); } return new GenericMessage<>("hi", headers.getMessageHeaders()); } @Controller static class MyController { @MessageMapping("/authentication") void authentication(@AuthenticationPrincipal String un) { // ... do something ... } } @Configuration @EnableWebSocketSecurity static class WebSocketSecurityConfig { @Bean AuthorizationManager> authorizationManager( MessageMatcherDelegatingAuthorizationManager.Builder messages) { messages.nullDestMatcher() .authenticated() // <1> .simpSubscribeDestMatchers("/user/queue/errors") .permitAll() // <2> .simpDestMatchers("/app/**") .hasRole("USER") // <3> .simpSubscribeDestMatchers("/user/**", "/topic/friends/*") .hasRole("USER") // <4> .simpTypeMatchers(SimpMessageType.MESSAGE, SimpMessageType.SUBSCRIBE) .denyAll() // <5> .anyMessage() .denyAll(); // <6> return messages.build(); } } @Configuration @EnableWebSocketMessageBroker static class WebSocketConfig implements WebSocketMessageBrokerConfigurer { @Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/chat").withSockJS(); } @Override public void configureMessageBroker(MessageBrokerRegistry registry) { registry.enableSimpleBroker("/queue/", "/topic/"); registry.setApplicationDestinationPrefixes("/permitAll", "/denyAll"); } @Bean MyController myController() { return new MyController(); } } @Configuration static class SyncExecutorConfig { @Bean static SyncExecutorSubscribableChannelPostProcessor postProcessor() { return new SyncExecutorSubscribableChannelPostProcessor(); } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/annotation/web/socket/WebSocketMessageBrokerSecurityConfigurationTests.java ================================================ /* * 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 org.springframework.security.config.annotation.web.socket; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.function.Consumer; import java.util.stream.Stream; import io.micrometer.observation.Observation; import io.micrometer.observation.ObservationHandler; import io.micrometer.observation.ObservationRegistry; import io.micrometer.observation.ObservationTextPublisher; import jakarta.servlet.http.HttpServletRequest; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.springframework.beans.BeansException; import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.core.MethodParameter; import org.springframework.http.server.PathContainer; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.messaging.Message; import org.springframework.messaging.MessageChannel; import org.springframework.messaging.MessageDeliveryException; import org.springframework.messaging.handler.annotation.MessageMapping; import org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver; import org.springframework.messaging.simp.SimpMessageHeaderAccessor; import org.springframework.messaging.simp.SimpMessageType; import org.springframework.messaging.simp.config.MessageBrokerRegistry; import org.springframework.messaging.support.AbstractMessageChannel; import org.springframework.messaging.support.ChannelInterceptor; import org.springframework.messaging.support.GenericMessage; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.mock.web.MockServletConfig; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.authentication.AnonymousAuthenticationToken; import org.springframework.security.authentication.RememberMeAuthenticationToken; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.authorization.AuthorizationDecision; import org.springframework.security.authorization.AuthorizationManager; import org.springframework.security.config.annotation.SecurityContextChangedListenerConfig; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.observation.SecurityObservationSettings; import org.springframework.security.config.web.messaging.PathPatternMessageMatcherBuilderFactoryBean; import org.springframework.security.core.Authentication; import org.springframework.security.core.annotation.AnnotationTemplateExpressionDefaults; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.security.messaging.access.intercept.AuthorizationChannelInterceptor; import org.springframework.security.messaging.access.intercept.MessageAuthorizationContext; import org.springframework.security.messaging.access.intercept.MessageMatcherDelegatingAuthorizationManager; import org.springframework.security.messaging.context.SecurityContextChannelInterceptor; import org.springframework.security.messaging.web.csrf.XorCsrfChannelInterceptor; import org.springframework.security.web.csrf.CsrfToken; import org.springframework.security.web.csrf.DefaultCsrfToken; import org.springframework.security.web.csrf.DeferredCsrfToken; import org.springframework.security.web.csrf.MissingCsrfTokenException; import org.springframework.stereotype.Controller; import org.springframework.test.util.ReflectionTestUtils; import org.springframework.util.AntPathMatcher; import org.springframework.web.HttpRequestHandler; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; import org.springframework.web.servlet.HandlerMapping; import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; import org.springframework.web.socket.config.annotation.StompEndpointRegistry; import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer; import org.springframework.web.socket.server.HandshakeFailureException; import org.springframework.web.socket.server.HandshakeHandler; import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor; import org.springframework.web.socket.sockjs.transport.handler.SockJsWebSocketHandler; import org.springframework.web.socket.sockjs.transport.session.WebSocketServerSockJsSession; import org.springframework.web.util.pattern.PathPatternParser; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; import static org.springframework.security.web.csrf.CsrfTokenAssert.assertThatCsrfToken; public class WebSocketMessageBrokerSecurityConfigurationTests { private static final String XOR_CSRF_TOKEN_VALUE = "wpe7zB62-NCpcA=="; AnnotationConfigWebApplicationContext context; Authentication messageUser; CsrfToken token; String sessionAttr; @BeforeEach public void setup() { this.token = new DefaultCsrfToken("header", "param", "token"); this.sessionAttr = "sessionAttr"; this.messageUser = new TestingAuthenticationToken("user", "pass", "ROLE_USER"); } @AfterEach public void cleanup() { if (this.context != null) { this.context.close(); } } @Test public void simpleRegistryMappings() { loadConfig(SockJsSecurityConfig.class); clientInboundChannel().send(message("/permitAll")); assertThatExceptionOfType(MessageDeliveryException.class) .isThrownBy(() -> clientInboundChannel().send(message("/denyAll"))) .withCauseInstanceOf(AccessDeniedException.class); } @Test public void annonymousSupported() { loadConfig(SockJsSecurityConfig.class); this.messageUser = null; clientInboundChannel().send(message("/permitAll")); } // gh-3797 @Test public void beanResolver() { loadConfig(SockJsSecurityConfig.class); this.messageUser = null; clientInboundChannel().send(message("/beanResolver")); } @Test public void addsAuthenticationPrincipalResolver() { loadConfig(SockJsSecurityConfig.class); MessageChannel messageChannel = clientInboundChannel(); Message message = message("/permitAll/authentication"); messageChannel.send(message); assertThat(this.context.getBean(MyController.class).authenticationPrincipal) .isEqualTo((String) this.messageUser.getPrincipal()); } @Test public void addsAuthenticationPrincipalResolverWhenNoAuthorization() { loadConfig(NoInboundSecurityConfig.class); MessageChannel messageChannel = clientInboundChannel(); Message message = message("/permitAll/authentication"); messageChannel.send(message); assertThat(this.context.getBean(MyController.class).authenticationPrincipal) .isEqualTo((String) this.messageUser.getPrincipal()); } @Test public void sendMessageWhenMetaAnnotationThenParsesExpression() { loadConfig(NoInboundSecurityConfig.class); this.messageUser = new TestingAuthenticationToken("harold", "password", "ROLE_USER"); clientInboundChannel().send(message("/permitAll/hi")); assertThat(this.context.getBean(MyController.class).message).isEqualTo("Hi, Harold!"); this.messageUser = new TestingAuthenticationToken("user", "password", "ROLE_USER"); clientInboundChannel().send(message("/permitAll/hi")); assertThat(this.context.getBean(MyController.class).message).isEqualTo("Hi, Stranger!"); } @Test public void addsCsrfProtectionWhenNoAuthorization() { loadConfig(NoInboundSecurityConfig.class); SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.create(SimpMessageType.CONNECT); Message message = message(headers, "/authentication"); MessageChannel messageChannel = clientInboundChannel(); assertThatExceptionOfType(MessageDeliveryException.class).isThrownBy(() -> messageChannel.send(message)) .withCauseInstanceOf(MissingCsrfTokenException.class); } @Test public void csrfProtectionForConnect() { loadConfig(SockJsSecurityConfig.class); SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.create(SimpMessageType.CONNECT); Message message = message(headers, "/authentication"); MessageChannel messageChannel = clientInboundChannel(); assertThatExceptionOfType(MessageDeliveryException.class).isThrownBy(() -> messageChannel.send(message)) .withCauseInstanceOf(MissingCsrfTokenException.class); } @Test @Disabled // to be added back in with the introduction of DSL support public void csrfProtectionDisabledForConnect() { loadConfig(CsrfDisabledSockJsSecurityConfig.class); SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.create(SimpMessageType.CONNECT); Message message = message(headers, "/permitAll/connect"); MessageChannel messageChannel = clientInboundChannel(); messageChannel.send(message); } @Test public void csrfProtectionDefinedByBean() { loadConfig(SockJsProxylessSecurityConfig.class); MessageChannel messageChannel = clientInboundChannel(); Stream> interceptors = ((AbstractMessageChannel) messageChannel) .getInterceptors() .stream() .map(ChannelInterceptor::getClass); assertThat(interceptors).contains(XorCsrfChannelInterceptor.class); } @Test public void messagesConnectUseCsrfTokenHandshakeInterceptor() throws Exception { loadConfig(SockJsSecurityConfig.class); SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.create(SimpMessageType.CONNECT); Message message = message(headers, "/authentication"); MockHttpServletRequest request = sockjsHttpRequest("/chat"); HttpRequestHandler handler = handler(request); handler.handleRequest(request, new MockHttpServletResponse()); assertHandshake(request); } @Test public void messagesConnectUseCsrfTokenHandshakeInterceptorMultipleMappings() throws Exception { loadConfig(SockJsSecurityConfig.class); SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.create(SimpMessageType.CONNECT); Message message = message(headers, "/authentication"); MockHttpServletRequest request = sockjsHttpRequest("/other"); HttpRequestHandler handler = handler(request); handler.handleRequest(request, new MockHttpServletResponse()); assertHandshake(request); } @Test public void messagesConnectWebSocketUseCsrfTokenHandshakeInterceptor() throws Exception { loadConfig(WebSocketSecurityConfig.class); SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.create(SimpMessageType.CONNECT); Message message = message(headers, "/authentication"); MockHttpServletRequest request = websocketHttpRequest("/websocket"); HttpRequestHandler handler = handler(request); handler.handleRequest(request, new MockHttpServletResponse()); assertHandshake(request); } @Test public void messagesContextWebSocketUseSecurityContextHolderStrategy() { loadConfig(WebSocketSecurityConfig.class, SecurityContextChangedListenerConfig.class); SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.create(SimpMessageType.CONNECT); headers.setNativeHeader(this.token.getHeaderName(), XOR_CSRF_TOKEN_VALUE); Message message = message(headers, "/authenticated"); headers.getSessionAttributes().put(CsrfToken.class.getName(), this.token); MessageChannel messageChannel = clientInboundChannel(); messageChannel.send(message); verify(this.context.getBean(SecurityContextHolderStrategy.class), atLeastOnce()).getContext(); } @Test public void msmsRegistryCustomPatternMatcher() { loadConfig(MsmsRegistryCustomPatternMatcherConfig.class); clientInboundChannel().send(message("/app/a.b")); assertThatExceptionOfType(MessageDeliveryException.class) .isThrownBy(() -> clientInboundChannel().send(message("/app/a.b.c"))) .withCauseInstanceOf(AccessDeniedException.class); } @Test public void overrideMsmsRegistryCustomPatternMatcher() { loadConfig(OverrideMsmsRegistryCustomPatternMatcherConfig.class); clientInboundChannel().send(message("/app/a/b")); assertThatExceptionOfType(MessageDeliveryException.class) .isThrownBy(() -> clientInboundChannel().send(message("/app/a/b/c"))) .withCauseInstanceOf(AccessDeniedException.class); } @Test public void defaultPatternMatcher() { loadConfig(DefaultPatternMatcherConfig.class); clientInboundChannel().send(message("/app/a/b")); assertThatExceptionOfType(MessageDeliveryException.class) .isThrownBy(() -> clientInboundChannel().send(message("/app/a/b/c"))) .withCauseInstanceOf(AccessDeniedException.class); } @Test public void customExpression() { loadConfig(CustomExpressionConfig.class); clientInboundChannel().send(message("/denyRob")); this.messageUser = new TestingAuthenticationToken("rob", "password", "ROLE_USER"); assertThatExceptionOfType(MessageDeliveryException.class) .isThrownBy(() -> clientInboundChannel().send(message("/denyRob"))) .withCauseInstanceOf(AccessDeniedException.class); } @Test public void channelSecurityInterceptorUsesMetadataSourceBeanWhenProxyingDisabled() { loadConfig(SockJsProxylessSecurityConfig.class); AbstractMessageChannel messageChannel = clientInboundChannel(); AuthorizationManager> authorizationManager = this.context.getBean(AuthorizationManager.class); for (ChannelInterceptor interceptor : messageChannel.getInterceptors()) { if (interceptor instanceof AuthorizationChannelInterceptor) { assertThat(ReflectionTestUtils.getField(interceptor, "preSendAuthorizationManager")) .isSameAs(authorizationManager); return; } } fail("did not find AuthorizationChannelInterceptor"); } @Test public void securityContextChannelInterceptorDefinedByBean() { loadConfig(SockJsProxylessSecurityConfig.class); MessageChannel messageChannel = clientInboundChannel(); Stream> interceptors = ((AbstractMessageChannel) messageChannel) .getInterceptors() .stream() .map(ChannelInterceptor::getClass); assertThat(interceptors).contains(SecurityContextChannelInterceptor.class); } @Test public void inboundChannelSecurityDefinedByBean() { loadConfig(SockJsProxylessSecurityConfig.class); MessageChannel messageChannel = clientInboundChannel(); Stream> interceptors = ((AbstractMessageChannel) messageChannel) .getInterceptors() .stream() .map(ChannelInterceptor::getClass); assertThat(interceptors).contains(AuthorizationChannelInterceptor.class); } @Test public void sendMessageWhenFullyAuthenticatedConfiguredAndRememberMeTokenThenAccessDeniedException() { loadConfig(WebSocketSecurityConfig.class); this.messageUser = new RememberMeAuthenticationToken("key", "user", AuthorityUtils.createAuthorityList("ROLE_USER")); assertThatExceptionOfType(MessageDeliveryException.class) .isThrownBy(() -> clientInboundChannel().send(message("/fullyAuthenticated"))) .withCauseInstanceOf(AccessDeniedException.class); } @Test public void sendMessageWhenFullyAuthenticatedConfiguredAndUserThenPasses() { loadConfig(WebSocketSecurityConfig.class); clientInboundChannel().send(message("/fullyAuthenticated")); } @Test public void sendMessageWhenRememberMeConfiguredAndNoUserThenAccessDeniedException() { loadConfig(WebSocketSecurityConfig.class); this.messageUser = null; assertThatExceptionOfType(MessageDeliveryException.class) .isThrownBy(() -> clientInboundChannel().send(message("/rememberMe"))) .withCauseInstanceOf(AccessDeniedException.class); } @Test public void sendMessageWhenRememberMeConfiguredAndRememberMeTokenThenPasses() { loadConfig(WebSocketSecurityConfig.class); this.messageUser = new RememberMeAuthenticationToken("key", "user", AuthorityUtils.createAuthorityList("ROLE_USER")); clientInboundChannel().send(message("/rememberMe")); } @Test public void sendMessageWhenAnonymousConfiguredAndAnonymousUserThenPasses() { loadConfig(WebSocketSecurityConfig.class); this.messageUser = new AnonymousAuthenticationToken("key", "user", AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS")); clientInboundChannel().send(message("/anonymous")); } @Test public void sendMessageWhenObservationRegistryThenObserves() { loadConfig(WebSocketSecurityConfig.class, ObservationRegistryConfig.class); SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.create(SimpMessageType.CONNECT); headers.setNativeHeader(this.token.getHeaderName(), XOR_CSRF_TOKEN_VALUE); Message message = message(headers, "/authenticated"); headers.getSessionAttributes().put(CsrfToken.class.getName(), this.token); clientInboundChannel().send(message); ObservationHandler observationHandler = this.context.getBean(ObservationHandler.class); verify(observationHandler).onStart(any()); verify(observationHandler).onStop(any()); headers = SimpMessageHeaderAccessor.create(SimpMessageType.CONNECT); headers.setNativeHeader(this.token.getHeaderName(), XOR_CSRF_TOKEN_VALUE); message = message(headers, "/denyAll"); headers.getSessionAttributes().put(CsrfToken.class.getName(), this.token); try { clientInboundChannel().send(message); } catch (MessageDeliveryException ex) { // okay } verify(observationHandler).onError(any()); } @Test public void sendMessageWhenExcludeAuthorizationObservationsThenUnobserved() { loadConfig(WebSocketSecurityConfig.class, ObservationRegistryConfig.class, SelectableObservationsConfig.class); SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.create(SimpMessageType.CONNECT); headers.setNativeHeader(this.token.getHeaderName(), XOR_CSRF_TOKEN_VALUE); Message message = message(headers, "/authenticated"); headers.getSessionAttributes().put(CsrfToken.class.getName(), this.token); clientInboundChannel().send(message); ObservationHandler observationHandler = this.context.getBean(ObservationHandler.class); headers = SimpMessageHeaderAccessor.create(SimpMessageType.CONNECT); headers.setNativeHeader(this.token.getHeaderName(), XOR_CSRF_TOKEN_VALUE); message = message(headers, "/denyAll"); headers.getSessionAttributes().put(CsrfToken.class.getName(), this.token); try { clientInboundChannel().send(message); } catch (MessageDeliveryException ex) { // okay } verifyNoInteractions(observationHandler); } // gh-16011 @Test public void enableWebSocketSecurityWhenWebSocketSecurityUsedThenAutowires() { loadConfig(WithWebSecurity.class); } private void assertHandshake(HttpServletRequest request) { TestHandshakeHandler handshakeHandler = this.context.getBean(TestHandshakeHandler.class); assertThatCsrfToken(handshakeHandler.attributes.get(CsrfToken.class.getName())).isEqualTo(this.token); assertThat(handshakeHandler.attributes).containsEntry(this.sessionAttr, request.getSession().getAttribute(this.sessionAttr)); } private HttpRequestHandler handler(HttpServletRequest request) throws Exception { HandlerMapping handlerMapping = this.context.getBean(HandlerMapping.class); return (HttpRequestHandler) handlerMapping.getHandler(request).getHandler(); } private MockHttpServletRequest websocketHttpRequest(String mapping) { MockHttpServletRequest request = sockjsHttpRequest(mapping); request.setRequestURI(mapping); return request; } private MockHttpServletRequest sockjsHttpRequest(String mapping) { MockHttpServletRequest request = new MockHttpServletRequest("GET", ""); request.setMethod("GET"); request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "/289/tpyx6mde/websocket"); request.setRequestURI(mapping + "/289/tpyx6mde/websocket"); request.getSession().setAttribute(this.sessionAttr, "sessionValue"); request.setAttribute(DeferredCsrfToken.class.getName(), new TestDeferredCsrfToken(this.token)); return request; } private Message message(String destination) { SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.create(); return message(headers, destination); } private Message message(SimpMessageHeaderAccessor headers, String destination) { headers.setSessionId("123"); headers.setSessionAttributes(new HashMap<>()); if (destination != null) { headers.setDestination(destination); } if (this.messageUser != null) { headers.setUser(this.messageUser); } return new GenericMessage<>("hi", headers.getMessageHeaders()); } private T clientInboundChannel() { return (T) this.context.getBean("clientInboundChannel", MessageChannel.class); } private void loadConfig(Class... configs) { this.context = new AnnotationConfigWebApplicationContext(); this.context.setAllowBeanDefinitionOverriding(false); this.context.register(configs); this.context.setServletConfig(new MockServletConfig()); this.context.refresh(); } @Configuration @EnableWebSocketMessageBroker @EnableWebSocketSecurity @Import(SyncExecutorConfig.class) static class MsmsRegistryCustomPatternMatcherConfig implements WebSocketMessageBrokerConfigurer { @Bean PathPatternMessageMatcherBuilderFactoryBean messageMatcherBuilder() { PathPatternParser parser = new PathPatternParser(); parser.setPathOptions(PathContainer.Options.MESSAGE_ROUTE); return new PathPatternMessageMatcherBuilderFactoryBean(parser); } // @formatter:off @Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry .addEndpoint("/other") .setHandshakeHandler(testHandshakeHandler()); } // @formatter:on @Override public void configureMessageBroker(MessageBrokerRegistry registry) { registry.enableSimpleBroker("/queue/", "/topic/"); registry.setApplicationDestinationPrefixes("/app"); } // @formatter:off @Bean AuthorizationManager> authorizationManager(MessageMatcherDelegatingAuthorizationManager.Builder messages) { messages .simpDestMatchers("/app/a.*").permitAll() .anyMessage().denyAll(); return messages.build(); } // @formatter:on @Bean TestHandshakeHandler testHandshakeHandler() { return new TestHandshakeHandler(); } } @Configuration @EnableWebSocketMessageBroker @EnableWebSocketSecurity @Import(SyncExecutorConfig.class) static class OverrideMsmsRegistryCustomPatternMatcherConfig implements WebSocketMessageBrokerConfigurer { // @formatter:off @Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry .addEndpoint("/other") .setHandshakeHandler(testHandshakeHandler()); } // @formatter:on @Override public void configureMessageBroker(MessageBrokerRegistry registry) { registry.setPathMatcher(new AntPathMatcher(".")); registry.enableSimpleBroker("/queue/", "/topic/"); registry.setApplicationDestinationPrefixes("/app"); } // @formatter:off @Bean AuthorizationManager> authorizationManager(MessageMatcherDelegatingAuthorizationManager.Builder messages) { messages .simpDestMatchers("/app/a/*").permitAll() .anyMessage().denyAll(); return messages.build(); } // @formatter:on @Bean TestHandshakeHandler testHandshakeHandler() { return new TestHandshakeHandler(); } } @Configuration @EnableWebSocketMessageBroker @EnableWebSocketSecurity @Import(SyncExecutorConfig.class) static class DefaultPatternMatcherConfig implements WebSocketMessageBrokerConfigurer { // @formatter:off @Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry .addEndpoint("/other") .setHandshakeHandler(testHandshakeHandler()); } // @formatter:on @Override public void configureMessageBroker(MessageBrokerRegistry registry) { registry.enableSimpleBroker("/queue/", "/topic/"); registry.setApplicationDestinationPrefixes("/app"); } // @formatter:off @Bean AuthorizationManager> authorizationManager(MessageMatcherDelegatingAuthorizationManager.Builder messages) { messages .simpDestMatchers("/app/a/*").permitAll() .anyMessage().denyAll(); return messages.build(); } // @formatter:on @Bean TestHandshakeHandler testHandshakeHandler() { return new TestHandshakeHandler(); } } @Configuration @EnableWebSocketMessageBroker @EnableWebSocketSecurity @Import(SyncExecutorConfig.class) static class CustomExpressionConfig implements WebSocketMessageBrokerConfigurer { // @formatter:off @Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry .addEndpoint("/other") .setHandshakeHandler(testHandshakeHandler()); } // @formatter:on @Override public void configureMessageBroker(MessageBrokerRegistry registry) { registry.enableSimpleBroker("/queue/", "/topic/"); registry.setApplicationDestinationPrefixes("/app"); } @Bean AuthorizationManager> authorizationManager() { return (authentication, message) -> { Authentication auth = authentication.get(); return new AuthorizationDecision(auth != null && !"rob".equals(auth.getName())); }; } @Bean TestHandshakeHandler testHandshakeHandler() { return new TestHandshakeHandler(); } } @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.PARAMETER) @AuthenticationPrincipal(expression = "#this.equals('{value}')") @interface IsUser { String value() default "user"; } @Controller static class MyController { String authenticationPrincipal; MyCustomArgument myCustomArgument; String message; @MessageMapping("/authentication") void authentication(@AuthenticationPrincipal String un) { this.authenticationPrincipal = un; } @MessageMapping("/myCustom") void myCustom(MyCustomArgument myCustomArgument) { this.myCustomArgument = myCustomArgument; } @MessageMapping("/hi") void sayHello(@IsUser("harold") boolean isHarold) { this.message = isHarold ? "Hi, Harold!" : "Hi, Stranger!"; } } static class MyCustomArgument { MyCustomArgument(String notDefaultConstr) { } } static class MyCustomArgumentResolver implements HandlerMethodArgumentResolver { @Override public boolean supportsParameter(MethodParameter parameter) { return parameter.getParameterType().isAssignableFrom(MyCustomArgument.class); } @Override public Object resolveArgument(MethodParameter parameter, Message message) { return new MyCustomArgument(""); } } static class TestHandshakeHandler implements HandshakeHandler { Map attributes; @Override public boolean doHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map attributes) throws HandshakeFailureException { this.attributes = attributes; if (wsHandler instanceof SockJsWebSocketHandler sockJs) { // work around SPR-12716 WebSocketServerSockJsSession session = (WebSocketServerSockJsSession) ReflectionTestUtils .getField(sockJs, "sockJsSession"); this.attributes = session.getAttributes(); } return true; } } @Configuration @EnableWebSocketSecurity @EnableWebSocketMessageBroker @Import(SyncExecutorConfig.class) static class SockJsSecurityConfig implements WebSocketMessageBrokerConfigurer { @Override public void registerStompEndpoints(StompEndpointRegistry registry) { // @formatter:off registry.addEndpoint("/other").setHandshakeHandler(testHandshakeHandler()) .withSockJS().setInterceptors(new HttpSessionHandshakeInterceptor()); registry.addEndpoint("/chat").setHandshakeHandler(testHandshakeHandler()) .withSockJS().setInterceptors(new HttpSessionHandshakeInterceptor()); // @formatter:on } // @formatter:off @Bean AuthorizationManager> authorizationManager(MessageMatcherDelegatingAuthorizationManager.Builder messages, SecurityCheck security) { AuthorizationManager> beanResolver = (authentication, context) -> new AuthorizationDecision(security.check()); messages .simpDestMatchers("/permitAll/**").permitAll() .simpDestMatchers("/beanResolver/**").access(beanResolver) .anyMessage().denyAll(); return messages.build(); } // @formatter:on @Override public void configureMessageBroker(MessageBrokerRegistry registry) { registry.enableSimpleBroker("/queue/", "/topic/"); registry.setApplicationDestinationPrefixes("/permitAll", "/denyAll"); } @Bean MyController myController() { return new MyController(); } @Bean TestHandshakeHandler testHandshakeHandler() { return new TestHandshakeHandler(); } @Bean SecurityCheck security() { return new SecurityCheck(); } static class SecurityCheck { private boolean check; boolean check() { this.check = !this.check; return this.check; } } } @Configuration @EnableWebSocketSecurity @EnableWebSocketMessageBroker @Import(SyncExecutorConfig.class) static class NoInboundSecurityConfig implements WebSocketMessageBrokerConfigurer { @Override public void registerStompEndpoints(StompEndpointRegistry registry) { // @formatter:off registry.addEndpoint("/other") .withSockJS().setInterceptors(new HttpSessionHandshakeInterceptor()); registry.addEndpoint("/chat") .withSockJS().setInterceptors(new HttpSessionHandshakeInterceptor()); // @formatter:on } @Override public void configureMessageBroker(MessageBrokerRegistry registry) { registry.enableSimpleBroker("/queue/", "/topic/"); registry.setApplicationDestinationPrefixes("/permitAll", "/denyAll"); } @Bean MyController myController() { return new MyController(); } @Bean AnnotationTemplateExpressionDefaults templateExpressionDefaults() { return new AnnotationTemplateExpressionDefaults(); } } @Configuration @Import(SockJsSecurityConfig.class) static class CsrfDisabledSockJsSecurityConfig { @Bean Consumer> channelInterceptorCustomizer() { return (interceptors) -> interceptors.remove(1); } } @Configuration @EnableWebSocketSecurity @EnableWebSocketMessageBroker @Import(SyncExecutorConfig.class) static class WebSocketSecurityConfig implements WebSocketMessageBrokerConfigurer { @Override public void registerStompEndpoints(StompEndpointRegistry registry) { // @formatter:off registry.addEndpoint("/websocket") .setHandshakeHandler(testHandshakeHandler()) .addInterceptors(new HttpSessionHandshakeInterceptor()); // @formatter:on } @Bean AuthorizationManager> authorizationManager( MessageMatcherDelegatingAuthorizationManager.Builder messages) { // @formatter:off messages .simpDestMatchers("/permitAll/**").permitAll() .simpDestMatchers("/authenticated/**").authenticated() .simpDestMatchers("/fullyAuthenticated/**").fullyAuthenticated() .simpDestMatchers("/rememberMe/**").rememberMe() .simpDestMatchers("/anonymous/**").anonymous() .anyMessage().denyAll(); // @formatter:on return messages.build(); } @Bean TestHandshakeHandler testHandshakeHandler() { return new TestHandshakeHandler(); } } @Configuration(proxyBeanMethods = false) @EnableWebSocketSecurity @EnableWebSocketMessageBroker @Import(SyncExecutorConfig.class) static class SockJsProxylessSecurityConfig implements WebSocketMessageBrokerConfigurer { private ApplicationContext context; @Override public void registerStompEndpoints(StompEndpointRegistry registry) { // @formatter:off registry.addEndpoint("/chat") .setHandshakeHandler(this.context.getBean(TestHandshakeHandler.class)) .withSockJS().setInterceptors(new HttpSessionHandshakeInterceptor()); // @formatter:on } @Autowired void setContext(ApplicationContext context) { this.context = context; } // @formatter:off @Bean AuthorizationManager> authorizationManager(MessageMatcherDelegatingAuthorizationManager.Builder messages) { messages .anyMessage().denyAll(); return messages.build(); } // @formatter:on @Bean TestHandshakeHandler testHandshakeHandler() { return new TestHandshakeHandler(); } } @Configuration(proxyBeanMethods = false) @EnableWebSecurity @Import(WebSocketSecurityConfig.class) static class WithWebSecurity { } @Configuration static class SyncExecutorConfig { @Bean static SyncExecutorSubscribableChannelPostProcessor postProcessor() { return new SyncExecutorSubscribableChannelPostProcessor(); } } @Configuration static class ObservationRegistryConfig { private final ObservationRegistry registry = ObservationRegistry.create(); private final ObservationHandler handler = spy(new ObservationTextPublisher()); @Bean ObservationRegistry observationRegistry() { return this.registry; } @Bean ObservationHandler observationHandler() { return this.handler; } @Bean ObservationRegistryPostProcessor observationRegistryPostProcessor( ObjectProvider> handler) { return new ObservationRegistryPostProcessor(handler); } } static class ObservationRegistryPostProcessor implements BeanPostProcessor { private final ObjectProvider> handler; ObservationRegistryPostProcessor(ObjectProvider> handler) { this.handler = handler; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { if (bean instanceof ObservationRegistry registry) { registry.observationConfig().observationHandler(this.handler.getObject()); } return bean; } } @Configuration static class SelectableObservationsConfig { @Bean SecurityObservationSettings observabilityDefaults() { return SecurityObservationSettings.withDefaults().shouldObserveAuthorizations(false).build(); } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/aot/hint/OAuth2LoginRuntimeHintsTests.java ================================================ /* * 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 org.springframework.security.config.aot.hint; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.aot.hint.MemberCategory; import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.RuntimeHintsRegistrar; import org.springframework.aot.hint.predicate.RuntimeHintsPredicates; import org.springframework.core.io.support.SpringFactoriesLoader; import org.springframework.security.oauth2.jwt.JwtDecoder; import org.springframework.util.ClassUtils; import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link OAuth2LoginRuntimeHints} * * @author Marcus Da Coregio */ class OAuth2LoginRuntimeHintsTests { private final RuntimeHints hints = new RuntimeHints(); @BeforeEach void setup() { SpringFactoriesLoader.forResourceLocation("META-INF/spring/aot.factories") .load(RuntimeHintsRegistrar.class) .forEach((registrar) -> registrar.registerHints(this.hints, ClassUtils.getDefaultClassLoader())); } @Test void jwtDecoderHasHints() { assertThat(RuntimeHintsPredicates.reflection() .onType(JwtDecoder.class) .withMemberCategories(MemberCategory.INVOKE_PUBLIC_METHODS)).accepts(this.hints); } } ================================================ FILE: config/src/test/java/org/springframework/security/config/aot/hint/WebMvcSecurityConfigurationRuntimeHintsTests.java ================================================ /* * 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 org.springframework.security.config.aot.hint; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.aot.hint.MemberCategory; import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.RuntimeHintsRegistrar; import org.springframework.aot.hint.TypeReference; import org.springframework.aot.hint.predicate.RuntimeHintsPredicates; import org.springframework.core.io.support.SpringFactoriesLoader; import org.springframework.util.ClassUtils; import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link WebMvcSecurityConfigurationRuntimeHints} * * @author Marcus da Coregio */ class WebMvcSecurityConfigurationRuntimeHintsTests { private final RuntimeHints hints = new RuntimeHints(); @BeforeEach void setup() { SpringFactoriesLoader.forResourceLocation("META-INF/spring/aot.factories") .load(RuntimeHintsRegistrar.class) .forEach((registrar) -> registrar.registerHints(this.hints, ClassUtils.getDefaultClassLoader())); } @Test void compositeFilterChainProxyHasHints() { assertThat(RuntimeHintsPredicates.reflection() .onType(TypeReference .of("org.springframework.security.config.annotation.web.configuration.WebMvcSecurityConfiguration$CompositeFilterChainProxy")) .withMemberCategory(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS)).accepts(this.hints); } } ================================================ FILE: config/src/test/java/org/springframework/security/config/aot/hint/WebSecurityConfigurationRuntimeHintsTests.java ================================================ /* * 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 org.springframework.security.config.aot.hint; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.aot.hint.MemberCategory; import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.RuntimeHintsRegistrar; import org.springframework.aot.hint.TypeReference; import org.springframework.aot.hint.predicate.RuntimeHintsPredicates; import org.springframework.core.io.support.SpringFactoriesLoader; import org.springframework.util.ClassUtils; import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link WebSecurityConfigurationRuntimeHints} * * @author Marcus da Coregio */ class WebSecurityConfigurationRuntimeHintsTests { private final RuntimeHints hints = new RuntimeHints(); @BeforeEach void setup() { SpringFactoriesLoader.forResourceLocation("META-INF/spring/aot.factories") .load(RuntimeHintsRegistrar.class) .forEach((registrar) -> registrar.registerHints(this.hints, ClassUtils.getDefaultClassLoader())); } @Test void compositeFilterChainProxyHasHints() { assertThat(RuntimeHintsPredicates.reflection() .onType(TypeReference .of("org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration$CompositeFilterChainProxy")) .withMemberCategory(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS)).accepts(this.hints); assertThat(RuntimeHintsPredicates.reflection() .onType(TypeReference.of("org.springframework.web.filter.ServletRequestPathFilter")) .withMemberCategory(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS)).accepts(this.hints); } } ================================================ FILE: config/src/test/java/org/springframework/security/config/authentication/AuthenticationConfigurationGh3935Tests.java ================================================ /* * 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 org.springframework.security.config.authentication; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; import org.springframework.security.config.annotation.authentication.configuration.GlobalAuthenticationConfigurerAdapter; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.core.Authentication; import org.springframework.security.core.userdetails.PasswordEncodedUser; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.web.FilterChainProxy; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit.jupiter.SpringExtension; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; /** * @author Rob Winch */ @ExtendWith(SpringExtension.class) @ContextConfiguration public class AuthenticationConfigurationGh3935Tests { @Autowired FilterChainProxy springSecurityFilterChain; @Autowired UserDetailsService uds; @Autowired BootGlobalAuthenticationConfigurationAdapter adapter; // gh-3935 @Test public void loads() { assertThat(this.springSecurityFilterChain).isNotNull(); } @Test public void delegateUsesExisitingAuthentication() { String username = "user"; String password = "password"; given(this.uds.loadUserByUsername(username)).willReturn(PasswordEncodedUser.user()); AuthenticationManager authenticationManager = this.adapter.authenticationManager; assertThat(authenticationManager).isNotNull(); Authentication auth = authenticationManager .authenticate(UsernamePasswordAuthenticationToken.unauthenticated(username, password)); verify(this.uds).loadUserByUsername(username); assertThat(auth.getPrincipal()).isEqualTo(PasswordEncodedUser.user()); } @Configuration @EnableWebSecurity static class WebSecurity { } static class BootGlobalAuthenticationConfigurationAdapter extends GlobalAuthenticationConfigurerAdapter { private final ApplicationContext context; private AuthenticationManager authenticationManager; @Autowired BootGlobalAuthenticationConfigurationAdapter(ApplicationContext context) { this.context = context; } @Override public void init(AuthenticationManagerBuilder auth) { AuthenticationConfiguration configuration = this.context.getBean(AuthenticationConfiguration.class); this.authenticationManager = configuration.getAuthenticationManager(); } } @Configuration static class AutoConfig { @Bean static BootGlobalAuthenticationConfigurationAdapter adapter(ApplicationContext context) { return new BootGlobalAuthenticationConfigurationAdapter(context); } @Bean UserDetailsService userDetailsService() { return mock(UserDetailsService.class); } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/authentication/AuthenticationManagerBeanDefinitionParserTests.java ================================================ /* * 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 org.springframework.security.config.authentication; import java.util.ArrayList; import java.util.List; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationListener; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.security.authentication.AuthenticationEventPublisher; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.DefaultAuthenticationEventPublisher; import org.springframework.security.authentication.ProviderManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.authentication.event.AbstractAuthenticationEvent; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.config.util.InMemoryXmlWebApplicationContext; import org.springframework.security.util.FieldUtils; import org.springframework.test.web.servlet.MockMvc; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * @author Luke Taylor */ @ExtendWith(SpringTestContextExtension.class) public class AuthenticationManagerBeanDefinitionParserTests { // @formatter:off private static final String CONTEXT = "" + " " + " " + " " + " " + " " + ""; // @formatter:on // Issue #7282 // @formatter:off private static final String CONTEXT_MULTI = "" + " " + " " + " " + " " + " " + ""; // @formatter:on public final SpringTestContext spring = new SpringTestContext(this); @Test // SEC-1225 public void providersAreRegisteredAsTopLevelBeans() { ConfigurableApplicationContext context = this.spring.context(CONTEXT).getContext(); assertThat(context.getBeansOfType(AuthenticationProvider.class)).hasSize(1); } @Test public void eventPublishersAreRegisteredAsTopLevelBeans() { ConfigurableApplicationContext context = this.spring.context(CONTEXT).getContext(); assertThat(context.getBeansOfType(AuthenticationEventPublisher.class)).hasSize(1); } @Test public void onlyOneEventPublisherIsRegisteredForMultipleAuthenticationManagers() { ConfigurableApplicationContext context = this.spring.context(CONTEXT + '\n' + CONTEXT_MULTI).getContext(); assertThat(context.getBeansOfType(AuthenticationEventPublisher.class)).hasSize(1); } @Test // gh-8767 public void multipleAuthenticationManagersAndDisableBeanDefinitionOverridingThenNoException() { InMemoryXmlWebApplicationContext xmlContext = new InMemoryXmlWebApplicationContext( CONTEXT + '\n' + CONTEXT_MULTI); xmlContext.setAllowBeanDefinitionOverriding(false); ConfigurableApplicationContext context = this.spring.context(xmlContext).getContext(); assertThat(context.getBeansOfType(AuthenticationManager.class)).hasSize(2); } @Test public void eventsArePublishedByDefault() throws Exception { ConfigurableApplicationContext appContext = this.spring.context(CONTEXT).getContext(); AuthListener listener = new AuthListener(); appContext.addApplicationListener(listener); ProviderManager pm = (ProviderManager) appContext.getBeansOfType(ProviderManager.class).values().toArray()[0]; Object eventPublisher = FieldUtils.getFieldValue(pm, "eventPublisher"); assertThat(eventPublisher).isNotNull(); assertThat(eventPublisher instanceof DefaultAuthenticationEventPublisher).isTrue(); pm.authenticate(UsernamePasswordAuthenticationToken.unauthenticated("bob", "bobspassword")); assertThat(listener.events).hasSize(1); } @Test public void credentialsAreClearedByDefault() { ConfigurableApplicationContext appContext = this.spring.context(CONTEXT).getContext(); ProviderManager pm = (ProviderManager) appContext.getBeansOfType(ProviderManager.class).values().toArray()[0]; assertThat(pm.isEraseCredentialsAfterAuthentication()).isTrue(); } @Test public void clearCredentialsPropertyIsRespected() { ConfigurableApplicationContext appContext = this.spring .context("") .getContext(); ProviderManager pm = (ProviderManager) appContext.getBeansOfType(ProviderManager.class).values().toArray()[0]; assertThat(pm.isEraseCredentialsAfterAuthentication()).isFalse(); } @Autowired MockMvc mockMvc; @Test public void passwordEncoderBeanUsed() throws Exception { // @formatter:off this.spring.context("" + "" + " " + "" + "" + " " + " " + "") .mockMvcAfterSpringSecurityOk() .autowire(); this.mockMvc.perform(get("/").with(httpBasic("user", "password"))) .andExpect(status().isOk()); // @formatter:on } private static class AuthListener implements ApplicationListener { List events = new ArrayList<>(); @Override public void onApplicationEvent(AbstractAuthenticationEvent event) { this.events.add(event); } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/authentication/AuthenticationProviderBeanDefinitionParserTests.java ================================================ /* * 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 org.springframework.security.config.authentication; import java.util.List; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.parsing.BeanDefinitionParsingException; import org.springframework.context.support.AbstractXmlApplicationContext; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.ProviderManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.config.BeanIds; import org.springframework.security.config.util.InMemoryXmlApplicationContext; import org.springframework.security.crypto.password.LdapShaPasswordEncoder; import org.springframework.security.crypto.password.MessageDigestPasswordEncoder; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; /** * Tests for {@link AuthenticationProviderBeanDefinitionParser}. * * @author Luke Taylor */ public class AuthenticationProviderBeanDefinitionParserTests { private AbstractXmlApplicationContext appContext; private UsernamePasswordAuthenticationToken bob = UsernamePasswordAuthenticationToken.unauthenticated("bob", "bobspassword"); @AfterEach public void closeAppContext() { if (this.appContext != null) { this.appContext.close(); } } @Test public void worksWithEmbeddedUserService() { // @formatter:off setContext(" " + " " + " " + " " + " "); // @formatter:on getProvider().authenticate(this.bob); } @Test public void externalUserServiceRefWorks() { // @formatter:off this.appContext = new InMemoryXmlApplicationContext( " " + " " + " " + " " + " " + " "); // @formatter:on getProvider().authenticate(this.bob); } @Test public void providerWithBCryptPasswordEncoderWorks() { // @formatter:off setContext(" " + " " + " " + " " + " " // @formatter:on + " "); getProvider().authenticate(this.bob); } @Test public void providerWithMd5PasswordEncoderWorks() { // @formatter:off this.appContext = new InMemoryXmlApplicationContext(" " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " "); // @formatter:on getProvider().authenticate(this.bob); } @Test public void providerWithShaPasswordEncoderWorks() { // @formatter:off this.appContext = new InMemoryXmlApplicationContext(" " + " " + " " + " " + " " + " " + " " + " " + " "); // @formatter:on getProvider().authenticate(this.bob); } @Test public void passwordIsBase64EncodedWhenBase64IsEnabled() { // @formatter:off this.appContext = new InMemoryXmlApplicationContext(" " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " "); // @formatter:on getProvider().authenticate(this.bob); } // SEC-1466 @Test public void exernalProviderDoesNotSupportChildElements() { assertThatExceptionOfType(BeanDefinitionParsingException.class).isThrownBy(() -> // @formatter:off this.appContext = new InMemoryXmlApplicationContext(" " + " " + " " + " " + " " + " " + " ") // @formatter:on ); } private AuthenticationProvider getProvider() { List providers = ((ProviderManager) this.appContext .getBean(BeanIds.AUTHENTICATION_MANAGER)).getProviders(); return providers.get(0); } private void setContext(String context) { this.appContext = new InMemoryXmlApplicationContext( "" + context + ""); } } ================================================ FILE: config/src/test/java/org/springframework/security/config/authentication/JdbcUserServiceBeanDefinitionParserTests.java ================================================ /* * 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 org.springframework.security.config.authentication; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import org.w3c.dom.Element; import org.xml.sax.SAXParseException; import org.springframework.beans.factory.parsing.BeanDefinitionParsingException; import org.springframework.beans.factory.xml.XmlBeanDefinitionStoreException; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.CachingUserDetailsService; import org.springframework.security.authentication.ProviderManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.authentication.dao.DaoAuthenticationProvider; import org.springframework.security.config.BeanIds; import org.springframework.security.config.util.InMemoryXmlApplicationContext; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.provisioning.JdbcUserDetailsManager; import org.springframework.security.util.FieldUtils; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.mockito.Mockito.mock; /** * @author Ben Alex * @author Luke Taylor * @author Eddú Meléndez */ public class JdbcUserServiceBeanDefinitionParserTests { private static String USER_CACHE_XML = ""; // @formatter:off private static String DATA_SOURCE = " " + " " + " " + " " + " " + " "; // @formatter:on private InMemoryXmlApplicationContext appContext; @AfterEach public void closeAppContext() { if (this.appContext != null) { this.appContext.close(); } } @Test public void beanNameIsCorrect() { assertThat(JdbcUserDetailsManager.class.getName()) .isEqualTo(new JdbcUserServiceBeanDefinitionParser().getBeanClassName(mock(Element.class))); } @Test public void validUsernameIsFound() { setContext("" + DATA_SOURCE); JdbcUserDetailsManager mgr = (JdbcUserDetailsManager) this.appContext.getBean(BeanIds.USER_DETAILS_SERVICE); assertThat(mgr.loadUserByUsername("rod")).isNotNull(); } @Test public void beanIdIsParsedCorrectly() { setContext("" + DATA_SOURCE); assertThat(this.appContext.getBean("myUserService") instanceof JdbcUserDetailsManager).isTrue(); } @Test public void usernameAndAuthorityQueriesAreParsedCorrectly() throws Exception { String userQuery = "select username, password, true from users where username = ?"; String authoritiesQuery = "select username, authority from authorities where username = ? and 1 = 1"; // @formatter:off setContext("" + DATA_SOURCE); // @formatter:on JdbcUserDetailsManager mgr = (JdbcUserDetailsManager) this.appContext.getBean("myUserService"); assertThat(FieldUtils.getFieldValue(mgr, "usersByUsernameQuery")).isEqualTo(userQuery); assertThat(FieldUtils.getFieldValue(mgr, "authoritiesByUsernameQuery")).isEqualTo(authoritiesQuery); assertThat(mgr.loadUserByUsername("rod") != null).isTrue(); } @Test public void groupQueryIsParsedCorrectly() throws Exception { setContext("" + DATA_SOURCE); JdbcUserDetailsManager mgr = (JdbcUserDetailsManager) this.appContext.getBean("myUserService"); assertThat(FieldUtils.getFieldValue(mgr, "groupAuthoritiesByUsernameQuery")).isEqualTo("blah blah"); assertThat((Boolean) FieldUtils.getFieldValue(mgr, "enableGroups")).isTrue(); } @Test public void cacheRefIsparsedCorrectly() { setContext("" + DATA_SOURCE + USER_CACHE_XML); CachingUserDetailsService cachingUserService = (CachingUserDetailsService) this.appContext .getBean("myUserService" + AbstractUserDetailsServiceBeanDefinitionParser.CACHING_SUFFIX); assertThat(this.appContext.getBean("userCache")).isSameAs(cachingUserService.getUserCache()); assertThat(cachingUserService.loadUserByUsername("rod")).isNotNull(); assertThat(cachingUserService.loadUserByUsername("rod")).isNotNull(); } @Test public void isSupportedByAuthenticationProviderElement() { // @formatter:off setContext("" + " " + " " + " " + "" + DATA_SOURCE); // @formatter:on AuthenticationManager mgr = (AuthenticationManager) this.appContext.getBean(BeanIds.AUTHENTICATION_MANAGER); mgr.authenticate(UsernamePasswordAuthenticationToken.unauthenticated("rod", "koala")); } @Test public void cacheIsInjectedIntoAuthenticationProvider() { // @formatter:off setContext("" + " " + " " + " " + "" + DATA_SOURCE + USER_CACHE_XML); // @formatter:on ProviderManager mgr = (ProviderManager) this.appContext.getBean(BeanIds.AUTHENTICATION_MANAGER); DaoAuthenticationProvider provider = (DaoAuthenticationProvider) mgr.getProviders().get(0); assertThat(this.appContext.getBean("userCache")).isSameAs(provider.getUserCache()); provider.authenticate(UsernamePasswordAuthenticationToken.unauthenticated("rod", "koala")); assertThat(provider.getUserCache().getUserFromCache("rod")).isNotNull() .withFailMessage("Cache should contain user after authentication"); } @Test public void rolePrefixIsUsedWhenSet() { setContext("" + DATA_SOURCE); JdbcUserDetailsManager mgr = (JdbcUserDetailsManager) this.appContext.getBean("myUserService"); UserDetails rod = mgr.loadUserByUsername("rod"); assertThat(AuthorityUtils.authorityListToSet(rod.getAuthorities())).contains("PREFIX_ROLE_SUPERVISOR"); } @Test public void testEmptyDataSourceRef() { // @formatter:off String xml = "" + " " + " " + " " + ""; assertThatExceptionOfType(BeanDefinitionParsingException.class) .isThrownBy(() -> setContext(xml)) .withFailMessage("Expected exception due to empty data-source-ref") .withMessageContaining("data-source-ref is required for jdbc-user-service"); // @formatter:on } @Test public void testMissingDataSourceRef() { // @formatter:off String xml = "" + " " + " " + " " + ""; assertThatExceptionOfType(XmlBeanDefinitionStoreException.class) .isThrownBy(() -> setContext(xml)) .withFailMessage("Expected exception due to missing data-source-ref") .havingRootCause() .isInstanceOf(SAXParseException.class) .withMessageContaining("Attribute 'data-source-ref' must appear on element 'jdbc-user-service'"); // @formatter:on } private void setContext(String context) { this.appContext = new InMemoryXmlApplicationContext(context); } } ================================================ FILE: config/src/test/java/org/springframework/security/config/authentication/PasswordEncoderParserTests.java ================================================ /* * 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 org.springframework.security.config.authentication; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.test.web.servlet.MockMvc; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * @author Rob Winch * @since 5.0 */ @ExtendWith(SpringTestContextExtension.class) public class PasswordEncoderParserTests { public final SpringTestContext spring = new SpringTestContext(this); @Autowired MockMvc mockMvc; @Test public void passwordEncoderDefaultsToDelegatingPasswordEncoder() throws Exception { this.spring.configLocations( "classpath:org/springframework/security/config/authentication/PasswordEncoderParserTests-default.xml") .mockMvcAfterSpringSecurityOk() .autowire(); // @formatter:off this.mockMvc.perform(get("/").with(httpBasic("user", "password"))) .andExpect(status().isOk()); // @formatter:on } @Test public void passwordEncoderDefaultsToPasswordEncoderBean() throws Exception { this.spring .configLocations( "classpath:org/springframework/security/config/authentication/PasswordEncoderParserTests-bean.xml") .mockMvcAfterSpringSecurityOk() .autowire(); // @formatter:off this.mockMvc.perform(get("/").with(httpBasic("user", "password"))) .andExpect(status().isOk()); // @formatter:on } @Test void testCreatePasswordEncoderBeanDefinition() throws Exception { String hash = "bcrypt"; Class expectedBeanClass = BCryptPasswordEncoder.class; BeanDefinition beanDefinition = PasswordEncoderParser.createPasswordEncoderBeanDefinition(hash); Class actualBeanClass = Class.forName(beanDefinition.getBeanClassName()); assertThat(actualBeanClass).isEqualTo(expectedBeanClass); } } ================================================ FILE: config/src/test/java/org/springframework/security/config/authentication/UserServiceBeanDefinitionParserTests.java ================================================ /* * 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 org.springframework.security.config.authentication; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import org.springframework.beans.FatalBeanException; import org.springframework.context.support.AbstractXmlApplicationContext; import org.springframework.security.config.util.InMemoryXmlApplicationContext; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; /** * @author Luke Taylor */ public class UserServiceBeanDefinitionParserTests { private AbstractXmlApplicationContext appContext; @AfterEach public void closeAppContext() { if (this.appContext != null) { this.appContext.close(); } } @Test public void userServiceWithValidPropertiesFileWorksSuccessfully() { setContext(""); UserDetailsService userService = (UserDetailsService) this.appContext.getBean("service"); userService.loadUserByUsername("bob"); userService.loadUserByUsername("joe"); } @Test public void userServiceWithEmbeddedUsersWorksSuccessfully() { // @formatter:off setContext("" + " " + ""); // @formatter:on UserDetailsService userService = (UserDetailsService) this.appContext.getBean("service"); userService.loadUserByUsername("joe"); } @Test public void namePasswordAndAuthoritiesSupportPlaceholders() { System.setProperty("principal.name", "joe"); System.setProperty("principal.pass", "joespassword"); System.setProperty("principal.authorities", "ROLE_A,ROLE_B"); // @formatter:off setContext("" + "" + " " + ""); // @formatter:on UserDetailsService userService = (UserDetailsService) this.appContext.getBean("service"); UserDetails joe = userService.loadUserByUsername("joe"); assertThat(joe.getPassword()).isEqualTo("joespassword"); assertThat(joe.getAuthorities()).hasSize(2); } @Test public void embeddedUsersWithNoPasswordIsGivenGeneratedValue() { // @formatter:off setContext("" + " " + ""); // @formatter:on UserDetailsService userService = (UserDetailsService) this.appContext.getBean("service"); UserDetails joe = userService.loadUserByUsername("joe"); assertThat(joe.getPassword().length() > 0).isTrue(); Long.parseLong(joe.getPassword()); } @Test public void disabledAndEmbeddedFlagsAreSupported() { // @formatter:off setContext("" + " " + " " + ""); // @formatter:on UserDetailsService userService = (UserDetailsService) this.appContext.getBean("service"); UserDetails joe = userService.loadUserByUsername("joe"); assertThat(joe.isAccountNonLocked()).isFalse(); // Check case-sensitive lookup SEC-1432 UserDetails bob = userService.loadUserByUsername("Bob"); assertThat(bob.isEnabled()).isFalse(); } @Test public void userWithBothPropertiesAndEmbeddedUsersThrowsException() { assertThatExceptionOfType(FatalBeanException.class).isThrownBy(() -> // @formatter:off setContext("" + " " + "") // @formatter:on ); } @Test public void multipleTopLevelUseWithoutIdThrowsException() { assertThatExceptionOfType(FatalBeanException.class).isThrownBy(() -> setContext( "" + "")); } @Test public void userServiceWithMissingPropertiesFileThrowsException() { assertThatExceptionOfType(FatalBeanException.class) .isThrownBy(() -> setContext("")); } private void setContext(String context) { this.appContext = new InMemoryXmlApplicationContext(context); } } ================================================ FILE: config/src/test/java/org/springframework/security/config/core/GrantedAuthorityDefaultsJcTests.java ================================================ /* * 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 org.springframework.security.config.core; import java.io.IOException; import jakarta.servlet.ServletException; import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.mock.web.MockFilterChain; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.FilterChainProxy; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.context.HttpSessionSecurityContextRepository; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit.jupiter.SpringExtension; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; @ExtendWith(SpringExtension.class) @ContextConfiguration public class GrantedAuthorityDefaultsJcTests { @Autowired FilterChainProxy springSecurityFilterChain; @Autowired MessageService messageService; MockHttpServletRequest request; MockHttpServletResponse response; MockFilterChain chain; @BeforeEach public void setup() { setup("USER"); this.request = new MockHttpServletRequest("GET", ""); this.request.setMethod("GET"); this.response = new MockHttpServletResponse(); this.chain = new MockFilterChain(); } @AfterEach public void cleanup() { SecurityContextHolder.clearContext(); } @Test public void doFilter() throws Exception { SecurityContext context = SecurityContextHolder.getContext(); this.request.getSession() .setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, context); this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); } @Test public void doFilterDenied() throws Exception { setup("DENIED"); SecurityContext context = SecurityContextHolder.getContext(); this.request.getSession() .setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, context); this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_FORBIDDEN); } @Test public void message() { this.messageService.getMessage(); } @Test public void jsrMessage() { this.messageService.getJsrMessage(); } @Test public void messageDenied() { setup("DENIED"); assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.messageService::getMessage); } @Test public void jsrMessageDenied() { setup("DENIED"); assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.messageService::getJsrMessage); } // SEC-2926 @Test public void doFilterIsUserInRole() throws Exception { SecurityContext context = SecurityContextHolder.getContext(); this.request.getSession() .setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, context); this.chain = new MockFilterChain() { @Override public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException { HttpServletRequest httpRequest = (HttpServletRequest) request; assertThat(httpRequest.isUserInRole("USER")).isTrue(); assertThat(httpRequest.isUserInRole("INVALID")).isFalse(); super.doFilter(request, response); } }; this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); assertThat(this.chain.getRequest()).isNotNull(); } private void setup(String role) { TestingAuthenticationToken user = new TestingAuthenticationToken("user", "password", role); SecurityContextHolder.getContext().setAuthentication(user); } @Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true, jsr250Enabled = true) static class Config { @Autowired void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { // @formatter:off auth .inMemoryAuthentication() .withUser("user").password("password").roles("USER"); // @formatter:on } @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests((requests) -> requests.anyRequest().hasRole("USER")); return http.build(); // @formatter:on } @Bean MessageService messageService() { return new HelloWorldMessageService(); } @Bean static GrantedAuthorityDefaults grantedAuthorityDefaults() { return new GrantedAuthorityDefaults(""); } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/core/GrantedAuthorityDefaultsXmlTests.java ================================================ /* * 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 org.springframework.security.config.core; import java.io.IOException; import jakarta.servlet.ServletException; import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.mock.web.MockFilterChain; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.FilterChainProxy; import org.springframework.security.web.context.HttpSessionSecurityContextRepository; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit.jupiter.SpringExtension; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; @ExtendWith(SpringExtension.class) @ContextConfiguration public class GrantedAuthorityDefaultsXmlTests { @Autowired FilterChainProxy springSecurityFilterChain; @Autowired MessageService messageService; MockHttpServletRequest request; MockHttpServletResponse response; MockFilterChain chain; @BeforeEach public void setup() { setup("USER"); this.request = new MockHttpServletRequest("GET", ""); this.request.setMethod("GET"); this.response = new MockHttpServletResponse(); this.chain = new MockFilterChain(); } @AfterEach public void cleanup() { SecurityContextHolder.clearContext(); } @Test public void doFilter() throws Exception { SecurityContext context = SecurityContextHolder.getContext(); this.request.getSession() .setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, context); this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); } @Test public void doFilterDenied() throws Exception { setup("DENIED"); SecurityContext context = SecurityContextHolder.getContext(); this.request.getSession() .setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, context); this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_FORBIDDEN); } @Test public void message() { this.messageService.getMessage(); } @Test public void jsrMessage() { this.messageService.getJsrMessage(); } @Test public void messageDenied() { setup("DENIED"); assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.messageService::getMessage); } @Test public void jsrMessageDenied() { setup("DENIED"); assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.messageService::getJsrMessage); } // SEC-2926 @Test public void doFilterIsUserInRole() throws Exception { SecurityContext context = SecurityContextHolder.getContext(); this.request.getSession() .setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, context); this.chain = new MockFilterChain() { @Override public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException { HttpServletRequest httpRequest = (HttpServletRequest) request; assertThat(httpRequest.isUserInRole("USER")).isTrue(); assertThat(httpRequest.isUserInRole("INVALID")).isFalse(); super.doFilter(request, response); } }; this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); assertThat(this.chain.getRequest()).isNotNull(); } private void setup(String role) { TestingAuthenticationToken user = new TestingAuthenticationToken("user", "password", role); SecurityContextHolder.getContext().setAuthentication(user); } } ================================================ FILE: config/src/test/java/org/springframework/security/config/core/HelloWorldMessageService.java ================================================ /* * 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 org.springframework.security.config.core; import jakarta.annotation.security.RolesAllowed; import org.springframework.security.access.prepost.PreAuthorize; /** * @author Rob Winch */ public class HelloWorldMessageService implements MessageService { @Override @PreAuthorize("hasRole('USER')") public String getMessage() { return "Hello World"; } @Override @RolesAllowed("USER") public String getJsrMessage() { return "Hello JSR"; } } ================================================ FILE: config/src/test/java/org/springframework/security/config/core/MessageService.java ================================================ /* * 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 org.springframework.security.config.core; /** * @author Rob Winch */ public interface MessageService { String getMessage(); String getJsrMessage(); } ================================================ FILE: config/src/test/java/org/springframework/security/config/core/userdetails/ReactiveUserDetailsServiceResourceFactoryBeanPropertiesResourceITests.java ================================================ /* * 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 org.springframework.security.config.core.userdetails; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.core.userdetails.ReactiveUserDetailsService; import org.springframework.security.util.InMemoryResource; import org.springframework.test.context.junit.jupiter.SpringExtension; import static org.assertj.core.api.Assertions.assertThat; /** * @author Rob Winch * @since 5.0 */ @ExtendWith(SpringExtension.class) public class ReactiveUserDetailsServiceResourceFactoryBeanPropertiesResourceITests { @Autowired ReactiveUserDetailsService users; @Test public void loadUserByUsernameWhenUserFoundThenNotNull() { assertThat(this.users.findByUsername("user").block()).isNotNull(); } @Configuration static class Config { @Bean ReactiveUserDetailsServiceResourceFactoryBean userDetailsService() { return ReactiveUserDetailsServiceResourceFactoryBean .fromResource(new InMemoryResource("user=password,ROLE_USER")); } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/core/userdetails/ReactiveUserDetailsServiceResourceFactoryBeanPropertiesResourceLocationITests.java ================================================ /* * 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 org.springframework.security.config.core.userdetails; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.core.userdetails.ReactiveUserDetailsService; import org.springframework.test.context.junit.jupiter.SpringExtension; import static org.assertj.core.api.Assertions.assertThat; /** * @author Rob Winch * @since 5.0 */ @ExtendWith(SpringExtension.class) public class ReactiveUserDetailsServiceResourceFactoryBeanPropertiesResourceLocationITests { @Autowired ReactiveUserDetailsService users; @Test public void loadUserByUsernameWhenUserFoundThenNotNull() { assertThat(this.users.findByUsername("user").block()).isNotNull(); } @Configuration static class Config { @Bean ReactiveUserDetailsServiceResourceFactoryBean userDetailsService() { return ReactiveUserDetailsServiceResourceFactoryBean.fromResourceLocation("classpath:users.properties"); } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/core/userdetails/ReactiveUserDetailsServiceResourceFactoryBeanStringITests.java ================================================ /* * 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 org.springframework.security.config.core.userdetails; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.core.userdetails.ReactiveUserDetailsService; import org.springframework.test.context.junit.jupiter.SpringExtension; import static org.assertj.core.api.Assertions.assertThat; /** * @author Rob Winch * @since 5.0 */ @ExtendWith(SpringExtension.class) public class ReactiveUserDetailsServiceResourceFactoryBeanStringITests { @Autowired ReactiveUserDetailsService users; @Test public void findByUsernameWhenUserFoundThenNotNull() { assertThat(this.users.findByUsername("user").block()).isNotNull(); } @Configuration static class Config { @Bean ReactiveUserDetailsServiceResourceFactoryBean userDetailsService() { return ReactiveUserDetailsServiceResourceFactoryBean.fromString("user=password,ROLE_USER"); } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/core/userdetails/UserDetailsResourceFactoryBeanTests.java ================================================ /* * 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 org.springframework.security.config.core.userdetails; import java.util.Collection; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.core.io.ResourceLoader; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.util.InMemoryResource; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.assertj.core.api.Assertions.assertThatIllegalStateException; /** * @author Rob Winch * @since 5.0 */ @ExtendWith(MockitoExtension.class) public class UserDetailsResourceFactoryBeanTests { @Mock ResourceLoader resourceLoader; UserDetailsResourceFactoryBean factory = new UserDetailsResourceFactoryBean(); @Test public void setResourceLoaderWhenNullThenThrowsException() { // @formatter:off assertThatIllegalArgumentException() .isThrownBy(() -> this.factory.setResourceLoader(null)) .withStackTraceContaining("resourceLoader cannot be null"); // @formatter:on } @Test public void getObjectWhenPropertiesResourceLocationNullThenThrowsIllegalStateException() { this.factory.setResourceLoader(this.resourceLoader); // @formatter:off assertThatIllegalArgumentException() .isThrownBy(() -> this.factory.getObject()) .withStackTraceContaining("resource cannot be null if resourceLocation is null"); // @formatter:on } @Test public void getObjectWhenPropertiesResourceLocationSingleUserThenThrowsGetsSingleUser() throws Exception { this.factory.setResourceLocation("classpath:users.properties"); Collection users = this.factory.getObject(); assertLoaded(); } @Test public void getObjectWhenPropertiesResourceSingleUserThenThrowsGetsSingleUser() throws Exception { this.factory.setResource(new InMemoryResource("user=password,ROLE_USER")); assertLoaded(); } @Test public void getObjectWhenInvalidUserThenThrowsMeaningfulException() { this.factory.setResource(new InMemoryResource("user=invalidFormatHere")); // @formatter:off assertThatIllegalStateException() .isThrownBy(() -> this.factory.getObject()) .withStackTraceContaining("user") .withStackTraceContaining("invalidFormatHere"); // @formatter:on } @Test public void getObjectWhenStringSingleUserThenGetsSingleUser() throws Exception { this.factory = UserDetailsResourceFactoryBean.fromString("user=password,ROLE_USER"); assertLoaded(); } private void assertLoaded() throws Exception { Collection users = this.factory.getObject(); // @formatter:off UserDetails expectedUser = User.withUsername("user") .password("password") .authorities("ROLE_USER") .build(); // @formatter:on assertThat(users).containsExactly(expectedUser); } } ================================================ FILE: config/src/test/java/org/springframework/security/config/crypto/RsaKeyConversionServicePostProcessorTests.java ================================================ /* * 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 org.springframework.security.config.crypto; import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.support.GenericConversionService; import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; /** * Tests for {@link RsaKeyConversionServicePostProcessor} */ @ExtendWith(SpringTestContextExtension.class) public class RsaKeyConversionServicePostProcessorTests { // @formatter:off private static final String PKCS8_PRIVATE_KEY = "-----BEGIN PRIVATE KEY-----\n" + "MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCMk7CKSTfu3QoV\n" + "HoPVXxwZO+qweztd36cVWYqGOZinrOR2crWFu50AgR2CsdIH0+cqo7F4Vx7/3O8i\n" + "RpYYZPe2VoO5sumzJt8P6fS80/TAKjhJDAqgZKRJTgGN8KxCM6p/aJli1ZeDBqiV\n" + "v7vJJe+ZgJuPGRS+HMNa/wPxEkqqXsglcJcQV1ZEtfKXSHB7jizKpRL38185SyAC\n" + "pwyjvBu6Cmm1URfhQo88mf239ONh4dZ2HoDfzN1q6Ssu4F4hgutxr9B0DVLDP5u+\n" + "WFrm3nsJ76zf99uJ+ntMUHJ+bY+gOjSlVWIVBIZeAaEGKCNWRk/knjvjbijpvm3U\n" + "acGlgdL3AgMBAAECggEACxxxS7zVyu91qI2s5eSKmAQAXMqgup6+2hUluc47nqUv\n" + "uZz/c/6MPkn2Ryo+65d4IgqmMFjSfm68B/2ER5FTcvoLl1Xo2twrrVpUmcg3BClS\n" + "IZPuExdhVNnxjYKEWwcyZrehyAoR261fDdcFxLRW588efIUC+rPTTRHzAc7sT+Ln\n" + "t/uFeYNWJm3LaegOLoOmlMAhJ5puAWSN1F0FxtRf/RVgzbLA9QC975SKHJsfWCSr\n" + "IZyPsdeaqomKaF65l8nfqlE0Ua2L35gIOGKjUwb7uUE8nI362RWMtYdoi3zDDyoY\n" + "hSFbgjylCHDM0u6iSh6KfqOHtkYyJ8tUYgVWl787wQKBgQDYO3wL7xuDdD101Lyl\n" + "AnaDdFB9fxp83FG1cWr+t7LYm9YxGfEUsKHAJXN6TIayDkOOoVwIl+Gz0T3Z06Bm\n" + "eBGLrB9mrVA7+C7NJwu5gTMlzP6HxUR9zKJIQ/VB1NUGM77LSmvOFbHc9Q0+z8EH\n" + "X5WO516a3Z7lNtZJcCoPOtu2rwKBgQCmbj41Fh+SSEUApCEKms5ETRpe7LXQlJgx\n" + "yW7zcJNNuIb1C3vBLPxjiOTMgYKOeMg5rtHTGLT43URHLh9ArjawasjSAr4AM3J4\n" + "xpoi/sKGDdiKOsuDWIGfzdYL8qyTHSdpZLQsCTMRiRYgAHZFPgNa7SLZRfZicGlr\n" + "GHN1rJW6OQKBgEjiM/upyrJSWeypUDSmUeAZMpA6aWkwsfHgmtnkfUn5rQa74cDB\n" + "kKO9e+D7LmOR3z+SL/1NhGwh2SE07dncGr3jdGodfO/ZxZyszozmeaECKcEFwwJM\n" + "GV8WWPKplGwUwPiwywmZ0mvRxXcoe73KgBS88+xrSwWjqDL0tZiQlEJNAoGATkei\n" + "GMQMG3jEg9Wu+NbxV6zQT3+U0MNjhl9RQU1c63x0dcNt9OFc4NAdlZcAulRTENaK\n" + "OHjxffBM0hH+fySx8m53gFfr2BpaqDX5f6ZGBlly1SlsWZ4CchCVsc71nshipi7I\n" + "k8HL9F5/OpQdDNprJ5RMBNfkWE65Nrcsb1e6oPkCgYAxwgdiSOtNg8PjDVDmAhwT\n" + "Mxj0Dtwi2fAqQ76RVrrXpNp3uCOIAu4CfruIb5llcJ3uak0ZbnWri32AxSgk80y3\n" + "EWiRX/WEDu5znejF+5O3pI02atWWcnxifEKGGlxwkcMbQdA67MlrJLFaSnnGpNXo\n" + "yPfcul058SOqhafIZQMEKQ==\n" + "-----END PRIVATE KEY-----"; // @formatter:on private static final String X509_PUBLIC_KEY_LOCATION = "classpath:org/springframework/security/config/annotation/web/configuration/simple.pub"; private final RsaKeyConversionServicePostProcessor postProcessor = new RsaKeyConversionServicePostProcessor(); private ConversionService service; @Value("classpath:org/springframework/security/config/annotation/web/configuration/simple.pub") RSAPublicKey publicKey; @Value("classpath:org/springframework/security/config/annotation/web/configuration/simple.priv") RSAPrivateKey privateKey; @Value("custom:simple.pub") RSAPublicKey samePublicKey; public final SpringTestContext spring = new SpringTestContext(this); @BeforeEach public void setUp() { ConfigurableListableBeanFactory beanFactory = new DefaultListableBeanFactory(); beanFactory.setConversionService(new GenericConversionService()); this.postProcessor.postProcessBeanFactory(beanFactory); this.service = beanFactory.getConversionService(); } @Test public void convertWhenUsingConversionServiceForRawKeyThenOk() { RSAPrivateKey key = this.service.convert(PKCS8_PRIVATE_KEY, RSAPrivateKey.class); assertThat(key.getModulus().bitLength()).isEqualTo(2048); } @Test public void convertWhenUsingConversionServiceForClasspathThenOk() { RSAPublicKey key = this.service.convert(X509_PUBLIC_KEY_LOCATION, RSAPublicKey.class); assertThat(key.getModulus().bitLength()).isEqualTo(1024); } @Test public void valueWhenReferringToClasspathPublicKeyThenConverts() { this.spring.register(CustomResourceLoaderConfig.class, DefaultConfig.class).autowire(); assertThat(this.publicKey.getModulus().bitLength()).isEqualTo(1024); } @Test public void valueWhenReferringToClasspathPrivateKeyThenConverts() { this.spring.register(CustomResourceLoaderConfig.class, DefaultConfig.class).autowire(); assertThat(this.privateKey.getModulus().bitLength()).isEqualTo(2048); } @Test public void valueWhenReferringToCustomResourceLoadedPublicKeyThenConverts() { this.spring.register(CustomResourceLoaderConfig.class, DefaultConfig.class).autowire(); assertThat(this.samePublicKey.getModulus().bitLength()).isEqualTo(1024); } @Test public void valueWhenOverridingConversionServiceThenUsed() { assertThatExceptionOfType(Exception.class) .isThrownBy( () -> this.spring.register(OverrideConversionServiceConfig.class, DefaultConfig.class).autowire()) .withRootCauseInstanceOf(IllegalArgumentException.class); } @Configuration @EnableWebSecurity static class DefaultConfig { } @Configuration static class CustomResourceLoaderConfig { @Bean BeanFactoryPostProcessor conversionServiceCustomizer() { return (beanFactory) -> beanFactory.getBean(RsaKeyConversionServicePostProcessor.class) .setResourceLoader(new CustomResourceLoader()); } } @Configuration static class OverrideConversionServiceConfig { @Bean ConversionService conversionService() { GenericConversionService service = new GenericConversionService(); service.addConverter(String.class, RSAPublicKey.class, (source) -> { throw new IllegalArgumentException("unsupported"); }); return service; } } private static class CustomResourceLoader implements ResourceLoader { private final ResourceLoader delegate = new DefaultResourceLoader(); @Override public Resource getResource(String location) { if (location.startsWith("classpath:")) { return this.delegate.getResource(location); } else if (location.startsWith("custom:")) { String[] parts = location.split(":"); return this.delegate.getResource( "classpath:org/springframework/security/config/annotation/web/configuration/" + parts[1]); } throw new IllegalArgumentException("unsupported resource"); } @Override public ClassLoader getClassLoader() { return this.delegate.getClassLoader(); } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/debug/AuthProviderDependency.java ================================================ /* * 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 org.springframework.security.config.debug; import org.springframework.stereotype.Component; /** * Fake depenency for {@link TestAuthenticationProvider} * * @author Rob Winch * */ @Component public class AuthProviderDependency { } ================================================ FILE: config/src/test/java/org/springframework/security/config/debug/SecurityDebugBeanFactoryPostProcessorTests.java ================================================ /* * 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 org.springframework.security.config.debug; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.security.config.BeanIds; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.web.FilterChainProxy; import org.springframework.security.web.debug.DebugFilter; import static org.assertj.core.api.Assertions.assertThat; /** * @author Rob Winch * @author Josh Cummings */ @ExtendWith(SpringTestContextExtension.class) public class SecurityDebugBeanFactoryPostProcessorTests { public final SpringTestContext spring = new SpringTestContext(this); @Test public void contextRefreshWhenInDebugModeAndDependencyHasAutowiredConstructorThenDebugModeStillWorks() { // SEC-1885 this.spring.configLocations( "classpath:org/springframework/security/config/debug/SecurityDebugBeanFactoryPostProcessorTests-context.xml") .autowire(); assertThat(this.spring.getContext().getBean(BeanIds.SPRING_SECURITY_FILTER_CHAIN)) .isInstanceOf(DebugFilter.class); assertThat(this.spring.getContext().getBean(BeanIds.FILTER_CHAIN_PROXY)).isInstanceOf(FilterChainProxy.class); } } ================================================ FILE: config/src/test/java/org/springframework/security/config/debug/TestAuthenticationProvider.java ================================================ /* * 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 org.springframework.security.config.debug; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.stereotype.Service; /** * An {@link AuthenticationProvider} that has an {@link Autowired} constructor which is * necessary to recreate SEC-1885. * * @author Rob Winch * */ @Service("authProvider") public class TestAuthenticationProvider implements AuthenticationProvider { @Autowired public TestAuthenticationProvider(AuthProviderDependency authProviderDependency) { } @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { throw new UnsupportedOperationException(); } @Override public boolean supports(Class authentication) { throw new UnsupportedOperationException(); } } ================================================ FILE: config/src/test/java/org/springframework/security/config/doc/Attribute.java ================================================ /* * 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 org.springframework.security.config.doc; /** * Represents a Spring Security XSD Attribute. It is created when parsing the current xsd * to compare to the documented appendix. * * @author Rob Winch * @author Josh Cummings * @see SpringSecurityXsdParser * @see XsdDocumentedTests */ public class Attribute { private String name; private String desc; private Element elmt; public Attribute(String desc, String name) { this.desc = desc; this.name = name; } public String getName() { return this.name; } public void setName(String name) { this.name = name; } public String getDesc() { return this.desc; } public void setDesc(String desc) { this.desc = desc; } public Element getElmt() { return this.elmt; } public void setElmt(Element elmt) { this.elmt = elmt; } public String getId() { return String.format("%s-%s", this.elmt.getId(), this.name); } } ================================================ FILE: config/src/test/java/org/springframework/security/config/doc/Element.java ================================================ /* * 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 org.springframework.security.config.doc; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Map; /** * Represents a Spring Security XSD Element. It is created when parsing the current xsd to * compare to the documented appendix. * * @author Rob Winch * @author Josh Cummings * @see SpringSecurityXsdParser * @see XsdDocumentedTests */ public class Element { private String name; private String desc; private Collection attrs = new ArrayList<>(); /** * Contains the elements that extend this element (i.e. any-user-service contains * ldap-user-service) */ private Collection subGrps = new ArrayList<>(); private Map childElmts = new HashMap<>(); private Map parentElmts = new HashMap<>(); public String getId() { return String.format("nsa-%s", this.name); } public String getName() { return this.name; } public void setName(String name) { this.name = name; } public String getDesc() { return this.desc; } public void setDesc(String desc) { this.desc = desc; } public Collection getAttrs() { return this.attrs; } public void setAttrs(Collection attrs) { this.attrs = attrs; } public Collection getSubGrps() { return this.subGrps; } public void setSubGrps(Collection subGrps) { this.subGrps = subGrps; } public Map getChildElmts() { return this.childElmts; } public void setChildElmts(Map childElmts) { this.childElmts = childElmts; } public Map getParentElmts() { return this.parentElmts; } public void setParentElmts(Map parentElmts) { this.parentElmts = parentElmts; } /** * Gets all the ids related to this Element including attributes, parent elements, and * child elements. * *

* The expected ids to be found are documented below. *

    *
  • Elements - any xml element will have the nsa-<element>. For example the * http element will have the id nsa-http
  • *
  • Parent Section - Any element with a parent other than beans will have a section * named nsa-<element>-parents. For example, authentication-provider would have * a section id of nsa-authentication-provider-parents. The section would then contain * a list of links pointing to the documentation for each parent element.
  • *
  • Attributes Section - Any element with attributes will have a section with the * id nsa-<element>-attributes. For example the http element would require a * section with the id http-attributes.
  • *
  • Attribute - Each attribute of an element would have an id of * nsa-<element>-<attributeName>. For example the attribute create-session * for the http attribute would have the id http-create-session.
  • *
  • Child Section - Any element with a child element will have a section named * nsa-<element>-children. For example, authentication-provider would have a * section id of nsa-authentication-provider-children. The section would then contain * a list of links pointing to the documentation for each child element.
  • *
* @return */ public Collection getIds() { Collection ids = new ArrayList<>(); ids.add(getId()); this.childElmts.values().forEach((elmt) -> ids.add(elmt.getId())); this.attrs.forEach((attr) -> ids.add(attr.getId())); if (!this.childElmts.isEmpty()) { ids.add(getId() + "-children"); } if (!this.attrs.isEmpty()) { ids.add(getId() + "-attributes"); } if (!this.parentElmts.isEmpty()) { ids.add(getId() + "-parents"); } return ids; } public Map getAllChildElmts() { Map result = new HashMap<>(); this.childElmts.values() .forEach((elmt) -> elmt.subGrps.forEach((subElmt) -> result.put(subElmt.name, subElmt))); result.putAll(this.childElmts); return result; } public Map getAllParentElmts() { Map result = new HashMap<>(); this.parentElmts.values() .forEach((elmt) -> elmt.subGrps.forEach((subElmt) -> result.put(subElmt.name, subElmt))); result.putAll(this.parentElmts); return result; } } ================================================ FILE: config/src/test/java/org/springframework/security/config/doc/SpringSecurityXsdParser.java ================================================ /* * 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 org.springframework.security.config.doc; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; import java.util.stream.Stream; import org.springframework.util.StringUtils; /** * Parses the Spring Security Xsd Document * * @author Rob Winch * @author Josh Cummings */ public class SpringSecurityXsdParser { private XmlNode rootElement; private Set attrElmts = new LinkedHashSet<>(); private Map elementNameToElement = new HashMap<>(); public SpringSecurityXsdParser(XmlNode rootElement) { this.rootElement = rootElement; } /** * Returns a map of the element name to the {@link Element}. * @return */ public Map parse() { elements(this.rootElement); return this.elementNameToElement; } /** * Creates a Map of the name to an Element object of all the children of element. * @param node * @return */ private Map elements(XmlNode node) { Map elementNameToElement = new HashMap<>(); node.children().forEach((child) -> { if ("element".equals(child.simpleName())) { Element e = elmt(child); elementNameToElement.put(e.getName(), e); } else { elementNameToElement.putAll(elements(child)); } }); return elementNameToElement; } /** * Any children that are attribute will be returned as an Attribute object. * @param element * @return a collection of Attribute objects that are children of element. */ private Collection attrs(XmlNode element) { Collection attrs = new ArrayList<>(); element.children().forEach((c) -> { String name = c.simpleName(); if ("attribute".equals(name)) { attrs.add(attr(c)); } else if (!"element".equals(name)) { attrs.addAll(attrs(c)); } }); return attrs; } /** * Any children will be searched for an attributeGroup, each of its children will be * returned as an Attribute * @param element * @return */ private Collection attrgrps(XmlNode element) { Collection attrgrp = new ArrayList<>(); element.children().forEach((c) -> { if (!"element".equals(c.simpleName())) { if ("attributeGroup".equals(c.simpleName())) { if (c.attribute("name") != null) { attrgrp.addAll(attrgrp(c)); } else { String name = c.attribute("ref").split(":")[1]; XmlNode attrGrp = findNode(element, name); attrgrp.addAll(attrgrp(attrGrp)); } } else { attrgrp.addAll(attrgrps(c)); } } }); return attrgrp; } private XmlNode findNode(XmlNode c, String name) { XmlNode root = c; while (!"schema".equals(root.simpleName())) { root = root.parent().get(); } // @formatter:off return expand(root) .filter((node) -> name.equals(node.attribute("name"))) .findFirst() .orElseThrow(IllegalArgumentException::new); // @formatter:on } private Stream expand(XmlNode root) { // @formatter:off return Stream.concat(Stream.of(root), root.children() .flatMap(this::expand)); // @formatter:on } /** * Processes an individual attributeGroup by obtaining all the attributes and then * looking for more attributeGroup elements and prcessing them. * @param e * @return all the attributes for a specific attributeGroup and any child * attributeGroups */ private Collection attrgrp(XmlNode e) { Collection attrs = attrs(e); attrs.addAll(attrgrps(e)); return attrs; } /** * Obtains the description for a specific element * @param element * @return */ private String desc(XmlNode element) { return element.child("annotation") .flatMap((annotation) -> annotation.child("documentation")) .map((documentation) -> documentation.text()) .orElse(null); } /** * Given an element creates an attribute from it. * @param n * @return */ private Attribute attr(XmlNode n) { return new Attribute(desc(n), n.attribute("name")); } /** * Given an element creates an Element out of it by collecting all its attributes and * child elements. * @param n * @return */ private Element elmt(XmlNode n) { String name = n.attribute("ref"); if (!StringUtils.hasLength(name)) { name = n.attribute("name"); } else { name = name.split(":")[1]; n = findNode(n, name); } if (this.elementNameToElement.containsKey(name)) { return this.elementNameToElement.get(name); } this.attrElmts.add(name); Element e = new Element(); e.setName(n.attribute("name")); e.setDesc(desc(n)); e.setChildElmts(elements(n)); e.setAttrs(attrs(n)); e.getAttrs().addAll(attrgrps(n)); e.getAttrs().forEach((attr) -> attr.setElmt(e)); e.getChildElmts().values().forEach((element) -> element.getParentElmts().put(e.getName(), e)); String subGrpName = n.attribute("substitutionGroup"); if (StringUtils.hasLength(subGrpName)) { Element subGrp = elmt(findNode(n, subGrpName.split(":")[1])); subGrp.getSubGrps().add(e); } this.elementNameToElement.put(name, e); return e; } } ================================================ FILE: config/src/test/java/org/springframework/security/config/doc/XmlNode.java ================================================ /* * 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 org.springframework.security.config.doc; import java.util.Optional; import java.util.stream.IntStream; import java.util.stream.Stream; import org.w3c.dom.Node; import org.w3c.dom.NodeList; /** * @author Josh Cummings */ public class XmlNode { private final Node node; public XmlNode(Node node) { this.node = node; } public String simpleName() { String[] parts = this.node.getNodeName().split(":"); return parts[parts.length - 1]; } public String text() { return this.node.getTextContent(); } public Stream children() { NodeList children = this.node.getChildNodes(); // @formatter:off return IntStream.range(0, children.getLength()) .mapToObj(children::item) .map(XmlNode::new); // @formatter:on } public Optional child(String name) { return this.children().filter((child) -> name.equals(child.simpleName())).findFirst(); } public Optional parent() { // @formatter:off return Optional.ofNullable(this.node.getParentNode()).map(XmlNode::new); // @formatter:on } public String attribute(String name) { // @formatter:off return Optional.ofNullable(this.node.getAttributes()) .map((attrs) -> attrs.getNamedItem(name)) .map(Node::getTextContent) .orElse(null); // @formatter:on } public Node node() { return this.node; } } ================================================ FILE: config/src/test/java/org/springframework/security/config/doc/XmlParser.java ================================================ /* * 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 org.springframework.security.config.doc; import java.io.IOException; import java.io.InputStream; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import org.xml.sax.SAXException; /** * @author Josh Cummings */ public class XmlParser implements AutoCloseable { private InputStream xml; public XmlParser(InputStream xml) { this.xml = xml; } public XmlNode parse() { try { DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); DocumentBuilder dBuilder = dbFactory.newDocumentBuilder(); return new XmlNode(dBuilder.parse(this.xml)); } catch (IOException | ParserConfigurationException | SAXException ex) { throw new IllegalStateException(ex); } } @Override public void close() throws IOException { this.xml.close(); } } ================================================ FILE: config/src/test/java/org/springframework/security/config/doc/XmlSupport.java ================================================ /* * 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 org.springframework.security.config.doc; import java.io.IOException; import java.util.Map; import org.springframework.core.io.ClassPathResource; /** * Support for ensuring preparing the givens in {@link XsdDocumentedTests} * * @author Josh Cummings */ public class XmlSupport { private XmlParser parser; public XmlNode parse(String location) throws IOException { ClassPathResource resource = new ClassPathResource(location); this.parser = new XmlParser(resource.getInputStream()); return this.parser.parse(); } public Map elementsByElementName(String location) throws IOException { XmlNode node = parse(location); return new SpringSecurityXsdParser(node).parse(); } public void close() throws IOException { if (this.parser != null) { this.parser.close(); } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/doc/XsdDocumentedTests.java ================================================ /* * 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 org.springframework.security.config.doc; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.stream.Collectors; import java.util.stream.Stream; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import org.springframework.core.io.ClassPathResource; import org.springframework.security.config.http.SecurityFiltersAssertions; import org.springframework.util.StringUtils; import static org.assertj.core.api.Assertions.assertThat; /** * Tests to ensure that the xsd is properly documented. * * @author Rob Winch * @author Josh Cummings */ public class XsdDocumentedTests { // @formatter:off Collection ignoredIds = Arrays.asList("nsa-any-user-service", "nsa-any-user-service-parents", "nsa-authentication", "nsa-websocket-security", "nsa-ldap", "nsa-web", // deprecated and for removal "nsa-frame-options-strategy", "nsa-frame-options-ref", "nsa-frame-options-value", "nsa-frame-options-from-parameter"); // @formatter:on String referenceLocation = "../docs/modules/ROOT/pages/servlet/appendix/namespace"; String schema31xDocumentLocation = "org/springframework/security/config/spring-security-3.1.xsd"; String schemaDocumentLocation = "org/springframework/security/config/spring-security-7.1.xsd"; XmlSupport xml = new XmlSupport(); @AfterEach public void close() throws IOException { this.xml.close(); } @Test public void parseWhenLatestXsdThenAllNamedSecurityFiltersAreDefinedAndOrderedProperly() throws IOException { XmlNode root = this.xml.parse(this.schemaDocumentLocation); // @formatter:off List nodes = root.child("schema") .map(XmlNode::children) .orElse(Stream.empty()) .filter((node) -> "simpleType".equals(node.simpleName()) && "named-security-filter".equals(node.attribute("name"))) .flatMap(XmlNode::children) .flatMap(XmlNode::children) .map((node) -> node.attribute("value")) .filter(StringUtils::hasText) .collect(Collectors.toList()); // @formatter:on SecurityFiltersAssertions.assertEquals(nodes); } @Test public void parseWhen31XsdThenAllNamedSecurityFiltersAreDefinedAndOrderedProperly() throws IOException { // @formatter:off List expected = Arrays.asList("FIRST", "CHANNEL_FILTER", "SECURITY_CONTEXT_FILTER", "CONCURRENT_SESSION_FILTER", "LOGOUT_FILTER", "X509_FILTER", "PRE_AUTH_FILTER", "CAS_FILTER", "FORM_LOGIN_FILTER", "OPENID_FILTER", "LOGIN_PAGE_FILTER", "DIGEST_AUTH_FILTER", "BASIC_AUTH_FILTER", "REQUEST_CACHE_FILTER", "SERVLET_API_SUPPORT_FILTER", "JAAS_API_SUPPORT_FILTER", "REMEMBER_ME_FILTER", "ANONYMOUS_FILTER", "SESSION_MANAGEMENT_FILTER", "EXCEPTION_TRANSLATION_FILTER", "FILTER_SECURITY_INTERCEPTOR", "SWITCH_USER_FILTER", "LAST"); // @formatter:on XmlNode root = this.xml.parse(this.schema31xDocumentLocation); // @formatter:off List nodes = root.child("schema") .map(XmlNode::children) .orElse(Stream.empty()) .filter((node) -> "simpleType".equals(node.simpleName()) && "named-security-filter".equals(node.attribute("name"))) .flatMap(XmlNode::children) .flatMap(XmlNode::children) .map((node) -> node.attribute("value")) .filter(StringUtils::hasText) .collect(Collectors.toList()); // @formatter:on assertThat(nodes).isEqualTo(expected); } /** * This will check to ensure that the expected number of xsd documents are found to * ensure that we are validating against the current xsd document. If this test fails, * all that is needed is to update the schemaDocument and the expected size for this * test. * @return */ @Test public void sizeWhenReadingFilesystemThenIsCorrectNumberOfSchemaFiles() throws IOException { ClassPathResource resource = new ClassPathResource(this.schemaDocumentLocation); // @formatter:off String[] schemas = resource.getFile() .getParentFile() .list((dir, name) -> name.endsWith(".xsd")); // @formatter:on assertThat(schemas.length) .withFailMessage("the count is equal to 29, if not then schemaDocument needs updating") .isEqualTo(29); } /** * This uses a naming convention for the ids of the appendix to ensure that the entire * appendix is documented. The naming convention for the ids is documented in * {@link Element#getIds()}. * @return */ @Test public void countReferencesWhenReviewingDocumentationThenEntireSchemaIsIncluded() throws IOException { Map elementsByElementName = this.xml.elementsByElementName(this.schemaDocumentLocation); // @formatter:off List documentIds = namespaceLines() .filter((line) -> line.matches("\\[\\[(nsa-.*)\\]\\]")) .map((line) -> line.substring(2, line.length() - 2)) .collect(Collectors.toList()); Set expectedIds = elementsByElementName.values() .stream() .flatMap((element) -> element.getIds().stream()) .collect(Collectors.toSet()); // @formatter:on documentIds.removeAll(this.ignoredIds); expectedIds.removeAll(this.ignoredIds); assertThat(documentIds).containsAll(expectedIds); assertThat(expectedIds).containsAll(documentIds); } /** * This test ensures that any element that has children or parents contains a section * that has links pointing to that documentation. * @return */ @Test public void countLinksWhenReviewingDocumentationThenParentsAndChildrenAreCorrectlyLinked() throws IOException { Map> docAttrNameToChildren = new TreeMap<>(); Map> docAttrNameToParents = new TreeMap<>(); String docAttrName = null; Map> currentDocAttrNameToElmt = null; List lines = namespaceLines().collect(Collectors.toList()); for (String line : lines) { if (line.matches("^\\[\\[.*\\]\\]$")) { String id = line.substring(2, line.length() - 2); if (id.endsWith("-children")) { docAttrName = id.substring(0, id.length() - 9); currentDocAttrNameToElmt = docAttrNameToChildren; } else if (id.endsWith("-parents")) { docAttrName = id.substring(0, id.length() - 8); currentDocAttrNameToElmt = docAttrNameToParents; } else if (id.endsWith("-attributes") || docAttrName != null && !id.startsWith(docAttrName)) { currentDocAttrNameToElmt = null; docAttrName = null; } } if (docAttrName != null && currentDocAttrNameToElmt != null) { String expression = ".*<<(nsa-.*),.*>>.*"; if (line.matches(expression)) { String elmtId = line.replaceAll(expression, "$1"); currentDocAttrNameToElmt.computeIfAbsent(docAttrName, (key) -> new ArrayList<>()).add(elmtId); } else { expression = ".*xref:.*#(nsa-.*)\\[.*\\]"; if (line.matches(expression)) { String elmtId = line.replaceAll(expression, "$1"); currentDocAttrNameToElmt.computeIfAbsent(docAttrName, (key) -> new ArrayList<>()).add(elmtId); } } } } Map elementNameToElement = this.xml.elementsByElementName(this.schemaDocumentLocation); Map> schemaAttrNameToChildren = new TreeMap<>(); Map> schemaAttrNameToParents = new TreeMap<>(); elementNameToElement.entrySet().stream().forEach((entry) -> { String key = "nsa-" + entry.getKey(); if (this.ignoredIds.contains(key)) { return; } // @formatter:off List parentIds = entry.getValue() .getAllParentElmts() .values() .stream() .filter((element) -> !this.ignoredIds.contains(element.getId())) .map((element) -> element.getId()) .sorted() .collect(Collectors.toList()); // @formatter:on if (!parentIds.isEmpty()) { schemaAttrNameToParents.put(key, parentIds); } // @formatter:off List childIds = entry.getValue() .getAllChildElmts() .values() .stream() .filter((element) -> !this.ignoredIds.contains(element.getId())).map((element) -> element.getId()) .sorted() .collect(Collectors.toList()); // @formatter:on if (!childIds.isEmpty()) { schemaAttrNameToChildren.put(key, childIds); } }); assertThat(docAttrNameToChildren) .describedAs(toString(docAttrNameToChildren) + "\n!=\n\n" + toString(schemaAttrNameToChildren)) .containsExactlyInAnyOrderEntriesOf(schemaAttrNameToChildren); assertThat(docAttrNameToParents) .describedAs(toString(docAttrNameToParents) + "\n!=\n\n" + toString(schemaAttrNameToParents)) .containsExactlyInAnyOrderEntriesOf(schemaAttrNameToParents); } private String toString(Map map) { StringBuffer buffer = new StringBuffer(); map.forEach((k, v) -> { buffer.append(k); buffer.append("="); buffer.append(v); buffer.append("\n"); }); return buffer.toString(); } /** * This test checks each xsd element and ensures there is documentation for it. * @return */ @Test public void countWhenReviewingDocumentationThenAllElementsDocumented() throws IOException { Map elementNameToElement = this.xml.elementsByElementName(this.schemaDocumentLocation); // @formatter:off String notDocElmtIds = elementNameToElement.values() .stream() .filter((element) -> StringUtils.isEmpty(element.getDesc()) && !this.ignoredIds.contains(element.getId())) .map((element) -> element.getId()) .sorted() .collect(Collectors.joining("\n")); String notDocAttrIds = elementNameToElement.values() .stream() .flatMap((element) -> element.getAttrs().stream()) .filter((element) -> StringUtils.isEmpty(element.getDesc()) && !this.ignoredIds.contains(element.getId())) .map((element) -> element.getId()) .sorted() .collect(Collectors.joining("\n")); // @formatter:on assertThat(notDocElmtIds).isEmpty(); assertThat(notDocAttrIds).isEmpty(); } private Stream namespaceLines() { return Stream.of(new File(this.referenceLocation).listFiles()).map(File::toPath).flatMap(this::fileLines); } private Stream fileLines(Path path) { try { return Files.lines(path); } catch (Exception ex) { throw new RuntimeException(ex); } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/http/AccessDeniedConfigTests.java ================================================ /* * 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 org.springframework.security.config.http; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.parsing.BeanDefinitionParsingException; import org.springframework.http.HttpStatus; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners; import org.springframework.security.test.context.support.WithMockUser; import org.springframework.security.web.access.AccessDeniedHandler; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.servlet.MockMvc; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * @author Luke Taylor * @author Josh Cummings */ @ExtendWith({ SpringExtension.class, SpringTestContextExtension.class }) @SecurityTestExecutionListeners public class AccessDeniedConfigTests { private static final String CONFIG_LOCATION_PREFIX = "classpath:org/springframework/security/config/http/AccessDeniedConfigTests"; @Autowired MockMvc mvc; public final SpringTestContext spring = new SpringTestContext(this); @Test public void configureWhenAccessDeniedHandlerIsMissingLeadingSlashThenException() { SpringTestContext context = this.spring.configLocations(this.xml("NoLeadingSlash")); /* * NOTE: Original error message "errorPage must begin with '/'" no longer shows up * in stack trace as of Spring Framework 6.x. * * See https://github.com/spring-projects/spring-framework/issues/25162. */ assertThatExceptionOfType(BeanCreationException.class).isThrownBy(() -> context.autowire()) .havingRootCause() .withMessageContaining("Property 'errorPage' threw exception"); } @Test @WithMockUser public void configureWhenAccessDeniedHandlerRefThenAutowire() throws Exception { this.spring.configLocations(this.xml("AccessDeniedHandler")).autowire(); this.mvc.perform(get("/")).andExpect(status().is(HttpStatus.GONE.value())); } @Test public void configureWhenAccessDeniedHandlerUsesPathAndRefThenException() { SpringTestContext context = this.spring.configLocations(this.xml("UsesPathAndRef")); assertThatExceptionOfType(BeanDefinitionParsingException.class).isThrownBy(() -> context.autowire()) .withMessageContaining("attribute error-page cannot be used together with the 'ref' attribute"); } private String xml(String configName) { return CONFIG_LOCATION_PREFIX + "-" + configName + ".xml"; } public static class GoneAccessDeniedHandler implements AccessDeniedHandler { @Override public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) { response.setStatus(HttpStatus.GONE.value()); } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/http/CsrfBeanDefinitionParserTests.java ================================================ /* * 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 org.springframework.security.config.http; import org.junit.jupiter.api.Test; import org.springframework.context.support.ClassPathXmlApplicationContext; /** * @author Ankur Pathak */ public class CsrfBeanDefinitionParserTests { private static final String CONFIG_LOCATION_PREFIX = "classpath:org/springframework/security/config/http/CsrfBeanDefinitionParserTests"; @Test public void registerDataValueProcessorOnlyIfNotRegistered() { try (ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext()) { context.setAllowBeanDefinitionOverriding(false); context.setConfigLocation(this.xml("RegisterDataValueProcessorOnyIfNotRegistered")); context.refresh(); } } private String xml(String configName) { return CONFIG_LOCATION_PREFIX + "-" + configName + ".xml"; } } ================================================ FILE: config/src/test/java/org/springframework/security/config/http/CsrfConfigTests.java ================================================ /* * 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 org.springframework.security.config.http; import java.net.URI; import java.util.List; import jakarta.servlet.Filter; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpSession; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners; import org.springframework.security.test.context.support.WithMockUser; import org.springframework.security.test.web.servlet.RequestCacheResultMatcher; import org.springframework.security.test.web.support.WebTestUtils; import org.springframework.security.web.FilterChainProxy; import org.springframework.security.web.access.AccessDeniedHandler; import org.springframework.security.web.csrf.CsrfFilter; import org.springframework.security.web.csrf.CsrfToken; import org.springframework.security.web.csrf.CsrfTokenRepository; import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.stereotype.Controller; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.ResultMatcher; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.servlet.support.RequestDataValueProcessor; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.head; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.options; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.request; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * @author Rob Winch * @author Josh Cummings */ @ExtendWith({ SpringExtension.class, SpringTestContextExtension.class }) @SecurityTestExecutionListeners public class CsrfConfigTests { private static final String CONFIG_LOCATION_PREFIX = "classpath:org/springframework/security/config/http/CsrfConfigTests"; public final SpringTestContext spring = new SpringTestContext(this); @Autowired MockMvc mvc; @Test public void postWhenDefaultConfigurationThenForbiddenSinceCsrfIsEnabled() throws Exception { this.spring.configLocations(this.xml("AutoConfig")).autowire(); // @formatter:off this.mvc.perform(post("/csrf")) .andExpect(status().isForbidden()) .andExpect(csrfCreated()); // @formatter:on } @Test public void putWhenDefaultConfigurationThenForbiddenSinceCsrfIsEnabled() throws Exception { this.spring.configLocations(this.xml("AutoConfig")).autowire(); // @formatter:off this.mvc.perform(put("/csrf")) .andExpect(status().isForbidden()) .andExpect(csrfCreated()); // @formatter:on } @Test public void patchWhenDefaultConfigurationThenForbiddenSinceCsrfIsEnabled() throws Exception { this.spring.configLocations(this.xml("AutoConfig")).autowire(); // @formatter:off this.mvc.perform(patch("/csrf")) .andExpect(status().isForbidden()) .andExpect(csrfCreated()); // @formatter:on } @Test public void deleteWhenDefaultConfigurationThenForbiddenSinceCsrfIsEnabled() throws Exception { this.spring.configLocations(this.xml("AutoConfig")).autowire(); // @formatter:off this.mvc.perform(delete("/csrf")) .andExpect(status().isForbidden()) .andExpect(csrfCreated()); // @formatter:on } @Test public void invalidWhenDefaultConfigurationThenForbiddenSinceCsrfIsEnabled() throws Exception { this.spring.configLocations(this.xml("AutoConfig")).autowire(); // @formatter:off this.mvc.perform(request("INVALID", new URI("/csrf"))) .andExpect(status().isForbidden()) .andExpect(csrfCreated()); // @formatter:on } @Test public void getWhenDefaultConfigurationThenCsrfIsEnabled() throws Exception { this.spring.configLocations(this.xml("shared-controllers"), this.xml("AutoConfig")).autowire(); // @formatter:off this.mvc.perform(get("/csrf")) .andExpect(csrfInBody()); // @formatter:on } @Test public void headWhenDefaultConfigurationThenCsrfIsEnabled() throws Exception { this.spring.configLocations(this.xml("shared-controllers"), this.xml("AutoConfig")).autowire(); // @formatter:off this.mvc.perform(head("/csrf-in-header")) .andExpect(csrfInHeader()); // @formatter:on } @Test public void traceWhenDefaultConfigurationThenCsrfIsEnabled() throws Exception { this.spring.configLocations(this.xml("shared-controllers"), this.xml("AutoConfig")).autowire(); // @formatter:off MockMvc traceEnabled = MockMvcBuilders.webAppContextSetup(this.spring.getContext()) .apply(springSecurity()) .addDispatcherServletCustomizer((dispatcherServlet) -> dispatcherServlet.setDispatchTraceRequest(true)) .build(); traceEnabled.perform(request(HttpMethod.TRACE, "/csrf-in-header")) .andExpect(csrfInHeader()); // @formatter:on } @Test public void optionsWhenDefaultConfigurationThenCsrfIsEnabled() throws Exception { this.spring.configLocations(this.xml("shared-controllers"), this.xml("AutoConfig")).autowire(); // @formatter:off this.mvc.perform(options("/csrf-in-header")) .andExpect(csrfInHeader()); // @formatter:on } @Test public void postWhenCsrfDisabledThenRequestAllowed() throws Exception { this.spring.configLocations(this.xml("shared-controllers"), this.xml("CsrfDisabled")).autowire(); // @formatter:off this.mvc.perform(post("/ok")) .andExpect(status().isOk()); // @formatter:on assertThat(getFilter(this.spring, CsrfFilter.class)).isNull(); } @Test public void postWhenCsrfElementEnabledThenForbidden() throws Exception { this.spring.configLocations(this.xml("CsrfEnabled")).autowire(); // @formatter:off this.mvc.perform(post("/csrf")) .andExpect(status().isForbidden()) .andExpect(csrfCreated()); // @formatter:on } @Test public void putWhenCsrfElementEnabledThenForbidden() throws Exception { this.spring.configLocations(this.xml("CsrfEnabled")).autowire(); // @formatter:off this.mvc.perform(put("/csrf")) .andExpect(status().isForbidden()) .andExpect(csrfCreated()); // @formatter:on } @Test public void patchWhenCsrfElementEnabledThenForbidden() throws Exception { this.spring.configLocations(this.xml("CsrfEnabled")).autowire(); // @formatter:off this.mvc.perform(patch("/csrf")) .andExpect(status().isForbidden()) .andExpect(csrfCreated()); // @formatter:on } @Test public void deleteWhenCsrfElementEnabledThenForbidden() throws Exception { this.spring.configLocations(this.xml("CsrfEnabled")).autowire(); // @formatter:off this.mvc.perform(delete("/csrf")) .andExpect(status().isForbidden()) .andExpect(csrfCreated()); // @formatter:on } @Test public void invalidWhenCsrfElementEnabledThenForbidden() throws Exception { this.spring.configLocations(this.xml("CsrfEnabled")).autowire(); // @formatter:off this.mvc.perform(request("INVALID", new URI("/csrf"))) .andExpect(status().isForbidden()) .andExpect(csrfCreated()); // @formatter:on } @Test public void getWhenCsrfElementEnabledThenOk() throws Exception { this.spring.configLocations(this.xml("shared-controllers"), this.xml("CsrfEnabled")).autowire(); // @formatter:off this.mvc.perform(get("/csrf")) .andExpect(csrfInBody()); // @formatter:on } @Test public void headWhenCsrfElementEnabledThenOk() throws Exception { this.spring.configLocations(this.xml("shared-controllers"), this.xml("CsrfEnabled")).autowire(); // @formatter:off this.mvc.perform(head("/csrf-in-header")) .andExpect(csrfInHeader()); // @formatter:on } @Test public void traceWhenCsrfElementEnabledThenOk() throws Exception { this.spring.configLocations(this.xml("shared-controllers"), this.xml("CsrfEnabled")).autowire(); // @formatter:off MockMvc traceEnabled = MockMvcBuilders.webAppContextSetup(this.spring.getContext()) .apply(springSecurity()) .addDispatcherServletCustomizer((dispatcherServlet) -> dispatcherServlet.setDispatchTraceRequest(true)) .build(); // @formatter:on traceEnabled.perform(request(HttpMethod.TRACE, "/csrf-in-header")).andExpect(csrfInHeader()); } @Test public void optionsWhenCsrfElementEnabledThenOk() throws Exception { this.spring.configLocations(this.xml("shared-controllers"), this.xml("CsrfEnabled")).autowire(); // @formatter:off this.mvc.perform(options("/csrf-in-header")) .andExpect(csrfInHeader()); // @formatter:on } @Test public void autowireWhenCsrfElementEnabledThenCreatesCsrfRequestDataValueProcessor() { this.spring.configLocations(this.xml("CsrfEnabled")).autowire(); assertThat(this.spring.getContext().getBean(RequestDataValueProcessor.class)).isNotNull(); } @Test public void postWhenUsingCsrfAndCustomAccessDeniedHandlerThenTheHandlerIsAppropriatelyEngaged() throws Exception { this.spring.configLocations(this.xml("WithAccessDeniedHandler"), this.xml("shared-access-denied-handler")) .autowire(); // @formatter:off this.mvc.perform(post("/ok")) .andExpect(status().isIAmATeapot()); // @formatter:on } @Test public void getWhenUsingCsrfAndCustomRequestAttributeThenSetUsingCsrfAttrName() throws Exception { this.spring.configLocations(this.xml("WithRequestAttrName")).autowire(); // @formatter:off MvcResult result = this.mvc.perform(get("/ok")).andReturn(); assertThat(result.getRequest().getAttribute("csrf-attribute-name")).isInstanceOf(CsrfToken.class); // @formatter:on } @Test public void postWhenUsingCsrfAndXorCsrfTokenRequestAttributeHandlerThenOk() throws Exception { this.spring.configLocations(this.xml("WithXorCsrfTokenRequestAttributeHandler"), this.xml("shared-controllers")) .autowire(); // @formatter:off MvcResult mvcResult = this.mvc.perform(get("/ok")) .andExpect(status().isOk()) .andReturn(); MockHttpSession session = (MockHttpSession) mvcResult.getRequest().getSession(); MockHttpServletRequestBuilder ok = post("/ok") .with(csrf()) .session(session); this.mvc.perform(ok).andExpect(status().isOk()); // @formatter:on } @Test public void postWhenUsingCsrfAndXorCsrfTokenRequestAttributeHandlerWithRawTokenThenForbidden() throws Exception { this.spring.configLocations(this.xml("WithXorCsrfTokenRequestAttributeHandler"), this.xml("shared-controllers")) .autowire(); // @formatter:off MvcResult mvcResult = this.mvc.perform(get("/csrf")) .andExpect(status().isOk()) .andReturn(); MockHttpServletRequest request = mvcResult.getRequest(); MockHttpSession session = (MockHttpSession) request.getSession(); CsrfTokenRepository repository = WebTestUtils.getCsrfTokenRepository(request); CsrfToken csrfToken = repository.loadToken(request); MockHttpServletRequestBuilder ok = post("/ok") .header(csrfToken.getHeaderName(), csrfToken.getToken()) .session(session); this.mvc.perform(ok).andExpect(status().isForbidden()); // @formatter:on } @Test public void postWhenUsingCsrfAndXorCsrfTokenRequestAttributeHandlerThenCsrfAuthenticationStrategyUses() throws Exception { this.spring.configLocations(this.xml("WithXorCsrfTokenRequestAttributeHandler"), this.xml("shared-controllers")) .autowire(); // @formatter:off MvcResult mvcResult1 = this.mvc.perform(get("/csrf")) .andExpect(status().isOk()) .andReturn(); // @formatter:on MockHttpServletRequest request1 = mvcResult1.getRequest(); MockHttpSession session = (MockHttpSession) request1.getSession(); CsrfTokenRepository repository = WebTestUtils.getCsrfTokenRepository(request1); // @formatter:off MockHttpServletRequestBuilder login = post("/login") .param("username", "user") .param("password", "password") .session(session) .with(csrf()); this.mvc.perform(login) .andExpect(status().is3xxRedirection()) .andExpect(redirectedUrl("/")); // @formatter:on assertThat(repository.loadToken(request1)).isNull(); // @formatter:off MvcResult mvcResult2 = this.mvc.perform(get("/csrf").session(session)) .andExpect(status().isOk()) .andReturn(); // @formatter:on MockHttpServletRequest request2 = mvcResult2.getRequest(); CsrfToken csrfToken = repository.loadToken(request2); CsrfToken csrfTokenAttribute = (CsrfToken) request2.getAttribute(CsrfToken.class.getName()); assertThat(csrfTokenAttribute).isNotNull(); assertThat(csrfTokenAttribute.getToken()).isNotBlank(); assertThat(csrfTokenAttribute.getToken()).isNotEqualTo(csrfToken.getToken()); } @Test public void postWhenHasCsrfTokenButSessionExpiresThenRequestIsCancelledAfterSuccessfulAuthentication() throws Exception { this.spring.configLocations(this.xml("CsrfEnabled")).autowire(); // simulates a request that has no authentication (e.g. session time-out) MvcResult result = this.mvc.perform(post("/authenticated").with(csrf())) .andExpect(redirectedUrl("/login")) .andReturn(); MockHttpSession session = (MockHttpSession) result.getRequest().getSession(); // if the request cache is consulted, then it will redirect back to /some-url, // which we don't want // @formatter:off MockHttpServletRequestBuilder login = post("/login") .param("username", "user") .param("password", "password") .session(session) .with(csrf()); this.mvc.perform(login) .andExpect(redirectedUrl("/")); // @formatter:on } @Test public void getWhenHasCsrfTokenButSessionExpiresThenRequestIsRememeberedAfterSuccessfulAuthentication() throws Exception { this.spring.configLocations(this.xml("CsrfEnabled")).autowire(); // simulates a request that has no authentication (e.g. session time-out) MvcResult result = this.mvc.perform(get("/authenticated")).andExpect(redirectedUrl("/login")).andReturn(); MockHttpSession session = (MockHttpSession) result.getRequest().getSession(); // if the request cache is consulted, then it will redirect back to /some-url, // which we do want // @formatter:off MockHttpServletRequestBuilder login = post("/login") .param("username", "user") .param("password", "password") .session(session) .with(csrf()); this.mvc.perform(login) .andExpect(RequestCacheResultMatcher.redirectToCachedRequest()); // @formatter:on } /** * SEC-2422: csrf expire CSRF token and session-management invalid-session-url */ @Test public void postWhenUsingCsrfAndCustomSessionManagementAndNoSessionThenStillRedirectsToInvalidSessionUrl() throws Exception { this.spring.configLocations(this.xml("WithSessionManagement")).autowire(); // @formatter:off MockHttpServletRequestBuilder postToOk = post("/ok") .param("_csrf", "abc"); MvcResult result = this.mvc.perform(postToOk) .andExpect(redirectedUrl("/error/sessionError")) .andReturn(); MockHttpSession session = (MockHttpSession) result.getRequest().getSession(); this.mvc.perform(post("/csrf").session(session)) .andExpect(status().isForbidden()); // @formatter:on } @Test public void requestWhenUsingCustomRequestMatcherConfiguredThenAppliesAccordingly() throws Exception { SpringTestContext context = this.spring.configLocations(this.xml("shared-controllers"), this.xml("WithRequestMatcher"), this.xml("mock-request-matcher")); context.autowire(); RequestMatcher matcher = context.getContext().getBean(RequestMatcher.class); given(matcher.matches(any(HttpServletRequest.class))).willReturn(false); // @formatter:off this.mvc.perform(post("/ok")) .andExpect(status().isOk()); // @formatter:on given(matcher.matches(any(HttpServletRequest.class))).willReturn(true); // @formatter:off this.mvc.perform(get("/ok")) .andExpect(status().isForbidden()); // @formatter:on } @Test public void getWhenDefaultConfigurationThenSessionNotImmediatelyCreated() throws Exception { this.spring.configLocations(this.xml("shared-controllers"), this.xml("AutoConfig")).autowire(); // @formatter:off MvcResult result = this.mvc.perform(get("/ok")) .andExpect(status().isOk()).andReturn(); // @formatter:on assertThat(result.getRequest().getSession(false)).isNull(); } @Test @WithMockUser public void postWhenCsrfMismatchesThenForbidden() throws Exception { this.spring.configLocations(this.xml("shared-controllers"), this.xml("AutoConfig")).autowire(); MvcResult result = this.mvc.perform(get("/ok")).andReturn(); MockHttpSession session = (MockHttpSession) result.getRequest().getSession(); // @formatter:off MockHttpServletRequestBuilder postOk = post("/ok") .session(session) .with(csrf().useInvalidToken()); this.mvc.perform(postOk) .andExpect(status().isForbidden()); // @formatter:on } @Test public void loginWhenDefaultConfigurationThenCsrfCleared() throws Exception { this.spring.configLocations(this.xml("shared-controllers"), this.xml("AutoConfig")).autowire(); MvcResult result = this.mvc.perform(get("/csrf")).andReturn(); MockHttpSession session = (MockHttpSession) result.getRequest().getSession(); // @formatter:off MockHttpServletRequestBuilder loginRequest = post("/login") .param("username", "user") .param("password", "password") .session(session) .with(csrf()); this.mvc.perform(loginRequest) .andExpect(status().isFound()); this.mvc.perform(get("/csrf").session(session)) .andExpect(csrfChanged(result)); // @formatter:on } @Test public void logoutWhenDefaultConfigurationThenCsrfCleared() throws Exception { this.spring.configLocations(this.xml("shared-controllers"), this.xml("AutoConfig")).autowire(); MvcResult result = this.mvc.perform(get("/csrf")).andReturn(); MockHttpSession session = (MockHttpSession) result.getRequest().getSession(); // @formatter:off this.mvc.perform(post("/logout").session(session).with(csrf())) .andExpect(status().isFound()); this.mvc.perform(get("/csrf").session(session)) .andExpect(csrfChanged(result)); // @formatter:on } /** * SEC-2495: csrf disables logout on GET */ @Test @WithMockUser public void logoutWhenDefaultConfigurationThenDisabled() throws Exception { this.spring.configLocations(this.xml("shared-controllers"), this.xml("CsrfEnabled")).autowire(); // renders form to log out but does not perform a redirect // @formatter:off this.mvc.perform(get("/logout")) .andExpect(status().isOk()); // @formatter:on // still logged in // @formatter:off this.mvc.perform(get("/authenticated")) .andExpect(status().isOk()); // @formatter:on } private T getFilter(SpringTestContext context, Class type) { FilterChainProxy chain = context.getContext().getBean(FilterChainProxy.class); List filters = chain.getFilters("/any"); for (Filter filter : filters) { if (type.isAssignableFrom(filter.getClass())) { return (T) filter; } } return null; } private String xml(String configName) { return CONFIG_LOCATION_PREFIX + "-" + configName + ".xml"; } ResultMatcher csrfChanged(MvcResult first) { return (second) -> { assertThat(first).isNotNull(); assertThat(second).isNotNull(); assertThat(first.getResponse().getContentAsString()) .isNotEqualTo(second.getResponse().getContentAsString()); }; } ResultMatcher csrfCreated() { return new CsrfCreatedResultMatcher(); } ResultMatcher csrfInHeader() { return new CsrfReturnedResultMatcher((result) -> result.getResponse().getHeader("X-CSRF-TOKEN")); } ResultMatcher csrfInBody() { return new CsrfReturnedResultMatcher((result) -> result.getResponse().getContentAsString()); } @Controller public static class RootController { @RequestMapping(value = "/csrf-in-header", method = { RequestMethod.HEAD, RequestMethod.TRACE, RequestMethod.OPTIONS }) @ResponseBody String csrfInHeaderAndBody(CsrfToken token, HttpServletResponse response) { response.setHeader(token.getHeaderName(), token.getToken()); return csrfInBody(token); } @RequestMapping(value = "/csrf", method = { RequestMethod.POST, RequestMethod.PUT, RequestMethod.PATCH, RequestMethod.DELETE, RequestMethod.GET }) @ResponseBody String csrfInBody(CsrfToken token) { return token.getToken(); } @RequestMapping(value = "/ok", method = { RequestMethod.POST, RequestMethod.GET }) @ResponseBody String ok() { return "ok"; } @GetMapping("/authenticated") @ResponseBody String authenticated() { return "authenticated"; } } private static class TeapotAccessDeniedHandler implements AccessDeniedHandler { @Override public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) { response.setStatus(HttpStatus.I_AM_A_TEAPOT.value()); } } @FunctionalInterface interface ExceptionalFunction { OUT apply(IN in) throws Exception; } static class CsrfCreatedResultMatcher implements ResultMatcher { @Override public void match(MvcResult result) { MockHttpServletRequest request = result.getRequest(); CsrfToken token = WebTestUtils.getCsrfTokenRepository(request).loadToken(request); assertThat(token).isNotNull(); } } static class CsrfReturnedResultMatcher implements ResultMatcher { ExceptionalFunction token; CsrfReturnedResultMatcher(ExceptionalFunction token) { this.token = token; } @Override public void match(MvcResult result) throws Exception { MockHttpServletRequest request = result.getRequest(); CsrfToken token = (CsrfToken) request.getAttribute(CsrfToken.class.getName()); assertThat(token).isNotNull(); assertThat(token.getToken()).isEqualTo(this.token.apply(result)); } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/http/DefaultFilterChainValidatorTests.java ================================================ /* * 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 org.springframework.security.config.http; import java.util.ArrayList; import java.util.Collection; import java.util.List; import jakarta.servlet.http.HttpServletRequest; import org.apache.commons.logging.Log; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.security.access.AccessDecisionManager; import org.springframework.security.authorization.AuthorizationDecision; import org.springframework.security.authorization.AuthorizationManager; import org.springframework.security.core.Authentication; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.security.web.DefaultSecurityFilterChain; import org.springframework.security.web.FilterChainProxy; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.UnreachableFilterChainException; import org.springframework.security.web.access.ExceptionTranslationFilter; import org.springframework.security.web.access.intercept.AuthorizationFilter; import org.springframework.security.web.access.intercept.DefaultFilterInvocationSecurityMetadataSource; import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource; import org.springframework.security.web.access.intercept.FilterSecurityInterceptor; import org.springframework.security.web.authentication.AnonymousAuthenticationFilter; import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint; import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher; import org.springframework.security.web.util.matcher.AnyRequestMatcher; import org.springframework.test.util.ReflectionTestUtils; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatNoException; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.willThrow; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; /** * @author Rob Winch */ @ExtendWith(MockitoExtension.class) public class DefaultFilterChainValidatorTests { private DefaultFilterChainValidator validator; private FilterChainProxy chain; private FilterChainProxy chainAuthorizationFilter; @Mock private Log logger; @Mock private DefaultFilterInvocationSecurityMetadataSource metadataSource; @Mock private AccessDecisionManager accessDecisionManager; private FilterSecurityInterceptor authorizationInterceptor; @Mock private AuthorizationManager authorizationManager; private AuthorizationFilter authorizationFilter; @BeforeEach public void setUp() { AnonymousAuthenticationFilter aaf = new AnonymousAuthenticationFilter("anonymous"); this.authorizationInterceptor = new FilterSecurityInterceptor(); this.authorizationInterceptor.setAccessDecisionManager(this.accessDecisionManager); this.authorizationInterceptor.setSecurityMetadataSource(this.metadataSource); this.authorizationFilter = new AuthorizationFilter(this.authorizationManager); AuthenticationEntryPoint authenticationEntryPoint = new LoginUrlAuthenticationEntryPoint("/login"); ExceptionTranslationFilter etf = new ExceptionTranslationFilter(authenticationEntryPoint); DefaultSecurityFilterChain securityChain = new DefaultSecurityFilterChain(AnyRequestMatcher.INSTANCE, aaf, etf, this.authorizationInterceptor); this.chain = new FilterChainProxy(securityChain); DefaultSecurityFilterChain securityChainAuthorizationFilter = new DefaultSecurityFilterChain( AnyRequestMatcher.INSTANCE, aaf, etf, this.authorizationFilter); this.chainAuthorizationFilter = new FilterChainProxy(securityChainAuthorizationFilter); this.validator = new DefaultFilterChainValidator(); ReflectionTestUtils.setField(this.validator, "logger", this.logger); } @Test void validateWhenFilterSecurityInterceptorConfiguredThenValidates() { assertThatNoException().isThrownBy(() -> this.validator.validate(this.chain)); } // SEC-1878 @SuppressWarnings("unchecked") @Test public void validateCheckLoginPageIsntProtectedThrowsIllegalArgumentException() { IllegalArgumentException toBeThrown = new IllegalArgumentException("failed to eval expression"); willThrow(toBeThrown).given(this.accessDecisionManager) .decide(any(Authentication.class), any(), any(Collection.class)); this.validator.validate(this.chain); verify(this.logger).info( "Unable to check access to the login page to determine if anonymous access is allowed. This might be an error, but can happen under normal circumstances.", toBeThrown); } @Test public void validateCheckLoginPageAllowsAnonymous() { given(this.authorizationManager.authorize(any(), any())).willReturn(new AuthorizationDecision(false)); this.validator.validate(this.chainAuthorizationFilter); verify(this.logger).warn("Anonymous access to the login page doesn't appear to be enabled. " + "This is almost certainly an error. Please check your configuration allows unauthenticated " + "access to the configured login page. (Simulated access was rejected)"); } // SEC-1957 @Test public void validateCustomMetadataSource() { FilterInvocationSecurityMetadataSource customMetaDataSource = mock( FilterInvocationSecurityMetadataSource.class); this.authorizationInterceptor.setSecurityMetadataSource(customMetaDataSource); this.validator.validate(this.chain); verify(customMetaDataSource, atLeastOnce()).getAttributes(any()); } @Test void validateWhenSameRequestMatchersArePresentThenUnreachableFilterChainException() { PathPatternRequestMatcher.Builder builder = PathPatternRequestMatcher.withDefaults(); AnonymousAuthenticationFilter authenticationFilter = mock(AnonymousAuthenticationFilter.class); ExceptionTranslationFilter exceptionTranslationFilter = mock(ExceptionTranslationFilter.class); SecurityFilterChain chain1 = new DefaultSecurityFilterChain(builder.matcher("/api"), authenticationFilter, exceptionTranslationFilter, this.authorizationInterceptor); SecurityFilterChain chain2 = new DefaultSecurityFilterChain(builder.matcher("/api"), authenticationFilter, exceptionTranslationFilter, this.authorizationInterceptor); List chains = new ArrayList<>(); chains.add(chain2); chains.add(chain1); FilterChainProxy proxy = new FilterChainProxy(chains); assertThatExceptionOfType(UnreachableFilterChainException.class) .isThrownBy(() -> this.validator.validate(proxy)); } } ================================================ FILE: config/src/test/java/org/springframework/security/config/http/DeferHttpSessionXmlConfigTests.java ================================================ /* * 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 org.springframework.security.config.http; import jakarta.servlet.FilterChain; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.web.FilterChainProxy; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; /** * @author Rob Winch */ @ExtendWith(SpringTestContextExtension.class) public class DeferHttpSessionXmlConfigTests { private static final String CONFIG_LOCATION_PREFIX = "classpath:org/springframework/security/config/http/DeferHttpSessionTests"; @Autowired FilterChainProxy springSecurityFilterChain; @Autowired private Service service; public final SpringTestContext spring = new SpringTestContext(this); @Test public void explicitDeferHttpSession() throws Exception { this.spring.configLocations(xml("Explicit")).autowire(); MockHttpServletRequest request = new MockHttpServletRequest("GET", "/"); MockHttpServletRequest mockRequest = spy(request); MockHttpServletResponse response = new MockHttpServletResponse(); FilterChain chain = (httpRequest, httpResponse) -> httpResponse.getWriter().write(this.service.getMessage()); this.springSecurityFilterChain.doFilter(mockRequest, response, chain); verify(mockRequest, never()).isRequestedSessionIdValid(); verify(mockRequest, never()).changeSessionId(); verify(mockRequest, never()).getSession(anyBoolean()); verify(mockRequest, never()).getSession(); } private static String xml(String configName) { return CONFIG_LOCATION_PREFIX + "-" + configName + ".xml"; } public static class Service { @PreAuthorize("permitAll") public String getMessage() { return "message"; } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/http/FilterSecurityMetadataSourceBeanDefinitionParserTests.java ================================================ /* * 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 org.springframework.security.config.http; import java.util.Collection; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.parsing.BeanDefinitionParsingException; import org.springframework.context.support.AbstractXmlApplicationContext; import org.springframework.mock.web.MockFilterChain; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.access.SecurityConfig; import org.springframework.security.config.BeanIds; import org.springframework.security.config.ConfigTestUtils; import org.springframework.security.config.util.InMemoryXmlApplicationContext; import org.springframework.security.web.FilterInvocation; import org.springframework.security.web.access.expression.ExpressionBasedFilterInvocationSecurityMetadataSource; import org.springframework.security.web.access.intercept.DefaultFilterInvocationSecurityMetadataSource; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; /** * Tests for {@link FilterInvocationSecurityMetadataSourceParser}. * * @author Luke Taylor */ public class FilterSecurityMetadataSourceBeanDefinitionParserTests { private AbstractXmlApplicationContext appContext; @AfterEach public void closeAppContext() { if (this.appContext != null) { this.appContext.close(); this.appContext = null; } } private void setContext(String context) { this.appContext = new InMemoryXmlApplicationContext(context); } @Test public void parsingMinimalConfigurationIsSuccessful() { // @formatter:off setContext("" + " " + ""); // @formatter:on DefaultFilterInvocationSecurityMetadataSource fids = (DefaultFilterInvocationSecurityMetadataSource) this.appContext .getBean("fids"); Collection cad = fids.getAttributes(createFilterInvocation("/anything", "GET")); assertThat(cad).contains(new SecurityConfig("ROLE_A")); } @Test public void expressionsAreSupported() { // @formatter:off setContext("" + " " + ""); // @formatter:on ExpressionBasedFilterInvocationSecurityMetadataSource fids = (ExpressionBasedFilterInvocationSecurityMetadataSource) this.appContext .getBean("fids"); ConfigAttribute[] cad = fids.getAttributes(createFilterInvocation("/anything", "GET")) .toArray(new ConfigAttribute[0]); assertThat(cad).hasSize(1); assertThat(cad[0].toString()).isEqualTo("hasRole('ROLE_A')"); } // SEC-1201 @Test public void interceptUrlsSupportPropertyPlaceholders() { System.setProperty("secure.url", "/secure"); System.setProperty("secure.role", "ROLE_A"); setContext("" + "" + " " + ""); DefaultFilterInvocationSecurityMetadataSource fids = (DefaultFilterInvocationSecurityMetadataSource) this.appContext .getBean("fids"); Collection cad = fids.getAttributes(createFilterInvocation("/secure", "GET")); assertThat(cad).containsExactly(new SecurityConfig("ROLE_A")); } @Test public void parsingWithinFilterSecurityInterceptorIsSuccessful() { // @formatter:off setContext("" + "" + " " + " " + " " + " " + " " + " " + " " + " " + "" + ConfigTestUtils.AUTH_PROVIDER_XML); // @formatter:on } @Test public void parsingInterceptUrlServletPathFails() { assertThatExceptionOfType(BeanDefinitionParsingException.class) .isThrownBy(() -> setContext("" + " " + "")); } private FilterInvocation createFilterInvocation(String path, String method) { MockHttpServletRequest request = new MockHttpServletRequest(method, path); return new FilterInvocation(request, new MockHttpServletResponse(), new MockFilterChain()); } } ================================================ FILE: config/src/test/java/org/springframework/security/config/http/FormLoginBeanDefinitionParserTests.java ================================================ /* * 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 org.springframework.security.config.http; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.web.WebAttributes; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.CoreMatchers.nullValue; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.forwardedUrl; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.request; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * @author Luke Taylor * @author Josh Cummings */ @ExtendWith(SpringTestContextExtension.class) public class FormLoginBeanDefinitionParserTests { private static final String CONFIG_LOCATION_PREFIX = "classpath:org/springframework/security/config/http/FormLoginBeanDefinitionParserTests"; public final SpringTestContext spring = new SpringTestContext(this); @Autowired MockMvc mvc; @Test public void getLoginWhenAutoConfigThenShowsDefaultLoginPage() throws Exception { this.spring.configLocations(this.xml("Simple")).autowire(); String expectedContent = """ Please sign in
"""; this.mvc.perform(get("/login")).andExpect(content().string(expectedContent)); } @Test public void getLogoutWhenAutoConfigThenShowsDefaultLogoutPage() throws Exception { this.spring.configLocations(this.xml("AutoConfig")).autowire(); this.mvc.perform(get("/logout")).andExpect(content().string(containsString("action=\"/logout\""))); } @Test public void getLoginWhenConfiguredWithCustomAttributesThenLoginPageReflects() throws Exception { this.spring.configLocations(this.xml("WithCustomAttributes")).autowire(); String expectedContent = """ Please sign in
"""; this.mvc.perform(get("/login")).andExpect(content().string(expectedContent)); this.mvc.perform(get("/logout")).andExpect(status().is3xxRedirection()); } @Test public void failedLoginWhenConfiguredWithCustomAuthenticationFailureThenForwardsAccordingly() throws Exception { this.spring.configLocations(this.xml("WithAuthenticationFailureForwardUrl")).autowire(); // @formatter:off MockHttpServletRequestBuilder loginRequest = post("/login") .param("username", "bob") .param("password", "invalidpassword"); this.mvc.perform(loginRequest) .andExpect(status().isOk()) .andExpect(forwardedUrl("/failure_forward_url")) .andExpect(request().attribute(WebAttributes.AUTHENTICATION_EXCEPTION, not(nullValue()))); // @formatter:on } @Test public void successfulLoginWhenConfiguredWithCustomAuthenticationSuccessThenForwardsAccordingly() throws Exception { this.spring.configLocations(this.xml("WithAuthenticationSuccessForwardUrl")).autowire(); // @formatter:off MockHttpServletRequestBuilder loginRequest = post("/login") .param("username", "user") .param("password", "password"); this.mvc.perform(loginRequest) .andExpect(status().isOk()) .andExpect(forwardedUrl("/success_forward_url")); // @formatter:on } private String xml(String configName) { return CONFIG_LOCATION_PREFIX + "-" + configName + ".xml"; } } ================================================ FILE: config/src/test/java/org/springframework/security/config/http/FormLoginConfigTests.java ================================================ /* * 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 org.springframework.security.config.http; import java.util.List; import jakarta.servlet.Filter; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.http.HttpStatus; import org.springframework.security.config.BeanIds; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.security.web.FilterChainProxy; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.verify; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * @author Luke Taylor * @author Josh Cummings */ @ExtendWith(SpringTestContextExtension.class) public class FormLoginConfigTests { private static final String CONFIG_LOCATION_PREFIX = "classpath:org/springframework/security/config/http/FormLoginConfigTests"; public final SpringTestContext spring = new SpringTestContext(this); @Autowired MockMvc mvc; @Test public void getProtectedPageWhenFormLoginConfiguredThenRedirectsToDefaultLoginPage() throws Exception { this.spring.configLocations(this.xml("WithRequestMatcher")).autowire(); // @formatter:off this.mvc.perform(get("/")) .andExpect(redirectedUrl("/login")); // @formatter:on } @Test public void authenticateWhenDefaultTargetUrlConfiguredThenRedirectsAccordingly() throws Exception { this.spring.configLocations(this.xml("WithDefaultTargetUrl")).autowire(); // @formatter:off MockHttpServletRequestBuilder loginRequest = post("/login") .param("username", "user") .param("password", "password") .with(csrf()); this.mvc.perform(loginRequest) .andExpect(redirectedUrl("/default")); // @formatter:on } @Test public void authenticateWhenConfiguredWithSpelThenRedirectsAccordingly() throws Exception { this.spring.configLocations(this.xml("UsingSpel")).autowire(); // @formatter:off MockHttpServletRequestBuilder loginRequest = post("/login") .param("username", "user") .param("password", "password") .with(csrf()); this.mvc.perform(loginRequest) .andExpect(redirectedUrl(WebConfigUtilsTests.URL + "/default")); MockHttpServletRequestBuilder invalidPassword = post("/login") .param("username", "user") .param("password", "wrong") .with(csrf()); this.mvc.perform(invalidPassword) .andExpect(redirectedUrl(WebConfigUtilsTests.URL + "/failure")); this.mvc.perform(get("/")) .andExpect(redirectedUrl(WebConfigUtilsTests.URL + "/login")); // @formatter:on } @Test public void autowireWhenLoginPageIsMisconfiguredThenDetects() { assertThatExceptionOfType(BeanCreationException.class) .isThrownBy(() -> this.spring.configLocations(this.xml("NoLeadingSlashLoginPage")).autowire()); } @Test public void autowireWhenDefaultTargetUrlIsMisconfiguredThenDetects() { assertThatExceptionOfType(BeanCreationException.class) .isThrownBy(() -> this.spring.configLocations(this.xml("NoLeadingSlashDefaultTargetUrl")).autowire()); } @Test public void authenticateWhenCustomHandlerBeansConfiguredThenInvokesAccordingly() throws Exception { this.spring.configLocations(this.xml("WithSuccessAndFailureHandlers")).autowire(); // @formatter:off MockHttpServletRequestBuilder loginRequest = post("/login") .param("username", "user") .param("password", "password") .with(csrf()); this.mvc.perform(loginRequest) .andExpect(status().isIAmATeapot()); MockHttpServletRequestBuilder invalidPassword = post("/login") .param("username", "user") .param("password", "wrong") .with(csrf()); this.mvc.perform(invalidPassword) .andExpect(status().isIAmATeapot()); // @formatter:on } @Test public void authenticateWhenCustomUsernameAndPasswordParametersThenSucceeds() throws Exception { this.spring.configLocations(this.xml("WithUsernameAndPasswordParameters")).autowire(); this.mvc.perform(post("/login").param("xname", "user").param("xpass", "password").with(csrf())) .andExpect(redirectedUrl("/")); } @Test public void authenticateWhenCustomSecurityContextHolderStrategyThenUses() throws Exception { this.spring.configLocations(this.xml("WithCustomSecurityContextHolderStrategy")).autowire(); SecurityContextHolderStrategy strategy = this.spring.getContext().getBean(SecurityContextHolderStrategy.class); this.mvc.perform(post("/login").with(csrf())).andExpect(redirectedUrl("/login?error")); verify(strategy, atLeastOnce()).getContext(); } /** * SEC-2919 - DefaultLoginGeneratingFilter incorrectly used if login-url="/login" */ @Test public void autowireWhenCustomLoginPageIsSlashLoginThenNoDefaultLoginPageGeneratingFilterIsWired() throws Exception { this.spring.configLocations(this.xml("ForSec2919")).autowire(); this.mvc.perform(get("/login")).andExpect(content().string("teapot")); assertThat(getFilter(this.spring.getContext(), DefaultLoginPageGeneratingFilter.class)).isNull(); } @Test public void authenticateWhenCsrfIsEnabledThenRequiresToken() throws Exception { this.spring.configLocations(this.xml("WithCsrfEnabled")).autowire(); // @formatter:off MockHttpServletRequestBuilder loginRequest = post("/login") .param("username", "user") .param("password", "password"); this.mvc.perform(loginRequest) .andExpect(status().isForbidden()); // @formatter:on } @Test public void authenticateWhenCsrfIsDisabledThenDoesNotRequireToken() throws Exception { this.spring.configLocations(this.xml("WithCsrfDisabled")).autowire(); // @formatter:off MockHttpServletRequestBuilder loginRequest = post("/login") .param("username", "user") .param("password", "password"); this.mvc.perform(loginRequest) .andExpect(status().isFound()); // @formatter:on } /** * SEC-3147: authentication-failure-url should be contained "error" parameter if * login-page="/login" */ @Test public void authenticateWhenLoginPageIsSlashLoginAndAuthenticationFailsThenRedirectContainsErrorParameter() throws Exception { this.spring.configLocations(this.xml("ForSec3147")).autowire(); // @formatter:off MockHttpServletRequestBuilder loginRequest = post("/login") .param("username", "user") .param("password", "wrong") .with(csrf()); this.mvc.perform(loginRequest) .andExpect(redirectedUrl("/login?error")); // @formatter:on } private Filter getFilter(ApplicationContext context, Class filterClass) { FilterChainProxy filterChain = context.getBean(BeanIds.FILTER_CHAIN_PROXY, FilterChainProxy.class); List filters = filterChain.getFilters("/any"); for (Filter filter : filters) { if (filter.getClass() == filterClass) { return filter; } } return null; } private String xml(String configName) { return CONFIG_LOCATION_PREFIX + "-" + configName + ".xml"; } @RestController public static class LoginController { @GetMapping("/login") public String ok() { return "teapot"; } } public static class TeapotAuthenticationHandler implements AuthenticationSuccessHandler, AuthenticationFailureHandler { @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) { response.setStatus(HttpStatus.I_AM_A_TEAPOT.value()); } @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) { response.setStatus(HttpStatus.I_AM_A_TEAPOT.value()); } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/http/HttpConfigTests.java ================================================ /* * 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 org.springframework.security.config.http; import java.util.Iterator; import io.micrometer.observation.Observation; import io.micrometer.observation.ObservationHandler; import io.micrometer.observation.ObservationRegistry; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponseWrapper; import org.apache.http.HttpStatus; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.security.authorization.AuthorizationDecision; import org.springframework.security.authorization.AuthorizationManager; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.web.FilterChainProxy; import org.springframework.test.web.servlet.MockMvc; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * @author Rob Winch * @author Josh Cummings */ @ExtendWith(SpringTestContextExtension.class) public class HttpConfigTests { private static final String CONFIG_LOCATION_PREFIX = "classpath:org/springframework/security/config/http/HttpConfigTests"; public final SpringTestContext spring = new SpringTestContext(this); @Autowired MockMvc mvc; @Test public void getWhenUsingMinimalConfigurationThenRedirectsToLogin() throws Exception { this.spring.configLocations(this.xml("Minimal")).autowire(); // @formatter:off this.mvc.perform(get("/")) .andExpect(status().isFound()) .andExpect(redirectedUrl("/login")); // @formatter:on } @Test public void getWhenUsingMinimalAuthorizationManagerThenRedirectsToLogin() throws Exception { this.spring.configLocations(this.xml("MinimalAuthorizationManager")).autowire(); // @formatter:off this.mvc.perform(get("/")) .andExpect(status().isFound()) .andExpect(redirectedUrl("/login")); // @formatter:on } @Test public void getWhenUsingAuthorizationManagerThenRedirectsToLogin() throws Exception { this.spring.configLocations(this.xml("AuthorizationManager")).autowire(); AuthorizationManager authorizationManager = this.spring.getContext() .getBean(AuthorizationManager.class); given(authorizationManager.authorize(any(), any())).willReturn(new AuthorizationDecision(false)); // @formatter:off this.mvc.perform(get("/")) .andExpect(status().isFound()) .andExpect(redirectedUrl("/login")); // @formatter:on verify(authorizationManager).authorize(any(), any()); } @Test public void getWhenUsingMinimalConfigurationThenPreventsSessionAsUrlParameter() throws Exception { this.spring.configLocations(this.xml("Minimal")).autowire(); MockHttpServletRequest request = new MockHttpServletRequest("GET", "/"); MockHttpServletResponse response = new MockHttpServletResponse(); FilterChainProxy proxy = this.spring.getContext().getBean(FilterChainProxy.class); proxy.doFilter(request, new EncodeUrlDenyingHttpServletResponseWrapper(response), (req, resp) -> { }); assertThat(response.getStatus()).isEqualTo(HttpStatus.SC_MOVED_TEMPORARILY); assertThat(response.getRedirectedUrl()).isEqualTo("/login"); } @Test public void getWhenUsingObservationRegistryThenObservesRequest() throws Exception { this.spring.configLocations(this.xml("WithObservationRegistry")).autowire(); // @formatter:off this.mvc.perform(get("/").with(httpBasic("user", "password"))) .andExpect(status().isNotFound()); // @formatter:on ObservationHandler handler = this.spring.getContext().getBean(ObservationHandler.class); ArgumentCaptor captor = ArgumentCaptor.forClass(Observation.Context.class); verify(handler, times(5)).onStart(captor.capture()); Iterator contexts = captor.getAllValues().iterator(); assertThat(contexts.next().getContextualName()).isEqualTo("security filterchain before"); assertThat(contexts.next().getName()).isEqualTo("spring.security.authentications"); assertThat(contexts.next().getName()).isEqualTo("spring.security.authorizations"); assertThat(contexts.next().getName()).isEqualTo("spring.security.http.secured.requests"); assertThat(contexts.next().getContextualName()).isEqualTo("security filterchain after"); } private String xml(String configName) { return CONFIG_LOCATION_PREFIX + "-" + configName + ".xml"; } private static class EncodeUrlDenyingHttpServletResponseWrapper extends HttpServletResponseWrapper { EncodeUrlDenyingHttpServletResponseWrapper(HttpServletResponse response) { super(response); } @Override public String encodeURL(String url) { throw new RuntimeException("Unexpected invocation of encodeURL"); } @Override public String encodeRedirectURL(String url) { throw new RuntimeException("Unexpected invocation of encodeURL"); } } public static final class MockObservationRegistry implements FactoryBean { private ObservationHandler handler = mock(ObservationHandler.class); @Override public ObservationRegistry getObject() { ObservationRegistry registry = ObservationRegistry.create(); registry.observationConfig().observationHandler(this.handler); given(this.handler.supportsContext(any())).willReturn(true); return registry; } @Override public Class getObjectType() { return ObservationRegistry.class; } public void setHandler(ObservationHandler handler) { this.handler = handler; } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/http/HttpCorsConfigTests.java ================================================ /* * 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 org.springframework.security.config.http; import java.util.Arrays; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.ResultMatcher; import org.springframework.test.web.servlet.request.RequestPostProcessor; import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.options; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * @author Rob Winch * @author Tim Ysewyn * @author Josh Cummings */ @ExtendWith(SpringTestContextExtension.class) public class HttpCorsConfigTests { private static final String CONFIG_LOCATION_PREFIX = "classpath:org/springframework/security/config/http/HttpCorsConfigTests"; public final SpringTestContext spring = new SpringTestContext(this); @Autowired MockMvc mvc; @Test public void autowireWhenMissingMvcThenGivesInformativeError() { assertThatExceptionOfType(BeanCreationException.class) .isThrownBy(() -> this.spring.configLocations(this.xml("RequiresMvc")).autowire()) .havingRootCause() .withMessageContaining( "Please ensure Spring Security & Spring MVC are configured in a shared ApplicationContext"); } @Test public void getWhenUsingCorsThenDoesSpringSecurityCorsHandshake() throws Exception { this.spring.configLocations(this.xml("WithCors")).autowire(); // @formatter:off this.mvc.perform(get("/").with(this.approved())) .andExpect(corsResponseHeaders()) .andExpect((status().isIAmATeapot())); this.mvc.perform(options("/").with(this.preflight())) .andExpect(corsResponseHeaders()) .andExpect(status().isOk()); // @formatter:on } @Test public void getWhenUsingCustomCorsConfigurationSourceThenDoesSpringSecurityCorsHandshake() throws Exception { this.spring.configLocations(this.xml("WithCorsConfigurationSource")).autowire(); // @formatter:off this.mvc.perform(get("/").with(this.approved())) .andExpect(corsResponseHeaders()) .andExpect((status().isIAmATeapot())); this.mvc.perform(options("/").with(this.preflight())) .andExpect(corsResponseHeaders()) .andExpect(status().isOk()); // @formatter:on } @Test public void getWhenUsingCustomCorsFilterThenDoesSPringSecurityCorsHandshake() throws Exception { this.spring.configLocations(this.xml("WithCorsFilter")).autowire(); // @formatter:off this.mvc.perform(get("/").with(this.approved())) .andExpect(corsResponseHeaders()) .andExpect((status().isIAmATeapot())); this.mvc.perform(options("/").with(this.preflight())) .andExpect(corsResponseHeaders()) .andExpect(status().isOk()); // @formatter:on } private String xml(String configName) { return CONFIG_LOCATION_PREFIX + "-" + configName + ".xml"; } private RequestPostProcessor preflight() { return cors(true); } private RequestPostProcessor approved() { return cors(false); } private RequestPostProcessor cors(boolean preflight) { return (request) -> { request.addHeader(HttpHeaders.ORIGIN, "https://example.com"); if (preflight) { request.setMethod(HttpMethod.OPTIONS.name()); request.addHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, HttpMethod.POST.name()); } return request; }; } private ResultMatcher corsResponseHeaders() { return (result) -> { header().exists("Access-Control-Allow-Origin").match(result); header().exists("X-Content-Type-Options").match(result); }; } @RestController @CrossOrigin(methods = { RequestMethod.GET, RequestMethod.POST }) static class CorsController { @RequestMapping("/") String hello() { return "Hello"; } } static class MyCorsConfigurationSource extends UrlBasedCorsConfigurationSource { MyCorsConfigurationSource() { CorsConfiguration configuration = new CorsConfiguration(); configuration.setAllowedOrigins(Arrays.asList("*")); configuration.setAllowedMethods(Arrays.asList(RequestMethod.GET.name(), RequestMethod.POST.name())); super.registerCorsConfiguration("/**", configuration); } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/http/HttpHeadersConfigTests.java ================================================ /* * 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 org.springframework.security.config.http; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import com.google.common.collect.ImmutableMap; import jakarta.servlet.http.HttpSession; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.parsing.BeanDefinitionParsingException; import org.springframework.beans.factory.xml.XmlBeanDefinitionStoreException; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.core.Authentication; import org.springframework.security.web.authentication.session.SessionLimit; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.ResultMatcher; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * @author Rob Winch * @author Tim Ysewyn * @author Josh Cummings * @author Rafiullah Hamedy * @author Marcus Da Coregio * @author Claudenir Freitas */ @ExtendWith(SpringTestContextExtension.class) public class HttpHeadersConfigTests { private static final String CONFIG_LOCATION_PREFIX = "classpath:org/springframework/security/config/http/HttpHeadersConfigTests"; // @formatter:off static final Map defaultHeaders = ImmutableMap.builder() .put("X-Content-Type-Options", "nosniff").put("X-Frame-Options", "DENY") .put("Strict-Transport-Security", "max-age=31536000 ; includeSubDomains") .put("Cache-Control", "no-cache, no-store, max-age=0, must-revalidate") .put("Expires", "0") .put("Pragma", "no-cache") .put("X-XSS-Protection", "0") .build(); // @formatter:on public final SpringTestContext spring = new SpringTestContext(this); @Autowired MockMvc mvc; @Test public void requestWhenHeadersDisabledThenResponseExcludesAllSecureHeaders() throws Exception { this.spring.configLocations(this.xml("HeadersDisabled")).autowire(); // @formatter:off this.mvc.perform(get("/")) .andExpect(status().isOk()) .andExpect(excludesDefaults()); // @formatter:on } @Test public void requestWhenHeadersDisabledViaPlaceholderThenResponseExcludesAllSecureHeaders() throws Exception { System.setProperty("security.headers.disabled", "true"); this.spring.configLocations(this.xml("DisabledWithPlaceholder")).autowire(); // @formatter:off this.mvc.perform(get("/").secure(true)) .andExpect(status().isOk()) .andExpect(excludesDefaults()); // @formatter:on } @Test public void requestWhenHeadersEnabledViaPlaceholderThenResponseIncludesAllSecureHeaders() throws Exception { System.setProperty("security.headers.disabled", "false"); this.spring.configLocations(this.xml("DisabledWithPlaceholder")).autowire(); // @formatter:off this.mvc.perform(get("/").secure(true)) .andExpect(status().isOk()) .andExpect(includesDefaults()); // @formatter:on } @Test public void requestWhenHeadersDisabledRefMissingPlaceholderThenResponseIncludesAllSecureHeaders() throws Exception { System.clearProperty("security.headers.disabled"); this.spring.configLocations(this.xml("DisabledWithPlaceholder")).autowire(); // @formatter:off this.mvc.perform(get("/").secure(true)) .andExpect(status().isOk()) .andExpect(includesDefaults()); // @formatter:on } @Test public void configureWhenHeadersDisabledHavingChildElementThenAutowireFails() { assertThatExceptionOfType(BeanDefinitionParsingException.class) .isThrownBy(() -> this.spring.configLocations(this.xml("HeadersDisabledHavingChildElement")).autowire()) .withMessageContaining("Cannot specify with child elements"); } @Test public void requestWhenHeadersEnabledThenResponseContainsAllSecureHeaders() throws Exception { this.spring.configLocations(this.xml("DefaultConfig")).autowire(); // @formatter:off this.mvc.perform(get("/").secure(true)) .andExpect(status().isOk()) .andExpect(includesDefaults()); // @formatter:on } @Test public void requestWhenHeadersElementUsedThenResponseContainsAllSecureHeaders() throws Exception { this.spring.configLocations(this.xml("HeadersEnabled")).autowire(); // @formatter:off this.mvc.perform(get("/").secure(true)) .andExpect(status().isOk()) .andExpect(includesDefaults()); // @formatter:on } @Test public void requestWhenFrameOptionsConfiguredThenIncludesHeader() throws Exception { Map headers = new HashMap<>(defaultHeaders); headers.put("X-Frame-Options", "SAMEORIGIN"); this.spring.configLocations(this.xml("WithFrameOptions")).autowire(); // @formatter:off this.mvc.perform(get("/").secure(true)) .andExpect(status().isOk()) .andExpect(includes(headers)); // @formatter:on } /** * gh-3986 */ @Test public void requestWhenDefaultsDisabledWithNoOverrideThenExcludesAllSecureHeaders() throws Exception { this.spring.configLocations(this.xml("DefaultsDisabledWithNoOverride")).autowire(); // @formatter:off this.mvc.perform(get("/").secure(true)) .andExpect(status().isOk()) .andExpect(excludesDefaults()); // @formatter:on } @Test public void requestWhenDefaultsDisabledWithPlaceholderTrueThenExcludesAllSecureHeaders() throws Exception { System.setProperty("security.headers.defaults.disabled", "true"); this.spring.configLocations(this.xml("DefaultsDisabledWithPlaceholder")).autowire(); // @formatter:off this.mvc.perform(get("/").secure(true)) .andExpect(status().isOk()) .andExpect(excludesDefaults()); // @formatter:on } @Test public void requestWhenDefaultsDisabledWithPlaceholderFalseThenIncludeAllSecureHeaders() throws Exception { System.setProperty("security.headers.defaults.disabled", "false"); this.spring.configLocations(this.xml("DefaultsDisabledWithPlaceholder")).autowire(); // @formatter:off this.mvc.perform(get("/").secure(true)) .andExpect(status().isOk()) .andExpect(includesDefaults()); // @formatter:on } @Test public void requestWhenDefaultsDisabledWithPlaceholderMissingThenIncludeAllSecureHeaders() throws Exception { System.clearProperty("security.headers.defaults.disabled"); this.spring.configLocations(this.xml("DefaultsDisabledWithPlaceholder")).autowire(); // @formatter:off this.mvc.perform(get("/").secure(true)) .andExpect(status().isOk()) .andExpect(includesDefaults()); // @formatter:on } @Test public void requestWhenUsingContentTypeOptionsThenDefaultsToNoSniff() throws Exception { Set excludedHeaders = new HashSet<>(defaultHeaders.keySet()); excludedHeaders.remove("X-Content-Type-Options"); this.spring.configLocations(this.xml("DefaultsDisabledWithContentTypeOptions")).autowire(); // @formatter:off this.mvc.perform(get("/")) .andExpect(status().isOk()) .andExpect(header().string("X-Content-Type-Options", "nosniff")) .andExpect(excludes(excludedHeaders)); // @formatter:on } @Test public void requestWhenUsingFrameOptionsThenDefaultsToDeny() throws Exception { Set excludedHeaders = new HashSet<>(defaultHeaders.keySet()); excludedHeaders.remove("X-Frame-Options"); this.spring.configLocations(this.xml("DefaultsDisabledWithFrameOptions")).autowire(); // @formatter:off this.mvc.perform(get("/")) .andExpect(status().isOk()) .andExpect(header().string("X-Frame-Options", "DENY")) .andExpect(excludes(excludedHeaders)); // @formatter:on } @Test public void requestWhenUsingFrameOptionsDenyThenRespondsWithDeny() throws Exception { Set excludedHeaders = new HashSet<>(defaultHeaders.keySet()); excludedHeaders.remove("X-Frame-Options"); this.spring.configLocations(this.xml("DefaultsDisabledWithFrameOptionsDeny")).autowire(); // @formatter:off this.mvc.perform(get("/")) .andExpect(status().isOk()) .andExpect(header().string("X-Frame-Options", "DENY")) .andExpect(excludes(excludedHeaders)); // @formatter:on } @Test public void requestWhenUsingFrameOptionsSameOriginThenRespondsWithSameOrigin() throws Exception { Set excludedHeaders = new HashSet<>(defaultHeaders.keySet()); excludedHeaders.remove("X-Frame-Options"); this.spring.configLocations(this.xml("DefaultsDisabledWithFrameOptionsSameOrigin")).autowire(); // @formatter:off this.mvc.perform(get("/")) .andExpect(status().isOk()) .andExpect(header().string("X-Frame-Options", "SAMEORIGIN")) .andExpect(excludes(excludedHeaders)); // @formatter:on } @Test public void configureWhenUsingFrameOptionsAllowFromNoOriginThenAutowireFails() { assertThatExceptionOfType(BeanDefinitionParsingException.class) .isThrownBy(() -> this.spring.configLocations(this.xml("DefaultsDisabledWithFrameOptionsAllowFromNoOrigin")) .autowire()) .withMessageContaining("Strategy requires a 'value' to be set."); // FIXME better error message? } @Test public void configureWhenUsingFrameOptionsAllowFromBlankOriginThenAutowireFails() { assertThatExceptionOfType(BeanDefinitionParsingException.class) .isThrownBy( () -> this.spring.configLocations(this.xml("DefaultsDisabledWithFrameOptionsAllowFromBlankOrigin")) .autowire()) .withMessageContaining("Strategy requires a 'value' to be set."); // FIXME better error message? } @Test public void requestWhenUsingFrameOptionsAllowFromThenRespondsWithAllowFrom() throws Exception { Set excludedHeaders = new HashSet<>(defaultHeaders.keySet()); excludedHeaders.remove("X-Frame-Options"); this.spring.configLocations(this.xml("DefaultsDisabledWithFrameOptionsAllowFrom")).autowire(); // @formatter:off this.mvc.perform(get("/")) .andExpect(status().isOk()) .andExpect(header().string("X-Frame-Options", "ALLOW-FROM https://example.org")) .andExpect(excludes(excludedHeaders)); // @formatter:on } @Test public void requestWhenUsingFrameOptionsAllowFromWhitelistThenRespondsWithAllowFrom() throws Exception { Set excludedHeaders = new HashSet<>(defaultHeaders.keySet()); excludedHeaders.remove("X-Frame-Options"); this.spring.configLocations(this.xml("DefaultsDisabledWithFrameOptionsAllowFromWhitelist")).autowire(); // @formatter:off this.mvc.perform(get("/").param("from", "https://example.org")) .andExpect(status().isOk()) .andExpect(header().string("X-Frame-Options", "ALLOW-FROM https://example.org")) .andExpect(excludes(excludedHeaders)); this.mvc.perform(get("/")) .andExpect(status().isOk()) .andExpect(header().string("X-Frame-Options", "DENY")) .andExpect(excludes(excludedHeaders)); // @formatter:on } @Test public void requestWhenUsingCustomHeaderThenRespondsWithThatHeader() throws Exception { this.spring.configLocations(this.xml("DefaultsDisabledWithCustomHeader")).autowire(); // @formatter:off this.mvc.perform(get("/")) .andExpect(status().isOk()) .andExpect(header().string("a", "b")) .andExpect(header().string("c", "d")) .andExpect(excludesDefaults()); // @formatter:on } @Test public void requestWhenUsingCustomHeaderWriterThenRespondsWithThatHeader() throws Exception { this.spring.configLocations(this.xml("DefaultsDisabledWithCustomHeaderWriter")).autowire(); // @formatter:off this.mvc.perform(get("/")) .andExpect(status().isOk()) .andExpect(header().string("abc", "def")) .andExpect(excludesDefaults()); // @formatter:on } @Test public void configureWhenUsingCustomHeaderNameOnlyThenAutowireFails() { assertThatExceptionOfType(BeanCreationException.class) .isThrownBy(() -> this.spring.configLocations(this.xml("DefaultsDisabledWithOnlyHeaderName")).autowire()); } @Test public void configureWhenUsingCustomHeaderValueOnlyThenAutowireFails() { assertThatExceptionOfType(BeanCreationException.class) .isThrownBy(() -> this.spring.configLocations(this.xml("DefaultsDisabledWithOnlyHeaderValue")).autowire()); } @Test public void requestWhenPermissionsPolicyConfiguredWithGeolocationSelfThenGeolocationSelf() throws Exception { this.spring.configLocations(this.xml("DefaultsDisabledWithPermissionsPolicy")).autowire(); // @formatter:off this.mvc.perform(get("/")) .andExpect(status().isOk()) .andExpect(excludesDefaults()) .andExpect(header().string("Permissions-Policy", "geolocation=(self)")); // @formatter:on } @Test public void requestWhenUsingXssProtectionThenDefaultsToModeBlock() throws Exception { Set excludedHeaders = new HashSet<>(defaultHeaders.keySet()); excludedHeaders.remove("X-XSS-Protection"); this.spring.configLocations(this.xml("DefaultsDisabledWithXssProtection")).autowire(); // @formatter:off this.mvc.perform(get("/")) .andExpect(status().isOk()) .andExpect(header().string("X-XSS-Protection", "0")) .andExpect(excludes(excludedHeaders)); // @formatter:on } @Test public void requestWhenSettingXssProtectionHeaderValueToZeroThenDefaultsToZero() throws Exception { Set excludedHeaders = new HashSet<>(defaultHeaders.keySet()); excludedHeaders.remove("X-XSS-Protection"); this.spring.configLocations(this.xml("DefaultsDisabledWithXssProtectionHeaderValueZero")).autowire(); // @formatter:off this.mvc.perform(get("/")) .andExpect(status().isOk()) .andExpect(header().string("X-XSS-Protection", "0")) .andExpect(excludes(excludedHeaders)); // @formatter:on } @Test public void requestWhenSettingXssProtectionHeaderValueToOneThenDefaultsToOne() throws Exception { Set excludedHeaders = new HashSet<>(defaultHeaders.keySet()); excludedHeaders.remove("X-XSS-Protection"); this.spring.configLocations(this.xml("DefaultsDisabledWithXssProtectionHeaderValueOne")).autowire(); // @formatter:off this.mvc.perform(get("/")) .andExpect(status().isOk()) .andExpect(header().string("X-XSS-Protection", "1")) .andExpect(excludes(excludedHeaders)); // @formatter:on } @Test public void requestWhenSettingXssProtectionHeaderValueToOneModeBlockThenDefaultsToOneModeBlock() throws Exception { Set excludedHeaders = new HashSet<>(defaultHeaders.keySet()); excludedHeaders.remove("X-XSS-Protection"); this.spring.configLocations(this.xml("DefaultsDisabledWithXssProtectionHeaderValueOneModeBlock")).autowire(); // @formatter:off this.mvc.perform(get("/")) .andExpect(status().isOk()) .andExpect(header().string("X-XSS-Protection", "1; mode=block")) .andExpect(excludes(excludedHeaders)); // @formatter:on } @Test public void requestWhenUsingCacheControlThenRespondsWithCorrespondingHeaders() throws Exception { Map includedHeaders = ImmutableMap.builder() .put("Cache-Control", "no-cache, no-store, max-age=0, must-revalidate") .put("Expires", "0") .put("Pragma", "no-cache") .build(); this.spring.configLocations(this.xml("DefaultsDisabledWithCacheControl")).autowire(); // @formatter:off this.mvc.perform(get("/")) .andExpect(status().isOk()) .andExpect(includes(includedHeaders)); // @formatter:on } @Test public void requestWhenUsingHstsThenRespondsWithHstsHeader() throws Exception { Set excludedHeaders = new HashSet<>(defaultHeaders.keySet()); excludedHeaders.remove("Strict-Transport-Security"); this.spring.configLocations(this.xml("DefaultsDisabledWithHsts")).autowire(); // @formatter:off this.mvc.perform(get("/").secure(true)) .andExpect(status().isOk()) .andExpect(header().string("Strict-Transport-Security", "max-age=31536000 ; includeSubDomains")) .andExpect(excludes(excludedHeaders)); // @formatter:on } @Test public void insecureRequestWhenUsingHstsThenExcludesHstsHeader() throws Exception { this.spring.configLocations(this.xml("DefaultsDisabledWithHsts")).autowire(); // @formatter:off this.mvc.perform(get("/")) .andExpect(status().isOk()) .andExpect(excludesDefaults()); // @formatter:on } @Test public void insecureRequestWhenUsingCustomHstsRequestMatcherThenIncludesHstsHeader() throws Exception { Set excludedHeaders = new HashSet<>(defaultHeaders.keySet()); excludedHeaders.remove("Strict-Transport-Security"); this.spring.configLocations(this.xml("DefaultsDisabledWithCustomHstsRequestMatcher")).autowire(); // @formatter:off this.mvc.perform(get("/")) .andExpect(status().isOk()) .andExpect(header().string("Strict-Transport-Security", "max-age=1")) .andExpect(excludes(excludedHeaders)); // @formatter:on } @Test public void configureWhenUsingHpkpWithoutPinsThenAutowireFails() { assertThatExceptionOfType(XmlBeanDefinitionStoreException.class) .isThrownBy(() -> this.spring.configLocations(this.xml("DefaultsDisabledWithEmptyHpkp")).autowire()) .havingRootCause() .withMessageContaining("The content of element 'hpkp' is not complete"); } @Test public void configureWhenUsingHpkpWithEmptyPinsThenAutowireFails() { assertThatExceptionOfType(XmlBeanDefinitionStoreException.class) .isThrownBy(() -> this.spring.configLocations(this.xml("DefaultsDisabledWithEmptyPins")).autowire()) .havingRootCause() .withMessageContaining("The content of element 'pins' is not complete"); } @Test public void requestWhenUsingHpkpThenIncludesHpkpHeader() throws Exception { this.spring.configLocations(this.xml("DefaultsDisabledWithHpkp")).autowire(); // @formatter:off this.mvc.perform(get("/").secure(true)) .andExpect(status().isOk()) .andExpect(header().string("Public-Key-Pins-Report-Only", "max-age=5184000 ; pin-sha256=\"d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=\"")) .andExpect(excludesDefaults()); // @formatter:on } @Test public void requestWhenUsingHpkpDefaultsThenIncludesHpkpHeaderUsingSha256() throws Exception { this.spring.configLocations(this.xml("DefaultsDisabledWithHpkpDefaults")).autowire(); // @formatter:off this.mvc.perform(get("/").secure(true)) .andExpect(status().isOk()) .andExpect(header().string("Public-Key-Pins-Report-Only", "max-age=5184000 ; pin-sha256=\"d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=\"")) .andExpect(excludesDefaults()); // @formatter:on } @Test public void insecureRequestWhenUsingHpkpThenExcludesHpkpHeader() throws Exception { this.spring.configLocations(this.xml("DefaultsDisabledWithHpkpDefaults")).autowire(); // @formatter:off this.mvc.perform(get("/")) .andExpect(status().isOk()) .andExpect(header().doesNotExist("Public-Key-Pins-Report-Only")) .andExpect(excludesDefaults()); // @formatter:on } @Test public void requestWhenUsingHpkpCustomMaxAgeThenIncludesHpkpHeaderAccordingly() throws Exception { this.spring.configLocations(this.xml("DefaultsDisabledWithHpkpMaxAge")).autowire(); // @formatter:off this.mvc.perform(get("/").secure(true)) .andExpect(status().isOk()) .andExpect(header().string("Public-Key-Pins-Report-Only", "max-age=604800 ; pin-sha256=\"d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=\"")) .andExpect(excludesDefaults()); // @formatter:on } @Test public void requestWhenUsingHpkpReportThenIncludesHpkpHeaderAccordingly() throws Exception { this.spring.configLocations(this.xml("DefaultsDisabledWithHpkpReport")).autowire(); // @formatter:off this.mvc.perform(get("/").secure(true)) .andExpect(status().isOk()) .andExpect(header().string("Public-Key-Pins", "max-age=5184000 ; pin-sha256=\"d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=\"")) .andExpect(excludesDefaults()); // @formatter:on } @Test public void requestWhenUsingHpkpIncludeSubdomainsThenIncludesHpkpHeaderAccordingly() throws Exception { this.spring.configLocations(this.xml("DefaultsDisabledWithHpkpIncludeSubdomains")).autowire(); // @formatter:off this.mvc.perform(get("/").secure(true)) .andExpect(status().isOk()) .andExpect(header().string("Public-Key-Pins-Report-Only", "max-age=5184000 ; pin-sha256=\"d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=\" ; includeSubDomains")) .andExpect(excludesDefaults()); // @formatter:on } @Test public void requestWhenUsingHpkpReportUriThenIncludesHpkpHeaderAccordingly() throws Exception { this.spring.configLocations(this.xml("DefaultsDisabledWithHpkpReportUri")).autowire(); // @formatter:off this.mvc.perform(get("/").secure(true)) .andExpect(status().isOk()) .andExpect(header().string("Public-Key-Pins-Report-Only", "max-age=5184000 ; pin-sha256=\"d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=\" ; report-uri=\"https://example.net/pkp-report\"")) .andExpect(excludesDefaults()); // @formatter:on } @Test public void requestWhenCacheControlDisabledThenExcludesHeader() throws Exception { Collection cacheControl = Arrays.asList("Cache-Control", "Expires", "Pragma"); Map allButCacheControl = remove(defaultHeaders, cacheControl); this.spring.configLocations(this.xml("CacheControlDisabled")).autowire(); // @formatter:off this.mvc.perform(get("/").secure(true)) .andExpect(status().isOk()) .andExpect(includes(allButCacheControl)) .andExpect(excludes(cacheControl)); // @formatter:on } @Test public void requestWhenContentTypeOptionsDisabledThenExcludesHeader() throws Exception { Collection contentTypeOptions = Arrays.asList("X-Content-Type-Options"); Map allButContentTypeOptions = remove(defaultHeaders, contentTypeOptions); this.spring.configLocations(this.xml("ContentTypeOptionsDisabled")).autowire(); // @formatter:off this.mvc.perform(get("/").secure(true)) .andExpect(status().isOk()) .andExpect(includes(allButContentTypeOptions)) .andExpect(excludes(contentTypeOptions)); // @formatter:on } @Test public void requestWhenHstsDisabledThenExcludesHeader() throws Exception { Collection hsts = Arrays.asList("Strict-Transport-Security"); Map allButHsts = remove(defaultHeaders, hsts); this.spring.configLocations(this.xml("HstsDisabled")).autowire(); // @formatter:off this.mvc.perform(get("/").secure(true)) .andExpect(status().isOk()) .andExpect(includes(allButHsts)) .andExpect(excludes(hsts)); // @formatter:on } @Test public void requestWhenHpkpDisabledThenExcludesHeader() throws Exception { this.spring.configLocations(this.xml("HpkpDisabled")).autowire(); // @formatter:off this.mvc.perform(get("/").secure(true)) .andExpect(status().isOk()) .andExpect(includesDefaults()); // @formatter:on } @Test public void requestWhenFrameOptionsDisabledThenExcludesHeader() throws Exception { Collection frameOptions = Arrays.asList("X-Frame-Options"); Map allButFrameOptions = remove(defaultHeaders, frameOptions); this.spring.configLocations(this.xml("FrameOptionsDisabled")).autowire(); // @formatter:off this.mvc.perform(get("/").secure(true)) .andExpect(status().isOk()) .andExpect(includes(allButFrameOptions)) .andExpect(excludes(frameOptions)); // @formatter:on } @Test public void requestWhenXssProtectionDisabledThenExcludesHeader() throws Exception { Collection xssProtection = Arrays.asList("X-XSS-Protection"); Map allButXssProtection = remove(defaultHeaders, xssProtection); this.spring.configLocations(this.xml("XssProtectionDisabled")).autowire(); // @formatter:off this.mvc.perform(get("/").secure(true)) .andExpect(status().isOk()) .andExpect(includes(allButXssProtection)) .andExpect(excludes(xssProtection)); // @formatter:on } @Test public void configureWhenHstsDisabledAndIncludeSubdomainsSpecifiedThenAutowireFails() { assertThatExceptionOfType(BeanDefinitionParsingException.class) .isThrownBy( () -> this.spring.configLocations(this.xml("HstsDisabledSpecifyingIncludeSubdomains")).autowire()) .withMessageContaining("include-subdomains"); } @Test public void configureWhenHstsDisabledAndMaxAgeSpecifiedThenAutowireFails() { assertThatExceptionOfType(BeanDefinitionParsingException.class) .isThrownBy(() -> this.spring.configLocations(this.xml("HstsDisabledSpecifyingMaxAge")).autowire()) .withMessageContaining("max-age"); } @Test public void configureWhenHstsDisabledAndRequestMatcherSpecifiedThenAutowireFails() { assertThatExceptionOfType(BeanDefinitionParsingException.class) .isThrownBy(() -> this.spring.configLocations(this.xml("HstsDisabledSpecifyingRequestMatcher")).autowire()) .withMessageContaining("request-matcher-ref"); } @Test public void configureWhenXssProtectionDisabledAndHeaderValueSpecifiedThenAutowireFails() { assertThatExceptionOfType(BeanDefinitionParsingException.class).isThrownBy( () -> this.spring.configLocations(this.xml("XssProtectionDisabledSpecifyingHeaderValue")).autowire()) .withMessageContaining("header-value"); } @Test public void configureWhenFrameOptionsDisabledAndPolicySpecifiedThenAutowireFails() { assertThatExceptionOfType(BeanDefinitionParsingException.class) .isThrownBy(() -> this.spring.configLocations(this.xml("FrameOptionsDisabledSpecifyingPolicy")).autowire()) .withMessageContaining("policy"); } @Test public void requestWhenContentSecurityPolicyDirectivesConfiguredThenIncludesDirectives() throws Exception { Map includedHeaders = new HashMap<>(defaultHeaders); includedHeaders.put("Content-Security-Policy", "default-src 'self'"); this.spring.configLocations(this.xml("ContentSecurityPolicyWithPolicyDirectives")).autowire(); // @formatter:off this.mvc.perform(get("/").secure(true)) .andExpect(status().isOk()) .andExpect(includes(includedHeaders)); // @formatter:on } @Test public void requestWhenHeadersDisabledAndContentSecurityPolicyConfiguredThenExcludesHeader() throws Exception { this.spring.configLocations(this.xml("HeadersDisabledWithContentSecurityPolicy")).autowire(); // @formatter:off this.mvc.perform(get("/")) .andExpect(status().isOk()) .andExpect(excludesDefaults()) .andExpect(excludes("Content-Security-Policy")); // @formatter:on } @Test public void requestWhenDefaultsDisabledAndContentSecurityPolicyConfiguredThenIncludesHeader() throws Exception { this.spring.configLocations(this.xml("DefaultsDisabledWithContentSecurityPolicy")).autowire(); // @formatter:off this.mvc.perform(get("/")) .andExpect(status().isOk()) .andExpect(excludesDefaults()) .andExpect(header().string("Content-Security-Policy", "default-src 'self'")); // @formatter:on } @Test public void configureWhenContentSecurityPolicyConfiguredWithEmptyDirectivesThenAutowireFails() { assertThatExceptionOfType(BeanDefinitionParsingException.class).isThrownBy( () -> this.spring.configLocations(this.xml("ContentSecurityPolicyWithEmptyDirectives")).autowire()); } @Test public void requestWhenContentSecurityPolicyConfiguredWithReportOnlyThenIncludesReportOnlyHeader() throws Exception { Map includedHeaders = new HashMap<>(defaultHeaders); includedHeaders.put("Content-Security-Policy-Report-Only", "default-src https:; report-uri https://example.org/"); this.spring.configLocations(this.xml("ContentSecurityPolicyWithReportOnly")).autowire(); // @formatter:off this.mvc.perform(get("/").secure(true)) .andExpect(status().isOk()) .andExpect(includes(includedHeaders)); // @formatter:on } @Test public void requestWhenReferrerPolicyConfiguredThenResponseDefaultsToNoReferrer() throws Exception { this.spring.configLocations(this.xml("DefaultsDisabledWithReferrerPolicy")).autowire(); // @formatter:off this.mvc.perform(get("/")) .andExpect(status().isOk()) .andExpect(excludesDefaults()) .andExpect(header().string("Referrer-Policy", "no-referrer")); // @formatter:on } @Test public void requestWhenReferrerPolicyConfiguredWithSameOriginThenRespondsWithSameOrigin() throws Exception { this.spring.configLocations(this.xml("DefaultsDisabledWithReferrerPolicySameOrigin")).autowire(); // @formatter:off this.mvc.perform(get("/")) .andExpect(status().isOk()) .andExpect(excludesDefaults()) .andExpect(header().string("Referrer-Policy", "same-origin")); // @formatter:on } @Test public void requestWhenCrossOriginOpenerPolicyWithSameOriginAllowPopupsThenRespondsWithSameOriginAllowPopups() throws Exception { this.spring.configLocations(this.xml("DefaultsDisabledWithCrossOriginOpenerPolicy")).autowire(); // @formatter:off this.mvc.perform(get("/")) .andExpect(status().isOk()) .andExpect(excludesDefaults()) .andExpect(header().string("Cross-Origin-Opener-Policy", "same-origin-allow-popups")); // @formatter:on } @Test public void requestWhenCrossOriginEmbedderPolicyWithRequireCorpThenRespondsWithRequireCorp() throws Exception { this.spring.configLocations(this.xml("DefaultsDisabledWithCrossOriginEmbedderPolicy")).autowire(); // @formatter:off this.mvc.perform(get("/")) .andExpect(status().isOk()) .andExpect(excludesDefaults()) .andExpect(header().string("Cross-Origin-Embedder-Policy", "require-corp")); // @formatter:on } @Test public void requestWhenCrossOriginResourcePolicyWithSameOriginThenRespondsWithSameOrigin() throws Exception { this.spring.configLocations(this.xml("DefaultsDisabledWithCrossOriginResourcePolicy")).autowire(); // @formatter:off this.mvc.perform(get("/")) .andExpect(status().isOk()) .andExpect(excludesDefaults()) .andExpect(header().string("Cross-Origin-Resource-Policy", "same-origin")); // @formatter:on } @Test public void requestWhenCrossOriginPoliciesRespondsCrossOriginPolicies() throws Exception { this.spring.configLocations(this.xml("DefaultsDisabledWithCrossOriginPolicies")).autowire(); // @formatter:off this.mvc.perform(get("/")) .andExpect(status().isOk()) .andExpect(excludesDefaults()) .andExpect(header().string("Cross-Origin-Opener-Policy", "same-origin")) .andExpect(header().string("Cross-Origin-Embedder-Policy", "require-corp")) .andExpect(header().string("Cross-Origin-Resource-Policy", "same-origin")); // @formatter:on } @Test public void requestWhenSessionManagementConcurrencyControlMaxSessionIsOne() throws Exception { System.setProperty("security.session-management.concurrency-control.max-sessions", "1"); this.spring.configLocations(this.xml("DefaultsSessionManagementConcurrencyControlMaxSessions")).autowire(); // @formatter:off MockHttpServletRequestBuilder requestBuilder = post("/login") .with(csrf()) .param("username", "user") .param("password", "password"); HttpSession firstSession = this.mvc.perform(requestBuilder) .andExpect(status().is3xxRedirection()) .andExpect(redirectedUrl("/")) .andReturn() .getRequest() .getSession(false); // @formatter:on assertThat(firstSession).isNotNull(); // @formatter:off this.mvc.perform(requestBuilder) .andExpect(status().isFound()) .andExpect(redirectedUrl("/login?error")); // @formatter:on } @Test public void requestWhenSessionManagementConcurrencyControlMaxSessionIsUnlimited() throws Exception { System.setProperty("security.session-management.concurrency-control.max-sessions", "-1"); this.spring.configLocations(this.xml("DefaultsSessionManagementConcurrencyControlMaxSessions")).autowire(); // @formatter:off MockHttpServletRequestBuilder requestBuilder = post("/login") .with(csrf()) .param("username", "user") .param("password", "password"); HttpSession firstSession = this.mvc.perform(requestBuilder) .andExpect(status().is3xxRedirection()) .andExpect(redirectedUrl("/")) .andReturn() .getRequest() .getSession(false); assertThat(firstSession).isNotNull(); HttpSession secondSession = this.mvc.perform(requestBuilder) .andExpect(status().is3xxRedirection()) .andExpect(redirectedUrl("/")) .andReturn() .getRequest() .getSession(false); assertThat(secondSession).isNotNull(); // @formatter:on assertThat(firstSession.getId()).isNotEqualTo(secondSession.getId()); } @Test public void requestWhenSessionManagementConcurrencyControlMaxSessionRefIsOneForNonAdminUsers() throws Exception { this.spring.configLocations(this.xml("DefaultsSessionManagementConcurrencyControlMaxSessionsRef")).autowire(); // @formatter:off MockHttpServletRequestBuilder requestBuilder = post("/login") .with(csrf()) .param("username", "user") .param("password", "password"); HttpSession firstSession = this.mvc.perform(requestBuilder) .andExpect(status().is3xxRedirection()) .andExpect(redirectedUrl("/")) .andReturn() .getRequest() .getSession(false); // @formatter:on assertThat(firstSession).isNotNull(); // @formatter:off this.mvc.perform(requestBuilder) .andExpect(status().isFound()) .andExpect(redirectedUrl("/login?error")); // @formatter:on } @Test public void requestWhenSessionManagementConcurrencyControlMaxSessionRefIsTwoForAdminUsers() throws Exception { this.spring.configLocations(this.xml("DefaultsSessionManagementConcurrencyControlMaxSessionsRef")).autowire(); // @formatter:off MockHttpServletRequestBuilder requestBuilder = post("/login") .with(csrf()) .param("username", "admin") .param("password", "password"); HttpSession firstSession = this.mvc.perform(requestBuilder) .andExpect(status().is3xxRedirection()) .andExpect(redirectedUrl("/")) .andReturn() .getRequest() .getSession(false); assertThat(firstSession).isNotNull(); HttpSession secondSession = this.mvc.perform(requestBuilder) .andExpect(status().is3xxRedirection()) .andExpect(redirectedUrl("/")) .andReturn() .getRequest() .getSession(false); assertThat(secondSession).isNotNull(); // @formatter:on assertThat(firstSession.getId()).isNotEqualTo(secondSession.getId()); // @formatter:off this.mvc.perform(requestBuilder) .andExpect(status().isFound()) .andExpect(redirectedUrl("/login?error")); // @formatter:on } @Test public void requestWhenSessionManagementConcurrencyControlWithInvalidMaxSessionConfig() { assertThatExceptionOfType(BeanDefinitionParsingException.class) .isThrownBy(() -> this.spring .configLocations(this.xml("DefaultsSessionManagementConcurrencyControlWithInvalidMaxSessionsConfig")) .autowire()) .withMessageContaining("Cannot use 'max-sessions' attribute and 'max-sessions-ref' attribute together."); } private static ResultMatcher includesDefaults() { return includes(defaultHeaders); } private static ResultMatcher includes(Map headers) { return (result) -> { for (Map.Entry header : headers.entrySet()) { header().string(header.getKey(), header.getValue()).match(result); } }; } private static ResultMatcher excludesDefaults() { return excludes(defaultHeaders.keySet()); } private static ResultMatcher excludes(Collection headers) { return (result) -> { for (String name : headers) { header().doesNotExist(name).match(result); } }; } private static ResultMatcher excludes(String... headers) { return excludes(Arrays.asList(headers)); } private static Map remove(Map map, Collection keys) { Map copy = new HashMap<>(map); for (K key : keys) { copy.remove(key); } return copy; } private String xml(String configName) { return CONFIG_LOCATION_PREFIX + "-" + configName + ".xml"; } @RestController public static class SimpleController { @GetMapping("/") public String ok() { return "ok"; } } public static class CustomSessionLimit implements SessionLimit { @Override public Integer apply(Authentication authentication) { if ("admin".equals(authentication.getName())) { return 2; } return 1; } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/http/HttpInterceptUrlTests.java ================================================ /* * 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 org.springframework.security.config.http; import jakarta.servlet.Filter; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import org.springframework.mock.web.MockServletContext; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.context.ConfigurableWebApplicationContext; import org.springframework.web.context.support.XmlWebApplicationContext; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; public class HttpInterceptUrlTests { ConfigurableWebApplicationContext context; MockMvc mockMvc; @AfterEach public void close() { if (this.context != null) { this.context.close(); } } @Test public void interceptUrlWhenRequestMatcherRefThenWorks() throws Exception { loadConfig("interceptUrlWhenRequestMatcherRefThenWorks.xml"); // @formatter:off this.mockMvc.perform(get("/foo")) .andExpect(status().isUnauthorized()); this.mockMvc.perform(get("/FOO")) .andExpect(status().isUnauthorized()); this.mockMvc.perform(get("/other")) .andExpect(status().isOk()); // @formatter:on } private void loadConfig(String... configLocations) { for (int i = 0; i < configLocations.length; i++) { configLocations[i] = getClass().getName().replaceAll("\\.", "/") + "-" + configLocations[i]; } XmlWebApplicationContext context = new XmlWebApplicationContext(); context.setConfigLocations(configLocations); context.setServletContext(new MockServletContext()); context.refresh(); this.context = context; context.getAutowireCapableBeanFactory().autowireBean(this); Filter springSecurityFilterChain = context.getBean("springSecurityFilterChain", Filter.class); this.mockMvc = MockMvcBuilders.standaloneSetup(new FooController()) .addFilters(springSecurityFilterChain) .build(); } @RestController static class FooController { @GetMapping("/*") String foo() { return "foo"; } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/http/InterceptUrlConfigTests.java ================================================ /* * 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 org.springframework.security.config.http; import java.util.Collections; import java.util.Map; import jakarta.servlet.DispatcherType; import jakarta.servlet.ServletRegistration; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.stubbing.Answer; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.parsing.BeanDefinitionParsingException; import org.springframework.mock.web.MockServletContext; import org.springframework.security.authorization.AuthorizationManager; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.request.RequestPostProcessor; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.util.WebUtils; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatNoException; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * @author Rob Winch * @author Josh Cummings */ @ExtendWith(SpringTestContextExtension.class) public class InterceptUrlConfigTests { private static final String CONFIG_LOCATION_PREFIX = "classpath:org/springframework/security/config/http/InterceptUrlConfigTests"; public final SpringTestContext spring = new SpringTestContext(this); @Autowired MockMvc mvc; /** * sec-2256 */ @Test public void requestWhenMethodIsSpecifiedThenItIsNotGivenPriority() throws Exception { this.spring.configLocations(this.xml("Sec2256")).autowire(); // @formatter:off this.mvc.perform(post("/path").with(userCredentials())) .andExpect(status().isOk()); this.mvc.perform(get("/path").with(userCredentials())) .andExpect(status().isOk()); // @formatter:on } /** * sec-2256 */ @Test public void requestWhenMethodIsSpecifiedAndAuthorizationManagerThenItIsNotGivenPriority() throws Exception { this.spring.configLocations(this.xml("Sec2256AuthorizationManager")).autowire(); // @formatter:off this.mvc.perform(post("/path").with(userCredentials())) .andExpect(status().isOk()); this.mvc.perform(get("/path").with(userCredentials())) .andExpect(status().isOk()); // @formatter:on assertThat(this.spring.getContext().getBean(AuthorizationManager.class)).isNotNull(); } /** * sec-2355 */ @Test public void requestWhenUsingPatchThenAuthorizesRequestsAccordingly() throws Exception { this.spring.configLocations(this.xml("PatchMethod")).autowire(); // @formatter:off this.mvc.perform(get("/path").with(userCredentials())) .andExpect(status().isOk()); this.mvc.perform(patch("/path").with(userCredentials())) .andExpect(status().isForbidden()); this.mvc.perform(patch("/path").with(adminCredentials())) .andExpect(status().isOk()); // @formatter:on } /** * sec-2355 */ @Test public void requestWhenUsingPatchAndAuthorizationManagerThenAuthorizesRequestsAccordingly() throws Exception { this.spring.configLocations(this.xml("PatchMethodAuthorizationManager")).autowire(); // @formatter:off this.mvc.perform(get("/path").with(userCredentials())) .andExpect(status().isForbidden()); this.mvc.perform(patch("/path").with(userCredentials())) .andExpect(status().isForbidden()); this.mvc.perform(patch("/path").with(adminCredentials())) .andExpect(status().isOk()); // @formatter:on assertThat(this.spring.getContext().getBean(AuthorizationManager.class)).isNotNull(); } @Test public void requestWhenUsingHasAnyRoleThenAuthorizesRequestsAccordingly() throws Exception { this.spring.configLocations(this.xml("HasAnyRole")).autowire(); // @formatter:off this.mvc.perform(get("/path").with(userCredentials())) .andExpect(status().isOk()); this.mvc.perform(get("/path").with(adminCredentials())) .andExpect(status().isForbidden()); // @formatter:on } @Test public void requestWhenUsingHasAnyRoleAndAuthorizationManagerThenAuthorizesRequestsAccordingly() throws Exception { this.spring.configLocations(this.xml("HasAnyRoleAuthorizationManager")).autowire(); // @formatter:off this.mvc.perform(get("/path").with(userCredentials())) .andExpect(status().isOk()); this.mvc.perform(get("/path").with(adminCredentials())) .andExpect(status().isForbidden()); // @formatter:on assertThat(this.spring.getContext().getBean(AuthorizationManager.class)).isNotNull(); } /** * sec-2059 */ @Test public void requestWhenUsingPathVariablesThenAuthorizesRequestsAccordingly() throws Exception { this.spring.configLocations(this.xml("PathVariables")).autowire(); // @formatter:off this.mvc.perform(get("/path/user/path").with(userCredentials())) .andExpect(status().isOk()); this.mvc.perform(get("/path/otheruser/path").with(userCredentials())) .andExpect(status().isForbidden()); this.mvc.perform(get("/path").with(userCredentials())) .andExpect(status().isForbidden()); // @formatter:on } /** * sec-2059 */ @Test public void requestWhenUsingPathVariablesAndAuthorizationManagerThenAuthorizesRequestsAccordingly() throws Exception { this.spring.configLocations(this.xml("PathVariablesAuthorizationManager")).autowire(); // @formatter:off this.mvc.perform(get("/path/user/path").with(userCredentials())) .andExpect(status().isOk()); this.mvc.perform(get("/path/otheruser/path").with(userCredentials())) .andExpect(status().isForbidden()); this.mvc.perform(get("/path").with(userCredentials())) .andExpect(status().isForbidden()); // @formatter:on assertThat(this.spring.getContext().getBean(AuthorizationManager.class)).isNotNull(); } /** * gh-3786 */ @Test public void requestWhenUsingCamelCasePathVariablesThenAuthorizesRequestsAccordingly() throws Exception { this.spring.configLocations(this.xml("CamelCasePathVariables")).autowire(); // @formatter:off this.mvc.perform(get("/path/user/path").with(userCredentials())) .andExpect(status().isOk()); this.mvc.perform(get("/path/otheruser/path").with(userCredentials())) .andExpect(status().isForbidden()); this.mvc.perform(get("/PATH/user/path").with(userCredentials())) .andExpect(status().isForbidden()); // @formatter:on } /** * gh-3786 */ @Test public void requestWhenUsingCamelCasePathVariablesAndAuthorizationManagerThenAuthorizesRequestsAccordingly() throws Exception { this.spring.configLocations(this.xml("CamelCasePathVariablesAuthorizationManager")).autowire(); // @formatter:off this.mvc.perform(get("/path/user/path").with(userCredentials())) .andExpect(status().isOk()); this.mvc.perform(get("/path/otheruser/path").with(userCredentials())) .andExpect(status().isForbidden()); this.mvc.perform(get("/PATH/user/path").with(userCredentials())) .andExpect(status().isForbidden()); // @formatter:on assertThat(this.spring.getContext().getBean(AuthorizationManager.class)).isNotNull(); } /** * sec-2059 */ @Test public void requestWhenUsingPathVariablesAndTypeConversionThenAuthorizesRequestsAccordingly() throws Exception { this.spring.configLocations(this.xml("TypeConversionPathVariables")).autowire(); // @formatter:off this.mvc.perform(get("/path/1/path").with(userCredentials())) .andExpect(status().isOk()); this.mvc.perform(get("/path/2/path").with(userCredentials())) .andExpect(status().isForbidden()); // @formatter:on } /** * sec-2059 */ @Test public void requestWhenUsingPathVariablesAndTypeConversionAndAuthorizationManagerThenAuthorizesRequestsAccordingly() throws Exception { this.spring.configLocations(this.xml("TypeConversionPathVariablesAuthorizationManager")).autowire(); // @formatter:off this.mvc.perform(get("/path/1/path").with(userCredentials())) .andExpect(status().isOk()); this.mvc.perform(get("/path/2/path").with(userCredentials())) .andExpect(status().isForbidden()); // @formatter:on assertThat(this.spring.getContext().getBean(AuthorizationManager.class)).isNotNull(); } @Test public void requestWhenUsingMvcMatchersAndPathVariablesThenAuthorizesRequestsAccordingly() throws Exception { this.spring.configLocations(this.xml("MvcMatchersPathVariables")).autowire(); // @formatter:off this.mvc.perform(get("/path/user/path").with(userCredentials())) .andExpect(status().isOk()); this.mvc.perform(get("/path/otheruser/path").with(userCredentials())) .andExpect(status().isForbidden()); this.mvc.perform(get("/PATH/user/path").with(userCredentials())) .andExpect(status().isForbidden()); // @formatter:on } @Test public void requestWhenUsingMvcMatchersAndPathVariablesAndAuthorizationManagerThenAuthorizesRequestsAccordingly() throws Exception { this.spring.configLocations(this.xml("MvcMatchersPathVariablesAuthorizationManager")).autowire(); // @formatter:off this.mvc.perform(get("/path/user/path").with(userCredentials())) .andExpect(status().isOk()); this.mvc.perform(get("/path/otheruser/path").with(userCredentials())) .andExpect(status().isForbidden()); this.mvc.perform(get("/PATH/user/path").with(userCredentials())) .andExpect(status().isForbidden()); // @formatter:on assertThat(this.spring.getContext().getBean(AuthorizationManager.class)).isNotNull(); } @Test public void configureWhenUsingRegexMatcherAndServletPathThenThrowsException() { assertThatExceptionOfType(BeanDefinitionParsingException.class) .isThrownBy(() -> this.spring.configLocations(this.xml("RegexMatcherServletPath")).autowire()); } @Test public void configureWhenUsingRegexMatcherAndServletPathAndAuthorizationManagerThenThrowsException() { assertThatExceptionOfType(BeanDefinitionParsingException.class).isThrownBy( () -> this.spring.configLocations(this.xml("RegexMatcherServletPathAuthorizationManager")).autowire()); } @Test public void configureWhenUsingCiRegexMatcherAndServletPathThenThrowsException() { assertThatExceptionOfType(BeanDefinitionParsingException.class) .isThrownBy(() -> this.spring.configLocations(this.xml("CiRegexMatcherServletPath")).autowire()); } @Test public void configureWhenUsingCiRegexMatcherAndServletPathAndAuthorizationManagerThenThrowsException() { assertThatExceptionOfType(BeanDefinitionParsingException.class) .isThrownBy(() -> this.spring.configLocations(this.xml("CiRegexMatcherServletPathAuthorizationManager")) .autowire()); } @Test public void configureWhenUsingDefaultMatcherAndServletPathThenNoException() { assertThatNoException() .isThrownBy(() -> this.spring.configLocations(this.xml("DefaultMatcherServletPath")).autowire()); } @Test public void configureWhenUsingDefaultMatcherAndServletPathAndAuthorizationManagerThenNoException() { assertThatNoException() .isThrownBy(() -> this.spring.configLocations(this.xml("DefaultMatcherServletPathAuthorizationManager")) .autowire()); } @Test public void requestWhenUsingFilterAllDispatcherTypesAndAuthorizationManagerThenAuthorizesRequestsAccordingly() throws Exception { this.spring.configLocations(this.xml("AuthorizationManagerFilterAllDispatcherTypes")).autowire(); // @formatter:off this.mvc.perform(get("/path").with(userCredentials())) .andExpect(status().isOk()); this.mvc.perform(get("/path").with(adminCredentials())) .andExpect(status().isForbidden()); this.mvc.perform(get("/error").with((request) -> { request.setAttribute(WebUtils.ERROR_REQUEST_URI_ATTRIBUTE, "/error"); request.setDispatcherType(DispatcherType.ERROR); return request; })).andExpect(status().isOk()); this.mvc.perform(get("/path").with((request) -> { request.setAttribute(WebUtils.ERROR_REQUEST_URI_ATTRIBUTE, "/path"); request.setDispatcherType(DispatcherType.ERROR); return request; })).andExpect(status().isUnauthorized()); // @formatter:on assertThat(this.spring.getContext().getBean(AuthorizationManager.class)).isNotNull(); } /** * gh-18503 */ @Test public void configWhenInterceptUrlMissingAccessThenException() { assertThatExceptionOfType(BeanDefinitionParsingException.class) .isThrownBy(() -> this.spring.configLocations(this.xml("MissingAccess")).autowire()) .withMessageContaining("access attribute cannot be empty or null"); } /** * gh-18503 */ @Test public void configWhenInterceptUrlEmptyAccessThenException() { assertThatExceptionOfType(BeanDefinitionParsingException.class) .isThrownBy(() -> this.spring.configLocations(this.xml("EmptyAccess")).autowire()) .withMessageContaining("access attribute cannot be empty or null"); } /** * gh-18503 */ @Test public void configWhenInterceptUrlValidAccessThenLoads() { assertThatNoException().isThrownBy(() -> this.spring.configLocations(this.xml("ValidAccess")).autowire()); } /** * gh-18503 */ @Test public void configWhenUseAuthorizationManagerFalseAndMissingAccessThenException() { assertThatExceptionOfType(BeanDefinitionParsingException.class) .isThrownBy(() -> this.spring.configLocations(this.xml("MissingAccessLegacy")).autowire()) .withMessageContaining("access attribute cannot be empty or null"); } /** * gh-18503 */ @Test public void configWhenUseAuthorizationManagerFalseAndEmptyAccessThenException() { assertThatExceptionOfType(BeanDefinitionParsingException.class) .isThrownBy(() -> this.spring.configLocations(this.xml("EmptyAccessLegacy")).autowire()) .withMessageContaining("access attribute cannot be empty or null"); } private static RequestPostProcessor adminCredentials() { return httpBasic("admin", "password"); } private static RequestPostProcessor userCredentials() { return httpBasic("user", "password"); } private MockServletContext mockServletContext(String servletPath) { MockServletContext servletContext = spy(new MockServletContext()); final ServletRegistration registration = mock(ServletRegistration.class); given(registration.getMappings()).willReturn(Collections.singleton(servletPath)); Answer> answer = (invocation) -> Collections.singletonMap("spring", registration); given(servletContext.getServletRegistrations()).willAnswer(answer); return servletContext; } private String xml(String configName) { return CONFIG_LOCATION_PREFIX + "-" + configName + ".xml"; } @RestController static class PathController { @RequestMapping("/path") String path() { return "path"; } @RequestMapping("/path/{un}/path") String path(@PathVariable("un") String name) { return name; } } @RestController static class ErrorController { @GetMapping("/error") String error() { return "error"; } } public static class Id { public boolean isOne(int i) { return i == 1; } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/http/MiscHttpConfigTests.java ================================================ /* * 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 org.springframework.security.config.http; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; import java.security.Principal; import java.security.cert.X509Certificate; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.concurrent.Callable; import java.util.stream.Collectors; import javax.security.auth.Subject; import javax.security.auth.callback.CallbackHandler; import javax.security.auth.spi.LoginModule; import ch.qos.logback.classic.Logger; import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.core.Appender; import jakarta.servlet.Filter; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponseWrapper; import org.apache.http.HttpStatus; import org.assertj.core.api.iterable.Extractor; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.stubbing.Answer; import org.slf4j.LoggerFactory; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.parsing.BeanDefinitionParsingException; import org.springframework.lang.NonNull; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.mock.web.MockHttpSession; import org.springframework.security.BeanNameCollectingPostProcessor; import org.springframework.security.access.AccessDecisionManager; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.access.PermissionEvaluator; import org.springframework.security.authentication.AnonymousAuthenticationToken; import org.springframework.security.authentication.AuthenticationDetailsSource; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.InsufficientAuthenticationException; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.authentication.jaas.AuthorityGranter; import org.springframework.security.config.TestDeferredSecurityContext; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.security.core.context.SecurityContextImpl; import org.springframework.security.test.web.servlet.RequestCacheResultMatcher; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.security.web.FilterChainProxy; import org.springframework.security.web.access.ExceptionTranslationFilter; import org.springframework.security.web.access.channel.ChannelProcessingFilter; import org.springframework.security.web.access.intercept.AuthorizationFilter; import org.springframework.security.web.access.intercept.FilterSecurityInterceptor; import org.springframework.security.web.authentication.AnonymousAuthenticationFilter; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.authentication.logout.LogoutFilter; import org.springframework.security.web.authentication.preauth.x509.X509AuthenticationFilter; import org.springframework.security.web.authentication.preauth.x509.X509TestUtils; import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter; import org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter; import org.springframework.security.web.authentication.ui.DefaultResourcesFilter; import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; import org.springframework.security.web.context.HttpRequestResponseHolder; import org.springframework.security.web.context.SecurityContextHolderFilter; import org.springframework.security.web.context.SecurityContextRepository; import org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter; import org.springframework.security.web.csrf.CsrfFilter; import org.springframework.security.web.firewall.FirewalledRequest; import org.springframework.security.web.firewall.HttpFirewall; import org.springframework.security.web.firewall.RequestRejectedException; import org.springframework.security.web.firewall.RequestRejectedHandler; import org.springframework.security.web.header.HeaderWriterFilter; import org.springframework.security.web.savedrequest.RequestCache; import org.springframework.security.web.savedrequest.RequestCacheAwareFilter; import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter; import org.springframework.security.web.session.DisableEncodeUrlFilter; import org.springframework.security.web.transport.HttpsRedirectFilter; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; import org.springframework.test.web.servlet.request.RequestPostProcessor; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.context.support.XmlWebApplicationContext; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.willAnswer; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.x509; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.asyncDispatch; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.request; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * @author Luke Taylor * @author Rob Winch */ @ExtendWith(SpringTestContextExtension.class) public class MiscHttpConfigTests { private static final String CONFIG_LOCATION_PREFIX = "classpath:org/springframework/security/config/http/MiscHttpConfigTests"; @Autowired MockMvc mvc; public final SpringTestContext spring = new SpringTestContext(this); @Test public void configureWhenUsingMinimalConfigurationThenParses() { this.spring.configLocations(xml("MinimalConfiguration")).autowire(); } @Test public void configureWhenUsingAutoConfigThenSetsUpCorrectFilterList() { this.spring.configLocations(xml("AutoConfig")).autowire(); assertThatFiltersMatchExpectedAutoConfigList(); } @Test public void configureWhenUsingSecurityNoneThenNoFiltersAreSetUp() { this.spring.configLocations(xml("NoSecurityForPattern")).autowire(); assertThat(getFilters("/unprotected")).isEmpty(); } @Test public void requestWhenUsingDebugFilterAndPatternIsNotConfigureForSecurityThenRespondsOk() throws Exception { this.spring.configLocations(xml("NoSecurityForPattern")).autowire(); // @formatter:off this.mvc.perform(get("/unprotected")) .andExpect(status().isNotFound()); this.mvc.perform(get("/nomatch")) .andExpect(status().isNotFound()); // @formatter:on } @Test public void requestWhenHttpPatternUsesRegexMatchingThenMatchesAccordingly() throws Exception { this.spring.configLocations(xml("RegexSecurityPattern")).autowire(); // @formatter:off this.mvc.perform(get("/protected")) .andExpect(status().isUnauthorized()); this.mvc.perform(get("/unprotected")) .andExpect(status().isNotFound()); // @formatter:on } @Test public void requestWhenHttpPatternUsesCiRegexMatchingThenMatchesAccordingly() throws Exception { this.spring.configLocations(xml("CiRegexSecurityPattern")).autowire(); // @formatter:off this.mvc.perform(get("/ProTectEd")) .andExpect(status().isUnauthorized()); this.mvc.perform(get("/UnProTectEd")) .andExpect(status().isNotFound()); // @formatter:on } @Test public void requestWhenHttpPatternUsesCustomRequestMatcherThenMatchesAccordingly() throws Exception { this.spring.configLocations(xml("CustomRequestMatcher")).autowire(); // @formatter:off this.mvc.perform(get("/protected")) .andExpect(status().isUnauthorized()); this.mvc.perform(get("/unprotected")) .andExpect(status().isNotFound()); // @formatter:on } /** * SEC-1152 */ @Test public void requestWhenUsingMinimalConfigurationThenHonorsAnonymousEndpoints() throws Exception { this.spring.configLocations(xml("AnonymousEndpoints")).autowire(); // @formatter:off this.mvc.perform(get("/protected")) .andExpect(status().isUnauthorized()); this.mvc.perform(get("/unprotected")) .andExpect(status().isNotFound()); // @formatter:on assertThat(getFilter(AnonymousAuthenticationFilter.class)).isNotNull(); } @Test public void requestWhenAnonymousIsDisabledThenRejectsAnonymousEndpoints() throws Exception { this.spring.configLocations(xml("AnonymousDisabled")).autowire(); // @formatter:off this.mvc.perform(get("/protected")) .andExpect(status().isUnauthorized()); this.mvc.perform(get("/unprotected")) .andExpect(status().isUnauthorized()); // @formatter:on assertThat(getFilter(AnonymousAuthenticationFilter.class)).isNull(); } @Test public void requestWhenAnonymousUsesCustomAttributesThenRespondsWithThoseAttributes() throws Exception { this.spring.configLocations(xml("AnonymousCustomAttributes")).autowire(); // @formatter:off this.mvc.perform(get("/protected").with(userCredentials())) .andExpect(status().isForbidden()); this.mvc.perform(get("/protected")) .andExpect(status().isOk()) .andExpect(content().string("josh")); this.mvc.perform(get("/customKey")) .andExpect(status().isOk()) .andExpect(content().string(String.valueOf("myCustomKey".hashCode()))); // @formatter:on } @Test public void requestWhenAnonymousUsesMultipleGrantedAuthoritiesThenRespondsWithThoseAttributes() throws Exception { this.spring.configLocations(xml("AnonymousMultipleAuthorities")).autowire(); // @formatter:off this.mvc.perform(get("/protected").with(userCredentials())) .andExpect(status().isForbidden()); this.mvc.perform(get("/protected")) .andExpect(status().isOk()) .andExpect(content().string("josh")); this.mvc.perform(get("/customKey")) .andExpect(status().isOk()) .andExpect(content().string(String.valueOf("myCustomKey".hashCode()))); // @formatter:on } @Test public void requestWhenInterceptUrlMatchesMethodThenSecuresAccordingly() throws Exception { this.spring.configLocations(xml("InterceptUrlMethod")).autowire(); // @formatter:off this.mvc.perform(get("/protected").with(userCredentials())) .andExpect(status().isOk()); this.mvc.perform(post("/protected").with(userCredentials())) .andExpect(status().isForbidden()); this.mvc.perform(post("/protected").with(postCredentials())) .andExpect(status().isOk()); this.mvc.perform(delete("/protected").with(postCredentials())) .andExpect(status().isForbidden()); this.mvc.perform(delete("/protected").with(adminCredentials())) .andExpect(status().isOk()); // @formatter:on } @Test public void requestWhenInterceptUrlMatchesMethodAndRequiresHttpsThenSecuresAccordingly() throws Exception { this.spring.configLocations(xml("InterceptUrlMethodRequiresHttps")).autowire(); // @formatter:off this.mvc.perform(post("/protected").with(csrf())) .andExpect(status().isOk()); this.mvc.perform(get("/protected").secure(true).with(userCredentials())) .andExpect(status().isForbidden()); this.mvc.perform(get("/protected").secure(true).with(adminCredentials())) .andExpect(status().isOk()); // @formatter:on } @Test public void requestWhenInterceptUrlMatchesAnyPatternAndRequiresHttpsThenSecuresAccordingly() throws Exception { this.spring.configLocations(xml("InterceptUrlMethodRequiresHttpsAny")).autowire(); // @formatter:off this.mvc.perform(post("/protected").with(csrf())) .andExpect(status().isOk()); this.mvc.perform(get("/protected").secure(true).with(userCredentials())) .andExpect(status().isForbidden()); this.mvc.perform(get("/protected").secure(true).with(adminCredentials())) .andExpect(status().isOk()); // @formatter:on } @Test public void configureWhenOncePerRequestIsFalseThenFilterSecurityInterceptorExercisedForForwards() { this.spring.configLocations(xml("OncePerRequest")).autowire(); FilterSecurityInterceptor filterSecurityInterceptor = getFilter(FilterSecurityInterceptor.class); assertThat(filterSecurityInterceptor.isObserveOncePerRequest()).isFalse(); } @Test public void configureWhenOncePerRequestIsTrueThenFilterSecurityInterceptorObserveOncePerRequestIsTrue() { this.spring.configLocations(xml("OncePerRequestTrue")).autowire(); FilterSecurityInterceptor filterSecurityInterceptor = getFilter(FilterSecurityInterceptor.class); assertThat(filterSecurityInterceptor.isObserveOncePerRequest()).isTrue(); } @Test public void requestWhenCustomHttpBasicEntryPointRefThenInvokesOnCommence() throws Exception { this.spring.configLocations(xml("CustomHttpBasicEntryPointRef")).autowire(); AuthenticationEntryPoint entryPoint = this.spring.getContext().getBean(AuthenticationEntryPoint.class); // @formatter:off this.mvc.perform(get("/protected")) .andExpect(status().isOk()); // @formatter:on verify(entryPoint).commence(any(HttpServletRequest.class), any(HttpServletResponse.class), any(AuthenticationException.class)); } @Test public void configureWhenInterceptUrlWithRequiresChannelThenAddedChannelFilterToChain() { this.spring.configLocations(xml("InterceptUrlMethodRequiresHttpsAny")).autowire(); assertThat(getFilter(ChannelProcessingFilter.class)).isNotNull(); } @Test public void configureWhenRedirectToHttpsThenFilterAdded() { this.spring.configLocations(xml("RedirectToHttpsRequiresHttpsAny")).autowire(); assertThat(getFilter(HttpsRedirectFilter.class)).isNotNull(); } @Test public void getWhenRedirectToHttpsAnyThenRedirects() throws Exception { this.spring.configLocations(xml("RedirectToHttpsRequiresHttpsAny")).autowire(); // @formatter:off this.mvc.perform(get("http://localhost")) .andExpect(redirectedUrl("https://localhost")); // @formatter:on } @Test public void getWhenPortsMappedThenRedirectedAccordingly() throws Exception { this.spring.configLocations(xml("PortsMappedInterceptUrlMethodRequiresAny")).autowire(); // @formatter:off this.mvc.perform(get("http://localhost:9080/protected")) .andExpect(redirectedUrl("https://localhost:9443/protected")); // @formatter:on } @Test public void configureWhenCustomFiltersThenAddedToChainInCorrectOrder() { System.setProperty("customFilterRef", "userFilter"); this.spring.configLocations(xml("CustomFilters")).autowire(); List filters = getFilters("/"); Class userFilterClass = this.spring.getContext().getBean("userFilter").getClass(); assertThat(filters).extracting((Extractor>) (filter) -> filter.getClass()) .containsSubsequence(userFilterClass, userFilterClass, SecurityContextHolderFilter.class, LogoutFilter.class, userFilterClass); } @Test public void configureWhenTwoFiltersWithSameOrderThenException() { assertThatExceptionOfType(BeanDefinitionParsingException.class) .isThrownBy(() -> this.spring.configLocations(xml("CollidingFilters")).autowire()); } @Test public void configureWhenUsingX509ThenAddsX509FilterCorrectly() { this.spring.configLocations(xml("X509")).autowire(); assertThat(getFilters("/")).extracting((Extractor>) (filter) -> filter.getClass()) .containsSubsequence(CsrfFilter.class, X509AuthenticationFilter.class, ExceptionTranslationFilter.class); } @Test public void getWhenUsingX509PrincipalExtractorRef() throws Exception { this.spring.configLocations(xml("X509PrincipalExtractorRef")).autowire(); X509Certificate certificate = X509TestUtils.buildTestCertificate(); RequestPostProcessor x509 = x509(certificate); // @formatter:off this.mvc.perform(get("/protected").with(x509)) .andExpect(status().isOk()); // @formatter:on } @Test public void getWhenUsingX509PrincipalExtractorRefAndSubjectPrincipalRegex() throws Exception { String xmlResourceName = "X509PrincipalExtractorRefAndSubjectPrincipalRegex"; // @formatter:off assertThatExceptionOfType(BeanDefinitionParsingException.class) .isThrownBy(() -> this.spring.configLocations(xml(xmlResourceName)).autowire()) .withMessage("Configuration problem: The attribute 'principal-extractor-ref' cannot be used together with the 'subject-principal-regex' attribute within \n" + "Offending resource: class path resource [org/springframework/security/config/http/MiscHttpConfigTests-X509PrincipalExtractorRefAndSubjectPrincipalRegex.xml]"); // @formatter:on } @Test public void getWhenUsingX509AndPropertyPlaceholderThenSubjectPrincipalRegexIsConfigured() throws Exception { System.setProperty("subject_principal_regex", "OU=(.*?)(?:,|$)"); this.spring.configLocations(xml("X509")).autowire(); RequestPostProcessor x509 = x509( "classpath:org/springframework/security/config/http/MiscHttpConfigTests-certificate.pem"); // @formatter:off this.mvc.perform(get("/protected").with(x509)) .andExpect(status().isOk()); // @formatter:on } @Test public void getWhenUsingX509CustomSecurityContextHolderStrategyThenUses() throws Exception { System.setProperty("subject_principal_regex", "OU=(.*?)(?:,|$)"); this.spring.configLocations(xml("X509WithSecurityContextHolderStrategy")).autowire(); RequestPostProcessor x509 = x509( "classpath:org/springframework/security/config/http/MiscHttpConfigTests-certificate.pem"); // @formatter:off this.mvc.perform(get("/protected").with(x509)) .andExpect(status().isOk()); // @formatter:on verify(this.spring.getContext().getBean(SecurityContextHolderStrategy.class), atLeastOnce()).getContext(); } @Test public void configureWhenUsingInvalidLogoutSuccessUrlThenThrowsException() { assertThatExceptionOfType(BeanCreationException.class) .isThrownBy(() -> this.spring.configLocations(xml("InvalidLogoutSuccessUrl")).autowire()); } @Test public void logoutWhenSpecifyingCookiesToDeleteThenSetCookieAdded() throws Exception { this.spring.configLocations(xml("DeleteCookies")).autowire(); MvcResult result = this.mvc.perform(post("/logout").with(csrf())).andReturn(); List values = result.getResponse().getHeaders("Set-Cookie"); assertThat(values).hasSize(2); assertThat(values).extracting((value) -> value.split("=")[0]).contains("JSESSIONID", "mycookie"); } @Test public void logoutWhenSpecifyingSuccessHandlerRefThenResponseHandledAccordingly() throws Exception { this.spring.configLocations(xml("LogoutSuccessHandlerRef")).autowire(); // @formatter:off this.mvc.perform(post("/logout").with(csrf())) .andExpect(redirectedUrl("/logoutSuccessEndpoint")); // @formatter:on } @Test public void getWhenUnauthenticatedThenUsesConfiguredRequestCache() throws Exception { this.spring.configLocations(xml("RequestCache")).autowire(); RequestCache requestCache = this.spring.getContext().getBean(RequestCache.class); this.mvc.perform(get("/")); verify(requestCache).saveRequest(any(HttpServletRequest.class), any(HttpServletResponse.class)); } @Test public void getWhenUnauthenticatedThenUsesConfiguredAuthenticationEntryPoint() throws Exception { this.spring.configLocations(xml("EntryPoint")).autowire(); AuthenticationEntryPoint entryPoint = this.spring.getContext().getBean(AuthenticationEntryPoint.class); this.mvc.perform(get("/")); verify(entryPoint).commence(any(HttpServletRequest.class), any(HttpServletResponse.class), any(AuthenticationException.class)); } /** * See SEC-750. If the http security post processor causes beans to be instantiated * too eagerly, they way miss additional processing. In this method we have a * UserDetailsService which is referenced from the namespace and also has a post * processor registered which will modify it. */ @Test public void configureWhenUsingCustomUserDetailsServiceThenBeanPostProcessorsAreStillApplied() { this.spring.configLocations(xml("Sec750")).autowire(); BeanNameCollectingPostProcessor postProcessor = this.spring.getContext() .getBean(BeanNameCollectingPostProcessor.class); assertThat(postProcessor.getBeforeInitPostProcessedBeans()).contains("authenticationProvider", "userService"); assertThat(postProcessor.getAfterInitPostProcessedBeans()).contains("authenticationProvider", "userService"); } /* SEC-934 */ @Test public void getWhenUsingTwoIdenticalInterceptUrlsThenTheSecondTakesPrecedence() throws Exception { this.spring.configLocations(xml("Sec934")).autowire(); // @formatter:off this.mvc.perform(get("/protected").with(userCredentials())) .andExpect(status().isOk()); this.mvc.perform(get("/protected").with(adminCredentials())) .andExpect(status().isForbidden()); // @formatter:on } @Test public void getWhenAuthenticatingThenConsultsCustomSecurityContextRepository() throws Exception { this.spring.configLocations(xml("SecurityContextRepository")).autowire(); SecurityContextRepository repository = this.spring.getContext().getBean(SecurityContextRepository.class); SecurityContext context = new SecurityContextImpl(new TestingAuthenticationToken("user", "password")); given(repository.loadDeferredContext(any(HttpServletRequest.class))) .willReturn(new TestDeferredSecurityContext(context, false)); // @formatter:off MvcResult result = this.mvc.perform(get("/protected").with(userCredentials())) .andExpect(status().isOk()) .andReturn(); // @formatter:on assertThat(result.getRequest().getSession(false)).isNotNull(); } @Test public void getWhenExplicitSaveAndRepositoryAndAuthenticatingThenConsultsCustomSecurityContextRepository() throws Exception { this.spring.configLocations(xml("ExplicitSaveAndExplicitRepository")).autowire(); SecurityContextRepository repository = this.spring.getContext().getBean(SecurityContextRepository.class); SecurityContext context = new SecurityContextImpl(new TestingAuthenticationToken("user", "password")); given(repository.loadDeferredContext(any(HttpServletRequest.class))) .willReturn(new TestDeferredSecurityContext(context, false)); // @formatter:off MvcResult result = this.mvc.perform(formLogin()) .andExpect(status().is3xxRedirection()) .andReturn(); // @formatter:on verify(repository, atLeastOnce()).saveContext(any(SecurityContext.class), any(HttpServletRequest.class), any(HttpServletResponse.class)); } @Test public void getWhenExplicitSaveAndExplicitSaveAndAuthenticatingThenConsultsCustomSecurityContextRepository() throws Exception { this.spring.configLocations(xml("ExplicitSave")).autowire(); SecurityContextRepository repository = this.spring.getContext().getBean(SecurityContextRepository.class); // @formatter:off MvcResult result = this.mvc.perform(formLogin()) .andExpect(status().is3xxRedirection()) .andReturn(); // @formatter:on assertThat(repository.loadContext(new HttpRequestResponseHolder(result.getRequest(), result.getResponse())) .getAuthentication()).isNotNull(); } @Test public void getWhenUsingInterceptUrlExpressionsThenAuthorizesAccordingly() throws Exception { this.spring.configLocations(xml("InterceptUrlExpressions")).autowire(); // @formatter:off this.mvc.perform(get("/protected").with(adminCredentials())) .andExpect(status().isOk()); this.mvc.perform(get("/protected").with(userCredentials())) .andExpect(status().isForbidden()); this.mvc.perform(get("/unprotected").with(userCredentials())) .andExpect(status().isOk()); // @formatter:on } @Test public void getWhenUsingCustomExpressionHandlerThenAuthorizesAccordingly() throws Exception { this.spring.configLocations(xml("ExpressionHandler")).autowire(); PermissionEvaluator permissionEvaluator = this.spring.getContext().getBean(PermissionEvaluator.class); given(permissionEvaluator.hasPermission(any(Authentication.class), any(Object.class), any(Object.class))) .willReturn(false); // @formatter:off this.mvc.perform(get("/").with(userCredentials())) .andExpect(status().isForbidden()); // @formatter:on verify(permissionEvaluator).hasPermission(any(Authentication.class), any(Object.class), any(Object.class)); } @Test public void configureWhenProtectingLoginPageThenWarningLogged() { ByteArrayOutputStream baos = new ByteArrayOutputStream(); redirectLogsTo(baos, DefaultFilterChainValidator.class); this.spring.configLocations(xml("ProtectedLoginPage")).autowire(); assertThat(baos.toString()).contains("[WARN]"); } @Test public void configureWhenProtectingLoginPageAuthorizationManagerThenWarningLogged() { ByteArrayOutputStream baos = new ByteArrayOutputStream(); redirectLogsTo(baos, DefaultFilterChainValidator.class); this.spring.configLocations(xml("ProtectedLoginPageAuthorizationManager")).autowire(); assertThat(baos.toString()).contains("[WARN]"); } @Test public void configureWhenUsingDisableUrlRewritingThenRedirectIsNotEncodedByResponse() throws IOException, ServletException { this.spring.configLocations(xml("DisableUrlRewriting")).autowire(); MockHttpServletRequest request = new MockHttpServletRequest("GET", "/"); MockHttpServletResponse response = new MockHttpServletResponse(); FilterChainProxy proxy = this.spring.getContext().getBean(FilterChainProxy.class); proxy.doFilter(request, new EncodeUrlDenyingHttpServletResponseWrapper(response), (req, resp) -> { }); assertThat(response.getStatus()).isEqualTo(HttpStatus.SC_MOVED_TEMPORARILY); assertThat(response.getRedirectedUrl()).isEqualTo("/login"); } @Test public void configureWhenUsingDisableUrlRewritingAndCustomRepositoryThenRedirectIsNotEncodedByResponse() throws IOException, ServletException { this.spring.configLocations(xml("DisableUrlRewriting-NullSecurityContextRepository")).autowire(); MockHttpServletRequest request = new MockHttpServletRequest("GET", "/"); MockHttpServletResponse responseToSpy = spy(new MockHttpServletResponse()); FilterChainProxy proxy = this.spring.getContext().getBean(FilterChainProxy.class); proxy.doFilter(request, responseToSpy, (req, resp) -> { HttpServletResponse httpResponse = (HttpServletResponse) resp; httpResponse.encodeURL("/"); httpResponse.encodeRedirectURL("/"); httpResponse.getWriter().write("encodeRedirect"); }); verify(responseToSpy, never()).encodeRedirectURL(any()); verify(responseToSpy, never()).encodeURL(any()); assertThat(responseToSpy.getContentAsString()).isEqualTo("encodeRedirect"); } @Test public void configureWhenUserDetailsServiceInParentContextThenLocatesSuccessfully() { assertThatExceptionOfType(BeansException.class).isThrownBy( () -> this.spring.configLocations(MiscHttpConfigTests.xml("MissingUserDetailsService")).autowire()); try (XmlWebApplicationContext parent = new XmlWebApplicationContext()) { parent.setConfigLocations(MiscHttpConfigTests.xml("AutoConfig")); parent.refresh(); try (XmlWebApplicationContext child = new XmlWebApplicationContext()) { child.setParent(parent); child.setConfigLocation(MiscHttpConfigTests.xml("MissingUserDetailsService")); child.refresh(); } } } @Test public void loginWhenConfiguredWithNoInternalAuthenticationProvidersThenSuccessfullyAuthenticates() throws Exception { this.spring.configLocations(xml("NoInternalAuthenticationProviders")).autowire(); // @formatter:off MockHttpServletRequestBuilder loginRequest = post("/login") .param("username", "user") .param("password", "password"); this.mvc.perform(loginRequest) .andExpect(redirectedUrl("/")); // @formatter:on } @Test public void loginWhenUsingDefaultsThenErasesCredentialsAfterAuthentication() throws Exception { this.spring.configLocations(xml("HttpBasic")).autowire(); // @formatter:off this.mvc.perform(get("/password").with(userCredentials())) .andExpect(content().string("")); // @formatter:on } @Test public void loginWhenAuthenticationManagerConfiguredToEraseCredentialsThenErasesCredentialsAfterAuthentication() throws Exception { this.spring.configLocations(xml("AuthenticationManagerEraseCredentials")).autowire(); // @formatter:off this.mvc.perform(get("/password").with(userCredentials())) .andExpect(content().string("")); // @formatter:on } /** * SEC-2020 */ @Test public void loginWhenAuthenticationManagerRefConfiguredToKeepCredentialsThenKeepsCredentialsAfterAuthentication() throws Exception { this.spring.configLocations(xml("AuthenticationManagerRefKeepCredentials")).autowire(); // @formatter:off this.mvc.perform(get("/password").with(userCredentials())) .andExpect(content().string("password")); // @formatter:on } @Test public void loginWhenAuthenticationManagerRefIsNotAProviderManagerThenKeepsCredentialsAccordingly() throws Exception { this.spring.configLocations(xml("AuthenticationManagerRefNotProviderManager")).autowire(); // @formatter:off this.mvc.perform(get("/password").with(userCredentials())) .andExpect(content().string("password")); // @formatter:on } @Test public void loginWhenJeeFilterThenExtractsRoles() throws Exception { this.spring.configLocations(xml("JeeFilter")).autowire(); Principal user = mock(Principal.class); given(user.getName()).willReturn("joe"); // @formatter:off MockHttpServletRequestBuilder rolesRequest = get("/roles") .principal(user) .with((request) -> { request.addUserRole("admin"); request.addUserRole("user"); request.addUserRole("unmapped"); return request; }); this.mvc.perform(rolesRequest) .andExpect(content().string("ROLE_admin,ROLE_user")); // @formatter:on } @Test public void loginWhenJeeFilterCustomSecurityContextHolderStrategyThenUses() throws Exception { this.spring.configLocations(xml("JeeFilterWithSecurityContextHolderStrategy")).autowire(); Principal user = mock(Principal.class); given(user.getName()).willReturn("joe"); // @formatter:off MockHttpServletRequestBuilder rolesRequest = get("/roles") .principal(user) .with((request) -> { request.addUserRole("admin"); request.addUserRole("user"); request.addUserRole("unmapped"); return request; }); this.mvc.perform(rolesRequest) .andExpect(content().string("ROLE_admin,ROLE_user")); // @formatter:on verify(this.spring.getContext().getBean(SecurityContextHolderStrategy.class), atLeastOnce()).getContext(); } @Test public void loginWhenUsingCustomAuthenticationDetailsSourceRefThenAuthenticationSourcesDetailsAccordingly() throws Exception { this.spring.configLocations(xml("CustomAuthenticationDetailsSourceRef")).autowire(); Object details = mock(Object.class); AuthenticationDetailsSource source = this.spring.getContext().getBean(AuthenticationDetailsSource.class); given(source.buildDetails(any(Object.class))).willReturn(details); RequestPostProcessor x509 = x509( "classpath:org/springframework/security/config/http/MiscHttpConfigTests-certificate.pem"); // @formatter:off this.mvc.perform(get("/details").with(userCredentials())) .andExpect(content().string(details.getClass().getName())); this.mvc.perform(get("/details").with(x509)) .andExpect(content().string(details.getClass().getName())); MockHttpServletRequestBuilder loginRequest = post("/login") .param("username", "user") .param("password", "password") .with(csrf()); MockHttpSession session = (MockHttpSession) this.mvc.perform(loginRequest) .andReturn() .getRequest() .getSession(false); this.mvc.perform(get("/details").session(session)) .andExpect(content().string(details.getClass().getName())); // @formatter:on } @Test public void loginWhenUsingJaasApiProvisionThenJaasSubjectContainsUsername() throws Exception { this.spring.configLocations(xml("Jaas")).autowire(); AuthorityGranter granter = this.spring.getContext().getBean(AuthorityGranter.class); given(granter.grant(any(Principal.class))).willReturn(new HashSet<>(Arrays.asList("USER"))); // @formatter:off this.mvc.perform(get("/username").with(userCredentials())) .andExpect(content().string("user")); // @formatter:on } @Test public void getWhenUsingCustomHttpFirewallThenFirewallIsInvoked() throws Exception { this.spring.configLocations(xml("HttpFirewall")).autowire(); FirewalledRequest request = new FirewalledRequest(new MockHttpServletRequest()) { @Override public void reset() { } }; HttpServletResponse response = new MockHttpServletResponse(); HttpFirewall firewall = this.spring.getContext().getBean(HttpFirewall.class); given(firewall.getFirewalledRequest(any(HttpServletRequest.class))).willReturn(request); given(firewall.getFirewalledResponse(any(HttpServletResponse.class))).willReturn(response); this.mvc.perform(get("/unprotected")); verify(firewall).getFirewalledRequest(any(HttpServletRequest.class)); verify(firewall).getFirewalledResponse(any(HttpServletResponse.class)); } @Test public void getWhenUsingCustomRequestRejectedHandlerThenRequestRejectedHandlerIsInvoked() throws Exception { this.spring.configLocations(xml("RequestRejectedHandler")).autowire(); HttpServletResponse response = new MockHttpServletResponse(); RequestRejectedException rejected = new RequestRejectedException("failed"); HttpFirewall firewall = this.spring.getContext().getBean(HttpFirewall.class); RequestRejectedHandler requestRejectedHandler = this.spring.getContext().getBean(RequestRejectedHandler.class); given(firewall.getFirewalledRequest(any(HttpServletRequest.class))).willThrow(rejected); this.mvc.perform(get("/unprotected")); verify(requestRejectedHandler).handle(any(), any(), any()); } @Test public void getWhenUsingCustomAccessDecisionManagerThenAuthorizesAccordingly() throws Exception { this.spring.configLocations(xml("CustomAccessDecisionManager")).autowire(); // @formatter:off this.mvc.perform(get("/unprotected").with(userCredentials())) .andExpect(status().isForbidden()); // @formatter:on } @Test public void asyncDispatchWhenCustomSecurityContextHolderStrategyThenUses() throws Exception { this.spring.configLocations(xml("WithSecurityContextHolderStrategy")).autowire(); // @formatter:off MockHttpServletRequestBuilder requestWithBob = get("/name").with(user("Bob")); MvcResult mvcResult = this.mvc.perform(requestWithBob) .andExpect(request().asyncStarted()) .andReturn(); this.mvc.perform(asyncDispatch(mvcResult)) .andExpect(status().isOk()) .andExpect(content().string("Bob")); // @formatter:on verify(this.spring.getContext().getBean(SecurityContextHolderStrategy.class), atLeastOnce()).getContext(); } /** * SEC-1893 */ @Test public void authenticateWhenUsingPortMapperThenRedirectsAppropriately() throws Exception { this.spring.configLocations(xml("PortsMappedRequiresHttps")).autowire(); // @formatter:off MockHttpSession session = (MockHttpSession) this.mvc.perform(get("https://localhost:9080/protected")) .andExpect(redirectedUrl("/login")) .andReturn() .getRequest() .getSession(false); MockHttpServletRequestBuilder loginRequest = post("/login") .param("username", "user") .param("password", "password") .session(session) .with(csrf()); session = (MockHttpSession) this.mvc.perform(loginRequest) .andExpect(RequestCacheResultMatcher.redirectToCachedRequest()) .andReturn() .getRequest() .getSession(false); this.mvc.perform(get("http://localhost:9080/protected").session(session)) .andExpect(redirectedUrl("https://localhost:9443/protected")); // @formatter:on } private void redirectLogsTo(OutputStream os, Class clazz) { Logger logger = (Logger) LoggerFactory.getLogger(clazz); Appender appender = mock(Appender.class); given(appender.isStarted()).willReturn(true); willAnswer(writeTo(os)).given(appender).doAppend(any(ILoggingEvent.class)); logger.addAppender(appender); } private Answer writeTo(OutputStream os) { return (invocation) -> { os.write(invocation.getArgument(0).toString().getBytes()); return null; }; } private void assertThatFiltersMatchExpectedAutoConfigList() { assertThatFiltersMatchExpectedAutoConfigList("/"); } private void assertThatFiltersMatchExpectedAutoConfigList(String url) { Iterator filters = getFilters(url).iterator(); assertThat(filters.next()).isInstanceOf(DisableEncodeUrlFilter.class); assertThat(filters.next()).isInstanceOf(SecurityContextHolderFilter.class); assertThat(filters.next()).isInstanceOf(WebAsyncManagerIntegrationFilter.class); assertThat(filters.next()).isInstanceOf(HeaderWriterFilter.class); assertThat(filters.next()).isInstanceOf(CsrfFilter.class); assertThat(filters.next()).isInstanceOf(LogoutFilter.class); assertThat(filters.next()).isInstanceOf(UsernamePasswordAuthenticationFilter.class); assertThat(filters.next()).isInstanceOf(DefaultResourcesFilter.class); assertThat(filters.next()).isInstanceOf(DefaultLoginPageGeneratingFilter.class); assertThat(filters.next()).isInstanceOf(DefaultLogoutPageGeneratingFilter.class); assertThat(filters.next()).isInstanceOf(BasicAuthenticationFilter.class); assertThat(filters.next()).isInstanceOf(RequestCacheAwareFilter.class); assertThat(filters.next()).isInstanceOf(SecurityContextHolderAwareRequestFilter.class); assertThat(filters.next()).isInstanceOf(AnonymousAuthenticationFilter.class); assertThat(filters.next()).isInstanceOf(ExceptionTranslationFilter.class); assertThat(filters.next()).isInstanceOf(AuthorizationFilter.class); } private T getFilter(Class filterClass) { return (T) getFilters("/").stream().filter(filterClass::isInstance).findFirst().orElse(null); } private List getFilters(String url) { FilterChainProxy proxy = this.spring.getContext().getBean(FilterChainProxy.class); return proxy.getFilters(url); } @NonNull private static RequestPostProcessor userCredentials() { return httpBasic("user", "password"); } @NonNull private static RequestPostProcessor adminCredentials() { return httpBasic("admin", "password"); } @NonNull private static RequestPostProcessor postCredentials() { return httpBasic("poster", "password"); } private static String xml(String configName) { return CONFIG_LOCATION_PREFIX + "-" + configName + ".xml"; } @RestController static class BasicController { @RequestMapping("/unprotected") String unprotected() { return "ok"; } @RequestMapping("/protected") String protectedMethod(@AuthenticationPrincipal String name) { return name; } } @RestController static class CustomKeyController { @GetMapping("/customKey") String customKey() { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (authentication != null && authentication instanceof AnonymousAuthenticationToken) { return String.valueOf(((AnonymousAuthenticationToken) authentication).getKeyHash()); } return null; } } @RestController static class AuthenticationController { @GetMapping("/password") String password(Authentication authentication) { return (String) authentication.getCredentials(); } @GetMapping("/roles") String roles(Authentication authentication) { return authentication.getAuthorities() .stream() .map(GrantedAuthority::getAuthority) .collect(Collectors.joining(",")); } @GetMapping("/details") String details(Authentication authentication) { return authentication.getDetails().getClass().getName(); } @GetMapping("/name") Callable name(Authentication authentication) { return () -> authentication.getName(); } } @RestController static class JaasController { @GetMapping("/username") String username() { Subject subject = Subject.current(); return subject.getPrincipals().iterator().next().getName(); } } public static class JaasLoginModule implements LoginModule { private Subject subject; @Override public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options) { this.subject = subject; } @Override public boolean login() { return this.subject.getPrincipals().add(() -> "user"); } @Override public boolean commit() { return true; } @Override public boolean abort() { return true; } @Override public boolean logout() { return true; } } static class MockAccessDecisionManager implements AccessDecisionManager { @Override public void decide(Authentication authentication, Object object, Collection configAttributes) throws AccessDeniedException, InsufficientAuthenticationException { throw new AccessDeniedException("teapot"); } @Override public boolean supports(ConfigAttribute attribute) { return true; } @Override public boolean supports(Class clazz) { return true; } } static class MockAuthenticationManager implements AuthenticationManager { @Override public Authentication authenticate(Authentication authentication) { return new TestingAuthenticationToken(authentication.getPrincipal(), authentication.getCredentials(), AuthorityUtils.createAuthorityList("ROLE_USER")); } } static class EncodeUrlDenyingHttpServletResponseWrapper extends HttpServletResponseWrapper { EncodeUrlDenyingHttpServletResponseWrapper(HttpServletResponse response) { super(response); } @Override public String encodeURL(String url) { throw new RuntimeException("Unexpected invocation of encodeURL"); } @Override public String encodeRedirectURL(String url) { throw new RuntimeException("Unexpected invocation of encodeURL"); } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/http/MultiHttpBlockConfigTests.java ================================================ /* * 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 org.springframework.security.config.http; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.stereotype.Controller; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; import org.springframework.web.bind.annotation.GetMapping; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Tests scenarios with multiple <http> elements. * * @author Luke Taylor */ @ExtendWith(SpringTestContextExtension.class) public class MultiHttpBlockConfigTests { private static final String CONFIG_LOCATION_PREFIX = "classpath:org/springframework/security/config/http/MultiHttpBlockConfigTests"; @Autowired MockMvc mvc; public final SpringTestContext spring = new SpringTestContext(this); @Test public void requestWhenUsingMutuallyExclusiveHttpElementsThenIsRoutedAccordingly() throws Exception { this.spring.configLocations(this.xml("DistinctHttpElements")).autowire(); // @formatter:off this.mvc.perform(get("/first").with(httpBasic("user", "password"))) .andExpect(status().isOk()); MockHttpServletRequestBuilder formLoginRequest = post("/second/login") .param("username", "user") .param("password", "password") .with(csrf()); this.mvc.perform(formLoginRequest) .andExpect(status().isFound()) .andExpect(redirectedUrl("/")); // @formatter:on } @Test public void configureWhenUsingDuplicateHttpElementsThenThrowsWiringException() { assertThatExceptionOfType(BeanCreationException.class) .isThrownBy(() -> this.spring.configLocations(this.xml("IdenticalHttpElements")).autowire()) .withCauseInstanceOf(IllegalArgumentException.class); } @Test public void configureWhenUsingIndenticallyPatternedHttpElementsThenThrowsWiringException() { assertThatExceptionOfType(BeanCreationException.class) .isThrownBy(() -> this.spring.configLocations(this.xml("IdenticallyPatternedHttpElements")).autowire()) .withCauseInstanceOf(IllegalArgumentException.class); } /** * SEC-1937 */ @Test public void requestWhenTargettingAuthenticationManagersToCorrespondingHttpElementsThenAuthenticationProceeds() throws Exception { this.spring.configLocations(this.xml("Sec1937")).autowire(); // @formatter:off MockHttpServletRequestBuilder basicLoginRequest = get("/first") .with(httpBasic("first", "password")) .with(csrf()); this.mvc.perform(basicLoginRequest) .andExpect(status().isOk()); MockHttpServletRequestBuilder formLoginRequest = post("/second/login") .param("username", "second") .param("password", "password") .with(csrf()); this.mvc.perform(formLoginRequest) .andExpect(redirectedUrl("/")); // @formatter:on } private String xml(String configName) { return CONFIG_LOCATION_PREFIX + "-" + configName + ".xml"; } @Controller static class BasicController { @GetMapping("/first") String first() { return "ok"; } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/http/NamespaceHttpBasicTests.java ================================================ /* * 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 org.springframework.security.config.http; import java.lang.reflect.Method; import java.util.Base64; import jakarta.servlet.Filter; import jakarta.servlet.http.HttpServletResponse; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mock; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.mock.web.MockFilterChain; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.security.config.util.InMemoryXmlApplicationContext; import org.springframework.security.core.context.SecurityContextHolderStrategy; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.verify; /** * @author Rob Winch */ public class NamespaceHttpBasicTests { @Mock Method method; MockHttpServletRequest request; MockHttpServletResponse response; MockFilterChain chain; ConfigurableApplicationContext context; Filter springSecurityFilterChain; @BeforeEach public void setup() { this.request = new MockHttpServletRequest("GET", ""); this.request.setMethod("GET"); this.response = new MockHttpServletResponse(); this.chain = new MockFilterChain(); } @AfterEach public void teardown() { if (this.context != null) { this.context.close(); } } // gh-3296 @Test public void httpBasicWithPasswordEncoder() throws Exception { // @formatter:off loadContext("\n" + " \n" + " \n" + "\n" + "\n" + "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n" + ""); // @formatter:on this.request.addHeader("Authorization", "Basic " + Base64.getEncoder().encodeToString("user:test".getBytes("UTF-8"))); this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); } @Test public void httpBasicCustomSecurityContextHolderStrategy() throws Exception { // @formatter:off loadContext("\n" + "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n" + "\n" + " \n" + " \n" + " \n" + ""); // @formatter:on this.request.addHeader("Authorization", "Basic " + Base64.getEncoder().encodeToString("user:test".getBytes("UTF-8"))); this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); verify(this.context.getBean(SecurityContextHolderStrategy.class), atLeastOnce()).getContext(); } // gh-4220 @Test public void httpBasicUnauthorizedOnDefault() throws Exception { // @formatter:off loadContext("\n" + " \n" + " \n" + "\n" + "\n" + ""); // @formatter:on this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); assertThat(this.response.getHeader("WWW-Authenticate")).isEqualTo("Basic realm=\"Realm\", charset=\"UTF-8\""); } private void loadContext(String context) { this.context = new InMemoryXmlApplicationContext(context); this.springSecurityFilterChain = this.context.getBean("springSecurityFilterChain", Filter.class); } } ================================================ FILE: config/src/test/java/org/springframework/security/config/http/OAuth2AuthorizedClientManagerRegistrarTests.java ================================================ /* * 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 org.springframework.security.config.http; import java.time.Duration; import java.time.Instant; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.config.oauth2.client.CommonOAuth2Provider; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.oauth2.client.AuthorizationCodeOAuth2AuthorizedClientProvider; import org.springframework.security.oauth2.client.ClientAuthorizationRequiredException; import org.springframework.security.oauth2.client.ClientCredentialsOAuth2AuthorizedClientProvider; import org.springframework.security.oauth2.client.JwtBearerOAuth2AuthorizedClientProvider; import org.springframework.security.oauth2.client.OAuth2AuthorizationContext; import org.springframework.security.oauth2.client.OAuth2AuthorizeRequest; import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager; import org.springframework.security.oauth2.client.RefreshTokenOAuth2AuthorizedClientProvider; import org.springframework.security.oauth2.client.TokenExchangeOAuth2AuthorizedClientProvider; import org.springframework.security.oauth2.client.endpoint.AbstractOAuth2AuthorizationGrantRequest; import org.springframework.security.oauth2.client.endpoint.JwtBearerGrantRequest; import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient; import org.springframework.security.oauth2.client.endpoint.OAuth2ClientCredentialsGrantRequest; import org.springframework.security.oauth2.client.endpoint.OAuth2RefreshTokenGrantRequest; import org.springframework.security.oauth2.client.endpoint.TokenExchangeGrantRequest; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository; import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.core.ClientAuthenticationMethod; import org.springframework.security.oauth2.core.OAuth2AccessToken; import org.springframework.security.oauth2.core.OAuth2AuthorizationException; import org.springframework.security.oauth2.core.OAuth2Error; import org.springframework.security.oauth2.core.TestOAuth2RefreshTokens; import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; import org.springframework.security.oauth2.core.endpoint.TestOAuth2AccessTokenResponses; import org.springframework.security.oauth2.jwt.JoseHeaderNames; import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.security.oauth2.jwt.JwtClaimNames; import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; /** * Tests for {@link OAuth2AuthorizedClientManagerRegistrar}. * * @author Steve Riesenberg */ public class OAuth2AuthorizedClientManagerRegistrarTests { private static final String CONFIG_LOCATION_PREFIX = "classpath:org/springframework/security/config/http/OAuth2AuthorizedClientManagerRegistrarTests"; private static OAuth2AccessTokenResponseClient MOCK_RESPONSE_CLIENT; public final SpringTestContext spring = new SpringTestContext(this); @Autowired private OAuth2AuthorizedClientManager authorizedClientManager; @Autowired private ClientRegistrationRepository clientRegistrationRepository; @Autowired private OAuth2AuthorizedClientRepository authorizedClientRepository; @Autowired(required = false) private AuthorizationCodeOAuth2AuthorizedClientProvider authorizationCodeAuthorizedClientProvider; private MockHttpServletRequest request; private MockHttpServletResponse response; @BeforeEach @SuppressWarnings("unchecked") public void setUp() { MOCK_RESPONSE_CLIENT = mock(OAuth2AccessTokenResponseClient.class); this.request = new MockHttpServletRequest(); this.response = new MockHttpServletResponse(); } @Test public void loadContextWhenOAuth2ClientEnabledThenConfigured() { this.spring.configLocations(xml("minimal")).autowire(); assertThat(this.authorizedClientManager).isNotNull(); } @Test public void authorizeWhenAuthorizationCodeAuthorizedClientProviderBeanThenUsed() { this.spring.configLocations(xml("providers")).autowire(); TestingAuthenticationToken authentication = new TestingAuthenticationToken("user", null); // @formatter:off OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest .withClientRegistrationId("google") .principal(authentication) .attribute(HttpServletRequest.class.getName(), this.request) .attribute(HttpServletResponse.class.getName(), this.response) .build(); assertThatExceptionOfType(ClientAuthorizationRequiredException.class) .isThrownBy(() -> this.authorizedClientManager.authorize(authorizeRequest)) .extracting(OAuth2AuthorizationException::getError) .extracting(OAuth2Error::getErrorCode) .isEqualTo("client_authorization_required"); // @formatter:on verify(this.authorizationCodeAuthorizedClientProvider).authorize(any(OAuth2AuthorizationContext.class)); } @Test public void authorizeWhenRefreshTokenAccessTokenResponseClientBeanThenUsed() { this.spring.configLocations(xml("clients")).autowire(); testRefreshTokenGrant(); } @Test public void authorizeWhenRefreshTokenAuthorizedClientProviderBeanThenUsed() { this.spring.configLocations(xml("providers")).autowire(); testRefreshTokenGrant(); } private void testRefreshTokenGrant() { OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build(); given(MOCK_RESPONSE_CLIENT.getTokenResponse(any(OAuth2RefreshTokenGrantRequest.class))) .willReturn(accessTokenResponse); TestingAuthenticationToken authentication = new TestingAuthenticationToken("user", null); ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId("google"); OAuth2AuthorizedClient existingAuthorizedClient = new OAuth2AuthorizedClient(clientRegistration, authentication.getName(), getExpiredAccessToken(), TestOAuth2RefreshTokens.refreshToken()); this.authorizedClientRepository.saveAuthorizedClient(existingAuthorizedClient, authentication, this.request, this.response); // @formatter:off OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest .withAuthorizedClient(existingAuthorizedClient) .principal(authentication) .attribute(HttpServletRequest.class.getName(), this.request) .attribute(HttpServletResponse.class.getName(), this.response) .build(); // @formatter:on OAuth2AuthorizedClient authorizedClient = this.authorizedClientManager.authorize(authorizeRequest); assertThat(authorizedClient).isNotNull(); ArgumentCaptor grantRequestCaptor = ArgumentCaptor .forClass(OAuth2RefreshTokenGrantRequest.class); verify(MOCK_RESPONSE_CLIENT).getTokenResponse(grantRequestCaptor.capture()); OAuth2RefreshTokenGrantRequest grantRequest = grantRequestCaptor.getValue(); assertThat(grantRequest.getClientRegistration().getRegistrationId()) .isEqualTo(clientRegistration.getRegistrationId()); assertThat(grantRequest.getGrantType()).isEqualTo(AuthorizationGrantType.REFRESH_TOKEN); assertThat(grantRequest.getAccessToken()).isEqualTo(existingAuthorizedClient.getAccessToken()); assertThat(grantRequest.getRefreshToken()).isEqualTo(existingAuthorizedClient.getRefreshToken()); } @Test public void authorizeWhenClientCredentialsAccessTokenResponseClientBeanThenUsed() { this.spring.configLocations(xml("clients")).autowire(); testClientCredentialsGrant(); } @Test public void authorizeWhenClientCredentialsAuthorizedClientProviderBeanThenUsed() { this.spring.configLocations(xml("providers")).autowire(); testClientCredentialsGrant(); } private void testClientCredentialsGrant() { OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build(); given(MOCK_RESPONSE_CLIENT.getTokenResponse(any(OAuth2ClientCredentialsGrantRequest.class))) .willReturn(accessTokenResponse); TestingAuthenticationToken authentication = new TestingAuthenticationToken("user", null); ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId("github"); // @formatter:off OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest .withClientRegistrationId(clientRegistration.getRegistrationId()) .principal(authentication) .attribute(HttpServletRequest.class.getName(), this.request) .attribute(HttpServletResponse.class.getName(), this.response) .build(); // @formatter:on OAuth2AuthorizedClient authorizedClient = this.authorizedClientManager.authorize(authorizeRequest); assertThat(authorizedClient).isNotNull(); ArgumentCaptor grantRequestCaptor = ArgumentCaptor .forClass(OAuth2ClientCredentialsGrantRequest.class); verify(MOCK_RESPONSE_CLIENT).getTokenResponse(grantRequestCaptor.capture()); OAuth2ClientCredentialsGrantRequest grantRequest = grantRequestCaptor.getValue(); assertThat(grantRequest.getClientRegistration().getRegistrationId()) .isEqualTo(clientRegistration.getRegistrationId()); assertThat(grantRequest.getGrantType()).isEqualTo(AuthorizationGrantType.CLIENT_CREDENTIALS); } @Test public void authorizeWhenJwtBearerAccessTokenResponseClientBeanThenUsed() { this.spring.configLocations(xml("clients")).autowire(); testJwtBearerGrant(); } @Test public void authorizeWhenJwtBearerAuthorizedClientProviderBeanThenUsed() { this.spring.configLocations(xml("providers")).autowire(); testJwtBearerGrant(); } private void testJwtBearerGrant() { OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build(); given(MOCK_RESPONSE_CLIENT.getTokenResponse(any(JwtBearerGrantRequest.class))).willReturn(accessTokenResponse); JwtAuthenticationToken authentication = new JwtAuthenticationToken(getJwt()); ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId("okta"); // @formatter:off OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest .withClientRegistrationId(clientRegistration.getRegistrationId()) .principal(authentication) .attribute(HttpServletRequest.class.getName(), this.request) .attribute(HttpServletResponse.class.getName(), this.response) .build(); // @formatter:on OAuth2AuthorizedClient authorizedClient = this.authorizedClientManager.authorize(authorizeRequest); assertThat(authorizedClient).isNotNull(); ArgumentCaptor grantRequestCaptor = ArgumentCaptor.forClass(JwtBearerGrantRequest.class); verify(MOCK_RESPONSE_CLIENT).getTokenResponse(grantRequestCaptor.capture()); JwtBearerGrantRequest grantRequest = grantRequestCaptor.getValue(); assertThat(grantRequest.getClientRegistration().getRegistrationId()) .isEqualTo(clientRegistration.getRegistrationId()); assertThat(grantRequest.getGrantType()).isEqualTo(AuthorizationGrantType.JWT_BEARER); assertThat(grantRequest.getJwt().getSubject()).isEqualTo("user"); } @Test public void authorizeWhenTokenExchangeAccessTokenResponseClientBeanThenUsed() { this.spring.configLocations(xml("clients")).autowire(); testTokenExchangeGrant(); } @Test public void authorizeWhenTokenExchangeAuthorizedClientProviderBeanThenUsed() { this.spring.configLocations(xml("providers")).autowire(); testTokenExchangeGrant(); } private void testTokenExchangeGrant() { OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build(); given(MOCK_RESPONSE_CLIENT.getTokenResponse(any(TokenExchangeGrantRequest.class))) .willReturn(accessTokenResponse); JwtAuthenticationToken authentication = new JwtAuthenticationToken(getJwt()); ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId("auth0"); // @formatter:off OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest .withClientRegistrationId(clientRegistration.getRegistrationId()) .principal(authentication) .attribute(HttpServletRequest.class.getName(), this.request) .attribute(HttpServletResponse.class.getName(), this.response) .build(); // @formatter:on OAuth2AuthorizedClient authorizedClient = this.authorizedClientManager.authorize(authorizeRequest); assertThat(authorizedClient).isNotNull(); ArgumentCaptor grantRequestCaptor = ArgumentCaptor .forClass(TokenExchangeGrantRequest.class); verify(MOCK_RESPONSE_CLIENT).getTokenResponse(grantRequestCaptor.capture()); TokenExchangeGrantRequest grantRequest = grantRequestCaptor.getValue(); assertThat(grantRequest.getClientRegistration().getRegistrationId()) .isEqualTo(clientRegistration.getRegistrationId()); assertThat(grantRequest.getGrantType()).isEqualTo(AuthorizationGrantType.TOKEN_EXCHANGE); assertThat(grantRequest.getSubjectToken()).isEqualTo(authentication.getToken()); } private static OAuth2AccessToken getExpiredAccessToken() { Instant expiresAt = Instant.now().minusSeconds(60); Instant issuedAt = expiresAt.minus(Duration.ofDays(1)); return new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, "scopes", issuedAt, expiresAt, new HashSet<>(Arrays.asList("read", "write"))); } private static Jwt getJwt() { Instant issuedAt = Instant.now(); return new Jwt("token", issuedAt, issuedAt.plusSeconds(300), Collections.singletonMap(JoseHeaderNames.ALG, "RS256"), Collections.singletonMap(JwtClaimNames.SUB, "user")); } private static String xml(String configName) { return CONFIG_LOCATION_PREFIX + "-" + configName + ".xml"; } public static List getClientRegistrations() { // @formatter:off return Arrays.asList( CommonOAuth2Provider.GOOGLE.getBuilder("google") .clientId("google-client-id") .clientSecret("google-client-secret") .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) .build(), CommonOAuth2Provider.GITHUB.getBuilder("github") .clientId("github-client-id") .clientSecret("github-client-secret") .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS) .build(), CommonOAuth2Provider.OKTA.getBuilder("okta") .clientId("okta-client-id") .clientSecret("okta-client-secret") .authorizationGrantType(AuthorizationGrantType.JWT_BEARER) .build(), ClientRegistration.withRegistrationId("auth0") .clientName("Auth0") .clientId("auth0-client-id") .clientSecret("auth0-client-secret") .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) .authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE) .scope("user.read", "user.write") .build()); // @formatter:on } public static AuthorizationCodeOAuth2AuthorizedClientProvider authorizationCode() { return spy(new AuthorizationCodeOAuth2AuthorizedClientProvider()); } public static RefreshTokenOAuth2AuthorizedClientProvider refreshToken() { RefreshTokenOAuth2AuthorizedClientProvider authorizedClientProvider = new RefreshTokenOAuth2AuthorizedClientProvider(); authorizedClientProvider.setAccessTokenResponseClient(refreshTokenAccessTokenResponseClient()); return authorizedClientProvider; } public static OAuth2AccessTokenResponseClient refreshTokenAccessTokenResponseClient() { return new MockAccessTokenResponseClient<>(); } public static ClientCredentialsOAuth2AuthorizedClientProvider clientCredentials() { ClientCredentialsOAuth2AuthorizedClientProvider authorizedClientProvider = new ClientCredentialsOAuth2AuthorizedClientProvider(); authorizedClientProvider.setAccessTokenResponseClient(clientCredentialsAccessTokenResponseClient()); return authorizedClientProvider; } public static OAuth2AccessTokenResponseClient clientCredentialsAccessTokenResponseClient() { return new MockAccessTokenResponseClient<>(); } public static JwtBearerOAuth2AuthorizedClientProvider jwtBearer() { JwtBearerOAuth2AuthorizedClientProvider authorizedClientProvider = new JwtBearerOAuth2AuthorizedClientProvider(); authorizedClientProvider.setAccessTokenResponseClient(jwtBearerAccessTokenResponseClient()); return authorizedClientProvider; } public static OAuth2AccessTokenResponseClient jwtBearerAccessTokenResponseClient() { return new MockAccessTokenResponseClient<>(); } public static TokenExchangeOAuth2AuthorizedClientProvider tokenExchange() { TokenExchangeOAuth2AuthorizedClientProvider authorizedClientProvider = new TokenExchangeOAuth2AuthorizedClientProvider(); authorizedClientProvider.setAccessTokenResponseClient(tokenExchangeAccessTokenResponseClient()); return authorizedClientProvider; } public static OAuth2AccessTokenResponseClient tokenExchangeAccessTokenResponseClient() { return new MockAccessTokenResponseClient<>(); } private static class MockAccessTokenResponseClient implements OAuth2AccessTokenResponseClient { @Override public OAuth2AccessTokenResponse getTokenResponse(T authorizationGrantRequest) { return MOCK_RESPONSE_CLIENT.getTokenResponse(authorizationGrantRequest); } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/http/OAuth2ClientBeanDefinitionParserTests.java ================================================ /* * 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 org.springframework.security.config.http; import java.util.HashMap; import java.util.Map; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.mock.web.MockHttpSession; import org.springframework.security.config.oauth2.client.CommonOAuth2Provider; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService; import org.springframework.security.oauth2.client.annotation.RegisteredOAuth2AuthorizedClient; import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient; import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository; import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver; import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository; import org.springframework.security.oauth2.core.TestOAuth2AccessTokens; import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; import org.springframework.security.oauth2.core.endpoint.TestOAuth2AccessTokenResponses; import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners; import org.springframework.security.test.context.support.WithMockUser; import org.springframework.security.web.RedirectStrategy; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.ui.Model; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.verify; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Tests for {@link OAuth2ClientBeanDefinitionParser}. * * @author Joe Grandja */ @ExtendWith({ SpringExtension.class, SpringTestContextExtension.class }) @SecurityTestExecutionListeners public class OAuth2ClientBeanDefinitionParserTests { private static final String CONFIG_LOCATION_PREFIX = "classpath:org/springframework/security/config/http/OAuth2ClientBeanDefinitionParserTests"; public final SpringTestContext spring = new SpringTestContext(this); @Autowired private ClientRegistrationRepository clientRegistrationRepository; @Autowired(required = false) private OAuth2AuthorizedClientRepository authorizedClientRepository; @Autowired(required = false) private OAuth2AuthorizedClientService authorizedClientService; @Autowired(required = false) private AuthorizationRequestRepository authorizationRequestRepository; @Autowired(required = false) private OAuth2AuthorizationRequestResolver authorizationRequestResolver; @Autowired(required = false) private RedirectStrategy authorizationRedirectStrategy; @Autowired(required = false) private OAuth2AccessTokenResponseClient accessTokenResponseClient; @Autowired private MockMvc mvc; @Test public void requestWhenAuthorizeThenRedirect() throws Exception { this.spring.configLocations(xml("Minimal")).autowire(); // @formatter:off MvcResult result = this.mvc.perform(get("/oauth2/authorization/google")) .andExpect(status().is3xxRedirection()) .andReturn(); // @formatter:on assertThat(result.getResponse().getRedirectedUrl()).matches("https://accounts.google.com/o/oauth2/v2/auth\\?" + "response_type=code&client_id=google-client-id&" + "scope=scope1%20scope2&state=.{15,}&redirect_uri=http://localhost/callback/google&code_challenge=([a-zA-Z0-9\\-\\.\\_\\~]){43}&code_challenge_method=S256"); } @Test public void requestWhenCustomClientRegistrationRepositoryThenCalled() throws Exception { this.spring.configLocations(xml("CustomClientRegistrationRepository")).autowire(); // @formatter:off ClientRegistration clientRegistration = CommonOAuth2Provider.GOOGLE.getBuilder("google") .clientId("google-client-id") .clientSecret("google-client-secret") .redirectUri("http://localhost/callback/google") .scope("scope1", "scope2") .build(); // @formatter:on given(this.clientRegistrationRepository.findByRegistrationId(any())).willReturn(clientRegistration); // @formatter:off MvcResult result = this.mvc.perform(get("/oauth2/authorization/google")) .andExpect(status().is3xxRedirection()) .andReturn(); // @formatter:on assertThat(result.getResponse().getRedirectedUrl()).matches("https://accounts.google.com/o/oauth2/v2/auth\\?" + "response_type=code&client_id=google-client-id&" + "scope=scope1%20scope2&state=.{15,}&redirect_uri=http://localhost/callback/google&code_challenge=([a-zA-Z0-9\\-\\.\\_\\~]){43}&code_challenge_method=S256"); verify(this.clientRegistrationRepository).findByRegistrationId(any()); } @Test public void requestWhenCustomAuthorizationRequestResolverThenCalled() throws Exception { this.spring.configLocations(xml("CustomConfiguration")).autowire(); ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId("google"); OAuth2AuthorizationRequest authorizationRequest = createAuthorizationRequest(clientRegistration); given(this.authorizationRequestResolver.resolve(any())).willReturn(authorizationRequest); // @formatter:off this.mvc.perform(get("/oauth2/authorization/google")) .andExpect(status().is3xxRedirection()) .andExpect(redirectedUrl("https://accounts.google.com/o/oauth2/v2/auth?" + "response_type=code&client_id=google-client-id&" + "scope=scope1%20scope2&state=state&redirect_uri=http://localhost/callback/google")); // @formatter:on verify(this.authorizationRequestResolver).resolve(any()); } @Test public void requestWhenCustomAuthorizationRedirectStrategyThenCalled() throws Exception { this.spring.configLocations(xml("CustomAuthorizationRedirectStrategy")).autowire(); // @formatter:off this.mvc.perform(get("/oauth2/authorization/google")) .andExpect(status().isOk()); // @formatter:on verify(this.authorizationRedirectStrategy).sendRedirect(any(), any(), anyString()); } @Test public void requestWhenAuthorizationResponseMatchThenProcess() throws Exception { this.spring.configLocations(xml("CustomConfiguration")).autowire(); ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId("google"); OAuth2AuthorizationRequest authorizationRequest = createAuthorizationRequest(clientRegistration); given(this.authorizationRequestRepository.loadAuthorizationRequest(any())).willReturn(authorizationRequest); given(this.authorizationRequestRepository.removeAuthorizationRequest(any(), any())) .willReturn(authorizationRequest); OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build(); given(this.accessTokenResponseClient.getTokenResponse(any())).willReturn(accessTokenResponse); MultiValueMap params = new LinkedMultiValueMap<>(); params.add("code", "code123"); params.add("state", authorizationRequest.getState()); // @formatter:off this.mvc.perform(get(authorizationRequest.getRedirectUri()).params(params)) .andExpect(status().is3xxRedirection()) .andExpect(redirectedUrl(authorizationRequest.getRedirectUri())); // @formatter:on ArgumentCaptor authorizedClientCaptor = ArgumentCaptor .forClass(OAuth2AuthorizedClient.class); verify(this.authorizedClientRepository).saveAuthorizedClient(authorizedClientCaptor.capture(), any(), any(), any()); OAuth2AuthorizedClient authorizedClient = authorizedClientCaptor.getValue(); assertThat(authorizedClient.getClientRegistration()).isEqualTo(clientRegistration); assertThat(authorizedClient.getAccessToken()).isEqualTo(accessTokenResponse.getAccessToken()); } @WithMockUser @Test public void requestWhenCustomAuthorizedClientServiceThenCalled() throws Exception { this.spring.configLocations(xml("CustomAuthorizedClientService")).autowire(); ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId("google"); OAuth2AuthorizationRequest authorizationRequest = createAuthorizationRequest(clientRegistration); given(this.authorizationRequestRepository.loadAuthorizationRequest(any())).willReturn(authorizationRequest); given(this.authorizationRequestRepository.removeAuthorizationRequest(any(), any())) .willReturn(authorizationRequest); OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build(); given(this.accessTokenResponseClient.getTokenResponse(any())).willReturn(accessTokenResponse); MultiValueMap params = new LinkedMultiValueMap<>(); params.add("code", "code123"); params.add("state", authorizationRequest.getState()); // @formatter:off this.mvc.perform(get(authorizationRequest.getRedirectUri()).params(params)) .andExpect(status().is3xxRedirection()) .andExpect(redirectedUrl(authorizationRequest.getRedirectUri())); // @formatter:on verify(this.authorizedClientService).saveAuthorizedClient(any(), any()); } @WithMockUser @Test public void requestWhenAuthorizedClientFoundThenMethodArgumentResolved() throws Exception { this.spring.configLocations(xml("AuthorizedClientArgumentResolver")).autowire(); ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId("google"); OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(clientRegistration, "user", TestOAuth2AccessTokens.noScopes()); MockHttpServletRequest request = new MockHttpServletRequest(); MockHttpServletResponse response = new MockHttpServletResponse(); this.authorizedClientRepository.saveAuthorizedClient(authorizedClient, null, request, response); this.mvc.perform(get("/authorized-client").session((MockHttpSession) request.getSession())) .andExpect(status().isOk()) .andExpect(content().string("resolved")); } private static OAuth2AuthorizationRequest createAuthorizationRequest(ClientRegistration clientRegistration) { Map attributes = new HashMap<>(); attributes.put(OAuth2ParameterNames.REGISTRATION_ID, clientRegistration.getRegistrationId()); // @formatter:off return OAuth2AuthorizationRequest.authorizationCode() .authorizationUri(clientRegistration.getProviderDetails().getAuthorizationUri()) .clientId(clientRegistration.getClientId()).redirectUri(clientRegistration.getRedirectUri()) .scopes(clientRegistration.getScopes()) .state("state") .attributes(attributes) .build(); // @formatter:on } private static String xml(String configName) { return CONFIG_LOCATION_PREFIX + "-" + configName + ".xml"; } @RestController static class AuthorizedClientController { @GetMapping("/authorized-client") String authorizedClient(Model model, @RegisteredOAuth2AuthorizedClient("google") OAuth2AuthorizedClient authorizedClient) { return (authorizedClient != null) ? "resolved" : "not-resolved"; } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/http/OAuth2LoginBeanDefinitionParserTests.java ================================================ /* * 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 org.springframework.security.config.http; import java.util.Collection; import java.util.HashMap; import java.util.Map; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationListener; import org.springframework.http.MediaType; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.mock.web.MockHttpSession; import org.springframework.security.authentication.SecurityAssertions; import org.springframework.security.authentication.event.AuthenticationSuccessEvent; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper; import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService; import org.springframework.security.oauth2.client.annotation.RegisteredOAuth2AuthorizedClient; import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient; import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; import org.springframework.security.oauth2.client.registration.TestClientRegistrations; import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; import org.springframework.security.oauth2.client.userinfo.OAuth2UserService; import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository; import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver; import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository; import org.springframework.security.oauth2.core.OAuth2AuthenticationException; import org.springframework.security.oauth2.core.TestOAuth2AccessTokens; import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; import org.springframework.security.oauth2.core.endpoint.TestOAuth2AccessTokenResponses; import org.springframework.security.oauth2.core.endpoint.TestOAuth2AuthorizationRequests; import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser; import org.springframework.security.oauth2.core.oidc.user.OidcUser; import org.springframework.security.oauth2.core.oidc.user.TestOidcUsers; import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.security.oauth2.core.user.TestOAuth2Users; import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.security.oauth2.jwt.JwtDecoderFactory; import org.springframework.security.oauth2.jwt.TestJwts; import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners; import org.springframework.security.test.context.support.WithMockUser; import org.springframework.security.web.RedirectStrategy; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.security.web.savedrequest.RequestCache; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.ui.Model; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Tests for {@link OAuth2LoginBeanDefinitionParser}. * * @author Ruby Hartono */ @ExtendWith({ SpringExtension.class, SpringTestContextExtension.class }) @SecurityTestExecutionListeners public class OAuth2LoginBeanDefinitionParserTests { private static final String CONFIG_LOCATION_PREFIX = "classpath:org/springframework/security/config/http/OAuth2LoginBeanDefinitionParserTests"; public final SpringTestContext spring = new SpringTestContext(this); @Autowired private ClientRegistrationRepository clientRegistrationRepository; @Autowired(required = false) private OAuth2AuthorizedClientRepository authorizedClientRepository; @Autowired(required = false) private OAuth2AuthorizedClientService authorizedClientService; @Autowired(required = false) private ApplicationListener authenticationSuccessListener; @Autowired(required = false) private AuthorizationRequestRepository authorizationRequestRepository; @Autowired(required = false) private OAuth2AuthorizationRequestResolver authorizationRequestResolver; @Autowired(required = false) private RedirectStrategy authorizationRedirectStrategy; @Autowired(required = false) private OAuth2AccessTokenResponseClient accessTokenResponseClient; @Autowired(required = false) private OAuth2UserService oauth2UserService; @Autowired(required = false) private OAuth2UserService oidcUserService; @Autowired(required = false) private JwtDecoderFactory jwtDecoderFactory; @Autowired(required = false) private GrantedAuthoritiesMapper userAuthoritiesMapper; @Autowired(required = false) private AuthenticationFailureHandler authenticationFailureHandler; @Autowired(required = false) private AuthenticationSuccessHandler authenticationSuccessHandler; @Autowired(required = false) private RequestCache requestCache; @Autowired(required = false) private SecurityContextHolderStrategy securityContextHolderStrategy; @Autowired private MockMvc mvc; @Test public void requestLoginWhenMultiClientRegistrationThenReturnLoginPageWithClients() throws Exception { this.spring.configLocations(this.xml("MultiClientRegistration")).autowire(); // @formatter:off MvcResult result = this.mvc.perform(get("/login")) .andExpect(status().is2xxSuccessful()) .andReturn(); // @formatter:on assertThat(result.getResponse().getContentAsString()) .contains("Google"); assertThat(result.getResponse().getContentAsString()) .contains("Github"); } // gh-5347 @Test public void requestWhenSingleClientRegistrationThenAutoRedirect() throws Exception { this.spring.configLocations(this.xml("SingleClientRegistration")).autowire(); // @formatter:off this.mvc.perform(get("/")) .andExpect(status().is3xxRedirection()) .andExpect(redirectedUrl("/oauth2/authorization/google-login")); // @formatter:on verify(this.requestCache).saveRequest(any(), any()); } // gh-5347 @Test public void requestWhenSingleClientRegistrationAndRequestFaviconNotAuthenticatedThenRedirectDefaultLoginPage() throws Exception { this.spring.configLocations(this.xml("SingleClientRegistration")).autowire(); // @formatter:off this.mvc.perform(get("/favicon.ico").accept(new MediaType("image", "*"))) .andExpect(status().is3xxRedirection()) .andExpect(redirectedUrl("/login")); // @formatter:on } // gh-6812 @Test public void requestWhenSingleClientRegistrationAndRequestXHRNotAuthenticatedThenDoesNotRedirectForAuthorization() throws Exception { this.spring.configLocations(this.xml("SingleClientRegistration")).autowire(); // @formatter:off this.mvc.perform(get("/").header("X-Requested-With", "XMLHttpRequest")) .andExpect(status().is3xxRedirection()) .andExpect(redirectedUrl("/login")); // @formatter:on } @Test public void requestWhenAuthorizationRequestNotFoundThenThrowAuthenticationException() throws Exception { this.spring.configLocations(this.xml("SingleClientRegistration-WithCustomAuthenticationFailureHandler")) .autowire(); MultiValueMap params = new LinkedMultiValueMap<>(); params.add("code", "code123"); params.add("state", "state123"); this.mvc.perform(get("/login/oauth2/code/google").params(params)); ArgumentCaptor exceptionCaptor = ArgumentCaptor .forClass(AuthenticationException.class); verify(this.authenticationFailureHandler).onAuthenticationFailure(any(), any(), exceptionCaptor.capture()); AuthenticationException exception = exceptionCaptor.getValue(); assertThat(exception).isInstanceOf(OAuth2AuthenticationException.class); assertThat(((OAuth2AuthenticationException) exception).getError().getErrorCode()) .isEqualTo("authorization_request_not_found"); } @Test public void requestWhenAuthorizationResponseValidThenAuthenticate() throws Exception { this.spring.configLocations(this.xml("MultiClientRegistration-WithCustomConfiguration")).autowire(); Map attributes = new HashMap<>(); attributes.put(OAuth2ParameterNames.REGISTRATION_ID, "github-login"); OAuth2AuthorizationRequest authorizationRequest = TestOAuth2AuthorizationRequests.request() .attributes(attributes) .build(); given(this.authorizationRequestRepository.removeAuthorizationRequest(any(), any())) .willReturn(authorizationRequest); OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build(); given(this.accessTokenResponseClient.getTokenResponse(any())).willReturn(accessTokenResponse); OAuth2User oauth2User = TestOAuth2Users.create(); given(this.oauth2UserService.loadUser(any())).willReturn(oauth2User); MultiValueMap params = new LinkedMultiValueMap<>(); params.add("code", "code123"); params.add("state", authorizationRequest.getState()); // @formatter:off this.mvc.perform(get("/login/oauth2/code/github-login").params(params)) .andExpect(status().is2xxSuccessful()); // @formatter:on ArgumentCaptor authenticationCaptor = ArgumentCaptor.forClass(Authentication.class); verify(this.authenticationSuccessHandler).onAuthenticationSuccess(any(), any(), authenticationCaptor.capture()); Authentication authentication = authenticationCaptor.getValue(); assertThat(authentication.getPrincipal()).isInstanceOf(OAuth2User.class); } // gh-6009 @Test public void requestWhenAuthorizationResponseValidThenAuthenticationSuccessEventPublished() throws Exception { this.spring.configLocations(this.xml("MultiClientRegistration-WithCustomConfiguration")).autowire(); Map attributes = new HashMap<>(); attributes.put(OAuth2ParameterNames.REGISTRATION_ID, "github-login"); OAuth2AuthorizationRequest authorizationRequest = TestOAuth2AuthorizationRequests.request() .attributes(attributes) .build(); given(this.authorizationRequestRepository.removeAuthorizationRequest(any(), any())) .willReturn(authorizationRequest); OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build(); given(this.accessTokenResponseClient.getTokenResponse(any())).willReturn(accessTokenResponse); OAuth2User oauth2User = TestOAuth2Users.create(); given(this.oauth2UserService.loadUser(any())).willReturn(oauth2User); MultiValueMap params = new LinkedMultiValueMap<>(); params.add("code", "code123"); params.add("state", authorizationRequest.getState()); this.mvc.perform(get("/login/oauth2/code/github-login").params(params)); verify(this.authenticationSuccessListener).onApplicationEvent(any(AuthenticationSuccessEvent.class)); } @Test public void requestWhenOidcAuthenticationResponseValidThenJwtDecoderFactoryCalled() throws Exception { this.spring.configLocations(this.xml("SingleClientRegistration-WithJwtDecoderFactoryAndDefaultSuccessHandler")) .autowire(); Map attributes = new HashMap<>(); attributes.put(OAuth2ParameterNames.REGISTRATION_ID, "google-login"); OAuth2AuthorizationRequest authorizationRequest = TestOAuth2AuthorizationRequests.oidcRequest() .attributes(attributes) .build(); given(this.authorizationRequestRepository.removeAuthorizationRequest(any(), any())) .willReturn(authorizationRequest); OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.oidcAccessTokenResponse() .build(); given(this.accessTokenResponseClient.getTokenResponse(any())).willReturn(accessTokenResponse); Jwt jwt = TestJwts.user(); given(this.jwtDecoderFactory.createDecoder(any())).willReturn((token) -> jwt); DefaultOidcUser oidcUser = TestOidcUsers.create(); given(this.oidcUserService.loadUser(any())).willReturn(oidcUser); MultiValueMap params = new LinkedMultiValueMap<>(); params.add("code", "code123"); params.add("state", authorizationRequest.getState()); // @formatter:off this.mvc.perform(get("/login/oauth2/code/google-login").params(params)) .andExpect(status().is3xxRedirection()) .andExpect(redirectedUrl("/")); // @formatter:on verify(this.jwtDecoderFactory).createDecoder(any()); verify(this.requestCache).getRequest(any(), any()); } @SuppressWarnings({ "unchecked", "rawtypes" }) @Test public void requestWhenCustomGrantedAuthoritiesMapperThenCalled() throws Exception { this.spring.configLocations(this.xml("MultiClientRegistration-WithCustomGrantedAuthorities")).autowire(); Map attributes = new HashMap<>(); attributes.put(OAuth2ParameterNames.REGISTRATION_ID, "github-login"); OAuth2AuthorizationRequest authorizationRequest = TestOAuth2AuthorizationRequests.request() .attributes(attributes) .build(); given(this.authorizationRequestRepository.removeAuthorizationRequest(any(), any())) .willReturn(authorizationRequest); OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build(); given(this.accessTokenResponseClient.getTokenResponse(any())).willReturn(accessTokenResponse); OAuth2User oauth2User = TestOAuth2Users.create(); given(this.oauth2UserService.loadUser(any())).willReturn(oauth2User); given(this.userAuthoritiesMapper.mapAuthorities(any())) .willReturn((Collection) AuthorityUtils.createAuthorityList("ROLE_OAUTH2_USER")); MultiValueMap params = new LinkedMultiValueMap<>(); params.add("code", "code123"); params.add("state", authorizationRequest.getState()); this.mvc.perform(get("/login/oauth2/code/github-login").params(params)).andExpect(status().is2xxSuccessful()); ArgumentCaptor authenticationCaptor = ArgumentCaptor.forClass(Authentication.class); verify(this.authenticationSuccessHandler).onAuthenticationSuccess(any(), any(), authenticationCaptor.capture()); Authentication authentication = authenticationCaptor.getValue(); assertThat(authentication.getPrincipal()).isInstanceOf(OAuth2User.class); SecurityAssertions.assertThat(authentication) .roles() .hasSize(1) .first() .isInstanceOf(SimpleGrantedAuthority.class) .hasToString("ROLE_OAUTH2_USER"); // re-setup for OIDC test attributes = new HashMap<>(); attributes.put(OAuth2ParameterNames.REGISTRATION_ID, "google-login"); authorizationRequest = TestOAuth2AuthorizationRequests.oidcRequest().attributes(attributes).build(); given(this.authorizationRequestRepository.removeAuthorizationRequest(any(), any())) .willReturn(authorizationRequest); accessTokenResponse = TestOAuth2AccessTokenResponses.oidcAccessTokenResponse().build(); given(this.accessTokenResponseClient.getTokenResponse(any())).willReturn(accessTokenResponse); Jwt jwt = TestJwts.user(); given(this.jwtDecoderFactory.createDecoder(any())).willReturn((token) -> jwt); DefaultOidcUser oidcUser = TestOidcUsers.create(); given(this.oidcUserService.loadUser(any())).willReturn(oidcUser); given(this.userAuthoritiesMapper.mapAuthorities(any())) .willReturn((Collection) AuthorityUtils.createAuthorityList("ROLE_OIDC_USER")); // @formatter:off this.mvc.perform(get("/login/oauth2/code/google-login").params(params)) .andExpect(status().is2xxSuccessful()); // @formatter:on authenticationCaptor = ArgumentCaptor.forClass(Authentication.class); verify(this.authenticationSuccessHandler, times(2)).onAuthenticationSuccess(any(), any(), authenticationCaptor.capture()); authentication = authenticationCaptor.getValue(); assertThat(authentication.getPrincipal()).isInstanceOf(OidcUser.class); assertThat(authentication.getAuthorities()).hasSize(1); assertThat(authentication.getAuthorities()).first() .isInstanceOf(SimpleGrantedAuthority.class) .hasToString("ROLE_OIDC_USER"); } // gh-5488 @Test public void requestWhenCustomLoginProcessingUrlThenProcessAuthentication() throws Exception { this.spring.configLocations(this.xml("MultiClientRegistration-WithCustomLoginProcessingUrl")).autowire(); Map attributes = new HashMap<>(); attributes.put(OAuth2ParameterNames.REGISTRATION_ID, "github-login"); OAuth2AuthorizationRequest authorizationRequest = TestOAuth2AuthorizationRequests.request() .attributes(attributes) .build(); given(this.authorizationRequestRepository.removeAuthorizationRequest(any(), any())) .willReturn(authorizationRequest); OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build(); given(this.accessTokenResponseClient.getTokenResponse(any())).willReturn(accessTokenResponse); OAuth2User oauth2User = TestOAuth2Users.create(); given(this.oauth2UserService.loadUser(any())).willReturn(oauth2User); MultiValueMap params = new LinkedMultiValueMap<>(); params.add("code", "code123"); params.add("state", authorizationRequest.getState()); // @formatter:off this.mvc.perform(get("/login/oauth2/github-login").params(params)) .andExpect(status().is2xxSuccessful()); // @formatter:on ArgumentCaptor authenticationCaptor = ArgumentCaptor.forClass(Authentication.class); verify(this.authenticationSuccessHandler).onAuthenticationSuccess(any(), any(), authenticationCaptor.capture()); Authentication authentication = authenticationCaptor.getValue(); assertThat(authentication.getPrincipal()).isInstanceOf(OAuth2User.class); } // gh-5521 @Test public void requestWhenCustomAuthorizationRequestResolverThenCalled() throws Exception { this.spring.configLocations(this.xml("SingleClientRegistration-WithCustomAuthorizationRequestResolver")) .autowire(); // @formatter:off this.mvc.perform(get("/oauth2/authorization/google-login")) .andExpect(status().is3xxRedirection()); // @formatter:on verify(this.authorizationRequestResolver).resolve(any()); } @Test public void requestWhenCustomAuthorizationRedirectStrategyThenCalled() throws Exception { this.spring.configLocations(this.xml("SingleClientRegistration-WithCustomAuthorizationRedirectStrategy")) .autowire(); // @formatter:off this.mvc.perform(get("/oauth2/authorization/google-login")) .andExpect(status().isOk()); // @formatter:on verify(this.authorizationRedirectStrategy).sendRedirect(any(), any(), anyString()); } // gh-5347 @Test public void requestWhenMultiClientRegistrationThenRedirectDefaultLoginPage() throws Exception { this.spring.configLocations(this.xml("MultiClientRegistration")).autowire(); // @formatter:off this.mvc.perform(get("/")) .andExpect(status().is3xxRedirection()) .andExpect(redirectedUrl("/login")); // @formatter:on } @Test public void requestWhenCustomLoginPageThenRedirectCustomLoginPage() throws Exception { this.spring.configLocations(this.xml("SingleClientRegistration-WithCustomLoginPage")).autowire(); // @formatter:off this.mvc.perform(get("/")) .andExpect(status().is3xxRedirection()) .andExpect(redirectedUrl("/custom-login")); // @formatter:on } // gh-6802 @Test public void requestWhenSingleClientRegistrationAndFormLoginConfiguredThenRedirectDefaultLoginPage() throws Exception { this.spring.configLocations(this.xml("SingleClientRegistration-WithFormLogin")).autowire(); // @formatter:off this.mvc.perform(get("/")) .andExpect(status().is3xxRedirection()) .andExpect(redirectedUrl("/login")); // @formatter:on } @Test public void requestWhenCustomClientRegistrationRepositoryThenCalled() throws Exception { this.spring.configLocations(this.xml("WithCustomClientRegistrationRepository")).autowire(); ClientRegistration clientRegistration = TestClientRegistrations.clientRegistration().build(); given(this.clientRegistrationRepository.findByRegistrationId(any())).willReturn(clientRegistration); Map attributes = new HashMap<>(); attributes.put(OAuth2ParameterNames.REGISTRATION_ID, clientRegistration.getRegistrationId()); OAuth2AuthorizationRequest authorizationRequest = TestOAuth2AuthorizationRequests.request() .attributes(attributes) .build(); given(this.authorizationRequestRepository.removeAuthorizationRequest(any(), any())) .willReturn(authorizationRequest); OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build(); given(this.accessTokenResponseClient.getTokenResponse(any())).willReturn(accessTokenResponse); OAuth2User oauth2User = TestOAuth2Users.create(); given(this.oauth2UserService.loadUser(any())).willReturn(oauth2User); MultiValueMap params = new LinkedMultiValueMap<>(); params.add("code", "code123"); params.add("state", authorizationRequest.getState()); this.mvc.perform(get("/login/oauth2/code/" + clientRegistration.getRegistrationId()).params(params)); verify(this.clientRegistrationRepository).findByRegistrationId(clientRegistration.getRegistrationId()); } @Test public void requestWhenCustomAuthorizedClientRepositoryThenCalled() throws Exception { this.spring.configLocations(this.xml("WithCustomAuthorizedClientRepository")).autowire(); ClientRegistration clientRegistration = TestClientRegistrations.clientRegistration().build(); given(this.clientRegistrationRepository.findByRegistrationId(any())).willReturn(clientRegistration); Map attributes = new HashMap<>(); attributes.put(OAuth2ParameterNames.REGISTRATION_ID, clientRegistration.getRegistrationId()); OAuth2AuthorizationRequest authorizationRequest = TestOAuth2AuthorizationRequests.request() .attributes(attributes) .build(); given(this.authorizationRequestRepository.removeAuthorizationRequest(any(), any())) .willReturn(authorizationRequest); OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build(); given(this.accessTokenResponseClient.getTokenResponse(any())).willReturn(accessTokenResponse); OAuth2User oauth2User = TestOAuth2Users.create(); given(this.oauth2UserService.loadUser(any())).willReturn(oauth2User); MultiValueMap params = new LinkedMultiValueMap<>(); params.add("code", "code123"); params.add("state", authorizationRequest.getState()); this.mvc.perform(get("/login/oauth2/code/" + clientRegistration.getRegistrationId()).params(params)); verify(this.authorizedClientRepository).saveAuthorizedClient(any(), any(), any(), any()); } @Test public void requestWhenCustomAuthorizedClientServiceThenCalled() throws Exception { this.spring.configLocations(this.xml("WithCustomAuthorizedClientService")).autowire(); ClientRegistration clientRegistration = TestClientRegistrations.clientRegistration().build(); given(this.clientRegistrationRepository.findByRegistrationId(any())).willReturn(clientRegistration); Map attributes = new HashMap<>(); attributes.put(OAuth2ParameterNames.REGISTRATION_ID, clientRegistration.getRegistrationId()); OAuth2AuthorizationRequest authorizationRequest = TestOAuth2AuthorizationRequests.request() .attributes(attributes) .build(); given(this.authorizationRequestRepository.removeAuthorizationRequest(any(), any())) .willReturn(authorizationRequest); OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build(); given(this.accessTokenResponseClient.getTokenResponse(any())).willReturn(accessTokenResponse); OAuth2User oauth2User = TestOAuth2Users.create(); given(this.oauth2UserService.loadUser(any())).willReturn(oauth2User); MultiValueMap params = new LinkedMultiValueMap<>(); params.add("code", "code123"); params.add("state", authorizationRequest.getState()); this.mvc.perform(get("/login/oauth2/code/" + clientRegistration.getRegistrationId()).params(params)); verify(this.authorizedClientService).saveAuthorizedClient(any(), any()); } @Test public void requestWhenCustomSecurityContextHolderStrategyThenCalled() throws Exception { this.spring.configLocations(this.xml("WithCustomSecurityContextHolderStrategy")).autowire(); ClientRegistration clientRegistration = TestClientRegistrations.clientRegistration().build(); given(this.clientRegistrationRepository.findByRegistrationId(any())).willReturn(clientRegistration); Map attributes = new HashMap<>(); attributes.put(OAuth2ParameterNames.REGISTRATION_ID, clientRegistration.getRegistrationId()); OAuth2AuthorizationRequest authorizationRequest = TestOAuth2AuthorizationRequests.request() .attributes(attributes) .build(); given(this.authorizationRequestRepository.removeAuthorizationRequest(any(), any())) .willReturn(authorizationRequest); OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build(); given(this.accessTokenResponseClient.getTokenResponse(any())).willReturn(accessTokenResponse); OAuth2User oauth2User = TestOAuth2Users.create(); given(this.oauth2UserService.loadUser(any())).willReturn(oauth2User); MultiValueMap params = new LinkedMultiValueMap<>(); params.add("code", "code123"); params.add("state", authorizationRequest.getState()); this.mvc.perform(get("/login/oauth2/code/" + clientRegistration.getRegistrationId()).params(params)); verify(this.securityContextHolderStrategy, atLeastOnce()).getContext(); } @WithMockUser @Test public void requestWhenAuthorizedClientFoundThenMethodArgumentResolved() throws Exception { this.spring.configLocations(xml("AuthorizedClientArgumentResolver")).autowire(); ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId("google-login"); OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(clientRegistration, "user", TestOAuth2AccessTokens.noScopes()); MockHttpServletRequest request = new MockHttpServletRequest(); MockHttpServletResponse response = new MockHttpServletResponse(); this.authorizedClientRepository.saveAuthorizedClient(authorizedClient, null, request, response); // @formatter:off this.mvc.perform(get("/authorized-client").session((MockHttpSession) request.getSession())) .andExpect(status().isOk()) .andExpect(content().string("resolved")); // @formatter:on } private String xml(String configName) { return CONFIG_LOCATION_PREFIX + "-" + configName + ".xml"; } @RestController static class AuthorizedClientController { @GetMapping("/authorized-client") String authorizedClient(Model model, @RegisteredOAuth2AuthorizedClient("google-login") OAuth2AuthorizedClient authorizedClient) { return (authorizedClient != null) ? "resolved" : "not-resolved"; } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests.java ================================================ /* * 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 org.springframework.security.config.http; import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; import java.security.interfaces.RSAPublicKey; import java.time.Clock; import java.time.Instant; import java.time.ZoneId; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Properties; import java.util.stream.Collectors; import com.nimbusds.jose.JWSAlgorithm; import com.nimbusds.jose.JWSHeader; import com.nimbusds.jose.JWSObject; import com.nimbusds.jose.Payload; import com.nimbusds.jose.crypto.RSASSASigner; import com.nimbusds.jose.jwk.JWKSet; import com.nimbusds.jose.jwk.RSAKey; import com.nimbusds.jose.util.JSONObjectUtils; import jakarta.servlet.http.HttpServletRequest; import net.minidev.json.JSONObject; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; import org.hamcrest.core.AllOf; import org.hamcrest.core.StringContains; import org.hamcrest.core.StringEndsWith; import org.hamcrest.core.StringStartsWith; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mockito; import org.w3c.dom.Element; import org.springframework.beans.factory.BeanDefinitionStoreException; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.parsing.BeanDefinitionParsingException; import org.springframework.beans.factory.xml.BeanDefinitionParserDelegate; import org.springframework.beans.factory.xml.ParserContext; import org.springframework.beans.factory.xml.XmlReaderContext; import org.springframework.core.ParameterizedTypeReference; import org.springframework.core.convert.converter.Converter; import org.springframework.core.io.ClassPathResource; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.RequestEntity; import org.springframework.http.ResponseEntity; import org.springframework.security.authentication.AuthenticationManagerResolver; import org.springframework.security.authentication.AuthenticationServiceException; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.config.http.OAuth2ResourceServerBeanDefinitionParser.JwtBeanDefinitionParser; import org.springframework.security.config.http.OAuth2ResourceServerBeanDefinitionParser.OpaqueTokenBeanDefinitionParser; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.security.oauth2.core.OAuth2Error; import org.springframework.security.oauth2.core.OAuth2TokenValidator; import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult; import org.springframework.security.oauth2.jose.TestKeys; import org.springframework.security.oauth2.jwt.BadJwtException; import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.security.oauth2.jwt.JwtClaimNames; import org.springframework.security.oauth2.jwt.JwtDecoder; import org.springframework.security.oauth2.jwt.NimbusJwtDecoder; import org.springframework.security.oauth2.jwt.TestJwts; import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthenticationToken; import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenAuthenticationConverter; import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector; import org.springframework.security.oauth2.server.resource.introspection.SpringOpaqueTokenIntrospector; import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver; import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners; import org.springframework.security.web.authentication.AuthenticationConverter; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.ResultMatcher; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.client.RestOperations; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.startsWith; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * @author Josh Cummings */ @ExtendWith({ SpringExtension.class, SpringTestContextExtension.class }) @SecurityTestExecutionListeners public class OAuth2ResourceServerBeanDefinitionParserTests { private static final String CONFIG_LOCATION_PREFIX = "classpath:org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests"; public final SpringTestContext spring = new SpringTestContext(this); @Autowired MockMvc mvc; @Autowired(required = false) MockWebServer web; @Test public void getWhenValidBearerTokenThenAcceptsRequest() throws Exception { this.spring.configLocations(xml("JwtRestOperations"), xml("Jwt")).autowire(); mockJwksRestOperations(jwks("Default")); String token = this.token("ValidNoScopes"); // @formatter:off this.mvc.perform(get("/").header("Authorization", "Bearer " + token)) .andExpect(status().isNotFound()); // @formatter:on } @Test public void getWhenCustomSecurityContextHolderStrategyThenUses() throws Exception { this.spring.configLocations(xml("JwtRestOperations"), xml("JwtCustomSecurityContextHolderStrategy")).autowire(); mockJwksRestOperations(jwks("Default")); String token = this.token("ValidNoScopes"); // @formatter:off this.mvc.perform(get("/").header("Authorization", "Bearer " + token)) .andExpect(status().isNotFound()); // @formatter:on SecurityContextHolderStrategy securityContextHolderStrategy = this.spring.getContext() .getBean(SecurityContextHolderStrategy.class); verify(securityContextHolderStrategy, atLeastOnce()).getContext(); } @Test public void getWhenUsingJwkSetUriThenAcceptsRequest() throws Exception { this.spring.configLocations(xml("WebServer"), xml("JwkSetUri")).autowire(); mockWebServer(jwks("Default")); String token = this.token("ValidNoScopes"); // @formatter:off this.mvc.perform(get("/").header("Authorization", "Bearer " + token)) .andExpect(status().isNotFound()); // @formatter:on } @Test public void getWhenExpiredBearerTokenThenInvalidToken() throws Exception { this.spring.configLocations(xml("JwtRestOperations"), xml("Jwt")).autowire(); mockJwksRestOperations(jwks("Default")); String token = this.token("Expired"); // @formatter:off this.mvc.perform(get("/").header("Authorization", "Bearer " + token)) .andExpect(status().isUnauthorized()) .andExpect(invalidTokenHeader("An error occurred while attempting to decode the Jwt")); // @formatter:on } @Test public void getWhenBadJwkEndpointThen500() throws Exception { this.spring.configLocations(xml("JwtRestOperations"), xml("Jwt")).autowire(); mockJwksRestOperations("malformed"); String token = this.token("ValidNoScopes"); // @formatter:off assertThatExceptionOfType(AuthenticationServiceException.class) .isThrownBy(() -> this.mvc.perform(get("/").header("Authorization", "Bearer " + token))); // @formatter:on } @Test public void getWhenUnavailableJwkEndpointThen500() throws Exception { this.spring.configLocations(xml("WebServer"), xml("JwkSetUri")).autowire(); this.web.shutdown(); String token = this.token("ValidNoScopes"); // @formatter:off assertThatExceptionOfType(AuthenticationServiceException.class) .isThrownBy(() -> this.mvc.perform(get("/").header("Authorization", "Bearer " + token))); // @formatter:on } @Test public void getWhenMalformedBearerTokenThenInvalidToken() throws Exception { this.spring.configLocations(xml("JwkSetUri")).autowire(); // @formatter:off this.mvc.perform(get("/").header("Authorization", "Bearer an\"invalid\"token")) .andExpect(status().isUnauthorized()) .andExpect(invalidTokenHeader("Bearer token is malformed")); // @formatter:on } @Test public void getWhenMalformedPayloadThenInvalidToken() throws Exception { this.spring.configLocations(xml("JwtRestOperations"), xml("Jwt")).autowire(); mockJwksRestOperations(jwks("Default")); String token = this.token("MalformedPayload"); // @formatter:off this.mvc.perform(get("/").header("Authorization", "Bearer " + token)) .andExpect(status().isUnauthorized()) .andExpect(invalidTokenHeader("An error occurred while attempting to decode the Jwt: Malformed payload")); // @formatter:on } @Test public void getWhenUnsignedBearerTokenThenInvalidToken() throws Exception { this.spring.configLocations(xml("JwkSetUri")).autowire(); String token = this.token("Unsigned"); // @formatter:off this.mvc.perform(get("/").header("Authorization", "Bearer " + token)) .andExpect(status().isUnauthorized()) .andExpect(invalidTokenHeader("Unsupported algorithm of none")); // @formatter:on } @Test public void getWhenBearerTokenBeforeNotBeforeThenInvalidToken() throws Exception { this.spring.configLocations(xml("JwtRestOperations"), xml("Jwt")).autowire(); this.mockJwksRestOperations(jwks("Default")); String token = this.token("TooEarly"); // @formatter:off this.mvc.perform(get("/").header("Authorization", "Bearer " + token)) .andExpect(status().isUnauthorized()) .andExpect(invalidTokenHeader("An error occurred while attempting to decode the Jwt")); // @formatter:on } @Test public void getWhenBearerTokenInTwoPlacesThenInvalidRequest() throws Exception { this.spring.configLocations(xml("JwkSetUri")).autowire(); // @formatter:off this.mvc.perform(get("/").header("Authorization", "Bearer token").param("access_token", "token")) .andExpect(status().isBadRequest()) .andExpect(invalidRequestHeader("Found multiple bearer tokens in the request")); // @formatter:on } @Test public void getWhenBearerTokenInTwoParametersThenInvalidRequest() throws Exception { this.spring.configLocations(xml("JwkSetUri")).autowire(); MultiValueMap params = new LinkedMultiValueMap<>(); params.add("access_token", "token1"); params.add("access_token", "token2"); // @formatter:off this.mvc.perform(get("/").params(params)) .andExpect(status().isBadRequest()) .andExpect(invalidRequestHeader("Found multiple bearer tokens in the request")); // @formatter:on } @Test public void postWhenBearerTokenAsFormParameterThenIgnoresToken() throws Exception { this.spring.configLocations(xml("JwkSetUri")).autowire(); this.mvc.perform(post("/") // engage csrf .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE) .param("access_token", "token")) .andExpect(status().isForbidden()) .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, "Bearer")); // different // from // DSL } @Test public void getWhenNoBearerTokenThenUnauthorized() throws Exception { this.spring.configLocations(xml("JwkSetUri")).autowire(); // @formatter:off this.mvc.perform(get("/")) .andExpect(status().isUnauthorized()) .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, "Bearer resource_metadata=\"http://localhost/.well-known/oauth-protected-resource\"")); // @formatter:on } @Test public void getWhenSufficientlyScopedBearerTokenThenAcceptsRequest() throws Exception { this.spring.configLocations(xml("JwtRestOperations"), xml("Jwt")).autowire(); mockJwksRestOperations(jwks("Default")); String token = this.token("ValidMessageReadScope"); // @formatter:off this.mvc.perform(get("/requires-read-scope").header("Authorization", "Bearer " + token)) .andExpect(status().isNotFound()); // @formatter:on } @Test public void getWhenInsufficientScopeThenInsufficientScopeError() throws Exception { this.spring.configLocations(xml("JwtRestOperations"), xml("Jwt")).autowire(); mockJwksRestOperations(jwks("Default")); String token = this.token("ValidNoScopes"); // @formatter:off this.mvc.perform(get("/requires-read-scope").header("Authorization", "Bearer " + token)) .andExpect(status().isForbidden()) .andExpect(insufficientScopeHeader()); // @formatter:on } @Test public void getWhenInsufficientScpThenInsufficientScopeError() throws Exception { this.spring.configLocations(xml("JwtRestOperations"), xml("Jwt")).autowire(); mockJwksRestOperations(jwks("Default")); String token = this.token("ValidMessageWriteScp"); // @formatter:off this.mvc.perform(get("/requires-read-scope").header("Authorization", "Bearer " + token)) .andExpect(status().isForbidden()) .andExpect(insufficientScopeHeader()); // @formatter:on } @Test public void getWhenAuthorizationServerHasNoMatchingKeyThenInvalidToken() throws Exception { this.spring.configLocations(xml("JwtRestOperations"), xml("Jwt")).autowire(); mockJwksRestOperations(jwks("Empty")); String token = this.token("ValidNoScopes"); // @formatter:off this.mvc.perform(get("/").header("Authorization", "Bearer " + token)) .andExpect(status().isUnauthorized()) .andExpect(invalidTokenHeader("An error occurred while attempting to decode the Jwt")); // @formatter:on } @Test public void getWhenAuthorizationServerHasMultipleMatchingKeysThenOk() throws Exception { this.spring.configLocations(xml("JwtRestOperations"), xml("Jwt")).autowire(); mockJwksRestOperations(jwks("TwoKeys")); String token = this.token("ValidNoScopes"); // @formatter:off this.mvc.perform(get("/authenticated").header("Authorization", "Bearer " + token)) .andExpect(status().isNotFound()); // @formatter:on } @Test public void getWhenKeyMatchesByKidThenOk() throws Exception { this.spring.configLocations(xml("JwtRestOperations"), xml("Jwt")).autowire(); mockJwksRestOperations(jwks("TwoKeys")); String token = this.token("Kid"); // @formatter:off this.mvc.perform(get("/authenticated").header("Authorization", "Bearer " + token)) .andExpect(status().isNotFound()); // @formatter:on } @Test public void postWhenValidBearerTokenAndNoCsrfTokenThenOk() throws Exception { this.spring.configLocations(xml("JwtRestOperations"), xml("Jwt")).autowire(); mockJwksRestOperations(jwks("Default")); String token = this.token("ValidNoScopes"); // @formatter:off this.mvc.perform(post("/authenticated").header("Authorization", "Bearer " + token)) .andExpect(status().isNotFound()); // @formatter:on } @Test public void postWhenNoBearerTokenThenCsrfDenies() throws Exception { this.spring.configLocations(xml("JwkSetUri")).autowire(); // @formatter:off this.mvc.perform(post("/authenticated")) .andExpect(status().isForbidden()) // different from DSL .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, "Bearer")); // @formatter:on } @Test public void postWhenExpiredBearerTokenAndNoCsrfThenInvalidToken() throws Exception { this.spring.configLocations(xml("JwtRestOperations"), xml("Jwt")).autowire(); mockJwksRestOperations(jwks("Default")); String token = this.token("Expired"); // @formatter:off this.mvc.perform(post("/authenticated").header("Authorization", "Bearer " + token)) .andExpect(status().isUnauthorized()) .andExpect(invalidTokenHeader("An error occurred while attempting to decode the Jwt")); // @formatter:on } @Test public void requestWhenJwtThenSessionIsNotCreated() throws Exception { this.spring.configLocations(xml("JwtRestOperations"), xml("Jwt")).autowire(); mockJwksRestOperations(jwks("Default")); String token = this.token("ValidNoScopes"); // @formatter:off MvcResult result = this.mvc.perform(get("/").header("Authorization", "Bearer " + token)) .andExpect(status().isNotFound()) .andReturn(); // @formatter:on assertThat(result.getRequest().getSession(false)).isNull(); } @Test public void requestWhenIntrospectionThenSessionIsNotCreated() throws Exception { this.spring.configLocations(xml("WebServer"), xml("IntrospectionUri")).autowire(); mockWebServer(json("Active")); // @formatter:off MvcResult result = this.mvc.perform(get("/authenticated").header("Authorization", "Bearer token")) .andExpect(status().isNotFound()) .andReturn(); // @formatter:on assertThat(result.getRequest().getSession(false)).isNull(); } @Test public void requestWhenNoBearerTokenThenSessionIsCreated() throws Exception { this.spring.configLocations(xml("JwkSetUri")).autowire(); // @formatter:off MvcResult result = this.mvc.perform(get("/")) .andExpect(status().isUnauthorized()) .andReturn(); // @formatter:on assertThat(result.getRequest().getSession(false)).isNotNull(); } @Test public void requestWhenSessionManagementConfiguredThenUses() throws Exception { this.spring.configLocations(xml("JwtRestOperations"), xml("AlwaysSessionCreation")).autowire(); mockJwksRestOperations(jwks("Default")); String token = this.token("ValidNoScopes"); // @formatter:off MvcResult result = this.mvc.perform(get("/").header("Authorization", "Bearer " + token)) .andExpect(status().isNotFound()) .andReturn(); // @formatter:on assertThat(result.getRequest().getSession(false)).isNotNull(); } @Test public void getWhenCustomBearerTokenResolverThenUses() throws Exception { this.spring.configLocations(xml("MockBearerTokenResolver"), xml("MockJwtDecoder"), xml("BearerTokenResolver")) .autowire(); JwtDecoder decoder = this.spring.getContext().getBean(JwtDecoder.class); given(decoder.decode("token")).willReturn(TestJwts.jwt().build()); BearerTokenResolver bearerTokenResolver = this.spring.getContext().getBean(BearerTokenResolver.class); given(bearerTokenResolver.resolve(any(HttpServletRequest.class))).willReturn("token"); this.mvc.perform(get("/")).andExpect(status().isNotFound()); verify(decoder).decode("token"); verify(bearerTokenResolver).resolve(any(HttpServletRequest.class)); } @Test public void getWhenCustomAuthenticationConverterThenUses() throws Exception { this.spring .configLocations(xml("MockAuthenticationConverter"), xml("MockJwtDecoder"), xml("AuthenticationConverter")) .autowire(); JwtDecoder decoder = this.spring.getContext().getBean(JwtDecoder.class); given(decoder.decode("token")).willReturn(TestJwts.jwt().build()); AuthenticationConverter authenticationConverter = this.spring.getContext() .getBean(AuthenticationConverter.class); given(authenticationConverter.convert(any(HttpServletRequest.class))) .willReturn(new BearerTokenAuthenticationToken("token")); this.mvc.perform(get("/")).andExpect(status().isNotFound()); verify(decoder).decode("token"); verify(authenticationConverter).convert(any(HttpServletRequest.class)); } @Test public void requestWhenBearerTokenResolverAllowsRequestBodyThenEitherHeaderOrRequestBodyIsAccepted() throws Exception { this.spring.configLocations(xml("MockJwtDecoder"), xml("AllowBearerTokenInBody")).autowire(); JwtDecoder decoder = this.spring.getContext().getBean(JwtDecoder.class); given(decoder.decode(anyString())).willReturn(TestJwts.jwt().build()); // @formatter:off this.mvc.perform(get("/authenticated").header("Authorization", "Bearer token")) .andExpect(status().isNotFound()); this.mvc.perform(post("/authenticated").header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE).param("access_token", "token")) .andExpect(status().isNotFound()); // @formatter:on } @Test public void requestWhenBearerTokenResolverAllowsQueryParameterThenEitherHeaderOrQueryParameterIsAccepted() throws Exception { this.spring.configLocations(xml("MockJwtDecoder"), xml("AllowBearerTokenInQuery")).autowire(); JwtDecoder decoder = this.spring.getContext().getBean(JwtDecoder.class); given(decoder.decode(anyString())).willReturn(TestJwts.jwt().build()); // @formatter:off this.mvc.perform(get("/authenticated").header("Authorization", "Bearer token")) .andExpect(status().isNotFound()); this.mvc.perform(get("/authenticated").param("access_token", "token")) .andExpect(status().isNotFound()); // @formatter:on verify(decoder, times(2)).decode("token"); } @Test public void requestWhenBearerTokenResolverAllowsRequestBodyAndRequestContainsTwoTokensThenInvalidRequest() throws Exception { this.spring.configLocations(xml("MockJwtDecoder"), xml("AllowBearerTokenInBody")).autowire(); // @formatter:off MockHttpServletRequestBuilder request = post("/authenticated") .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE) .param("access_token", "token") .header("Authorization", "Bearer token") .with(csrf()); this.mvc.perform(request) .andExpect(status().isBadRequest()) .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, containsString("invalid_request"))); // @formatter:on } @Test public void requestWhenBearerTokenResolverAllowsQueryParameterAndRequestContainsTwoTokensThenInvalidRequest() throws Exception { this.spring.configLocations(xml("MockJwtDecoder"), xml("AllowBearerTokenInQuery")).autowire(); // @formatter:off MockHttpServletRequestBuilder request = get("/authenticated") .header("Authorization", "Bearer token") .param("access_token", "token"); this.mvc.perform(request) .andExpect(status().isBadRequest()) .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, containsString("invalid_request"))); // @formatter:on } @Test public void requestWhenCustomJwtDecoderThenUsed() throws Exception { this.spring.configLocations(xml("MockJwtDecoder"), xml("Jwt")).autowire(); JwtDecoder decoder = this.spring.getContext().getBean(JwtDecoder.class); given(decoder.decode(anyString())).willReturn(TestJwts.jwt().build()); this.mvc.perform(get("/authenticated").header("Authorization", "Bearer token")) .andExpect(status().isNotFound()); verify(decoder).decode("token"); } @Test public void configureWhenDecoderAndJwkSetUriThenException() { assertThatExceptionOfType(BeanDefinitionParsingException.class) .isThrownBy(() -> this.spring.configLocations(xml("JwtDecoderAndJwkSetUri")).autowire()); } @Test public void configureWhenAuthenticationConverterAndJwkSetUriThenException() { assertThatExceptionOfType(BeanDefinitionStoreException.class).isThrownBy( () -> this.spring.configLocations(xml("AuthenticationConverterAndBearerTokenResolver")).autowire()); } @Test public void requestWhenRealmNameConfiguredThenUsesOnUnauthenticated() throws Exception { this.spring.configLocations(xml("MockJwtDecoder"), xml("AuthenticationEntryPoint")).autowire(); JwtDecoder decoder = this.spring.getContext().getBean(JwtDecoder.class); Mockito.when(decoder.decode(anyString())).thenThrow(BadJwtException.class); // @formatter:off this.mvc.perform(get("/authenticated").header("Authorization", "Bearer invalid_token")) .andExpect(status().isUnauthorized()) .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, startsWith("Bearer realm=\"myRealm\""))); // @formatter:on } @Test public void requestWhenRealmNameConfiguredThenUsesOnAccessDenied() throws Exception { this.spring.configLocations(xml("MockJwtDecoder"), xml("AccessDeniedHandler")).autowire(); JwtDecoder decoder = this.spring.getContext().getBean(JwtDecoder.class); given(decoder.decode(anyString())).willReturn(TestJwts.jwt().build()); // @formatter:off this.mvc.perform(get("/authenticated").header("Authorization", "Bearer insufficiently_scoped")) .andExpect(status().isForbidden()) .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, startsWith("Bearer realm=\"myRealm\""))); // @formatter:on } @Test public void requestWhenCustomJwtValidatorFailsThenCorrespondingErrorMessage() throws Exception { this.spring.configLocations(xml("MockJwtValidator"), xml("Jwt")).autowire(); mockJwksRestOperations(jwks("Default")); String token = this.token("ValidNoScopes"); OAuth2TokenValidator jwtValidator = this.spring.getContext().getBean(OAuth2TokenValidator.class); OAuth2Error error = new OAuth2Error("custom-error", "custom-description", "custom-uri"); given(jwtValidator.validate(any(Jwt.class))).willReturn(OAuth2TokenValidatorResult.failure(error)); // @formatter:off this.mvc.perform(get("/").header("Authorization", "Bearer " + token)) .andExpect(status().isUnauthorized()) .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, containsString("custom-description"))); // @formatter:on } @Test public void requestWhenClockSkewSetThenTimestampWindowRelaxedAccordingly() throws Exception { this.spring.configLocations(xml("UnexpiredJwtClockSkew"), xml("Jwt")).autowire(); mockJwksRestOperations(jwks("Default")); String token = this.token("ExpiresAt4687177990"); // @formatter:off this.mvc.perform(get("/").header("Authorization", "Bearer " + token)) .andExpect(status().isNotFound()); // @formatter:on } @Test public void requestWhenClockSkewSetButJwtStillTooLateThenReportsExpired() throws Exception { this.spring.configLocations(xml("ExpiredJwtClockSkew"), xml("Jwt")).autowire(); mockJwksRestOperations(jwks("Default")); String token = this.token("ExpiresAt4687177990"); // @formatter:off this.mvc.perform(get("/").header("Authorization", "Bearer " + token)) .andExpect(status().isUnauthorized()) .andExpect(invalidTokenHeader("Jwt expired at")); // @formatter:on } @Test public void requestWhenJwtAuthenticationConverterThenUsed() throws Exception { this.spring .configLocations(xml("MockJwtDecoder"), xml("MockJwtAuthenticationConverter"), xml("JwtAuthenticationConverter")) .autowire(); Converter jwtAuthenticationConverter = (Converter) this.spring .getContext() .getBean("jwtAuthenticationConverter"); given(jwtAuthenticationConverter.convert(any(Jwt.class))) .willReturn(new JwtAuthenticationToken(TestJwts.jwt().build(), Collections.emptyList())); JwtDecoder jwtDecoder = this.spring.getContext().getBean(JwtDecoder.class); given(jwtDecoder.decode(anyString())).willReturn(TestJwts.jwt().build()); // @formatter:off this.mvc.perform(get("/").header("Authorization", "Bearer token")) .andExpect(status().isNotFound()); // @formatter:on verify(jwtAuthenticationConverter).convert(any(Jwt.class)); } @Test public void requestWhenUsingPublicKeyAndValidTokenThenAuthenticates() throws Exception { this.spring.configLocations(xml("SingleKey"), xml("Jwt")).autowire(); String token = this.token("ValidNoScopes"); // @formatter:off this.mvc.perform(get("/").header("Authorization", "Bearer " + token)) .andExpect(status().isNotFound()); // @formatter:on } @Test public void requestWhenUsingPublicKeyAndSignatureFailsThenReturnsInvalidToken() throws Exception { this.spring.configLocations(xml("SingleKey"), xml("Jwt")).autowire(); String token = this.token("WrongSignature"); // @formatter:off this.mvc.perform(get("/").header("Authorization", "Bearer " + token)) .andExpect(invalidTokenHeader("signature")); // @formatter:on } @Test public void requestWhenUsingPublicKeyAlgorithmDoesNotMatchThenReturnsInvalidToken() throws Exception { this.spring.configLocations(xml("SingleKey"), xml("Jwt")).autowire(); String token = this.token("WrongAlgorithm"); // @formatter:off this.mvc.perform(get("/").header("Authorization", "Bearer " + token)) .andExpect(invalidTokenHeader("algorithm")); // @formatter:on } @Test public void getWhenIntrospectingThenOk() throws Exception { this.spring.configLocations(xml("OpaqueTokenRestOperations"), xml("OpaqueToken")).autowire(); mockJsonRestOperations(json("Active")); // @formatter:off this.mvc.perform(get("/authenticated").header("Authorization", "Bearer token")) .andExpect(status().isNotFound()); // @formatter:on } @Test public void configureWhenIntrospectingWithAuthenticationConverterThenUses() throws Exception { this.spring.configLocations(xml("OpaqueTokenRestOperations"), xml("OpaqueTokenAndAuthenticationConverter")) .autowire(); mockJsonRestOperations(json("Active")); OpaqueTokenAuthenticationConverter converter = bean(OpaqueTokenAuthenticationConverter.class); given(converter.convert(any(), any())).willReturn(new TestingAuthenticationToken("user", "pass", "app")); // @formatter:off this.mvc.perform(get("/authenticated").header("Authorization", "Bearer token")) .andExpect(status().isNotFound()); // @formatter:on verify(converter).convert(any(), any()); } @Test public void getWhenIntrospectionFailsThenUnauthorized() throws Exception { this.spring.configLocations(xml("OpaqueTokenRestOperations"), xml("OpaqueToken")).autowire(); mockJsonRestOperations(json("Inactive")); // @formatter:off MockHttpServletRequestBuilder request = get("/") .header("Authorization", "Bearer token"); this.mvc.perform(request) .andExpect(status().isUnauthorized()) .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, containsString("Provided token isn't active"))); // @formatter:on } @Test public void getWhenIntrospectionLacksScopeThenForbidden() throws Exception { this.spring.configLocations(xml("OpaqueTokenRestOperations"), xml("OpaqueToken")).autowire(); mockJsonRestOperations(json("ActiveNoScopes")); // @formatter:off this.mvc.perform(get("/requires-read-scope").header("Authorization", "Bearer token")) .andExpect(status().isForbidden()) .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, containsString("scope"))); // @formatter:on } @Test public void configureWhenOnlyIntrospectionUrlThenException() { assertThatExceptionOfType(BeanDefinitionParsingException.class) .isThrownBy(() -> this.spring.configLocations(xml("OpaqueTokenHalfConfigured")).autowire()); } @Test public void configureWhenIntrospectorAndIntrospectionUriThenError() { assertThatExceptionOfType(BeanDefinitionParsingException.class) .isThrownBy(() -> this.spring.configLocations(xml("OpaqueTokenAndIntrospectionUri")).autowire()); } @Test public void getWhenAuthenticationManagerResolverThenUses() throws Exception { this.spring.configLocations(xml("AuthenticationManagerResolver")).autowire(); AuthenticationManagerResolver authenticationManagerResolver = this.spring.getContext() .getBean(AuthenticationManagerResolver.class); given(authenticationManagerResolver.resolve(any(HttpServletRequest.class))).willReturn( (authentication) -> new JwtAuthenticationToken(TestJwts.jwt().build(), Collections.emptyList())); // @formatter:off this.mvc.perform(get("/").header("Authorization", "Bearer token")) .andExpect(status().isNotFound()); // @formatter:on verify(authenticationManagerResolver).resolve(any(HttpServletRequest.class)); } @Test public void getWhenMultipleIssuersThenUsesIssuerClaimToDifferentiate() throws Exception { this.spring.configLocations(xml("WebServer"), xml("MultipleIssuers")).autowire(); MockWebServer server = this.spring.getContext().getBean(MockWebServer.class); String metadata = "{\n" + " \"issuer\": \"%s\", \n" + " \"jwks_uri\": \"%s/.well-known/jwks.json\" \n" + "}"; String jwkSet = jwkSet(); String issuerOne = server.url("/issuerOne").toString(); String issuerTwo = server.url("/issuerTwo").toString(); String issuerThree = server.url("/issuerThree").toString(); String jwtOne = jwtFromIssuer(issuerOne); String jwtTwo = jwtFromIssuer(issuerTwo); String jwtThree = jwtFromIssuer(issuerThree); mockWebServer(String.format(metadata, issuerOne, issuerOne)); mockWebServer(jwkSet); // @formatter:off this.mvc.perform(get("/authenticated").header("Authorization", "Bearer " + jwtOne)) .andExpect(status().isNotFound()); // @formatter:on mockWebServer(String.format(metadata, issuerTwo, issuerTwo)); mockWebServer(jwkSet); // @formatter:off this.mvc.perform(get("/authenticated").header("Authorization", "Bearer " + jwtTwo)) .andExpect(status().isNotFound()); // @formatter:on mockWebServer(String.format(metadata, issuerThree, issuerThree)); mockWebServer(jwkSet); // @formatter:off this.mvc.perform(get("/authenticated").header("Authorization", "Bearer " + jwtThree)) .andExpect(status().isUnauthorized()) .andExpect(invalidTokenHeader("Invalid issuer")); // @formatter:on } @Test public void requestWhenBasicAndResourceServerEntryPointsThenBearerTokenPresides() throws Exception { // different from DSL this.spring.configLocations(xml("MockJwtDecoder"), xml("BasicAndResourceServer")).autowire(); JwtDecoder decoder = this.spring.getContext().getBean(JwtDecoder.class); given(decoder.decode(anyString())).willThrow(BadJwtException.class); // @formatter:off this.mvc.perform(get("/authenticated").with(httpBasic("some", "user"))) .andExpect(status().isUnauthorized()) .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, startsWith("Basic"))); this.mvc.perform(get("/authenticated")) .andExpect(status().isUnauthorized()) .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, startsWith("Bearer"))); this.mvc.perform(get("/authenticated").header("Authorization", "Bearer invalid_token")) .andExpect(status().isUnauthorized()) .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, startsWith("Bearer"))); // @formatter:on } @Test public void requestWhenFormLoginAndResourceServerEntryPointsThenSessionCreatedByRequest() throws Exception { // different from DSL this.spring.configLocations(xml("MockJwtDecoder"), xml("FormAndResourceServer")).autowire(); JwtDecoder decoder = this.spring.getContext().getBean(JwtDecoder.class); given(decoder.decode(anyString())).willThrow(BadJwtException.class); MvcResult result = this.mvc.perform(get("/authenticated")).andExpect(status().isUnauthorized()).andReturn(); assertThat(result.getRequest().getSession(false)).isNotNull(); // @formatter:off result = this.mvc.perform(get("/authenticated").header("Authorization", "Bearer token")) .andExpect(status().isUnauthorized()) .andReturn(); // @formatter:on assertThat(result.getRequest().getSession(false)).isNull(); } @Test public void getWhenAlsoUsingHttpBasicThenCorrectProviderEngages() throws Exception { this.spring.configLocations(xml("JwtRestOperations"), xml("BasicAndResourceServer")).autowire(); mockJwksRestOperations(jwks("Default")); String token = this.token("ValidNoScopes"); // @formatter:off this.mvc.perform(get("/authenticated").header("Authorization", "Bearer " + token)) .andExpect(status().isNotFound()); this.mvc.perform(get("/authenticated").with(httpBasic("user", "password"))) .andExpect(status().isNotFound()); // @formatter:on } @Test public void configuredWhenMissingJwtAuthenticationProviderThenWiringException() { assertThatExceptionOfType(BeanDefinitionParsingException.class) .isThrownBy(() -> this.spring.configLocations(xml("Jwtless")).autowire()) .withMessageContaining("Please select one"); } @Test public void configureWhenMissingJwkSetUriThenWiringException() { assertThatExceptionOfType(BeanDefinitionParsingException.class) .isThrownBy(() -> this.spring.configLocations(xml("JwtHalfConfigured")).autowire()) .withMessageContaining("Please specify either"); } @Test public void configureWhenUsingBothAuthenticationManagerResolverAndJwtThenException() { assertThatExceptionOfType(BeanDefinitionParsingException.class) .isThrownBy( () -> this.spring.configLocations(xml("AuthenticationManagerResolverPlusOtherConfig")).autowire()) .withMessageContaining("authentication-manager-resolver-ref"); } @Test public void validateConfigurationWhenMoreThanOneResourceServerModeThenError() { OAuth2ResourceServerBeanDefinitionParser parser = new OAuth2ResourceServerBeanDefinitionParser(null, null, null, null, null, null); Element element = mock(Element.class); given(element.hasAttribute(OAuth2ResourceServerBeanDefinitionParser.AUTHENTICATION_MANAGER_RESOLVER_REF)) .willReturn(true); Element child = mock(Element.class); ParserContext pc = new ParserContext(mock(XmlReaderContext.class), mock(BeanDefinitionParserDelegate.class)); parser.validateConfiguration(element, child, null, pc); verify(pc.getReaderContext()).error(anyString(), eq(element)); reset(pc.getReaderContext()); parser.validateConfiguration(element, null, child, pc); verify(pc.getReaderContext()).error(anyString(), eq(element)); } @Test public void validateConfigurationWhenNoResourceServerModeThenError() { OAuth2ResourceServerBeanDefinitionParser parser = new OAuth2ResourceServerBeanDefinitionParser(null, null, null, null, null, null); Element element = mock(Element.class); given(element.hasAttribute(OAuth2ResourceServerBeanDefinitionParser.AUTHENTICATION_MANAGER_RESOLVER_REF)) .willReturn(false); ParserContext pc = new ParserContext(mock(XmlReaderContext.class), mock(BeanDefinitionParserDelegate.class)); parser.validateConfiguration(element, null, null, pc); verify(pc.getReaderContext()).error(anyString(), eq(element)); } @Test public void validateConfigurationWhenBothJwtAttributesThenError() { JwtBeanDefinitionParser parser = new JwtBeanDefinitionParser(); Element element = mock(Element.class); given(element.hasAttribute(JwtBeanDefinitionParser.JWK_SET_URI)).willReturn(true); given(element.hasAttribute(JwtBeanDefinitionParser.DECODER_REF)).willReturn(true); ParserContext pc = new ParserContext(mock(XmlReaderContext.class), mock(BeanDefinitionParserDelegate.class)); parser.validateConfiguration(element, pc); verify(pc.getReaderContext()).error(anyString(), eq(element)); } @Test public void validateConfigurationWhenNoJwtAttributesThenError() { JwtBeanDefinitionParser parser = new JwtBeanDefinitionParser(); Element element = mock(Element.class); given(element.hasAttribute(JwtBeanDefinitionParser.JWK_SET_URI)).willReturn(false); given(element.hasAttribute(JwtBeanDefinitionParser.DECODER_REF)).willReturn(false); ParserContext pc = new ParserContext(mock(XmlReaderContext.class), mock(BeanDefinitionParserDelegate.class)); parser.validateConfiguration(element, pc); verify(pc.getReaderContext()).error(anyString(), eq(element)); } @Test public void validateConfigurationWhenBothOpaqueTokenModesThenError() { OpaqueTokenBeanDefinitionParser parser = new OpaqueTokenBeanDefinitionParser(); Element element = mock(Element.class); given(element.hasAttribute(OpaqueTokenBeanDefinitionParser.INTROSPECTION_URI)).willReturn(true); given(element.hasAttribute(OpaqueTokenBeanDefinitionParser.INTROSPECTOR_REF)).willReturn(true); ParserContext pc = new ParserContext(mock(XmlReaderContext.class), mock(BeanDefinitionParserDelegate.class)); parser.validateConfiguration(element, pc); verify(pc.getReaderContext()).error(anyString(), eq(element)); } @Test public void validateConfigurationWhenNoOpaqueTokenModeThenError() { OpaqueTokenBeanDefinitionParser parser = new OpaqueTokenBeanDefinitionParser(); Element element = mock(Element.class); given(element.hasAttribute(OpaqueTokenBeanDefinitionParser.INTROSPECTION_URI)).willReturn(false); given(element.hasAttribute(OpaqueTokenBeanDefinitionParser.INTROSPECTOR_REF)).willReturn(false); ParserContext pc = new ParserContext(mock(XmlReaderContext.class), mock(BeanDefinitionParserDelegate.class)); parser.validateConfiguration(element, pc); verify(pc.getReaderContext()).error(anyString(), eq(element)); } private static ResultMatcher invalidRequestHeader(String message) { return header().string(HttpHeaders.WWW_AUTHENTICATE, AllOf.allOf(new StringStartsWith("Bearer " + "error=\"invalid_request\", " + "error_description=\""), new StringContains(message), new StringContains(", " + "error_uri=\"https://tools.ietf.org/html/rfc6750#section-3.1\""), new StringEndsWith( ", " + "resource_metadata=\"http://localhost/.well-known/oauth-protected-resource\""))); } private static ResultMatcher invalidTokenHeader(String message) { return header().string(HttpHeaders.WWW_AUTHENTICATE, AllOf.allOf(new StringStartsWith("Bearer " + "error=\"invalid_token\", " + "error_description=\""), new StringContains(message), new StringContains(", " + "error_uri=\"https://tools.ietf.org/html/rfc6750#section-3.1\""), new StringEndsWith( ", " + "resource_metadata=\"http://localhost/.well-known/oauth-protected-resource\""))); } private static ResultMatcher insufficientScopeHeader() { return header().string(HttpHeaders.WWW_AUTHENTICATE, "Bearer " + "error=\"insufficient_scope\"" + ", error_description=\"The request requires higher privileges than provided by the access token.\"" + ", error_uri=\"https://tools.ietf.org/html/rfc6750#section-3.1\""); } private String jwkSet() { return new JWKSet(new RSAKey.Builder(TestKeys.DEFAULT_PUBLIC_KEY).keyID("1").build()).toString(); } private String jwtFromIssuer(String issuer) throws Exception { Map claims = new HashMap<>(); claims.put(JwtClaimNames.ISS, issuer); claims.put(JwtClaimNames.SUB, "test-subject"); claims.put("scope", "message:read"); JWSObject jws = new JWSObject(new JWSHeader.Builder(JWSAlgorithm.RS256).keyID("1").build(), new Payload(new JSONObject(claims))); jws.sign(new RSASSASigner(TestKeys.DEFAULT_PRIVATE_KEY)); return jws.serialize(); } private void mockWebServer(String response) { this.web.enqueue(new MockResponse().setResponseCode(200) .setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) .setBody(response)); } private void mockJwksRestOperations(String response) { RestOperations rest = this.spring.getContext().getBean(RestOperations.class); HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); ResponseEntity entity = new ResponseEntity<>(response, headers, HttpStatus.OK); given(rest.exchange(any(RequestEntity.class), eq(String.class))).willReturn(entity); } private void mockJsonRestOperations(String response) { try { RestOperations rest = this.spring.getContext().getBean(RestOperations.class); HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); ResponseEntity> entity = new ResponseEntity<>(JSONObjectUtils.parse(response), headers, HttpStatus.OK); given(rest.exchange(any(RequestEntity.class), eq(new ParameterizedTypeReference>() { }))).willReturn(entity); } catch (Exception ex) { throw new IllegalArgumentException(ex); } } private String json(String name) throws IOException { return resource(name + ".json"); } private String jwks(String name) throws IOException { return resource(name + ".jwks"); } private String token(String name) throws IOException { return resource(name + ".token"); } private String resource(String suffix) throws IOException { String name = this.getClass().getSimpleName() + "-" + suffix; ClassPathResource resource = new ClassPathResource(name, this.getClass()); try (BufferedReader reader = new BufferedReader(new FileReader(resource.getFile()))) { return reader.lines().collect(Collectors.joining()); } } private T bean(Class beanClass) { return this.spring.getContext().getBean(beanClass); } private String xml(String configName) { return CONFIG_LOCATION_PREFIX + "-" + configName + ".xml"; } static class JwtDecoderFactoryBean implements FactoryBean { private RestOperations rest; private RSAPublicKey key; private OAuth2TokenValidator jwtValidator; @Override public JwtDecoder getObject() { NimbusJwtDecoder decoder; if (this.key != null) { decoder = NimbusJwtDecoder.withPublicKey(this.key).build(); } else { decoder = NimbusJwtDecoder.withJwkSetUri("https://idp.example.org").restOperations(this.rest).build(); } if (this.jwtValidator != null) { decoder.setJwtValidator(this.jwtValidator); } return decoder; } @Override public Class getObjectType() { return JwtDecoder.class; } public void setJwtValidator(OAuth2TokenValidator jwtValidator) { this.jwtValidator = jwtValidator; } public void setKey(RSAPublicKey key) { this.key = key; } public void setRest(RestOperations rest) { this.rest = rest; } } static class OpaqueTokenIntrospectorFactoryBean implements FactoryBean { private RestOperations rest; @Override public OpaqueTokenIntrospector getObject() throws Exception { return new SpringOpaqueTokenIntrospector("https://idp.example.org", this.rest); } @Override public Class getObjectType() { return OpaqueTokenIntrospector.class; } public void setRest(RestOperations rest) { this.rest = rest; } } static class MockWebServerFactoryBean implements FactoryBean, DisposableBean { private final MockWebServer web = new MockWebServer(); @Override public void destroy() throws Exception { this.web.shutdown(); } @Override public MockWebServer getObject() { return this.web; } @Override public Class getObjectType() { return MockWebServer.class; } } static class MockWebServerPropertiesFactoryBean implements FactoryBean, DisposableBean { MockWebServer web; MockWebServerPropertiesFactoryBean(MockWebServer web) { this.web = web; } @Override public Properties getObject() { Properties p = new Properties(); p.setProperty("jwk-set-uri", this.web.url("").toString()); p.setProperty("introspection-uri", this.web.url("").toString()); p.setProperty("issuer-one", this.web.url("issuerOne").toString()); p.setProperty("issuer-two", this.web.url("issuerTwo").toString()); return p; } @Override public Class getObjectType() { return Properties.class; } @Override public void destroy() throws Exception { this.web.shutdown(); } } static class ClockFactoryBean implements FactoryBean { Clock clock; @Override public Clock getObject() { return this.clock; } @Override public Class getObjectType() { return Clock.class; } public void setMillis(long millis) { this.clock = Clock.fixed(Instant.ofEpochMilli(millis), ZoneId.systemDefault()); } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/http/PlaceHolderAndELConfigTests.java ================================================ /* * 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 org.springframework.security.config.http; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners; import org.springframework.security.test.context.support.WithMockUser; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.servlet.MockMvc; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.forwardedUrl; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * @author Josh Cummings */ @ExtendWith({ SpringExtension.class, SpringTestContextExtension.class }) @SecurityTestExecutionListeners public class PlaceHolderAndELConfigTests { private static final String CONFIG_LOCATION_PREFIX = "classpath:org/springframework/security/config/http/PlaceHolderAndELConfigTests"; public final SpringTestContext spring = new SpringTestContext(this); @Autowired MockMvc mvc; @Test public void getWhenUsingPlaceholderThenUnsecuredPatternCorrectlyConfigured() throws Exception { System.setProperty("pattern.nofilters", "/unsecured"); this.spring.configLocations(this.xml("UnsecuredPattern")).autowire(); // @formatter:off this.mvc.perform(get("/unsecured")) .andExpect(status().isOk()); // @formatter:on } /** * SEC-1201 */ @Test public void loginWhenUsingPlaceholderThenInterceptUrlsAndFormLoginWorks() throws Exception { System.setProperty("secure.Url", "/secured"); System.setProperty("secure.role", "ROLE_NUNYA"); System.setProperty("login.page", "/loginPage"); System.setProperty("default.target", "/defaultTarget"); System.setProperty("auth.failure", "/authFailure"); this.spring.configLocations(this.xml("InterceptUrlAndFormLogin")).autowire(); // login-page setting // @formatter:off this.mvc.perform(get("/secured")) .andExpect(redirectedUrl("/loginPage")); // login-processing-url setting // default-target-url setting this.mvc.perform(post("/loginPage").param("username", "user").param("password", "password")) .andExpect(redirectedUrl("/defaultTarget")); // authentication-failure-url setting this.mvc.perform(post("/loginPage").param("username", "user").param("password", "wrong")) .andExpect(redirectedUrl("/authFailure")); // @formatter:on } /** * SEC-1309 */ @Test public void loginWhenUsingSpELThenInterceptUrlsAndFormLoginWorks() throws Exception { System.setProperty("secure.url", "/secured"); System.setProperty("secure.role", "ROLE_NUNYA"); System.setProperty("login.page", "/loginPage"); System.setProperty("default.target", "/defaultTarget"); System.setProperty("auth.failure", "/authFailure"); this.spring.configLocations(this.xml("InterceptUrlAndFormLoginWithSpEL")).autowire(); // login-page setting // @formatter:off this.mvc.perform(get("/secured")) .andExpect(redirectedUrl("/loginPage")); // login-processing-url setting // default-target-url setting this.mvc.perform(post("/loginPage").param("username", "user").param("password", "password")) .andExpect(redirectedUrl("/defaultTarget")); // authentication-failure-url setting this.mvc.perform(post("/loginPage").param("username", "user").param("password", "wrong")) .andExpect(redirectedUrl("/authFailure")); // @formatter:on } @Test @WithMockUser public void requestWhenUsingPlaceholderOrSpELThenPortMapperWorks() throws Exception { System.setProperty("http", "9080"); System.setProperty("https", "9443"); this.spring.configLocations(this.xml("PortMapping")).autowire(); // @formatter:off this.mvc.perform(get("http://localhost:9080/secured")) .andExpect(status().isFound()) .andExpect(redirectedUrl("https://localhost:9443/secured")); this.mvc.perform(get("https://localhost:9443/unsecured")) .andExpect(status().isFound()) .andExpect(redirectedUrl("http://localhost:9080/unsecured")); // @formatter:on } @Test @WithMockUser public void requestWhenUsingPlaceholderThenRequiresChannelWorks() throws Exception { System.setProperty("secure.url", "/secured"); System.setProperty("required.channel", "https"); this.spring.configLocations(this.xml("RequiresChannel")).autowire(); // @formatter:off this.mvc.perform(get("http://localhost/secured")) .andExpect(status().isFound()) .andExpect(redirectedUrl("https://localhost/secured")); // @formatter:on } @Test @WithMockUser public void requestWhenUsingPlaceholderThenAccessDeniedPageWorks() throws Exception { System.setProperty("accessDenied", "/go-away"); this.spring.configLocations(this.xml("AccessDeniedPage")).autowire(); // @formatter:off this.mvc.perform(get("/secured")) .andExpect(forwardedUrl("/go-away")); // @formatter:on } @Test @WithMockUser public void requestWhenUsingSpELThenAccessDeniedPageWorks() throws Exception { this.spring.configLocations(this.xml("AccessDeniedPageWithSpEL")).autowire(); // @formatter:off this.mvc.perform(get("/secured")) .andExpect(forwardedUrl("/go-away")); // @formatter:on } private String xml(String configName) { return CONFIG_LOCATION_PREFIX + "-" + configName + ".xml"; } @RestController static class SimpleController { @GetMapping("/unsecured") String unsecured() { return "unsecured"; } @GetMapping("/secured") String secured() { return "secured"; } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/http/RememberMeConfigTests.java ================================================ /* * 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 org.springframework.security.config.http; import java.util.Collections; import jakarta.servlet.http.Cookie; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.FatalBeanException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.parsing.BeanDefinitionParsingException; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.security.TestDataSource; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.web.authentication.rememberme.AbstractRememberMeServices; import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.ResultActions; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.verify; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.cookie; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * @author Luke Taylor * @author Rob Winch * @author Oliver Becker */ @ExtendWith(SpringTestContextExtension.class) public class RememberMeConfigTests { private static final String CONFIG_LOCATION_PREFIX = "classpath:org/springframework/security/config/http/RememberMeConfigTests"; @Autowired MockMvc mvc; public final SpringTestContext spring = new SpringTestContext(this); @Test public void requestWithRememberMeWhenUsingCustomTokenRepositoryThenAutomaticallyReauthenticates() throws Exception { this.spring.configLocations(xml("WithTokenRepository")).autowire(); // @formatter:off MvcResult result = rememberAuthentication("user", "password") .andExpect(cookie().secure(AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY, false)) .andReturn(); // @formatter:on Cookie cookie = rememberMeCookie(result); // @formatter:off this.mvc.perform(get("/authenticated").cookie(cookie)) .andExpect(status().isOk()); // @formatter:on JdbcTemplate template = this.spring.getContext().getBean(JdbcTemplate.class); int count = template.queryForObject("select count(*) from persistent_logins", int.class); assertThat(count).isEqualTo(1); } @Test public void requestWithRememberMeWhenUsingCustomDataSourceThenAutomaticallyReauthenticates() throws Exception { this.spring.configLocations(xml("WithDataSource")).autowire(); TestDataSource dataSource = this.spring.getContext().getBean(TestDataSource.class); JdbcTemplate template = new JdbcTemplate(dataSource); template.execute(JdbcTokenRepositoryImpl.CREATE_TABLE_SQL); // @formatter:off MvcResult result = rememberAuthentication("user", "password") .andExpect(cookie().secure(AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY, false)) .andReturn(); // @formatter:on Cookie cookie = rememberMeCookie(result); // @formatter:off this.mvc.perform(get("/authenticated").cookie(cookie)) .andExpect(status().isOk()); // @formatter:on int count = template.queryForObject("select count(*) from persistent_logins", int.class); assertThat(count).isEqualTo(1); } @Test public void requestWithRememberMeWhenUsingAuthenticationSuccessHandlerThenInvokesHandler() throws Exception { this.spring.configLocations(xml("WithAuthenticationSuccessHandler")).autowire(); TestDataSource dataSource = this.spring.getContext().getBean(TestDataSource.class); JdbcTemplate template = new JdbcTemplate(dataSource); template.execute(JdbcTokenRepositoryImpl.CREATE_TABLE_SQL); // @formatter:off MvcResult result = rememberAuthentication("user", "password") .andExpect(cookie().secure(AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY, false)) .andReturn(); // @formatter:on Cookie cookie = rememberMeCookie(result); // @formatter:off this.mvc.perform(get("/authenticated").cookie(cookie)) .andExpect(redirectedUrl("/target")); // @formatter:on int count = template.queryForObject("select count(*) from persistent_logins", int.class); assertThat(count).isEqualTo(1); } @Test public void requestWithRememberMeWhenUsingCustomRememberMeServicesThenAuthenticates() throws Exception { // SEC-1281 - using key with external services this.spring.configLocations(xml("WithServicesRef")).autowire(); // @formatter:off MvcResult result = rememberAuthentication("user", "password") .andExpect(cookie().secure(AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY, false)) .andExpect(cookie().maxAge(AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY, 5000)) .andReturn(); // @formatter:on Cookie cookie = rememberMeCookie(result); // @formatter:off this.mvc.perform(get("/authenticated").cookie(cookie)) .andExpect(status().isOk()); // SEC-909 this.mvc.perform(post("/logout").cookie(cookie).with(csrf())) .andExpect(cookie().maxAge(AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY, 0)) .andReturn(); // @formatter:on } @Test public void logoutWhenUsingRememberMeDefaultsThenCookieIsCancelled() throws Exception { this.spring.configLocations(xml("DefaultConfig")).autowire(); MvcResult result = rememberAuthentication("user", "password").andReturn(); Cookie cookie = rememberMeCookie(result); // @formatter:off this.mvc.perform(post("/logout").cookie(cookie).with(csrf())) .andExpect(cookie().maxAge(AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY, 0)); // @formatter:on } @Test public void requestWithRememberMeWhenTokenValidityIsConfiguredThenCookieReflectsCorrectExpiration() throws Exception { this.spring.configLocations(xml("TokenValidity")).autowire(); // @formatter:off MvcResult result = rememberAuthentication("user", "password") .andExpect(cookie().maxAge(AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY, 10000)) .andReturn(); // @formatter:on Cookie cookie = rememberMeCookie(result); // @formatter:off this.mvc.perform(get("/authenticated").cookie(cookie)) .andExpect(status().isOk()); // @formatter:on } @Test public void requestWithRememberMeWhenTokenValidityIsNegativeThenCookieReflectsCorrectExpiration() throws Exception { this.spring.configLocations(xml("NegativeTokenValidity")).autowire(); // @formatter:off rememberAuthentication("user", "password") .andExpect(cookie().maxAge(AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY, -1)); // @formatter:on } @Test public void configureWhenUsingDataSourceAndANegativeTokenValidityThenThrowsWiringException() { assertThatExceptionOfType(FatalBeanException.class) .isThrownBy(() -> this.spring.configLocations(xml("NegativeTokenValidityWithDataSource")).autowire()); } @Test public void requestWithRememberMeWhenTokenValidityIsResolvedByPropertyPlaceholderThenCookieReflectsCorrectExpiration() throws Exception { this.spring.configLocations(xml("Sec2165")).autowire(); rememberAuthentication("user", "password") .andExpect(cookie().maxAge(AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY, 30)); } @Test public void requestWithRememberMeWhenUseSecureCookieIsTrueThenCookieIsSecure() throws Exception { this.spring.configLocations(xml("SecureCookie")).autowire(); rememberAuthentication("user", "password") .andExpect(cookie().secure(AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY, true)); } /** * SEC-1827 */ @Test public void requestWithRememberMeWhenUseSecureCookieIsFalseThenCookieIsNotSecure() throws Exception { this.spring.configLocations(xml("Sec1827")).autowire(); rememberAuthentication("user", "password") .andExpect(cookie().secure(AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY, false)); } @Test public void configureWhenUsingPersistentTokenRepositoryAndANegativeTokenValidityThenThrowsWiringException() { assertThatExceptionOfType(BeanDefinitionParsingException.class).isThrownBy( () -> this.spring.configLocations(xml("NegativeTokenValidityWithPersistentRepository")).autowire()); } @Test public void rememberMeWhenCustomSecurityContextHolderStrategyThenUses() throws Exception { this.spring.configLocations(xml("WithSecurityContextHolderStrategy")).autowire(); MvcResult result = rememberAuthentication("user", "password").andReturn(); Cookie cookie = rememberMeCookie(result); // @formatter:off this.mvc.perform(get("/authenticated").cookie(cookie)) .andExpect(status().isOk()); // @formatter:on verify(this.spring.getContext().getBean(SecurityContextHolderStrategy.class), atLeastOnce()).getContext(); } @Test public void requestWithRememberMeWhenUsingCustomUserDetailsServiceThenInvokesThisUserDetailsService() throws Exception { this.spring.configLocations(xml("WithUserDetailsService")).autowire(); UserDetailsService userDetailsService = this.spring.getContext().getBean(UserDetailsService.class); given(userDetailsService.loadUserByUsername("user")) .willAnswer((invocation) -> new User("user", "{noop}password", Collections.emptyList())); MvcResult result = rememberAuthentication("user", "password").andReturn(); Cookie cookie = rememberMeCookie(result); // @formatter:off this.mvc.perform(get("/authenticated").cookie(cookie)) .andExpect(status().isOk()); // @formatter:on verify(userDetailsService, atLeastOnce()).loadUserByUsername("user"); } /** * SEC-742 */ @Test public void requestWithRememberMeWhenExcludingBasicAuthenticationFilterThenStillReauthenticates() throws Exception { this.spring.configLocations(xml("Sec742")).autowire(); // @formatter:off MvcResult result = this.mvc.perform(login("user", "password").param("remember-me", "true").with(csrf())) .andExpect(redirectedUrl("/messageList.html")) .andReturn(); // @formatter:on Cookie cookie = rememberMeCookie(result); // @formatter:off this.mvc.perform(get("/authenticated").cookie(cookie)) .andExpect(status().isOk()); // @formatter:on } /** * SEC-2119 */ @Test public void requestWithRememberMeWhenUsingCustomRememberMeParameterThenReauthenticates() throws Exception { this.spring.configLocations(xml("WithRememberMeParameter")).autowire(); // @formatter:off MockHttpServletRequestBuilder request = login("user", "password") .param("custom-remember-me-parameter", "true") .with(csrf()); MvcResult result = this.mvc.perform(request) .andExpect(redirectedUrl("/")) .andReturn(); // @formatter:on Cookie cookie = rememberMeCookie(result); // @formatter:off this.mvc.perform(get("/authenticated").cookie(cookie)) .andExpect(status().isOk()); // @formatter:on } @Test public void configureWhenUsingRememberMeParameterAndServicesRefThenThrowsWiringException() { assertThatExceptionOfType(BeanDefinitionParsingException.class) .isThrownBy(() -> this.spring.configLocations(xml("WithRememberMeParameterAndServicesRef")).autowire()); } /** * SEC-2826 */ @Test public void authenticateWhenUsingCustomRememberMeCookieNameThenIssuesCookieWithThatName() throws Exception { this.spring.configLocations(xml("WithRememberMeCookie")).autowire(); // @formatter:off rememberAuthentication("user", "password") .andExpect(cookie().exists("custom-remember-me-cookie")); // @formatter:on } /** * SEC-2826 */ @Test public void configureWhenUsingRememberMeCookieAndServicesRefThenThrowsWiringException() { assertThatExceptionOfType(BeanDefinitionParsingException.class) .isThrownBy(() -> this.spring.configLocations(xml("WithRememberMeCookieAndServicesRef")).autowire()) .withMessageContaining("Configuration problem: services-ref can't be used in combination with attributes " + "token-repository-ref,data-source-ref, user-service-ref, token-validity-seconds, " + "use-secure-cookie, remember-me-parameter or remember-me-cookie"); } private ResultActions rememberAuthentication(String username, String password) throws Exception { // @formatter:off MockHttpServletRequestBuilder request = login(username, password) .param(AbstractRememberMeServices.DEFAULT_PARAMETER, "true") .with(csrf()); return this.mvc.perform(request) .andExpect(redirectedUrl("/")); // @formatter:on } private static MockHttpServletRequestBuilder login(String username, String password) { // @formatter:off return post("/login") .param("username", username) .param("password", password); // @formatter:on } private static Cookie rememberMeCookie(MvcResult result) { return result.getResponse().getCookie("remember-me"); } private String xml(String configName) { return CONFIG_LOCATION_PREFIX + "-" + configName + ".xml"; } @RestController static class BasicController { @GetMapping("/authenticated") String ok() { return "ok"; } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/http/Saml2LoginBeanDefinitionParserTests.java ================================================ /* * 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 org.springframework.security.config.http; import java.nio.charset.StandardCharsets; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import net.shibboleth.shared.xml.SerializeSupport; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport; import org.opensaml.core.xml.io.Marshaller; import org.opensaml.saml.saml2.core.Assertion; import org.opensaml.saml.saml2.core.Response; import org.w3c.dom.Element; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.parsing.BeanDefinitionParsingException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationListener; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.event.AuthenticationSuccessEvent; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.security.saml2.core.OpenSamlInitializationService; import org.springframework.security.saml2.core.Saml2ParameterNames; import org.springframework.security.saml2.core.Saml2Utils; import org.springframework.security.saml2.core.TestSaml2X509Credentials; import org.springframework.security.saml2.provider.service.authentication.AbstractSaml2AuthenticationRequest; import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal; import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException; import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationToken; import org.springframework.security.saml2.provider.service.authentication.Saml2RedirectAuthenticationRequest; import org.springframework.security.saml2.provider.service.authentication.TestOpenSamlObjects; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository; import org.springframework.security.saml2.provider.service.registration.TestRelyingPartyRegistrations; import org.springframework.security.saml2.provider.service.web.Saml2AuthenticationRequestRepository; import org.springframework.security.saml2.provider.service.web.authentication.Saml2AuthenticationRequestResolver; import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners; import org.springframework.security.web.authentication.AuthenticationConverter; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.security.web.savedrequest.RequestCache; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; import org.springframework.test.web.servlet.result.MockMvcResultHandlers; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Tests for {@link Saml2LoginBeanDefinitionParser} * * @author Marcus da Coregio */ @ExtendWith({ SpringExtension.class, SpringTestContextExtension.class }) @SecurityTestExecutionListeners public class Saml2LoginBeanDefinitionParserTests { static { OpenSamlInitializationService.initialize(); } private static final String CONFIG_LOCATION_PREFIX = "classpath:org/springframework/security/config/http/Saml2LoginBeanDefinitionParserTests"; private static final RelyingPartyRegistration registration = TestRelyingPartyRegistrations.noCredentials() .signingX509Credentials((c) -> c.add(TestSaml2X509Credentials.assertingPartySigningCredential())) .assertingPartyMetadata((party) -> party .verificationX509Credentials((c) -> c.add(TestSaml2X509Credentials.relyingPartyVerifyingCredential()))) .build(); private static String SIGNED_RESPONSE; private static final String IDP_SSO_URL = "https://sso-url.example.com/IDP/SSO"; public final SpringTestContext spring = new SpringTestContext(this); @Autowired(required = false) private RequestCache requestCache; @Autowired(required = false) private AuthenticationFailureHandler authenticationFailureHandler; @Autowired(required = false) private AuthenticationSuccessHandler authenticationSuccessHandler; @Autowired(required = false) private RelyingPartyRegistrationRepository repository; @Autowired(required = false) private ApplicationListener authenticationSuccessListener; @Autowired(required = false) private AuthenticationConverter authenticationConverter; @Autowired(required = false) private Saml2AuthenticationRequestResolver authenticationRequestResolver; @Autowired(required = false) private Saml2AuthenticationRequestRepository authenticationRequestRepository; @Autowired(required = false) private ApplicationContext applicationContext; @Autowired private MockMvc mvc; @BeforeAll static void createResponse() throws Exception { String destination = registration.getAssertionConsumerServiceLocation(); String assertingPartyEntityId = registration.getAssertingPartyMetadata().getEntityId(); String relyingPartyEntityId = registration.getEntityId(); Response response = TestOpenSamlObjects.response(destination, assertingPartyEntityId); Assertion assertion = TestOpenSamlObjects.assertion("test@saml.user", assertingPartyEntityId, relyingPartyEntityId, destination); response.getAssertions().add(assertion); Response signed = TestOpenSamlObjects.signed(response, registration.getSigningX509Credentials().iterator().next(), relyingPartyEntityId); Marshaller marshaller = XMLObjectProviderRegistrySupport.getMarshallerFactory().getMarshaller(signed); Element element = marshaller.marshall(signed); String serialized = SerializeSupport.nodeToString(element); SIGNED_RESPONSE = Saml2Utils.samlEncode(serialized.getBytes(StandardCharsets.UTF_8)); } @Test public void requestWhenSingleRelyingPartyRegistrationThenAutoRedirect() throws Exception { this.spring.configLocations(this.xml("SingleRelyingPartyRegistration")).autowire(); // @formatter:off this.mvc.perform(get("/")) .andExpect(status().is3xxRedirection()) .andExpect(redirectedUrl("/saml2/authenticate/one")); // @formatter:on verify(this.requestCache).saveRequest(any(), any()); } @Test public void requestWhenMultiRelyingPartyRegistrationThenRedirectToLoginWithRelyingParties() throws Exception { this.spring.configLocations(this.xml("MultiRelyingPartyRegistration")).autowire(); // @formatter:off this.mvc.perform(get("/")) .andExpect(status().is3xxRedirection()) .andExpect(redirectedUrl("/login")); // @formatter:on } @Test public void requestLoginWhenMultiRelyingPartyRegistrationThenReturnLoginPageWithRelyingParties() throws Exception { this.spring.configLocations(this.xml("MultiRelyingPartyRegistration")).autowire(); // @formatter:off MvcResult mvcResult = this.mvc.perform(get("/login")) .andExpect(status().is2xxSuccessful()) .andReturn(); // @formatter:on String pageContent = mvcResult.getResponse().getContentAsString(); assertThat(pageContent).contains("two"); assertThat(pageContent).contains("one"); } @Test public void authenticateWhenAuthenticationResponseNotValidThenThrowAuthenticationException() throws Exception { this.spring.configLocations(this.xml("SingleRelyingPartyRegistration-WithCustomAuthenticationFailureHandler")) .autowire(); this.mvc.perform(get("/login/saml2/sso/one").param(Saml2ParameterNames.SAML_RESPONSE, "samlResponse123")); ArgumentCaptor exceptionCaptor = ArgumentCaptor .forClass(AuthenticationException.class); verify(this.authenticationFailureHandler).onAuthenticationFailure(any(), any(), exceptionCaptor.capture()); AuthenticationException exception = exceptionCaptor.getValue(); assertThat(exception).isInstanceOf(Saml2AuthenticationException.class); assertThat(((Saml2AuthenticationException) exception).getSaml2Error().getErrorCode()) .isEqualTo("invalid_response"); } @Test public void authenticateWhenAuthenticationResponseValidThenAuthenticate() throws Exception { this.spring.configLocations(this.xml("WithCustomRelyingPartyRepository")).autowire(); RelyingPartyRegistration relyingPartyRegistration = relyingPartyRegistrationWithVerifyingCredential(); // @formatter:off this.mvc.perform(post("/login/saml2/sso/" + relyingPartyRegistration.getRegistrationId()).param(Saml2ParameterNames.SAML_RESPONSE, SIGNED_RESPONSE)) .andDo(MockMvcResultHandlers.print()) .andExpect(status().is2xxSuccessful()); // @formatter:on ArgumentCaptor authenticationCaptor = ArgumentCaptor.forClass(Authentication.class); verify(this.authenticationSuccessHandler).onAuthenticationSuccess(any(), any(), authenticationCaptor.capture()); Authentication authentication = authenticationCaptor.getValue(); assertThat(authentication.getPrincipal()).isInstanceOf(Saml2AuthenticatedPrincipal.class); } @Test public void authenticateWhenCustomSecurityContextHolderStrategyThenUses() throws Exception { this.spring.configLocations(this.xml("WithCustomSecurityContextHolderStrategy")).autowire(); RelyingPartyRegistration relyingPartyRegistration = relyingPartyRegistrationWithVerifyingCredential(); // @formatter:off this.mvc.perform(post("/login/saml2/sso/" + relyingPartyRegistration.getRegistrationId()).param(Saml2ParameterNames.SAML_RESPONSE, SIGNED_RESPONSE)) .andDo(MockMvcResultHandlers.print()) .andExpect(status().is2xxSuccessful()); // @formatter:on ArgumentCaptor authenticationCaptor = ArgumentCaptor.forClass(Authentication.class); verify(this.authenticationSuccessHandler).onAuthenticationSuccess(any(), any(), authenticationCaptor.capture()); Authentication authentication = authenticationCaptor.getValue(); assertThat(authentication.getPrincipal()).isInstanceOf(Saml2AuthenticatedPrincipal.class); SecurityContextHolderStrategy strategy = this.spring.getContext().getBean(SecurityContextHolderStrategy.class); verify(strategy, atLeastOnce()).getContext(); } @Test public void authenticateWhenAuthenticationResponseValidThenAuthenticationSuccessEventPublished() throws Exception { this.spring.configLocations(this.xml("WithCustomRelyingPartyRepository")).autowire(); RelyingPartyRegistration relyingPartyRegistration = relyingPartyRegistrationWithVerifyingCredential(); // @formatter:off this.mvc.perform(post("/login/saml2/sso/" + relyingPartyRegistration.getRegistrationId()).param(Saml2ParameterNames.SAML_RESPONSE, SIGNED_RESPONSE)) .andDo(MockMvcResultHandlers.print()) .andExpect(status().is2xxSuccessful()); // @formatter:on verify(this.authenticationSuccessListener).onApplicationEvent(any(AuthenticationSuccessEvent.class)); } @Test public void authenticateWhenCustomAuthenticationConverterThenUses() throws Exception { this.spring.configLocations(this.xml("WithCustomRelyingPartyRepository-WithCustomAuthenticationConverter")) .autowire(); RelyingPartyRegistration relyingPartyRegistration = relyingPartyRegistrationWithVerifyingCredential(); String response = new String(Saml2Utils.samlDecode(SIGNED_RESPONSE)); given(this.authenticationConverter.convert(any(HttpServletRequest.class))) .willReturn(new Saml2AuthenticationToken(relyingPartyRegistration, response)); // @formatter:off MockHttpServletRequestBuilder request = post("/login/saml2/sso/" + relyingPartyRegistration.getRegistrationId()) .param("SAMLResponse", SIGNED_RESPONSE); // @formatter:on this.mvc.perform(request).andExpect(status().is3xxRedirection()).andExpect(redirectedUrl("/")); verify(this.authenticationConverter).convert(any(HttpServletRequest.class)); } @Test public void authenticateWhenCustomAuthenticationManagerThenUses() throws Exception { this.spring.configLocations(this.xml("WithCustomRelyingPartyRepository-WithCustomAuthenticationManager")) .autowire(); RelyingPartyRegistration relyingPartyRegistration = relyingPartyRegistrationWithVerifyingCredential(); AuthenticationManager authenticationManager = this.applicationContext.getBean("customAuthenticationManager", AuthenticationManager.class); String response = new String(Saml2Utils.samlDecode(SIGNED_RESPONSE)); given(authenticationManager.authenticate(any())) .willReturn(new Saml2AuthenticationToken(relyingPartyRegistration, response)); // @formatter:off MockHttpServletRequestBuilder request = post("/login/saml2/sso/" + relyingPartyRegistration.getRegistrationId()) .param("SAMLResponse", SIGNED_RESPONSE); // @formatter:on this.mvc.perform(request).andExpect(status().is3xxRedirection()).andExpect(redirectedUrl("/")); verify(authenticationManager).authenticate(any()); } @Test public void authenticationRequestWhenCustomAuthenticationRequestContextResolverThenUses() throws Exception { this.spring .configLocations(this.xml("WithCustomRelyingPartyRepository-WithCustomAuthenticationRequestResolver")) .autowire(); Saml2RedirectAuthenticationRequest request = Saml2RedirectAuthenticationRequest .withRelyingPartyRegistration(TestRelyingPartyRegistrations.noCredentials().build()) .samlRequest("request") .authenticationRequestUri(IDP_SSO_URL) .build(); given(this.authenticationRequestResolver.resolve(any(HttpServletRequest.class))).willReturn(request); this.mvc.perform(get("/saml2/authenticate/registration-id")).andExpect(status().isFound()); verify(this.authenticationRequestResolver).resolve(any(HttpServletRequest.class)); } @Test public void authenticationRequestWhenCustomAuthnRequestRepositoryThenUses() throws Exception { this.spring.configLocations(this.xml("WithCustomRelyingPartyRepository-WithCustomAuthnRequestRepository")) .autowire(); given(this.repository.findByRegistrationId(anyString())) .willReturn(TestRelyingPartyRegistrations.relyingPartyRegistration().build()); MockHttpServletRequestBuilder request = get("/saml2/authenticate/registration-id"); this.mvc.perform(request).andExpect(status().isFound()); verify(this.authenticationRequestRepository).saveAuthenticationRequest( any(AbstractSaml2AuthenticationRequest.class), any(HttpServletRequest.class), any(HttpServletResponse.class)); } @Test public void authenticateWhenCustomAuthnRequestRepositoryThenUses() throws Exception { this.spring.configLocations(this.xml("WithCustomRelyingPartyRepository-WithCustomAuthnRequestRepository")) .autowire(); RelyingPartyRegistrationRepository repository = mock(RelyingPartyRegistrationRepository.class); given(this.repository.findByRegistrationId(anyString())) .willReturn(TestRelyingPartyRegistrations.relyingPartyRegistration().build()); MockHttpServletRequestBuilder request = post("/login/saml2/sso/registration-id").param("SAMLResponse", SIGNED_RESPONSE); this.mvc.perform(request); verify(this.authenticationRequestRepository).loadAuthenticationRequest(any(HttpServletRequest.class)); verify(this.authenticationRequestRepository).removeAuthenticationRequest(any(HttpServletRequest.class), any(HttpServletResponse.class)); } @Test public void saml2LoginWhenLoginProcessingUrlWithoutRegistrationIdAndDefaultAuthenticationConverterThenValidates() { assertThatExceptionOfType(BeanDefinitionParsingException.class) .isThrownBy(() -> this.spring.configLocations(this.xml("WithCustomLoginProcessingUrl")).autowire()) .withMessageContaining("loginProcessingUrl must contain {registrationId} path variable"); } @Test public void authenticateWhenCustomLoginProcessingUrlAndCustomAuthenticationConverterThenAuthenticate() throws Exception { this.spring.configLocations(this.xml("WithCustomLoginProcessingUrl-WithCustomAuthenticationConverter")) .autowire(); String response = new String(Saml2Utils.samlDecode(SIGNED_RESPONSE)); given(this.authenticationConverter.convert(any(HttpServletRequest.class))) .willReturn(new Saml2AuthenticationToken(registration, response)); // @formatter:off MockHttpServletRequestBuilder request = post("/my/custom/url").param("SAMLResponse", SIGNED_RESPONSE); // @formatter:on this.mvc.perform(request).andExpect(redirectedUrl("/")); verify(this.authenticationConverter).convert(any(HttpServletRequest.class)); } private RelyingPartyRegistration relyingPartyRegistrationWithVerifyingCredential() { given(this.repository.findByRegistrationId(anyString())).willReturn(registration); return registration; } private String xml(String configName) { return CONFIG_LOCATION_PREFIX + "-" + configName + ".xml"; } } ================================================ FILE: config/src/test/java/org/springframework/security/config/http/Saml2LogoutBeanDefinitionParserTests.java ================================================ /* * 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 org.springframework.security.config.http; import java.nio.charset.StandardCharsets; import java.time.Instant; import java.util.Collections; import java.util.Map; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.opensaml.saml.saml2.core.LogoutRequest; import org.opensaml.xmlsec.signature.support.SignatureConstants; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.mock.web.MockHttpSession; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.security.saml2.core.Saml2Utils; import org.springframework.security.saml2.provider.service.authentication.DefaultSaml2AuthenticatedPrincipal; import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication; import org.springframework.security.saml2.provider.service.authentication.TestOpenSamlObjects; import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutRequest; import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutRequestValidator; import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutResponse; import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutResponseValidator; import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutValidatorResult; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository; import org.springframework.security.saml2.provider.service.web.authentication.logout.HttpSessionLogoutRequestRepository; import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutRequestRepository; import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutRequestResolver; import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutResponseResolver; import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners; import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.request.RequestPostProcessor; import org.springframework.web.util.UriComponentsBuilder; import org.springframework.web.util.UriUtils; import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.Matchers.containsString; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.verify; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Tests for {@link Saml2LogoutBeanDefinitionParser} * * @author Marcus da Coregio */ @ExtendWith({ SpringExtension.class, SpringTestContextExtension.class }) @SecurityTestExecutionListeners public class Saml2LogoutBeanDefinitionParserTests { public final SpringTestContext spring = new SpringTestContext(this); private final Saml2LogoutRequestRepository logoutRequestRepository = new HttpSessionLogoutRequestRepository(); private static final String CONFIG_LOCATION_PREFIX = "classpath:org/springframework/security/config/http/Saml2LogoutBeanDefinitionParserTests"; String apLogoutRequest = "nZFBa4MwGIb/iuQeE2NTXFDLQAaC26Hrdtgt1dQFNMnyxdH9+zlboeyww275SN7nzcOX787jEH0qD9qaAiUxRZEyre206Qv0cnjAGdqVOchxYE40trdT2KuPSUGI5qQBcbkq0OSNsBI0CCNHBSK04vn+sREspsJ5G2xrBxRVc1AbGZa29xAcCEK8i9VZjm5QsfU9GZYWsoCJv5ShqK4K1Ow5p5LyU4aP6XaLN3cpw9mGctydjrxNaZt1XM5vASZVGwjShAIxyhJMU8z4gSWCM8GSmDH+hqLX1Xv+JLpaiiXsb+3+lpMAyv8IoVI6rEzQ4QvrLie3uBX+NMfr6l/waT6t0AumvI6/FlN+Aw=="; String apLogoutRequestSigAlg = SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256; String apLogoutRequestRelayState = "33591874-b123-4f2c-ab0d-2d0d84aa8b56"; String apLogoutRequestSignature = "oKqdzrmn2YAqXcwkow2lzRXr5PNHm0s/gWsRnaZYhC+Oq5ekK5uIKQYvtmNR94HJjDe1VRs+vVQCYivgdoTzBV2ZlffTXZmYsCsY9q4jbCWR6R5CbhU73/MkKQsPcyVvMhNYxnDYapIlxDsfoZNTboDEz3GM+HRoGRfl9emCXY0lPRYwqC4kpu7oMDBkafR0A09jPIxFuNpqlLPwUxL9m+DGkvDK3mFDN1xJcgZaK73HcuJe7Qh4huOrKNFetwc5EvqfiwgiWF6sfq9A+rZBfCIYo10NNLY7fNQAR2IqwcKtawHgTGWbeshRyFrwVYMR64EnClfxUHsHKf5kiZ2dlw=="; String apLogoutResponse = "fZHRa4MwEMb/Fcl7jEadGqplrAwK3Uvb9WFvZ4ydoInk4uj++1nXbmWMvhwcd9/3Jb9bLE99530oi63RBQn9gHhKS1O3+liQ1/0zzciyXCD0HR/ExhzN6LYKB6NReZNUo/ieFWS0WhjAFoWGXqFwUuweXzaC+4EYrHFGmo54K4Wu1eDmuHfnBhSM2cFXJ+iHTvnGHlk3x7DZmNlLGvHWq4Jstk0GUSjjiIZJI2lcpQnNeRLTAOo4fwCeQg3Trr6+cm/OqmnWVHECVGWQ0jgCSatsKvXUxhFvZF7xSYU4qrVGB9oVhAc8pEFEebLnkeBc8NyPePpGvMOV1/Q3cqEjZrG9hXKfCSAqe+ZAShio0q51n7StF+zW7gf9zoEb8U/7ZGrlHaAb1f0onLfFbpRSIRJWXkJ+bdm/Fy6/AA=="; String apLogoutResponseSigAlg = SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256; String apLogoutResponseRelayState = "8f63887a-ec7e-4149-b6a0-dd730017f315"; String apLogoutResponseSignature = "h2fDqSIBfmnkRHKDMY4IxkCXcI0w98ydNsnPmv1b7GTZCWLbJ+oxaP2yZNPw7wOWXTv86cTPwKLjx5halKy5C+hhWnT0haKhuMcUvHlsgAMBbJKLV+1afzL4O77cvAQJmMNRK7ugXGNV5PTEnd1U4voy134OgdD5XycYiFVRZOwP5H84eJ9xxlvqQwqDvZTcgiF/ZS4ioZgzgnIFcbagZQ12LWNh26OMaUpIW04kCeO6t2dUsxOL6nZWvNrX/Zx1sORIpu4doDUa1RYC8YnjZeQEzDqUVC/dBO/mbVJ/hbF9tD0jBUx7YIgoXpqsWK4TcCsvmlmhrJXvGxDyoAWu2Q=="; String rpLogoutRequest = "nZFBa4MwGIb/iuQeY6NlGtQykIHgdui6HXaLmrqAJlm+OLp/v0wrlB122CXkI3mfNw/JD5dpDD6FBalVgXZhhAKhOt1LNRTo5fSAU3Qoc+DTSA1r9KBndxQfswAX+KQCth4VaLaKaQ4SmOKTAOY69nz/2DAaRsxY7XSnRxRUPigVd0vbu3MGGCHchOLCJzOKUNuBjEsLWcDErmUoqKsCNcc+yc5tsudYpPwOJzHvcJv6pfdjEtNzl7XU3wWYRa3AceUKRCO6w1GM6f5EY0Ypo1lIk+gNBa+bt38kulqyJWxv7f6W4wDC/gih0hoslJPuC8s+J7e4Df7k43X1L/jsdxt0xZTX8dfHlN8="; String rpLogoutRequestId = "LRd49fb45a-e8a7-43ac-b8ac-d8a7432fc9b2"; String rpLogoutRequestRelayState = "8f63887a-ec7e-4149-b6a0-dd730017f315"; String rpLogoutRequestSignature = "h2fDqSIBfmnkRHKDMY4IxkCXcI0w98ydNsnPmv1b7GTZCWLbJ+oxaP2yZNPw7wOWXTv86cTPwKLjx5halKy5C+hhWnT0haKhuMcUvHlsgAMBbJKLV+1afzL4O77cvAQJmMNRK7ugXGNV5PTEnd1U4voy134OgdD5XycYiFVRZOwP5H84eJ9xxlvqQwqDvZTcgiF/ZS4ioZgzgnIFcbagZQ12LWNh26OMaUpIW04kCeO6t2dUsxOL6nZWvNrX/Zx1sORIpu4doDUa1RYC8YnjZeQEzDqUVC/dBO/mbVJ/hbF9tD0jBUx7YIgoXpqsWK4TcCsvmlmhrJXvGxDyoAWu2Q=="; @Autowired(required = false) private RelyingPartyRegistrationRepository repository; @Autowired private MockMvc mvc; private Saml2Authentication saml2User; private MockHttpServletRequest request; private MockHttpServletResponse response; @BeforeEach public void setup() { DefaultSaml2AuthenticatedPrincipal principal = new DefaultSaml2AuthenticatedPrincipal("user", Collections.emptyMap()); principal.setRelyingPartyRegistrationId("registration-id"); this.saml2User = new Saml2Authentication(principal, "response", AuthorityUtils.createAuthorityList("ROLE_USER")); this.request = new MockHttpServletRequest("POST", "/login/saml2/sso/test-rp"); this.response = new MockHttpServletResponse(); } @Test public void logoutWhenLogoutSuccessHandlerAndNotSaml2LoginThenDefaultLogoutSuccessHandler() throws Exception { this.spring.configLocations(this.xml("LogoutSuccessHandler")).autowire(); TestingAuthenticationToken user = new TestingAuthenticationToken("user", "password"); MvcResult result = this.mvc.perform(post("/logout").with(authentication(user)).with(csrf())) .andExpect(status().isFound()) .andReturn(); String location = result.getResponse().getHeader("Location"); assertThat(location).isEqualTo("/logoutSuccessEndpoint"); } @Test public void saml2LogoutWhenDefaultsThenLogsOutAndSendsLogoutRequest() throws Exception { this.spring.configLocations(this.xml("Default")).autowire(); MvcResult result = this.mvc.perform(post("/logout").with(authentication(this.saml2User)).with(csrf())) .andExpect(status().isFound()) .andReturn(); String location = result.getResponse().getHeader("Location"); assertThat(location).startsWith("https://ap.example.org/logout/saml2/request"); } @Test public void saml2LogoutWhenUnauthenticatedThenEntryPoint() throws Exception { this.spring.configLocations(this.xml("Default")).autowire(); this.mvc.perform(post("/logout").with(csrf())) .andExpect(status().isFound()) .andExpect(redirectedUrl("/login?logout")); } @Test public void saml2LogoutWhenMissingCsrfThen403() throws Exception { this.spring.configLocations(this.xml("Default")).autowire(); this.mvc.perform(post("/logout").with(authentication(this.saml2User))).andExpect(status().isForbidden()); } @Test public void saml2LogoutWhenGetThenDefaultLogoutPage() throws Exception { this.spring.configLocations(this.xml("Default")).autowire(); MvcResult result = this.mvc.perform(get("/logout").with(authentication(this.saml2User))) .andExpect(status().isOk()) .andReturn(); assertThat(result.getResponse().getContentAsString()).contains("Are you sure you want to log out?"); } @Test public void saml2LogoutWhenPutOrDeleteThen404() throws Exception { this.spring.configLocations(this.xml("Default")).autowire(); this.mvc.perform(put("/logout").with(authentication(this.saml2User)).with(csrf())) .andExpect(status().isNotFound()); this.mvc.perform(delete("/logout").with(authentication(this.saml2User)).with(csrf())) .andExpect(status().isNotFound()); } @Test public void saml2LogoutWhenNoRegistrationThen401() throws Exception { this.spring.configLocations(this.xml("Default")).autowire(); DefaultSaml2AuthenticatedPrincipal principal = new DefaultSaml2AuthenticatedPrincipal("user", Collections.emptyMap()); principal.setRelyingPartyRegistrationId("wrong"); Saml2Authentication authentication = new Saml2Authentication(principal, "response", AuthorityUtils.createAuthorityList("ROLE_USER")); this.mvc.perform(post("/logout").with(authentication(authentication)).with(csrf())) .andExpect(status().isUnauthorized()); } @Test public void saml2LogoutWhenCsrfDisabledAndNoAuthenticationThenFinalRedirect() throws Exception { this.spring.configLocations(this.xml("CsrfDisabled-MockLogoutSuccessHandler")).autowire(); this.mvc.perform(post("/logout")); LogoutSuccessHandler logoutSuccessHandler = this.spring.getContext().getBean(LogoutSuccessHandler.class); verify(logoutSuccessHandler).onLogoutSuccess(any(), any(), any()); } @Test public void saml2LogoutWhenCustomLogoutRequestResolverThenUses() throws Exception { this.spring.configLocations(this.xml("CustomComponents")).autowire(); RelyingPartyRegistration registration = this.repository.findByRegistrationId("registration-id"); Saml2LogoutRequest logoutRequest = Saml2LogoutRequest.withRelyingPartyRegistration(registration) .samlRequest(this.rpLogoutRequest) .id(this.rpLogoutRequestId) .relayState(this.rpLogoutRequestRelayState) .parameters((params) -> params.put("Signature", this.rpLogoutRequestSignature)) .build(); given(getBean(Saml2LogoutRequestResolver.class).resolve(any(), any())).willReturn(logoutRequest); this.mvc.perform(post("/logout").with(authentication(this.saml2User)).with(csrf())); verify(getBean(Saml2LogoutRequestResolver.class)).resolve(any(), any()); } @Test public void saml2LogoutRequestWhenDefaultsThenLogsOutAndSendsLogoutResponse() throws Exception { this.spring.configLocations(this.xml("Default")).autowire(); DefaultSaml2AuthenticatedPrincipal principal = new DefaultSaml2AuthenticatedPrincipal("user", Collections.emptyMap()); principal.setRelyingPartyRegistrationId("get"); Saml2Authentication user = new Saml2Authentication(principal, "response", AuthorityUtils.createAuthorityList("ROLE_USER")); MvcResult result = this.mvc .perform(get("/logout/saml2/slo").param("SAMLRequest", this.apLogoutRequest) .param("RelayState", this.apLogoutRequestRelayState) .param("SigAlg", this.apLogoutRequestSigAlg) .param("Signature", this.apLogoutRequestSignature) .with(samlQueryString()) .with(authentication(user))) .andExpect(status().isFound()) .andReturn(); String location = result.getResponse().getHeader("Location"); assertThat(location).startsWith("https://ap.example.org/logout/saml2/response"); } @Test public void saml2LogoutRequestWhenCustomSecurityContextHolderStrategyThenUses() throws Exception { this.spring.configLocations(this.xml("WithSecurityContextHolderStrategy")).autowire(); DefaultSaml2AuthenticatedPrincipal principal = new DefaultSaml2AuthenticatedPrincipal("user", Collections.emptyMap()); principal.setRelyingPartyRegistrationId("get"); Saml2Authentication user = new Saml2Authentication(principal, "response", AuthorityUtils.createAuthorityList("ROLE_USER")); MvcResult result = this.mvc .perform(get("/logout/saml2/slo").param("SAMLRequest", this.apLogoutRequest) .param("RelayState", this.apLogoutRequestRelayState) .param("SigAlg", this.apLogoutRequestSigAlg) .param("Signature", this.apLogoutRequestSignature) .with(samlQueryString()) .with(authentication(user))) .andExpect(status().isFound()) .andReturn(); String location = result.getResponse().getHeader("Location"); assertThat(location).startsWith("https://ap.example.org/logout/saml2/response"); verify(getBean(SecurityContextHolderStrategy.class), atLeastOnce()).getContext(); } @Test public void saml2LogoutRequestWhenNoRegistrationThen400() throws Exception { this.spring.configLocations(this.xml("Default")).autowire(); DefaultSaml2AuthenticatedPrincipal principal = new DefaultSaml2AuthenticatedPrincipal("user", Collections.emptyMap()); principal.setRelyingPartyRegistrationId("wrong"); Saml2Authentication user = new Saml2Authentication(principal, "response", AuthorityUtils.createAuthorityList("ROLE_USER")); this.mvc .perform(get("/logout/saml2/slo").param("SAMLRequest", this.apLogoutRequest) .param("RelayState", this.apLogoutRequestRelayState) .param("SigAlg", this.apLogoutRequestSigAlg) .param("Signature", this.apLogoutRequestSignature) .with(authentication(user))) .andExpect(status().isBadRequest()); } // gh-14635 @Test public void saml2LogoutRequestWhenInvalidSamlRequestThen302Redirect() throws Exception { this.spring.configLocations(this.xml("Default")).autowire(); this.mvc .perform(get("/logout/saml2/slo").param("SAMLRequest", this.apLogoutRequest) .param("RelayState", this.apLogoutRequestRelayState) .param("SigAlg", this.apLogoutRequestSigAlg) .with(authentication(this.saml2User))) .andExpect(status().isFound()); } @Test public void saml2LogoutRequestWhenCustomLogoutRequestHandlerThenUses() throws Exception { this.spring.configLocations(this.xml("CustomComponents")).autowire(); RelyingPartyRegistration registration = this.repository.findByRegistrationId("registration-id"); LogoutRequest logoutRequest = TestOpenSamlObjects.assertingPartyLogoutRequest(registration); logoutRequest.setIssueInstant(Instant.now()); given(getBean(Saml2LogoutRequestValidator.class).validate(any())) .willReturn(Saml2LogoutValidatorResult.success()); Saml2LogoutResponse logoutResponse = Saml2LogoutResponse.withRelyingPartyRegistration(registration) .samlResponse("samlResponse") .build(); given(getBean(Saml2LogoutResponseResolver.class).resolve(any(), any())).willReturn(logoutResponse); this.mvc .perform(post("/logout/saml2/slo").param("SAMLRequest", "samlRequest").with(authentication(this.saml2User))) .andReturn(); verify(getBean(Saml2LogoutRequestValidator.class)).validate(any()); verify(getBean(Saml2LogoutResponseResolver.class)).resolve(any(), any()); } @Test public void saml2LogoutResponseWhenDefaultsThenRedirects() throws Exception { this.spring.configLocations(this.xml("Default")).autowire(); RelyingPartyRegistration registration = this.repository.findByRegistrationId("get"); Saml2LogoutRequest logoutRequest = Saml2LogoutRequest.withRelyingPartyRegistration(registration) .samlRequest(this.rpLogoutRequest) .id(this.rpLogoutRequestId) .relayState(this.rpLogoutRequestRelayState) .parameters((params) -> params.put("Signature", this.rpLogoutRequestSignature)) .build(); this.logoutRequestRepository.saveLogoutRequest(logoutRequest, this.request, this.response); this.request.setParameter("RelayState", logoutRequest.getRelayState()); assertThat(this.logoutRequestRepository.loadLogoutRequest(this.request)).isNotNull(); this.mvc .perform(get("/logout/saml2/slo").session(((MockHttpSession) this.request.getSession())) .param("SAMLResponse", this.apLogoutResponse) .param("RelayState", this.apLogoutResponseRelayState) .param("SigAlg", this.apLogoutResponseSigAlg) .param("Signature", this.apLogoutResponseSignature) .with(samlQueryString())) .andExpect(status().isFound()) .andExpect(redirectedUrl("/login?logout")); assertThat(this.logoutRequestRepository.loadLogoutRequest(this.request)).isNull(); } @Test public void saml2LogoutResponseWhenInvalidSamlResponseThen401() throws Exception { this.spring.configLocations(this.xml("Default")).autowire(); RelyingPartyRegistration registration = this.repository.findByRegistrationId("registration-id"); Saml2LogoutRequest logoutRequest = Saml2LogoutRequest.withRelyingPartyRegistration(registration) .samlRequest(this.rpLogoutRequest) .id(this.rpLogoutRequestId) .relayState(this.rpLogoutRequestRelayState) .parameters((params) -> params.put("Signature", this.rpLogoutRequestSignature)) .build(); this.logoutRequestRepository.saveLogoutRequest(logoutRequest, this.request, this.response); String deflatedApLogoutResponse = Saml2Utils.samlEncode( Saml2Utils.samlInflate(Saml2Utils.samlDecode(this.apLogoutResponse)).getBytes(StandardCharsets.UTF_8)); this.mvc .perform(post("/logout/saml2/slo").session((MockHttpSession) this.request.getSession()) .param("SAMLResponse", deflatedApLogoutResponse) .param("RelayState", this.rpLogoutRequestRelayState) .param("SigAlg", this.apLogoutRequestSigAlg) .param("Signature", this.apLogoutResponseSignature) .with(samlQueryString())) .andExpect(status().reason(containsString("invalid_signature"))) .andExpect(status().isUnauthorized()); } @Test public void saml2LogoutResponseWhenCustomLogoutResponseHandlerThenUses() throws Exception { this.spring.configLocations(this.xml("CustomComponents")).autowire(); RelyingPartyRegistration registration = this.repository.findByRegistrationId("get"); Saml2LogoutRequest logoutRequest = Saml2LogoutRequest.withRelyingPartyRegistration(registration) .samlRequest(this.rpLogoutRequest) .id(this.rpLogoutRequestId) .relayState(this.rpLogoutRequestRelayState) .parameters((params) -> params.put("Signature", this.rpLogoutRequestSignature)) .build(); given(getBean(Saml2LogoutRequestRepository.class).removeLogoutRequest(any(), any())).willReturn(logoutRequest); given(getBean(Saml2LogoutResponseValidator.class).validate(any())) .willReturn(Saml2LogoutValidatorResult.success()); this.mvc.perform(get("/logout/saml2/slo").param("SAMLResponse", "samlResponse")).andReturn(); verify(getBean(Saml2LogoutResponseValidator.class)).validate(any()); } // gh-11363 @Test public void saml2LogoutWhenCustomLogoutRequestRepositoryThenUses() throws Exception { this.spring.configLocations(this.xml("CustomComponents")).autowire(); RelyingPartyRegistration registration = this.repository.findByRegistrationId("get"); Saml2LogoutRequest logoutRequest = Saml2LogoutRequest.withRelyingPartyRegistration(registration) .samlRequest(this.rpLogoutRequest) .id(this.rpLogoutRequestId) .relayState(this.rpLogoutRequestRelayState) .parameters((params) -> params.put("Signature", this.rpLogoutRequestSignature)) .build(); given(getBean(Saml2LogoutRequestResolver.class).resolve(any(), any())).willReturn(logoutRequest); this.mvc.perform(post("/logout").with(authentication(this.saml2User)).with(csrf())); verify(getBean(Saml2LogoutRequestRepository.class)).saveLogoutRequest(eq(logoutRequest), any(), any()); } private T getBean(Class clazz) { return this.spring.getContext().getBean(clazz); } private String xml(String configName) { return CONFIG_LOCATION_PREFIX + "-" + configName + ".xml"; } private SamlQueryStringRequestPostProcessor samlQueryString() { return new SamlQueryStringRequestPostProcessor(); } static class SamlQueryStringRequestPostProcessor implements RequestPostProcessor { @Override public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) { UriComponentsBuilder builder = UriComponentsBuilder.newInstance(); for (Map.Entry entries : request.getParameterMap().entrySet()) { builder.queryParam(entries.getKey(), UriUtils.encode(entries.getValue()[0], StandardCharsets.ISO_8859_1)); } request.setQueryString(builder.build(true).toUriString().substring(1)); return request; } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/http/SecurityContextHolderAwareRequestConfigTests.java ================================================ /* * 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 org.springframework.security.config.http; import java.io.IOException; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.apache.http.HttpHeaders; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.mock.web.MockHttpSession; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners; import org.springframework.security.test.context.support.WithMockUser; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.CoreMatchers.containsString; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.cookie; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * @author Rob Winch * @author Josh Cummings */ @ExtendWith({ SpringExtension.class, SpringTestContextExtension.class }) @SecurityTestExecutionListeners public class SecurityContextHolderAwareRequestConfigTests { private static final String CONFIG_LOCATION_PREFIX = "classpath:org/springframework/security/config/http/SecurityContextHolderAwareRequestConfigTests"; public final SpringTestContext spring = new SpringTestContext(this); @Autowired private MockMvc mvc; @Test public void servletLoginWhenUsingDefaultConfigurationThenUsesSpringSecurity() throws Exception { this.spring.configLocations(this.xml("Simple")).autowire(); // @formatter:off this.mvc.perform(get("/good-login")) .andExpect(status().isOk()) .andExpect(content().string("user")); // @formatter:on } @Test public void servletAuthenticateWhenUsingDefaultConfigurationThenUsesSpringSecurity() throws Exception { this.spring.configLocations(this.xml("Simple")).autowire(); // @formatter:off this.mvc.perform(get("/authenticate")) .andExpect(status().isFound()) .andExpect(redirectedUrl("/login")); // @formatter:on } @Test public void servletLogoutWhenUsingDefaultConfigurationThenUsesSpringSecurity() throws Exception { this.spring.configLocations(this.xml("Simple")).autowire(); MvcResult result = this.mvc.perform(get("/good-login")).andReturn(); MockHttpSession session = (MockHttpSession) result.getRequest().getSession(false); assertThat(session).isNotNull(); // @formatter:off result = this.mvc.perform(get("/do-logout").session(session)) .andExpect(status().isOk()) .andExpect(content().string("")) .andReturn(); // @formatter:on session = (MockHttpSession) result.getRequest().getSession(false); assertThat(session).isNull(); } @Test public void servletAuthenticateWhenUsingHttpBasicThenUsesSpringSecurity() throws Exception { this.spring.configLocations(this.xml("HttpBasic")).autowire(); // @formatter:off this.mvc.perform(get("/authenticate")) .andExpect(status().isUnauthorized()) .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, containsString("discworld"))); // @formatter:on } @Test public void servletAuthenticateWhenUsingFormLoginThenUsesSpringSecurity() throws Exception { this.spring.configLocations(this.xml("FormLogin")).autowire(); // @formatter:off this.mvc.perform(get("/authenticate")) .andExpect(status().isFound()) .andExpect(redirectedUrl("/login")); // @formatter:on } @Test public void servletLoginWhenUsingMultipleHttpConfigsThenUsesSpringSecurity() throws Exception { this.spring.configLocations(this.xml("MultiHttp")).autowire(); // @formatter:off this.mvc.perform(get("/good-login")) .andExpect(status().isOk()) .andExpect(content().string("user")); this.mvc.perform(get("/v2/good-login")) .andExpect(status().isOk()) .andExpect(content().string("user2")); // @formatter:on } @Test public void servletAuthenticateWhenUsingMultipleHttpConfigsThenUsesSpringSecurity() throws Exception { this.spring.configLocations(this.xml("MultiHttp")).autowire(); // @formatter:off this.mvc.perform(get("/authenticate")) .andExpect(status().isFound()) .andExpect(redirectedUrl("/login")); this.mvc.perform(get("/v2/authenticate")) .andExpect(status().isFound()) .andExpect(redirectedUrl("/login2")); // @formatter:on } @Test public void servletLogoutWhenUsingMultipleHttpConfigsThenUsesSpringSecurity() throws Exception { this.spring.configLocations(this.xml("MultiHttp")).autowire(); MvcResult result = this.mvc.perform(get("/good-login")).andReturn(); MockHttpSession session = (MockHttpSession) result.getRequest().getSession(false); assertThat(session).isNotNull(); // @formatter:off result = this.mvc.perform(get("/do-logout").session(session)) .andExpect(status().isOk()) .andExpect(content().string("")) .andReturn(); // @formatter:on session = (MockHttpSession) result.getRequest().getSession(false); assertThat(session).isNotNull(); // @formatter:off result = this.mvc.perform(get("/v2/good-login")) .andReturn(); // @formatter:on session = (MockHttpSession) result.getRequest().getSession(false); assertThat(session).isNotNull(); // @formatter:off result = this.mvc.perform(get("/v2/do-logout").session(session)) .andExpect(status().isOk()) .andExpect(content().string("")) .andReturn(); // @formatter:on session = (MockHttpSession) result.getRequest().getSession(false); assertThat(session).isNull(); } @Test public void servletLogoutWhenUsingCustomLogoutThenUsesSpringSecurity() throws Exception { this.spring.configLocations(this.xml("Logout")).autowire(); this.mvc.perform(get("/authenticate")).andExpect(status().isFound()).andExpect(redirectedUrl("/signin")); // @formatter:off MvcResult result = this.mvc.perform(get("/good-login")) .andReturn(); // @formatter:on MockHttpSession session = (MockHttpSession) result.getRequest().getSession(false); assertThat(session).isNotNull(); // @formatter:off result = this.mvc.perform(get("/do-logout").session(session)) .andExpect(status().isOk()) .andExpect(content().string("")) .andExpect(cookie().maxAge("JSESSIONID", 0)) .andReturn(); // @formatter:on session = (MockHttpSession) result.getRequest().getSession(false); assertThat(session).isNotNull(); } /** * SEC-2926: Role Prefix is set */ @Test @WithMockUser public void servletIsUserInRoleWhenUsingDefaultConfigThenRoleIsSet() throws Exception { this.spring.configLocations(this.xml("Simple")).autowire(); // @formatter:off this.mvc.perform(get("/role")) .andExpect(content().string("true")); // @formatter:on } private String xml(String configName) { return CONFIG_LOCATION_PREFIX + "-" + configName + ".xml"; } @RestController public static class ServletAuthenticatedController { @GetMapping("/v2/good-login") public String v2Login(HttpServletRequest request) throws ServletException { request.login("user2", "password2"); return this.principal(); } @GetMapping("/good-login") public String login(HttpServletRequest request) throws ServletException { request.login("user", "password"); return this.principal(); } @GetMapping("/v2/authenticate") public String v2Authenticate(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { return this.authenticate(request, response); } @GetMapping("/authenticate") public String authenticate(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { request.authenticate(response); return this.principal(); } @GetMapping("/v2/do-logout") public String v2Logout(HttpServletRequest request) throws ServletException { return this.logout(request); } @GetMapping("/do-logout") public String logout(HttpServletRequest request) throws ServletException { request.logout(); return this.principal(); } @GetMapping("/role") public String role(HttpServletRequest request) { return String.valueOf(request.isUserInRole("USER")); } private String principal() { if (SecurityContextHolder.getContext().getAuthentication() != null) { return SecurityContextHolder.getContext().getAuthentication().getName(); } return null; } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/http/SecurityFiltersAssertions.java ================================================ /* * 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 org.springframework.security.config.http; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.stream.Collectors; import static org.assertj.core.api.Assertions.assertThat; /** * Assertions for tests that rely on confirming behavior of the package-private * SecurityFilters enum * * @author Josh Cummings */ public final class SecurityFiltersAssertions { private static Collection ordered = Arrays.asList(SecurityFilters.values()); private SecurityFiltersAssertions() { } public static void assertEquals(List filters) { List expected = ordered.stream().map(SecurityFilters::name).collect(Collectors.toList()); assertThat(filters).isEqualTo(expected); } } ================================================ FILE: config/src/test/java/org/springframework/security/config/http/SessionManagementConfigServlet31Tests.java ================================================ /* * 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 org.springframework.security.config.http; import jakarta.servlet.Filter; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.mock.web.MockFilterChain; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.security.config.util.InMemoryXmlApplicationContext; import org.springframework.security.web.servlet.TestMockHttpServletRequests; import static org.assertj.core.api.Assertions.assertThat; /** * @author Rob Winch * */ public class SessionManagementConfigServlet31Tests { // @formatter:off private static final String XML_AUTHENTICATION_MANAGER = "" + " " + " " + " " + " " + " " + ""; // @formatter:on MockHttpServletRequest request; MockHttpServletResponse response; MockFilterChain chain; ConfigurableApplicationContext context; Filter springSecurityFilterChain; @BeforeEach public void setup() { this.request = new MockHttpServletRequest(); this.response = new MockHttpServletResponse(); this.chain = new MockFilterChain(); } @AfterEach public void teardown() { if (this.context != null) { this.context.close(); } } @Test public void changeSessionIdThenPreserveParameters() throws Exception { MockHttpServletRequest request = TestMockHttpServletRequests.post("/login") .param("username", "user") .param("password", "password") .build(); request.getSession(); request.getSession().setAttribute("attribute1", "value1"); String id = request.getSession().getId(); // @formatter:off loadContext("\n" + " \n" + " \n" + " \n" + " \n" + " " + XML_AUTHENTICATION_MANAGER); // @formatter:on this.springSecurityFilterChain.doFilter(request, this.response, this.chain); assertThat(request.getSession().getId()).isNotEqualTo(id); assertThat(request.getSession().getAttribute("attribute1")).isEqualTo("value1"); } @Test public void changeSessionId() throws Exception { MockHttpServletRequest request = TestMockHttpServletRequests.post("/login") .param("username", "user") .param("password", "password") .build(); request.getSession(); String id = request.getSession().getId(); // @formatter:off loadContext("\n" + " \n" + " \n" + " \n" + " \n" + " " + XML_AUTHENTICATION_MANAGER); // @formatter:on this.springSecurityFilterChain.doFilter(request, this.response, this.chain); assertThat(request.getSession().getId()).isNotEqualTo(id); } private void loadContext(String context) { this.context = new InMemoryXmlApplicationContext(context); this.springSecurityFilterChain = this.context.getBean("springSecurityFilterChain", Filter.class); } } ================================================ FILE: config/src/test/java/org/springframework/security/config/http/SessionManagementConfigTests.java ================================================ /* * 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 org.springframework.security.config.http; import java.io.IOException; import java.security.Principal; import java.util.List; import jakarta.servlet.Filter; import jakarta.servlet.ServletContext; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponseWrapper; import org.apache.http.HttpStatus; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.mock.web.MockHttpSession; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.core.Authentication; import org.springframework.security.core.session.SessionRegistry; import org.springframework.security.util.FieldUtils; import org.springframework.security.web.FilterChainProxy; import org.springframework.security.web.authentication.RememberMeServices; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.authentication.logout.CompositeLogoutHandler; import org.springframework.security.web.authentication.logout.LogoutFilter; import org.springframework.security.web.authentication.logout.LogoutHandler; import org.springframework.security.web.authentication.logout.LogoutSuccessEventPublishingLogoutHandler; import org.springframework.security.web.authentication.session.SessionAuthenticationException; import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy; import org.springframework.security.web.context.HttpSessionSecurityContextRepository; import org.springframework.security.web.session.ConcurrentSessionFilter; import org.springframework.security.web.session.SessionManagementFilter; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.ResultMatcher; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.context.WebApplicationContext; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.cookie; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Tests session-related functionality for the <http> namespace element and * <session-management> * * @author Luke Taylor * @author Rob Winch * @author Josh Cummings * @author Onur Kagan Ozcan * @author Mazen Aissa */ @ExtendWith(SpringTestContextExtension.class) public class SessionManagementConfigTests { private static final String CONFIG_LOCATION_PREFIX = "classpath:org/springframework/security/config/http/SessionManagementConfigTests"; public final SpringTestContext spring = new SpringTestContext(this); @Autowired MockMvc mvc; @Test public void requestWhenCreateSessionAlwaysThenAlwaysCreatesSession() throws Exception { this.spring.configLocations(xml("CreateSessionAlways")).autowire(); MockHttpServletRequest request = get("/").buildRequest(this.servletContext()); MockHttpServletResponse response = request(request, this.spring.getContext()); assertThat(response.getStatus()).isEqualTo(HttpStatus.SC_OK); assertThat(request.getSession(false)).isNotNull(); } @Test public void requestWhenCreateSessionIsSetToNeverThenDoesNotCreateSessionOnLoginChallenge() throws Exception { this.spring.configLocations(xml("CreateSessionNever")).autowire(); MockHttpServletRequest request = get("/auth").buildRequest(this.servletContext()); MockHttpServletResponse response = request(request, this.spring.getContext()); assertThat(response.getStatus()).isEqualTo(HttpStatus.SC_MOVED_TEMPORARILY); assertThat(request.getSession(false)).isNull(); } @Test public void requestWhenCreateSessionIsSetToNeverThenDoesNotCreateSessionOnLogin() throws Exception { this.spring.configLocations(xml("CreateSessionNever")).autowire(); // @formatter:off MockHttpServletRequest request = post("/login") .param("username", "user") .param("password", "password") .buildRequest(this.servletContext()); // @formatter:on request = csrf().postProcessRequest(request); MockHttpServletResponse response = request(request, this.spring.getContext()); assertThat(response.getStatus()).isEqualTo(HttpStatus.SC_MOVED_TEMPORARILY); assertThat(request.getSession(false)).isNull(); } @Test public void requestWhenCreateSessionIsSetToNeverThenUsesExistingSession() throws Exception { this.spring.configLocations(xml("CreateSessionNever")).autowire(); // @formatter:off MockHttpServletRequest request = post("/login") .param("username", "user") .param("password", "password") .buildRequest(this.servletContext()); // @formatter:on request = csrf().postProcessRequest(request); MockHttpSession session = new MockHttpSession(); request.setSession(session); MockHttpServletResponse response = request(request, this.spring.getContext()); assertThat(response.getStatus()).isEqualTo(HttpStatus.SC_MOVED_TEMPORARILY); assertThat(request.getSession(false)).isNotNull(); assertThat(request.getSession(false) .getAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY)).isNotNull(); } @Test public void requestWhenCreateSessionIsSetToStatelessThenDoesNotCreateSessionOnLoginChallenge() throws Exception { this.spring.configLocations(xml("CreateSessionStateless")).autowire(); // @formatter:off this.mvc.perform(get("/auth")) .andExpect(status().isFound()) .andExpect(session().exists(false)); // @formatter:on } @Test public void requestWhenCreateSessionIsSetToStatelessThenDoesNotCreateSessionOnLogin() throws Exception { this.spring.configLocations(xml("CreateSessionStateless")).autowire(); // @formatter:off MockHttpServletRequestBuilder loginRequest = post("/login") .param("username", "user") .param("password", "password") .with(csrf()); this.mvc.perform(loginRequest) .andExpect(status().isFound()) .andExpect(session().exists(false)); // @formatter:on } @Test public void requestWhenCreateSessionIsSetToStatelessThenIgnoresExistingSession() throws Exception { this.spring.configLocations(xml("CreateSessionStateless")).autowire(); // @formatter:off MockHttpServletRequestBuilder loginRequest = post("/login") .param("username", "user") .param("password", "password") .session(new MockHttpSession()) .with(csrf()); MvcResult result = this.mvc.perform(loginRequest) .andExpect(status().isFound()) .andExpect(session()).andReturn(); // @formatter:on assertThat(result.getRequest() .getSession(false) .getAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY)).isNull(); } @Test public void requestWhenCreateSessionIsSetToIfRequiredThenDoesNotCreateSessionOnPublicInvocation() throws Exception { this.spring.configLocations(xml("CreateSessionIfRequired")).autowire(); ServletContext servletContext = this.mvc.getDispatcherServlet().getServletContext(); MockHttpServletRequest request = get("/").buildRequest(servletContext); MockHttpServletResponse response = request(request, this.spring.getContext()); assertThat(response.getStatus()).isEqualTo(HttpStatus.SC_OK); assertThat(request.getSession(false)).isNull(); } @Test public void requestWhenCreateSessionIsSetToIfRequiredThenCreatesSessionOnLoginChallenge() throws Exception { this.spring.configLocations(xml("CreateSessionIfRequired")).autowire(); ServletContext servletContext = this.mvc.getDispatcherServlet().getServletContext(); MockHttpServletRequest request = get("/auth").buildRequest(servletContext); MockHttpServletResponse response = request(request, this.spring.getContext()); assertThat(response.getStatus()).isEqualTo(HttpStatus.SC_MOVED_TEMPORARILY); assertThat(request.getSession(false)).isNotNull(); } @Test public void requestWhenCreateSessionIsSetToIfRequiredThenCreatesSessionOnLogin() throws Exception { this.spring.configLocations(xml("CreateSessionIfRequired")).autowire(); ServletContext servletContext = this.mvc.getDispatcherServlet().getServletContext(); // @formatter:off MockHttpServletRequest request = post("/login") .param("username", "user") .param("password", "password") .buildRequest(servletContext); // @formatter:on request = csrf().postProcessRequest(request); MockHttpServletResponse response = request(request, this.spring.getContext()); assertThat(response.getStatus()).isEqualTo(HttpStatus.SC_MOVED_TEMPORARILY); assertThat(request.getSession(false)).isNotNull(); } /** * SEC-1208 */ @Test public void requestWhenRejectingUserBasedOnMaxSessionsExceededThenDoesNotCreateSession() throws Exception { this.spring.configLocations(xml("Sec1208")).autowire(); // @formatter:off this.mvc.perform(get("/auth").with(httpBasic("user", "password"))) .andExpect(status().isOk()) .andExpect(session()); this.mvc.perform(get("/auth").with(httpBasic("user", "password"))) .andExpect(status().isUnauthorized()) .andExpect(session().exists(false)); // @formatter:on } /** * SEC-2137 */ @Test public void requestWhenSessionFixationProtectionDisabledAndConcurrencyControlEnabledThenSessionNotInvalidated() throws Exception { this.spring.configLocations(xml("Sec2137")).autowire(); MockHttpSession session = new MockHttpSession(); // @formatter:off this.mvc.perform(get("/auth").session(session).with(httpBasic("user", "password"))) .andExpect(status().isOk()) .andExpect(session().id(session.getId())); // @formatter:on } @Test public void autowireWhenExportingSessionRegistryBeanThenAvailableForWiring() { this.spring.configLocations(xml("ConcurrencyControlSessionRegistryAlias")).autowire(); this.sessionRegistryIsValid(); } @Test public void requestWhenExpiredUrlIsSetThenInvalidatesSessionAndRedirects() throws Exception { this.spring.configLocations(xml("ConcurrencyControlExpiredUrl")).autowire(); // @formatter:off MockHttpServletRequestBuilder request = get("/auth") .session(expiredSession()) .with(httpBasic("user", "password")); this.mvc.perform(request) .andExpect(redirectedUrl("/expired")) .andExpect(session().exists(false)); // @formatter:on } @Test public void requestWhenConcurrencyControlAndCustomLogoutHandlersAreSetThenAllAreInvokedWhenSessionExpires() throws Exception { this.spring.configLocations(xml("ConcurrencyControlLogoutAndRememberMeHandlers")).autowire(); // @formatter:off MockHttpServletRequestBuilder request = get("/auth") .session(expiredSession()) .with(httpBasic("user", "password")); this.mvc.perform(request) .andExpect(status().isOk()) .andExpect(cookie().maxAge("testCookie", 0)) .andExpect(cookie().exists("rememberMeCookie")) .andExpect(session().valid(true)); // @formatter:on } @Test public void requestWhenConcurrencyControlAndRememberMeAreSetThenInvokedWhenSessionExpires() throws Exception { this.spring.configLocations(xml("ConcurrencyControlRememberMeHandler")).autowire(); // @formatter:off MockHttpServletRequestBuilder request = get("/auth") .session(expiredSession()) .with(httpBasic("user", "password")); this.mvc.perform(request) .andExpect(status().isOk()).andExpect(cookie().exists("rememberMeCookie")) .andExpect(session().exists(false)); // @formatter:on } /** * SEC-2057 */ @Test public void autowireWhenConcurrencyControlIsSetThenLogoutHandlersGetAuthenticationObject() throws Exception { this.spring.configLocations(xml("ConcurrencyControlCustomLogoutHandler")).autowire(); MvcResult result = this.mvc.perform(get("/auth").with(httpBasic("user", "password"))) .andExpect(session()) .andReturn(); MockHttpSession session = (MockHttpSession) result.getRequest().getSession(false); SessionRegistry sessionRegistry = this.spring.getContext().getBean(SessionRegistry.class); sessionRegistry.getSessionInformation(session.getId()).expireNow(); // @formatter:off this.mvc.perform(get("/auth").session(session)) .andExpect(header().string("X-Username", "user")); // @formatter:on } @Test public void requestWhenConcurrencyControlIsSetThenDefaultsToResponseBodyExpirationResponse() throws Exception { this.spring.configLocations(xml("ConcurrencyControlSessionRegistryAlias")).autowire(); // @formatter:off MockHttpServletRequestBuilder request = get("/auth") .session(expiredSession()) .with(httpBasic("user", "password")); this.mvc.perform(request) .andExpect(content().string("This session has been expired (possibly due to multiple concurrent " + "logins being attempted as the same user).")); // @formatter:on } @Test public void requestWhenCustomSessionAuthenticationStrategyThenInvokesOnAuthentication() throws Exception { this.spring.configLocations(xml("SessionAuthenticationStrategyRef")).autowire(); // @formatter:off this.mvc.perform(get("/auth").with(httpBasic("user", "password"))) .andExpect(status().isIAmATeapot()); // @formatter:on } @Test public void autowireWhenSessionRegistryRefIsSetThenAvailableForWiring() { this.spring.configLocations(xml("ConcurrencyControlSessionRegistryRef")).autowire(); this.sessionRegistryIsValid(); } @Test public void requestWhenMaxSessionsIsSetThenErrorsWhenExceeded() throws Exception { this.spring.configLocations(xml("ConcurrencyControlMaxSessions")).autowire(); // @formatter:off this.mvc.perform(get("/auth").with(httpBasic("user", "password"))) .andExpect(status().isOk()); this.mvc.perform(get("/auth").with(httpBasic("user", "password"))) .andExpect(status().isOk()); this.mvc.perform(get("/auth").with(httpBasic("user", "password"))) .andExpect(redirectedUrl("/max-exceeded")); // @formatter:on } @Test public void requestWhenMaxSessionsIsSetWithPlaceHolderThenErrorsWhenExceeded() throws Exception { System.setProperty("sessionManagement.maxSessions", "1"); this.spring.configLocations(xml("ConcurrencyControlMaxSessionsPlaceHolder")).autowire(); // @formatter:off this.mvc.perform(get("/auth").with(httpBasic("user", "password"))) .andExpect(status().isOk()); this.mvc.perform(get("/auth").with(httpBasic("user", "password"))) .andExpect(redirectedUrl("/max-exceeded")); // @formatter:on } @Test public void autowireWhenSessionFixationProtectionIsNoneAndCsrfDisabledThenSessionManagementFilterIsNotWired() { this.spring.configLocations(xml("NoSessionManagementFilter")).autowire(); assertThat(this.getFilter(SessionManagementFilter.class)).isNull(); } @Test public void requestWhenSessionFixationProtectionIsNoneThenSessionNotInvalidated() throws Exception { this.spring.configLocations(xml("SessionFixationProtectionNone")).autowire(); MockHttpSession session = new MockHttpSession(); String sessionId = session.getId(); // @formatter:off this.mvc.perform(get("/auth").session(session).with(httpBasic("user", "password"))) .andExpect(session().id(sessionId)); // @formatter:on } @Test public void requestWhenSessionFixationProtectionIsMigrateSessionThenSessionIsReplaced() throws Exception { this.spring.configLocations(xml("SessionFixationProtectionMigrateSession")).autowire(); MockHttpSession session = new MockHttpSession(); String sessionId = session.getId(); // @formatter:off MvcResult result = this.mvc.perform(get("/auth").session(session).with(httpBasic("user", "password"))) .andExpect(session()) .andReturn(); // @formatter:on assertThat(result.getRequest().getSession(false).getId()).isNotEqualTo(sessionId); } @Test public void requestWhenSessionFixationProtectionIsNoneAndInvalidSessionUrlIsSetThenStillRedirectsOnInvalidSession() throws Exception { this.spring.configLocations(xml("SessionFixationProtectionNoneWithInvalidSessionUrl")).autowire(); // @formatter:off MockHttpServletRequestBuilder authRequest = get("/auth") .with((request) -> { request.setRequestedSessionId("1"); request.setRequestedSessionIdValid(false); return request; }); this.mvc.perform(authRequest) .andExpect(redirectedUrl("/timeoutUrl")); // @formatter:on } private void sessionRegistryIsValid() { SessionRegistry sessionRegistry = this.spring.getContext().getBean("sessionRegistry", SessionRegistry.class); assertThat(sessionRegistry).isNotNull(); assertThat(this.getFilter(ConcurrentSessionFilter.class)).returns(sessionRegistry, this::extractSessionRegistry); assertThat(this.getFilter(UsernamePasswordAuthenticationFilter.class)).returns(sessionRegistry, this::extractSessionRegistry); // SEC-1143 assertThat(this.getFilter(SessionManagementFilter.class)).returns(sessionRegistry, this::extractSessionRegistry); } private SessionRegistry extractSessionRegistry(ConcurrentSessionFilter filter) { return getFieldValue(filter, "sessionRegistry"); } private SessionRegistry extractSessionRegistry(UsernamePasswordAuthenticationFilter filter) { SessionAuthenticationStrategy strategy = getFieldValue(filter, "sessionStrategy"); List strategies = getFieldValue(strategy, "delegateStrategies"); return getFieldValue(strategies.get(0), "sessionRegistry"); } private SessionRegistry extractSessionRegistry(SessionManagementFilter filter) { SessionAuthenticationStrategy strategy = getFieldValue(filter, "sessionAuthenticationStrategy"); List strategies = getFieldValue(strategy, "delegateStrategies"); return getFieldValue(strategies.get(0), "sessionRegistry"); } private T getFieldValue(Object target, String fieldName) { try { return (T) FieldUtils.getFieldValue(target, fieldName); } catch (Exception ex) { throw new RuntimeException(ex); } } private static SessionResultMatcher session() { return new SessionResultMatcher(); } /** * SEC-2680 */ @Test public void checkConcurrencyAndLogoutFilterHasSameSizeAndHasLogoutSuccessEventPublishingLogoutHandler() { this.spring.configLocations(xml("ConcurrencyControlLogoutAndRememberMeHandlers")).autowire(); ConcurrentSessionFilter concurrentSessionFilter = getFilter(ConcurrentSessionFilter.class); LogoutFilter logoutFilter = getFilter(LogoutFilter.class); LogoutHandler csfLogoutHandler = getFieldValue(concurrentSessionFilter, "handlers"); LogoutHandler lfLogoutHandler = getFieldValue(logoutFilter, "handler"); assertThat(csfLogoutHandler).isInstanceOf(CompositeLogoutHandler.class); assertThat(lfLogoutHandler).isInstanceOf(CompositeLogoutHandler.class); List csfLogoutHandlers = getFieldValue(csfLogoutHandler, "logoutHandlers"); List lfLogoutHandlers = getFieldValue(lfLogoutHandler, "logoutHandlers"); assertThat(csfLogoutHandlers).hasSameSizeAs(lfLogoutHandlers); assertThat(csfLogoutHandlers).hasAtLeastOneElementOfType(LogoutSuccessEventPublishingLogoutHandler.class); assertThat(lfLogoutHandlers).hasAtLeastOneElementOfType(LogoutSuccessEventPublishingLogoutHandler.class); } private static MockHttpServletResponse request(MockHttpServletRequest request, ApplicationContext context) throws IOException, ServletException { MockHttpServletResponse response = new MockHttpServletResponse(); FilterChainProxy proxy = context.getBean(FilterChainProxy.class); proxy.doFilter(request, new EncodeUrlDenyingHttpServletResponseWrapper(response), (req, resp) -> { }); return response; } private MockHttpSession expiredSession() { MockHttpSession session = new MockHttpSession(); SessionRegistry sessionRegistry = this.spring.getContext().getBean(SessionRegistry.class); sessionRegistry.registerNewSession(session.getId(), "user"); sessionRegistry.getSessionInformation(session.getId()).expireNow(); return session; } private T getFilter(Class filterClass) { return (T) getFilters().stream().filter(filterClass::isInstance).findFirst().orElse(null); } private List getFilters() { FilterChainProxy proxy = this.spring.getContext().getBean(FilterChainProxy.class); return proxy.getFilters("/"); } private ServletContext servletContext() { WebApplicationContext context = this.spring.getContext(); return context.getServletContext(); } private String xml(String configName) { return CONFIG_LOCATION_PREFIX + "-" + configName + ".xml"; } static class TeapotSessionAuthenticationStrategy implements SessionAuthenticationStrategy { @Override public void onAuthentication(Authentication authentication, HttpServletRequest request, HttpServletResponse response) throws SessionAuthenticationException { response.setStatus(org.springframework.http.HttpStatus.I_AM_A_TEAPOT.value()); } } static class CustomRememberMeServices implements RememberMeServices, LogoutHandler { @Override public Authentication autoLogin(HttpServletRequest request, HttpServletResponse response) { return null; } @Override public void loginFail(HttpServletRequest request, HttpServletResponse response) { } @Override public void loginSuccess(HttpServletRequest request, HttpServletResponse response, Authentication successfulAuthentication) { } @Override public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) { response.addHeader("X-Username", authentication.getName()); } } @RestController static class BasicController { @GetMapping("/") String ok() { return "ok"; } @GetMapping("/auth") String auth(Principal principal) { return principal.getName(); } } private static class SessionResultMatcher implements ResultMatcher { private String id; private Boolean valid; private Boolean exists = true; ResultMatcher exists(boolean exists) { this.exists = exists; return this; } ResultMatcher valid(boolean valid) { this.valid = valid; return this.exists(true); } ResultMatcher id(String id) { this.id = id; return this.exists(true); } @Override public void match(MvcResult result) { if (!this.exists) { assertThat(result.getRequest().getSession(false)).isNull(); return; } assertThat(result.getRequest().getSession(false)).isNotNull(); MockHttpSession session = (MockHttpSession) result.getRequest().getSession(false); if (this.valid != null) { if (this.valid) { assertThat(session.isInvalid()).isFalse(); } else { assertThat(session.isInvalid()).isTrue(); } } if (this.id != null) { assertThat(session.getId()).isEqualTo(this.id); } } } private static class EncodeUrlDenyingHttpServletResponseWrapper extends HttpServletResponseWrapper { EncodeUrlDenyingHttpServletResponseWrapper(HttpServletResponse response) { super(response); } @Override public String encodeURL(String url) { throw new RuntimeException("Unexpected invocation of encodeURL"); } @Override public String encodeRedirectURL(String url) { throw new RuntimeException("Unexpected invocation of encodeURL"); } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/http/SessionManagementConfigTransientAuthenticationTests.java ================================================ /* * 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 org.springframework.security.config.http; import java.util.Collection; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.Transient; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; /** * @author Josh Cummings */ @ExtendWith(SpringTestContextExtension.class) public class SessionManagementConfigTransientAuthenticationTests { private static final String CONFIG_LOCATION_PREFIX = "classpath:org/springframework/security/config/http/SessionManagementConfigTransientAuthenticationTests"; @Autowired MockMvc mvc; public final SpringTestContext spring = new SpringTestContext(this); @Test public void postWhenTransientAuthenticationThenNoSessionCreated() throws Exception { this.spring.configLocations(this.xml("WithTransientAuthentication")).autowire(); MvcResult result = this.mvc.perform(post("/login")).andReturn(); assertThat(result.getRequest().getSession(false)).isNull(); } @Test public void postWhenTransientAuthenticationThenAlwaysSessionOverrides() throws Exception { this.spring.configLocations(this.xml("CreateSessionAlwaysWithTransientAuthentication")).autowire(); MvcResult result = this.mvc.perform(post("/login")).andReturn(); assertThat(result.getRequest().getSession(false)).isNotNull(); } private String xml(String configName) { return CONFIG_LOCATION_PREFIX + "-" + configName + ".xml"; } static class TransientAuthenticationProvider implements AuthenticationProvider { @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { return new SomeTransientAuthentication(); } @Override public boolean supports(Class authentication) { return true; } } @Transient static class SomeTransientAuthentication extends AbstractAuthenticationToken { SomeTransientAuthentication() { super((Collection) null); } @Override public Object getCredentials() { return null; } @Override public Object getPrincipal() { return null; } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/http/WebConfigUtilsTests.java ================================================ /* * 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 org.springframework.security.config.http; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.beans.factory.xml.ParserContext; import static org.mockito.Mockito.verifyNoMoreInteractions; @ExtendWith(MockitoExtension.class) public class WebConfigUtilsTests { public static final String URL = "/url"; @Mock private ParserContext parserContext; // SEC-1980 @Test public void validateHttpRedirectSpELNoParserWarning() { WebConfigUtils.validateHttpRedirect("#{T(org.springframework.security.config.http.WebConfigUtilsTest).URL}", this.parserContext, "fakeSource"); verifyNoMoreInteractions(this.parserContext); } } ================================================ FILE: config/src/test/java/org/springframework/security/config/http/WellKnownChangePasswordBeanDefinitionParserTests.java ================================================ /* * 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 org.springframework.security.config.http; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.test.web.servlet.MockMvc; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Tests for {@link WellKnownChangePasswordBeanDefinitionParser}. * * @author Evgeniy Cheban */ @ExtendWith(SpringTestContextExtension.class) public class WellKnownChangePasswordBeanDefinitionParserTests { private static final String CONFIG_LOCATION_PREFIX = "classpath:org/springframework/security/config/http/WellKnownChangePasswordBeanDefinitionParserTests"; public final SpringTestContext spring = new SpringTestContext(this); @Autowired MockMvc mvc; @Test public void whenChangePasswordPageNotSetThenDefaultChangePasswordPageUsed() throws Exception { this.spring.configLocations(xml("DefaultChangePasswordPage")).autowire(); this.mvc.perform(get("/.well-known/change-password")) .andExpect(status().isFound()) .andExpect(redirectedUrl("/change-password")); } @Test public void whenChangePasswordPageSetThenSpecifiedChangePasswordPageUsed() throws Exception { this.spring.configLocations(xml("CustomChangePasswordPage")).autowire(); this.mvc.perform(get("/.well-known/change-password")) .andExpect(status().isFound()) .andExpect(redirectedUrl("/custom-change-password-page")); } private String xml(String configName) { return CONFIG_LOCATION_PREFIX + "-" + configName + ".xml"; } } ================================================ FILE: config/src/test/java/org/springframework/security/config/http/customconfigurer/CustomConfigurer.java ================================================ /* * 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 org.springframework.security.config.http.customconfigurer; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.ApplicationContext; import org.springframework.security.config.annotation.SecurityConfigurerAdapter; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configurers.FormLoginConfigurer; import org.springframework.security.web.DefaultSecurityFilterChain; import static org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher.pathPattern; /** * @author Rob Winch * */ public class CustomConfigurer extends SecurityConfigurerAdapter { @Value("${permitAllPattern}") private String permitAllPattern; private String loginPage = "/login"; @SuppressWarnings("unchecked") @Override public void init(HttpSecurity http) { // autowire this bean ApplicationContext context = http.getSharedObject(ApplicationContext.class); context.getAutowireCapableBeanFactory().autowireBean(this); // @formatter:off http .authorizeHttpRequests((requests) -> requests .requestMatchers(pathPattern(this.permitAllPattern)).permitAll() .anyRequest().authenticated()); // @formatter:on if (http.getConfigurer(FormLoginConfigurer.class) == null) { // only apply if formLogin() was not invoked by the user // @formatter:off http .formLogin((login) -> login .loginPage(this.loginPage)); // @formatter:on } } public CustomConfigurer loginPage(String loginPage) { this.loginPage = loginPage; return this; } public static CustomConfigurer customConfigurer() { return new CustomConfigurer(); } } ================================================ FILE: config/src/test/java/org/springframework/security/config/http/customconfigurer/CustomHttpSecurityConfigurerTests.java ================================================ /* * 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 org.springframework.security.config.http.customconfigurer; import java.util.Properties; import jakarta.servlet.http.HttpServletResponse; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; import org.springframework.mock.web.MockFilterChain; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.web.FilterChainProxy; import org.springframework.security.web.SecurityFilterChain; import static org.assertj.core.api.Assertions.assertThat; /** * @author Rob Winch * */ public class CustomHttpSecurityConfigurerTests { @Autowired ConfigurableApplicationContext context; @Autowired FilterChainProxy springSecurityFilterChain; MockHttpServletRequest request; MockHttpServletResponse response; MockFilterChain chain; @BeforeEach public void setup() { this.request = new MockHttpServletRequest(); this.response = new MockHttpServletResponse(); this.chain = new MockFilterChain(); this.request.setMethod("GET"); } @AfterEach public void cleanup() { if (this.context != null) { this.context.close(); } } @Test public void customConfiguerPermitAll() throws Exception { loadContext(Config.class); this.request.setRequestURI("/public/something"); this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); } @Test public void customConfiguerFormLogin() throws Exception { loadContext(Config.class); this.request.setRequestURI("/requires-authentication"); this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); assertThat(this.response.getRedirectedUrl()).endsWith("/custom"); } @Test public void customConfiguerCustomizeDisablesCsrf() throws Exception { loadContext(ConfigCustomize.class); this.request.setRequestURI("/public/something"); this.request.setMethod("POST"); this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); } @Test public void customConfiguerCustomizeFormLogin() throws Exception { loadContext(ConfigCustomize.class); this.request.setRequestURI("/requires-authentication"); this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); assertThat(this.response.getRedirectedUrl()).endsWith("/other"); } private void loadContext(Class clazz) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(clazz); context.getAutowireCapableBeanFactory().autowireBean(this); } @Configuration @EnableWebSecurity static class Config { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off return http .with(CustomConfigurer.customConfigurer(), (c) -> c.loginPage("/custom")) .build(); // @formatter:on } @Bean static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() { // Typically externalize this as a properties file Properties properties = new Properties(); properties.setProperty("permitAllPattern", "/public/**"); PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer = new PropertySourcesPlaceholderConfigurer(); propertyPlaceholderConfigurer.setProperties(properties); return propertyPlaceholderConfigurer; } } @Configuration @EnableWebSecurity static class ConfigCustomize { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // @formatter:off http .with(CustomConfigurer.customConfigurer(), Customizer.withDefaults()) .csrf((csrf) -> csrf.disable()) .formLogin((login) -> login .loginPage("/other")); return http.build(); // @formatter:on } @Bean static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() { // Typically externalize this as a properties file Properties properties = new Properties(); properties.setProperty("permitAllPattern", "/public/**"); PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer = new PropertySourcesPlaceholderConfigurer(); propertyPlaceholderConfigurer.setProperties(properties); return propertyPlaceholderConfigurer; } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/method/Contact.java ================================================ /* * 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 org.springframework.security.config.method; /** * @author Rob Winch * */ public class Contact { private String name; /** * @param name */ public Contact(String name) { this.name = name; } /** * @return the name */ public String getName() { return this.name; } } ================================================ FILE: config/src/test/java/org/springframework/security/config/method/ContactPermission.java ================================================ /* * 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 org.springframework.security.config.method; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import org.springframework.security.access.prepost.PreAuthorize; /** * @author Rob Winch * */ @Retention(RetentionPolicy.RUNTIME) @PreAuthorize("#contact.name == authentication.name") public @interface ContactPermission { } ================================================ FILE: config/src/test/java/org/springframework/security/config/method/GlobalMethodSecurityBeanDefinitionParserTests.java ================================================ /* * 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 org.springframework.security.config.method; import java.util.ArrayList; import java.util.List; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import org.springframework.aop.Advisor; import org.springframework.aop.framework.Advised; import org.springframework.beans.BeansException; import org.springframework.beans.MutablePropertyValues; import org.springframework.beans.factory.parsing.BeanDefinitionParsingException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.support.AbstractXmlApplicationContext; import org.springframework.context.support.StaticApplicationContext; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.access.SecurityConfig; import org.springframework.security.access.annotation.BusinessService; import org.springframework.security.access.annotation.ExpressionProtectedBusinessServiceImpl; import org.springframework.security.access.intercept.AfterInvocationProviderManager; import org.springframework.security.access.intercept.RunAsManagerImpl; import org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor; import org.springframework.security.access.intercept.aopalliance.MethodSecurityMetadataSourceAdvisor; import org.springframework.security.access.prepost.PostInvocationAdviceProvider; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreInvocationAuthorizationAdviceVoter; import org.springframework.security.access.vote.AffirmativeBased; import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.config.ConfigTestUtils; import org.springframework.security.config.PostProcessedMockUserDetailsService; import org.springframework.security.config.util.InMemoryXmlApplicationContext; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.util.FieldUtils; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; /** * @author Ben Alex * @author Luke Taylor */ public class GlobalMethodSecurityBeanDefinitionParserTests { private final UsernamePasswordAuthenticationToken bob = UsernamePasswordAuthenticationToken.unauthenticated("bob", "bobspassword"); private AbstractXmlApplicationContext appContext; private BusinessService target; public void loadContext() { // @formatter:off setContext("" + "" + " " + " " + "" + ConfigTestUtils.AUTH_PROVIDER_XML); // @formatter:on this.target = (BusinessService) this.appContext.getBean("target"); } @AfterEach public void closeAppContext() { if (this.appContext != null) { this.appContext.close(); this.appContext = null; } SecurityContextHolder.clearContext(); this.target = null; } @Test public void targetShouldPreventProtectedMethodInvocationWithNoContext() { loadContext(); assertThatExceptionOfType(AuthenticationCredentialsNotFoundException.class) .isThrownBy(this.target::someUserMethod1); } @Test public void targetShouldAllowProtectedMethodInvocationWithCorrectRole() { loadContext(); UsernamePasswordAuthenticationToken token = UsernamePasswordAuthenticationToken.unauthenticated("user", "password"); SecurityContextHolder.getContext().setAuthentication(token); this.target.someUserMethod1(); // SEC-1213. Check the order Advisor[] advisors = ((Advised) this.target).getAdvisors(); assertThat(advisors).hasSize(1); assertThat(((MethodSecurityMetadataSourceAdvisor) advisors[0]).getOrder()).isEqualTo(1001); } @Test public void targetShouldPreventProtectedMethodInvocationWithIncorrectRole() { loadContext(); TestingAuthenticationToken token = new TestingAuthenticationToken("Test", "Password", "ROLE_SOMEOTHERROLE"); token.setAuthenticated(true); SecurityContextHolder.getContext().setAuthentication(token); assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.target::someAdminMethod); } @Test public void doesntInterfereWithBeanPostProcessing() { // @formatter:off setContext("" + "" + "" + " " + "" + ""); // @formatter:on PostProcessedMockUserDetailsService service = (PostProcessedMockUserDetailsService) this.appContext .getBean("myUserService"); assertThat(service.getPostProcessorWasHere()).isEqualTo("Hello from the post processor!"); } @Test public void worksWithAspectJAutoproxy() { // @formatter:off setContext("" + " " + "" + "" + "" + "" + " " + ""); // @formatter:on UserDetailsService service = (UserDetailsService) this.appContext.getBean("myUserService"); UsernamePasswordAuthenticationToken token = UsernamePasswordAuthenticationToken.authenticated("Test", "Password", AuthorityUtils.createAuthorityList("ROLE_SOMEOTHERROLE")); SecurityContextHolder.getContext().setAuthentication(token); assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(() -> service.loadUserByUsername("notused")); } @Test public void supportsMethodArgumentsInPointcut() { // @formatter:off setContext("" + "" + " " + " " + "" + ConfigTestUtils.AUTH_PROVIDER_XML); // @formatter:on SecurityContextHolder.getContext() .setAuthentication(UsernamePasswordAuthenticationToken.unauthenticated("user", "password")); this.target = (BusinessService) this.appContext.getBean("target"); // someOther(int) should not be matched by someOther(String), but should require // ROLE_USER this.target.someOther(0); // String version should required admin role assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(() -> this.target.someOther("somestring")); } @Test public void supportsBooleanPointcutExpressions() { // @formatter:off setContext("" + "" + " " + "" + ConfigTestUtils.AUTH_PROVIDER_XML); // @formatter:on this.target = (BusinessService) this.appContext.getBean("target"); // String method should not be protected this.target.someOther("somestring"); // All others should require ROLE_USER assertThatExceptionOfType(AuthenticationCredentialsNotFoundException.class) .isThrownBy(() -> this.target.someOther(0)); SecurityContextHolder.getContext() .setAuthentication(UsernamePasswordAuthenticationToken.unauthenticated("user", "password")); this.target.someOther(0); } @Test public void duplicateElementCausesError() { assertThatExceptionOfType(BeanDefinitionParsingException.class) .isThrownBy(() -> setContext("" + "")); } // Expression configuration tests @SuppressWarnings("unchecked") @Test public void expressionVoterAndAfterInvocationProviderUseSameExpressionHandlerInstance() throws Exception { setContext("" + ConfigTestUtils.AUTH_PROVIDER_XML); AffirmativeBased adm = (AffirmativeBased) this.appContext.getBeansOfType(AffirmativeBased.class) .values() .toArray()[0]; List voters = (List) FieldUtils.getFieldValue(adm, "decisionVoters"); PreInvocationAuthorizationAdviceVoter mev = (PreInvocationAuthorizationAdviceVoter) voters.get(0); MethodSecurityMetadataSourceAdvisor msi = (MethodSecurityMetadataSourceAdvisor) this.appContext .getBeansOfType(MethodSecurityMetadataSourceAdvisor.class) .values() .toArray()[0]; AfterInvocationProviderManager pm = (AfterInvocationProviderManager) ((MethodSecurityInterceptor) msi .getAdvice()).getAfterInvocationManager(); PostInvocationAdviceProvider aip = (PostInvocationAdviceProvider) pm.getProviders().get(0); assertThat(FieldUtils.getFieldValue(mev, "preAdvice.expressionHandler")) .isSameAs(FieldUtils.getFieldValue(aip, "postAdvice.expressionHandler")); } @Test public void accessIsDeniedForHasRoleExpression() { // @formatter:off setContext("" + "" + ConfigTestUtils.AUTH_PROVIDER_XML); // @formatter:on SecurityContextHolder.getContext().setAuthentication(this.bob); this.target = (BusinessService) this.appContext.getBean("target"); assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.target::someAdminMethod); } @Test public void beanNameExpressionPropertyIsSupported() { // @formatter:off setContext("" + "" + " " + "" + "" + ConfigTestUtils.AUTH_PROVIDER_XML); // @formatter:on SecurityContextHolder.getContext().setAuthentication(this.bob); ExpressionProtectedBusinessServiceImpl target = (ExpressionProtectedBusinessServiceImpl) this.appContext .getBean("target"); target.methodWithBeanNamePropertyAccessExpression("x"); } @Test public void preAndPostFilterAnnotationsWorkWithLists() { // @formatter:off setContext("" + "" + ConfigTestUtils.AUTH_PROVIDER_XML); // @formatter:on SecurityContextHolder.getContext().setAuthentication(this.bob); this.target = (BusinessService) this.appContext.getBean("target"); List arg = new ArrayList<>(); arg.add("joe"); arg.add("bob"); arg.add("sam"); List result = this.target.methodReturningAList(arg); // Expression is (filterObject == name or filterObject == 'sam'), so "joe" should // be gone after pre-filter // PostFilter should remove sam from the return object assertThat(result).hasSize(1); assertThat(result.get(0)).isEqualTo("bob"); } @Test public void prePostFilterAnnotationWorksWithArrays() { // @formatter:off setContext("" + "" + ConfigTestUtils.AUTH_PROVIDER_XML); // @formatter:on SecurityContextHolder.getContext().setAuthentication(this.bob); this.target = (BusinessService) this.appContext.getBean("target"); Object[] arg = new String[] { "joe", "bob", "sam" }; Object[] result = this.target.methodReturningAnArray(arg); assertThat(result).hasSize(1); assertThat(result[0]).isEqualTo("bob"); } // SEC-1392 @Test public void customPermissionEvaluatorIsSupported() { // @formatter:off setContext("" + " " + "" + "" + " " + "" + "" + ConfigTestUtils.AUTH_PROVIDER_XML); // @formatter:on } // SEC-1450 @Test @SuppressWarnings("unchecked") public void genericsAreMatchedByProtectPointcut() { // @formatter:off setContext( "" + "" + " " + "" + ConfigTestUtils.AUTH_PROVIDER_XML); // @formatter:on Foo foo = (Foo) this.appContext.getBean("target"); assertThatExceptionOfType(AuthenticationException.class).isThrownBy(() -> foo.foo(new SecurityConfig("A"))); } // SEC-1448 @Test @SuppressWarnings("unchecked") public void genericsMethodArgumentNamesAreResolved() { // @formatter:off setContext("" + "" + ConfigTestUtils.AUTH_PROVIDER_XML); // @formatter:on SecurityContextHolder.getContext().setAuthentication(this.bob); Foo foo = (Foo) this.appContext.getBean("target"); foo.foo(new SecurityConfig("A")); } @Test public void runAsManagerIsSetCorrectly() throws Exception { StaticApplicationContext parent = new StaticApplicationContext(); MutablePropertyValues props = new MutablePropertyValues(); props.addPropertyValue("key", "blah"); parent.registerSingleton("runAsMgr", RunAsManagerImpl.class, props); parent.refresh(); setContext("" + ConfigTestUtils.AUTH_PROVIDER_XML, parent); RunAsManagerImpl ram = (RunAsManagerImpl) this.appContext.getBean("runAsMgr"); MethodSecurityMetadataSourceAdvisor msi = (MethodSecurityMetadataSourceAdvisor) this.appContext .getBeansOfType(MethodSecurityMetadataSourceAdvisor.class) .values() .toArray()[0]; assertThat(ram).isSameAs(FieldUtils.getFieldValue(msi.getAdvice(), "runAsManager")); } @Test @SuppressWarnings("unchecked") public void supportsExternalMetadataSource() { // @formatter:off setContext("" + "" + " " + "" + "" + ConfigTestUtils.AUTH_PROVIDER_XML); // @formatter:on // External MDS should take precedence over PreAuthorize SecurityContextHolder.getContext().setAuthentication(this.bob); Foo foo = (Foo) this.appContext.getBean("target"); assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(() -> foo.foo(new SecurityConfig("A"))); SecurityContextHolder.getContext() .setAuthentication(UsernamePasswordAuthenticationToken.unauthenticated("admin", "password")); foo.foo(new SecurityConfig("A")); } @Test public void supportsCustomAuthenticationManager() { // @formatter:off setContext("" + "" + " " + "" + "" + "" + " " + "" + ConfigTestUtils.AUTH_PROVIDER_XML); // @formatter:on SecurityContextHolder.getContext().setAuthentication(this.bob); Foo foo = (Foo) this.appContext.getBean("target"); assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(() -> foo.foo(new SecurityConfig("A"))); SecurityContextHolder.getContext() .setAuthentication(UsernamePasswordAuthenticationToken.unauthenticated("admin", "password")); foo.foo(new SecurityConfig("A")); } private void setContext(String context) { this.appContext = new InMemoryXmlApplicationContext(context); } private void setContext(String context, ApplicationContext parent) { this.appContext = new InMemoryXmlApplicationContext(context, parent); } static class CustomAuthManager implements AuthenticationManager, ApplicationContextAware { private String beanName; private AuthenticationManager authenticationManager; CustomAuthManager(String beanName) { this.beanName = beanName; } @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { return this.authenticationManager.authenticate(authentication); } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.authenticationManager = applicationContext.getBean(this.beanName, AuthenticationManager.class); } } interface Foo { void foo(T action); } public static class ConcreteFoo implements Foo { @Override @PreAuthorize("#action.attribute == 'A'") public void foo(SecurityConfig action) { } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/method/InterceptMethodsBeanDefinitionDecoratorTests.java ================================================ /* * 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 org.springframework.security.config.method; import org.aopalliance.intercept.MethodInvocation; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.BeansException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.ApplicationListener; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.authentication.event.AuthenticationSuccessEvent; import org.springframework.security.authorization.AuthorizationDecision; import org.springframework.security.authorization.AuthorizationManager; import org.springframework.security.config.TestBusinessBean; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit.jupiter.SpringExtension; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.verify; /** * @author Luke Taylor */ @ExtendWith(SpringExtension.class) @ContextConfiguration(locations = "classpath:org/springframework/security/config/method-security.xml") public class InterceptMethodsBeanDefinitionDecoratorTests implements ApplicationContextAware { @Autowired @Qualifier("target") private TestBusinessBean target; @Autowired @Qualifier("transactionalTarget") private TestBusinessBean transactionalTarget; @Autowired @Qualifier("targetAuthorizationManager") private TestBusinessBean targetAuthorizationManager; @Autowired @Qualifier("transactionalTargetAuthorizationManager") private TestBusinessBean transactionalTargetAuthorizationManager; @Autowired @Qualifier("targetCustomAuthorizationManager") private TestBusinessBean targetCustomAuthorizationManager; @Autowired private AuthorizationManager mockAuthorizationManager; private ApplicationContext appContext; @BeforeAll public static void loadContext() { // Set value for placeholder System.setProperty("admin.role", "ROLE_ADMIN"); } @AfterEach public void clearContext() { SecurityContextHolder.clearContext(); } @Test public void targetDoesntLoseApplicationListenerInterface() { assertThat(this.appContext.getBeansOfType(ApplicationListener.class)).isNotEmpty(); assertThat(this.appContext.getBeanNamesForType(ApplicationListener.class)).isNotEmpty(); this.appContext.publishEvent(new AuthenticationSuccessEvent(new TestingAuthenticationToken("user", ""))); assertThat(this.target).isInstanceOf(ApplicationListener.class); assertThat(this.targetAuthorizationManager).isInstanceOf(ApplicationListener.class); assertThat(this.targetCustomAuthorizationManager).isInstanceOf(ApplicationListener.class); } @Test public void targetShouldAllowUnprotectedMethodInvocationWithNoContext() { this.target.unprotected(); } @Test public void targetShouldPreventProtectedMethodInvocationWithNoContext() { assertThatExceptionOfType(AuthenticationCredentialsNotFoundException.class) .isThrownBy(this.target::doSomething); } @Test public void targetShouldAllowProtectedMethodInvocationWithCorrectRole() { UsernamePasswordAuthenticationToken token = UsernamePasswordAuthenticationToken.authenticated("Test", "Password", AuthorityUtils.createAuthorityList("ROLE_USER")); SecurityContextHolder.getContext().setAuthentication(token); this.target.doSomething(); } @Test public void targetShouldPreventProtectedMethodInvocationWithIncorrectRole() { UsernamePasswordAuthenticationToken token = UsernamePasswordAuthenticationToken.authenticated("Test", "Password", AuthorityUtils.createAuthorityList("ROLE_SOMEOTHERROLE")); SecurityContextHolder.getContext().setAuthentication(token); assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.target::doSomething); } @Test public void transactionalMethodsShouldBeSecured() { assertThatExceptionOfType(AuthenticationException.class).isThrownBy(this.transactionalTarget::doSomething); } @Test public void targetAuthorizationManagerShouldAllowUnprotectedMethodInvocationWithNoContext() { this.targetAuthorizationManager.unprotected(); } @Test public void targetAuthorizationManagerShouldPreventProtectedMethodInvocationWithNoContext() { assertThatExceptionOfType(AuthenticationCredentialsNotFoundException.class) .isThrownBy(this.targetAuthorizationManager::doSomething); } @Test public void targetAuthorizationManagerShouldAllowProtectedMethodInvocationWithCorrectRole() { UsernamePasswordAuthenticationToken token = UsernamePasswordAuthenticationToken.authenticated("Test", "Password", AuthorityUtils.createAuthorityList("ROLE_USER")); SecurityContextHolder.getContext().setAuthentication(token); this.targetAuthorizationManager.doSomething(); } @Test public void targetAuthorizationManagerShouldPreventProtectedMethodInvocationWithIncorrectRole() { UsernamePasswordAuthenticationToken token = UsernamePasswordAuthenticationToken.authenticated("Test", "Password", AuthorityUtils.createAuthorityList("ROLE_SOMEOTHERROLE")); SecurityContextHolder.getContext().setAuthentication(token); assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.targetAuthorizationManager::doSomething); } @Test public void transactionalAuthorizationManagerMethodsShouldBeSecured() { assertThatExceptionOfType(AuthenticationException.class) .isThrownBy(this.transactionalTargetAuthorizationManager::doSomething); } @Test public void targetCustomAuthorizationManagerUsed() { given(this.mockAuthorizationManager.authorize(any(), any())).willReturn(new AuthorizationDecision(true)); this.targetCustomAuthorizationManager.doSomething(); verify(this.mockAuthorizationManager).authorize(any(), any()); } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.appContext = applicationContext; } } ================================================ FILE: config/src/test/java/org/springframework/security/config/method/Jsr250AnnotationDrivenBeanDefinitionParserTests.java ================================================ /* * 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 org.springframework.security.config.method; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.annotation.BusinessService; import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.config.ConfigTestUtils; import org.springframework.security.config.util.InMemoryXmlApplicationContext; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.context.SecurityContextHolder; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; /** * @author Luke Taylor */ public class Jsr250AnnotationDrivenBeanDefinitionParserTests { private InMemoryXmlApplicationContext appContext; private BusinessService target; @BeforeEach public void loadContext() { // @formatter:off this.appContext = new InMemoryXmlApplicationContext( "" + "" + ConfigTestUtils.AUTH_PROVIDER_XML); // @formatter:on this.target = (BusinessService) this.appContext.getBean("target"); } @AfterEach public void closeAppContext() { if (this.appContext != null) { this.appContext.close(); } SecurityContextHolder.clearContext(); } @Test public void targetShouldPreventProtectedMethodInvocationWithNoContext() { assertThatExceptionOfType(AuthenticationCredentialsNotFoundException.class) .isThrownBy(() -> this.target.someUserMethod1()); } @Test public void permitAllShouldBeDefaultAttribute() { UsernamePasswordAuthenticationToken token = UsernamePasswordAuthenticationToken.authenticated("Test", "Password", AuthorityUtils.createAuthorityList("ROLE_USER")); SecurityContextHolder.getContext().setAuthentication(token); this.target.someOther(0); } @Test public void targetShouldAllowProtectedMethodInvocationWithCorrectRole() { UsernamePasswordAuthenticationToken token = UsernamePasswordAuthenticationToken.authenticated("Test", "Password", AuthorityUtils.createAuthorityList("ROLE_USER")); SecurityContextHolder.getContext().setAuthentication(token); this.target.someUserMethod1(); } @Test public void targetShouldPreventProtectedMethodInvocationWithIncorrectRole() { UsernamePasswordAuthenticationToken token = UsernamePasswordAuthenticationToken.authenticated("Test", "Password", AuthorityUtils.createAuthorityList("ROLE_SOMEOTHERROLE")); SecurityContextHolder.getContext().setAuthentication(token); assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.target::someAdminMethod); } @Test public void hasAnyRoleAddsDefaultPrefix() { UsernamePasswordAuthenticationToken token = UsernamePasswordAuthenticationToken.authenticated("Test", "Password", AuthorityUtils.createAuthorityList("ROLE_USER")); SecurityContextHolder.getContext().setAuthentication(token); this.target.rolesAllowedUser(); } } ================================================ FILE: config/src/test/java/org/springframework/security/config/method/MethodSecurityBeanDefinitionParserTests.java ================================================ /* * 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 org.springframework.security.config.method; import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.function.Supplier; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.annotation.AnnotationConfigurationException; import org.springframework.lang.NonNull; import org.springframework.lang.Nullable; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.PermissionEvaluator; import org.springframework.security.access.annotation.BusinessService; import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.authorization.AuthorizationDecision; import org.springframework.security.authorization.AuthorizationManager; import org.springframework.security.authorization.AuthorizationResult; import org.springframework.security.config.annotation.method.configuration.MethodSecurityService; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.core.Authentication; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.security.core.context.SecurityContextImpl; import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners; import org.springframework.security.test.context.support.WithAnonymousUser; import org.springframework.security.test.context.support.WithMockUser; import org.springframework.test.context.junit.jupiter.SpringExtension; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.mockito.Mockito.verify; /** * @author Josh Cummings */ @ExtendWith({ SpringExtension.class, SpringTestContextExtension.class }) @SecurityTestExecutionListeners public class MethodSecurityBeanDefinitionParserTests { private static final String CONFIG_LOCATION_PREFIX = "classpath:org/springframework/security/config/method/MethodSecurityBeanDefinitionParserTests"; private final UsernamePasswordAuthenticationToken bob = UsernamePasswordAuthenticationToken.unauthenticated("bob", "bobspassword"); @Autowired(required = false) MethodSecurityService methodSecurityService; @Autowired(required = false) BusinessService businessService; public final SpringTestContext spring = new SpringTestContext(this); @WithMockUser(roles = "ADMIN") @Test public void preAuthorizeWhenRoleAdminThenAccessDeniedException() { this.spring.configLocations(xml("MethodSecurityService")).autowire(); assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.methodSecurityService::preAuthorize) .withMessage("Access Denied"); } @Test public void configureWhenAspectJThenRegistersAspects() { this.spring.configLocations(xml("AspectJMethodSecurityServiceEnabled")).autowire(); assertThat(this.spring.getContext().containsBean("preFilterAspect$0")).isTrue(); assertThat(this.spring.getContext().containsBean("postFilterAspect$0")).isTrue(); assertThat(this.spring.getContext().containsBean("preAuthorizeAspect$0")).isTrue(); assertThat(this.spring.getContext().containsBean("postAuthorizeAspect$0")).isTrue(); assertThat(this.spring.getContext().containsBean("securedAspect$0")).isTrue(); assertThat(this.spring.getContext().containsBean("annotationSecurityAspect$0")).isFalse(); } @WithAnonymousUser @Test public void preAuthorizePermitAllWhenRoleAnonymousThenPasses() { this.spring.configLocations(xml("MethodSecurityService")).autowire(); String result = this.methodSecurityService.preAuthorizePermitAll(); assertThat(result).isNull(); } @WithAnonymousUser @Test public void preAuthorizeNotAnonymousWhenRoleAnonymousThenAccessDeniedException() { this.spring.configLocations(xml("MethodSecurityService")).autowire(); assertThatExceptionOfType(AccessDeniedException.class) .isThrownBy(this.methodSecurityService::preAuthorizeNotAnonymous) .withMessage("Access Denied"); } @WithMockUser @Test public void preAuthorizeNotAnonymousWhenRoleUserThenPasses() { this.spring.configLocations(xml("MethodSecurityService")).autowire(); this.methodSecurityService.preAuthorizeNotAnonymous(); } @WithMockUser @Test public void securedWhenRoleUserThenAccessDeniedException() { this.spring.configLocations(xml("MethodSecurityServiceEnabled")).autowire(); assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.methodSecurityService::secured) .withMessage("Access Denied"); } @WithMockUser(roles = "ADMIN") @Test public void securedWhenRoleAdminThenPasses() { this.spring.configLocations(xml("MethodSecurityService")).autowire(); String result = this.methodSecurityService.secured(); assertThat(result).isNull(); } @Test public void securedWhenCustomSecurityContextHolderStrategyThenUses() { this.spring.configLocations(xml("MethodSecurityServiceEnabledCustomSecurityContextHolderStrategy")).autowire(); SecurityContextHolderStrategy strategy = this.spring.getContext().getBean(SecurityContextHolderStrategy.class); SecurityContext context = new SecurityContextImpl(new TestingAuthenticationToken("user", "pass")); strategy.setContext(context); assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.methodSecurityService::secured) .withMessage("Access Denied"); verify(strategy).getContext(); } @WithMockUser(roles = "ADMIN") @Test public void securedUserWhenRoleAdminThenAccessDeniedException() { this.spring.configLocations(xml("MethodSecurityServiceEnabled")).autowire(); assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.methodSecurityService::securedUser) .withMessage("Access Denied"); } @WithMockUser @Test public void securedUserWhenRoleUserThenPasses() { this.spring.configLocations(xml("MethodSecurityService")).autowire(); String result = this.methodSecurityService.securedUser(); assertThat(result).isNull(); } @WithMockUser @Test public void preAuthorizeAdminWhenRoleUserThenAccessDeniedException() { this.spring.configLocations(xml("MethodSecurityService")).autowire(); assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.methodSecurityService::preAuthorizeAdmin) .withMessage("Access Denied"); } @WithMockUser(roles = "ADMIN") @Test public void preAuthorizeAdminWhenRoleAdminThenPasses() { this.spring.configLocations(xml("MethodSecurityService")).autowire(); this.methodSecurityService.preAuthorizeAdmin(); } @Test public void preAuthorizeWhenCustomSecurityContextHolderStrategyThenUses() { this.spring.configLocations(xml("MethodSecurityServiceEnabledCustomSecurityContextHolderStrategy")).autowire(); SecurityContextHolderStrategy strategy = this.spring.getContext().getBean(SecurityContextHolderStrategy.class); SecurityContext context = new SecurityContextImpl(new TestingAuthenticationToken("user", "pass")); strategy.setContext(context); assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.methodSecurityService::preAuthorizeAdmin) .withMessage("Access Denied"); verify(strategy).getContext(); } @WithMockUser(authorities = "PREFIX_ADMIN") @Test public void preAuthorizeAdminWhenRoleAdminAndCustomPrefixThenPasses() { this.spring.configLocations(xml("CustomGrantedAuthorityDefaults")).autowire(); this.methodSecurityService.preAuthorizeAdmin(); } @WithMockUser @Test public void postHasPermissionWhenParameterIsNotGrantThenAccessDeniedException() { this.spring.configLocations(xml("CustomPermissionEvaluator")).autowire(); assertThatExceptionOfType(AccessDeniedException.class) .isThrownBy(() -> this.methodSecurityService.postHasPermission("deny")) .withMessage("Access Denied"); } @WithMockUser @Test public void postHasPermissionWhenParameterIsGrantThenPasses() { this.spring.configLocations(xml("CustomPermissionEvaluator")).autowire(); String result = this.methodSecurityService.postHasPermission("grant"); assertThat(result).isNull(); } @WithMockUser @Test public void postAnnotationWhenParameterIsNotGrantThenAccessDeniedException() { this.spring.configLocations(xml("MethodSecurityService")).autowire(); assertThatExceptionOfType(AccessDeniedException.class) .isThrownBy(() -> this.methodSecurityService.postAnnotation("deny")) .withMessage("Access Denied"); } @WithMockUser @Test public void postAnnotationWhenParameterIsGrantThenPasses() { this.spring.configLocations(xml("MethodSecurityService")).autowire(); String result = this.methodSecurityService.postAnnotation("grant"); assertThat(result).isNull(); } @Test public void preFilterWhenCustomSecurityContextHolderStrategyThenUses() { this.spring.configLocations(xml("MethodSecurityServiceEnabledCustomSecurityContextHolderStrategy")).autowire(); SecurityContextHolderStrategy strategy = this.spring.getContext().getBean(SecurityContextHolderStrategy.class); SecurityContext context = new SecurityContextImpl(new TestingAuthenticationToken("user", "pass")); strategy.setContext(context); List result = this.methodSecurityService .preFilterByUsername(new ArrayList<>(Arrays.asList("user", "bob", "joe"))); assertThat(result).containsExactly("user"); verify(strategy).getContext(); } @Test public void postFilterWhenCustomSecurityContextHolderStrategyThenUses() { this.spring.configLocations(xml("MethodSecurityServiceEnabledCustomSecurityContextHolderStrategy")).autowire(); SecurityContextHolderStrategy strategy = this.spring.getContext().getBean(SecurityContextHolderStrategy.class); SecurityContext context = new SecurityContextImpl(new TestingAuthenticationToken("user", "pass")); strategy.setContext(context); List result = this.methodSecurityService .postFilterByUsername(new ArrayList<>(Arrays.asList("user", "bob", "joe"))); assertThat(result).containsExactly("user"); verify(strategy).getContext(); } @WithMockUser("bob") @Test public void methodReturningAListWhenPrePostFiltersConfiguredThenFiltersList() { this.spring.configLocations(xml("BusinessService")).autowire(); List names = new ArrayList<>(); names.add("bob"); names.add("joe"); names.add("sam"); List result = this.businessService.methodReturningAList(names); assertThat(result).hasSize(1); assertThat(result.get(0)).isEqualTo("bob"); } @WithMockUser("bob") @Test public void methodReturningAnArrayWhenPostFilterConfiguredThenFiltersArray() { this.spring.configLocations(xml("BusinessService")).autowire(); List names = new ArrayList<>(); names.add("bob"); names.add("joe"); names.add("sam"); Object[] result = this.businessService.methodReturningAnArray(names.toArray()); assertThat(result).hasSize(1); assertThat(result[0]).isEqualTo("bob"); } @WithMockUser("bob") @Test public void securedUserWhenCustomBeforeAdviceConfiguredAndNameBobThenPasses() { this.spring.configLocations(xml("CustomAuthorizationManagerBeforeAdvice")).autowire(); String result = this.methodSecurityService.securedUser(); assertThat(result).isNull(); } @WithMockUser("joe") @Test public void securedUserWhenCustomBeforeAdviceConfiguredAndNameNotBobThenAccessDeniedException() { this.spring.configLocations(xml("CustomAuthorizationManagerBeforeAdvice")).autowire(); assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.methodSecurityService::securedUser) .withMessage("Access Denied"); } @WithMockUser("bob") @Test public void securedUserWhenCustomAfterAdviceConfiguredAndNameBobThenGranted() { this.spring.configLocations(xml("CustomAuthorizationManagerAfterAdvice")).autowire(); String result = this.methodSecurityService.securedUser(); assertThat(result).isEqualTo("granted"); } @WithMockUser("joe") @Test public void securedUserWhenCustomAfterAdviceConfiguredAndNameNotBobThenAccessDeniedException() { this.spring.configLocations(xml("CustomAuthorizationManagerAfterAdvice")).autowire(); assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.methodSecurityService::securedUser) .withMessage("Access Denied for User 'joe'"); } @WithMockUser(roles = "ADMIN") @Test public void jsr250WhenRoleAdminThenAccessDeniedException() { this.spring.configLocations(xml("MethodSecurityServiceEnabled")).autowire(); assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.methodSecurityService::jsr250) .withMessage("Access Denied"); } @Test public void jsr250WhenCustomSecurityContextHolderStrategyThenUses() { this.spring.configLocations(xml("MethodSecurityServiceEnabledCustomSecurityContextHolderStrategy")).autowire(); SecurityContextHolderStrategy strategy = this.spring.getContext().getBean(SecurityContextHolderStrategy.class); SecurityContext context = new SecurityContextImpl(new TestingAuthenticationToken("user", "pass")); strategy.setContext(context); assertThatExceptionOfType(AccessDeniedException.class) .isThrownBy(this.methodSecurityService::jsr250RolesAllowed) .withMessage("Access Denied"); verify(strategy).getContext(); } @WithAnonymousUser @Test public void jsr250PermitAllWhenRoleAnonymousThenPasses() { this.spring.configLocations(xml("MethodSecurityServiceEnabled")).autowire(); String result = this.methodSecurityService.jsr250PermitAll(); assertThat(result).isNull(); } @WithMockUser(roles = "ADMIN") @Test public void rolesAllowedUserWhenRoleAdminThenAccessDeniedException() { this.spring.configLocations(xml("BusinessService")).autowire(); assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.businessService::rolesAllowedUser) .withMessage("Access Denied"); } @WithMockUser @Test public void rolesAllowedUserWhenRoleUserThenPasses() { this.spring.configLocations(xml("BusinessService")).autowire(); this.businessService.rolesAllowedUser(); } @WithMockUser(roles = { "ADMIN", "USER" }) @Test public void manyAnnotationsWhenMeetsConditionsThenReturnsFilteredList() throws Exception { List names = Arrays.asList("harold", "jonathan", "pete", "bo"); this.spring.configLocations(xml("MethodSecurityServiceEnabled")).autowire(); List filtered = this.methodSecurityService.manyAnnotations(new ArrayList<>(names)); assertThat(filtered).hasSize(2); assertThat(filtered).containsExactly("harold", "jonathan"); } // gh-4003 // gh-4103 @WithMockUser @Test public void manyAnnotationsWhenUserThenFails() { List names = Arrays.asList("harold", "jonathan", "pete", "bo"); this.spring.configLocations(xml("MethodSecurityServiceEnabled")).autowire(); assertThatExceptionOfType(AccessDeniedException.class) .isThrownBy(() -> this.methodSecurityService.manyAnnotations(new ArrayList<>(names))); } @WithMockUser @Test public void manyAnnotationsWhenShortListThenFails() { List names = Arrays.asList("harold", "jonathan", "pete"); this.spring.configLocations(xml("MethodSecurityServiceEnabled")).autowire(); assertThatExceptionOfType(AccessDeniedException.class) .isThrownBy(() -> this.methodSecurityService.manyAnnotations(new ArrayList<>(names))); } @WithMockUser(roles = "ADMIN") @Test public void manyAnnotationsWhenAdminThenFails() { List names = Arrays.asList("harold", "jonathan", "pete", "bo"); this.spring.configLocations(xml("MethodSecurityServiceEnabled")).autowire(); assertThatExceptionOfType(AccessDeniedException.class) .isThrownBy(() -> this.methodSecurityService.manyAnnotations(new ArrayList<>(names))); } // gh-3183 @Test public void repeatedAnnotationsWhenPresentThenFails() { this.spring.configLocations(xml("MethodSecurityService")).autowire(); assertThatExceptionOfType(AnnotationConfigurationException.class) .isThrownBy(() -> this.methodSecurityService.repeatedAnnotations()); } // gh-3183 @Test public void repeatedJsr250AnnotationsWhenPresentThenFails() { this.spring.configLocations(xml("Jsr250")).autowire(); assertThatExceptionOfType(AnnotationConfigurationException.class) .isThrownBy(() -> this.businessService.repeatedAnnotations()); } // gh-3183 @Test public void repeatedSecuredAnnotationsWhenPresentThenFails() { this.spring.configLocations(xml("Secured")).autowire(); assertThatExceptionOfType(AnnotationConfigurationException.class) .isThrownBy(() -> this.businessService.repeatedAnnotations()); } @WithMockUser @Test public void supportsMethodArgumentsInPointcut() { this.spring.configLocations(xml("ProtectPointcut")).autowire(); this.businessService.someOther(0); assertThatExceptionOfType(AccessDeniedException.class) .isThrownBy(() -> this.businessService.someOther("somestring")); } @Test public void supportsBooleanPointcutExpressions() { this.spring.configLocations(xml("ProtectPointcutBoolean")).autowire(); this.businessService.someOther("somestring"); // All others should require ROLE_USER assertThatExceptionOfType(AuthenticationCredentialsNotFoundException.class) .isThrownBy(() -> this.businessService.someOther(0)); SecurityContextHolder.getContext() .setAuthentication(new TestingAuthenticationToken("user", "password", AuthorityUtils.createAuthorityList("ROLE_USER"))); this.businessService.someOther(0); SecurityContextHolder.clearContext(); } private static String xml(String configName) { return CONFIG_LOCATION_PREFIX + "-" + configName + ".xml"; } static class MyPermissionEvaluator implements PermissionEvaluator { @Override public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) { return "grant".equals(targetDomainObject); } @Override public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) { throw new UnsupportedOperationException(); } } static class MyAuthorizationManager implements AuthorizationManager { @Override public AuthorizationResult authorize( Supplier authentication, MethodInvocation object) { return new AuthorizationDecision("bob".equals(authentication.get().getName())); } } static class MyAdvice implements MethodInterceptor { @Nullable @Override public Object invoke(@NonNull MethodInvocation invocation) { Authentication auth = SecurityContextHolder.getContext().getAuthentication(); if ("bob".equals(auth.getName())) { return "granted"; } throw new AccessDeniedException("Access Denied for User '" + auth.getName() + "'"); } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/method/PreAuthorizeAdminRole.java ================================================ /* * 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 org.springframework.security.config.method; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import org.springframework.security.access.prepost.PreAuthorize; /** * @author Rob Winch * */ @Retention(RetentionPolicy.RUNTIME) @PreAuthorize("hasRole('ADMIN')") public @interface PreAuthorizeAdminRole { } ================================================ FILE: config/src/test/java/org/springframework/security/config/method/PreAuthorizeServiceImpl.java ================================================ /* * 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 org.springframework.security.config.method; /** * @author Rob Winch * */ public class PreAuthorizeServiceImpl { @PreAuthorizeAdminRole public void preAuthorizeAdminRole() { } @ContactPermission public void contactPermission(Contact contact) { } } ================================================ FILE: config/src/test/java/org/springframework/security/config/method/PreAuthorizeTests.java ================================================ /* * 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 org.springframework.security.config.method; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit.jupiter.SpringExtension; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; /** * @author Rob Winch * */ @ExtendWith(SpringExtension.class) @ContextConfiguration public class PreAuthorizeTests { @Autowired PreAuthorizeServiceImpl service; @AfterEach public void cleanup() { SecurityContextHolder.clearContext(); } @Test public void preAuthorizeAdminRoleDenied() { SecurityContextHolder.getContext() .setAuthentication(new TestingAuthenticationToken("user", "pass", "ROLE_USER")); assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.service::preAuthorizeAdminRole); } @Test public void preAuthorizeAdminRoleGranted() { SecurityContextHolder.getContext() .setAuthentication(new TestingAuthenticationToken("user", "pass", "ROLE_ADMIN")); this.service.preAuthorizeAdminRole(); } @Test public void preAuthorizeContactPermissionGranted() { SecurityContextHolder.getContext() .setAuthentication(new TestingAuthenticationToken("user", "pass", "ROLE_ADMIN")); this.service.contactPermission(new Contact("user")); } @Test public void preAuthorizeContactPermissionDenied() { SecurityContextHolder.getContext() .setAuthentication(new TestingAuthenticationToken("user", "pass", "ROLE_ADMIN")); assertThatExceptionOfType(AccessDeniedException.class) .isThrownBy(() -> this.service.contactPermission(new Contact("admin"))); } } ================================================ FILE: config/src/test/java/org/springframework/security/config/method/Sec2196Tests.java ================================================ /* * 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 org.springframework.security.config.method; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.config.util.InMemoryXmlApplicationContext; import org.springframework.security.core.context.SecurityContextHolder; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; /** * @author Rob Winch * */ public class Sec2196Tests { private ConfigurableApplicationContext context; @Test public void genericMethodsProtected() { loadContext("" + ""); SecurityContextHolder.getContext() .setAuthentication(new TestingAuthenticationToken("test", "pass", "ROLE_USER")); Service service = this.context.getBean(Service.class); assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(() -> service.save(new User())); } @Test public void genericMethodsAllowed() { loadContext("" + ""); SecurityContextHolder.getContext() .setAuthentication(new TestingAuthenticationToken("test", "pass", "saveUsers")); Service service = this.context.getBean(Service.class); service.save(new User()); } private void loadContext(String context) { this.context = new InMemoryXmlApplicationContext(context); } @AfterEach public void closeAppContext() { if (this.context != null) { this.context.close(); this.context = null; } SecurityContextHolder.clearContext(); } public static class Service { @PreAuthorize("hasAuthority('saveUsers')") public T save(T dto) { return dto; } } static class User { } } ================================================ FILE: config/src/test/java/org/springframework/security/config/method/SecuredAdminRole.java ================================================ /* * 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 org.springframework.security.config.method; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import org.springframework.security.access.annotation.Secured; /** * @author Rob Winch * */ @Retention(RetentionPolicy.RUNTIME) @Secured("ROLE_ADMIN") public @interface SecuredAdminRole { } ================================================ FILE: config/src/test/java/org/springframework/security/config/method/SecuredAnnotationDrivenBeanDefinitionParserTests.java ================================================ /* * 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 org.springframework.security.config.method; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.annotation.BusinessService; import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.config.ConfigTestUtils; import org.springframework.security.config.util.InMemoryXmlApplicationContext; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.context.SecurityContextHolder; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; /** * @author Ben Alex */ public class SecuredAnnotationDrivenBeanDefinitionParserTests { private InMemoryXmlApplicationContext appContext; private BusinessService target; @BeforeEach public void loadContext() { SecurityContextHolder.clearContext(); this.appContext = new InMemoryXmlApplicationContext( "" + "" + ConfigTestUtils.AUTH_PROVIDER_XML); this.target = (BusinessService) this.appContext.getBean("target"); } @AfterEach public void closeAppContext() { if (this.appContext != null) { this.appContext.close(); } SecurityContextHolder.clearContext(); } @Test public void targetShouldPreventProtectedMethodInvocationWithNoContext() { assertThatExceptionOfType(AuthenticationCredentialsNotFoundException.class) .isThrownBy(this.target::someUserMethod1); } @Test public void targetShouldAllowProtectedMethodInvocationWithCorrectRole() { UsernamePasswordAuthenticationToken token = UsernamePasswordAuthenticationToken.authenticated("Test", "Password", AuthorityUtils.createAuthorityList("ROLE_USER")); SecurityContextHolder.getContext().setAuthentication(token); this.target.someUserMethod1(); } @Test public void targetShouldPreventProtectedMethodInvocationWithIncorrectRole() { UsernamePasswordAuthenticationToken token = UsernamePasswordAuthenticationToken.authenticated("Test", "Password", AuthorityUtils.createAuthorityList("ROLE_SOMEOTHER")); SecurityContextHolder.getContext().setAuthentication(token); assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.target::someAdminMethod); } // SEC-1387 @Test public void targetIsSerializableBeforeUse() throws Exception { BusinessService chompedTarget = (BusinessService) serializeAndDeserialize(this.target); assertThatExceptionOfType(AuthenticationCredentialsNotFoundException.class) .isThrownBy(chompedTarget::someAdminMethod); } @Test public void targetIsSerializableAfterUse() throws Exception { assertThatExceptionOfType(AuthenticationCredentialsNotFoundException.class) .isThrownBy(this.target::someAdminMethod); SecurityContextHolder.getContext().setAuthentication(new TestingAuthenticationToken("u", "p", "ROLE_A")); BusinessService chompedTarget = (BusinessService) serializeAndDeserialize(this.target); assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(chompedTarget::someAdminMethod); } private Object serializeAndDeserialize(Object o) throws IOException, ClassNotFoundException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(o); oos.flush(); baos.flush(); byte[] bytes = baos.toByteArray(); ByteArrayInputStream is = new ByteArrayInputStream(bytes); ObjectInputStream ois = new ObjectInputStream(is); Object o2 = ois.readObject(); return o2; } } ================================================ FILE: config/src/test/java/org/springframework/security/config/method/SecuredServiceImpl.java ================================================ /* * 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 org.springframework.security.config.method; /** * @author Rob Winch * */ public class SecuredServiceImpl { @SecuredAdminRole public void securedAdminRole() { } } ================================================ FILE: config/src/test/java/org/springframework/security/config/method/SecuredTests.java ================================================ /* * 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 org.springframework.security.config.method; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit.jupiter.SpringExtension; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; /** * @author Rob Winch * */ @ExtendWith(SpringExtension.class) @ContextConfiguration public class SecuredTests { @Autowired SecuredServiceImpl service; @AfterEach public void cleanup() { SecurityContextHolder.clearContext(); } @Test public void securedAdminRoleDenied() { SecurityContextHolder.getContext() .setAuthentication(new TestingAuthenticationToken("user", "pass", "ROLE_USER")); assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.service::securedAdminRole); } @Test public void securedAdminRoleGranted() { SecurityContextHolder.getContext() .setAuthentication(new TestingAuthenticationToken("user", "pass", "ROLE_ADMIN")); this.service.securedAdminRole(); } } ================================================ FILE: config/src/test/java/org/springframework/security/config/method/TestPermissionEvaluator.java ================================================ /* * 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 org.springframework.security.config.method; import java.io.Serializable; import org.springframework.security.access.PermissionEvaluator; import org.springframework.security.core.Authentication; public class TestPermissionEvaluator implements PermissionEvaluator { @Override public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) { return false; } @Override public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) { return false; } } ================================================ FILE: config/src/test/java/org/springframework/security/config/method/configuration/Gh4020GlobalMethodSecurityConfigurationTests.java ================================================ /* * 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 org.springframework.security.config.method.configuration; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.access.PermissionEvaluator; import org.springframework.security.access.hierarchicalroles.RoleHierarchy; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException; import org.springframework.security.authentication.AuthenticationTrustResolver; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit.jupiter.SpringExtension; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.mockito.Mockito.mock; /** * @author Rob Winch */ @ExtendWith(SpringExtension.class) @ContextConfiguration public class Gh4020GlobalMethodSecurityConfigurationTests { @Autowired DenyAllService denyAll; // gh-4020 @Test public void denyAll() { assertThatExceptionOfType(AuthenticationCredentialsNotFoundException.class).isThrownBy(this.denyAll::denyAll); } @Configuration @EnableGlobalMethodSecurity(prePostEnabled = true) static class SecurityConfig { @Bean PermissionEvaluator permissionEvaluator() { return mock(PermissionEvaluator.class); } @Bean RoleHierarchy RoleHierarchy() { return mock(RoleHierarchy.class); } @Bean AuthenticationTrustResolver trustResolver() { return mock(AuthenticationTrustResolver.class); } @Autowired DenyAllService denyAll; } @Configuration static class ServiceConfig { @Bean DenyAllService denyAllService() { return new DenyAllService(); } } @PreAuthorize("denyAll") static class DenyAllService { void denyAll() { } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/method/sec2136/JpaPermissionEvaluator.java ================================================ /* * 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 org.springframework.security.config.method.sec2136; import java.io.Serializable; import jakarta.persistence.EntityManager; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.access.PermissionEvaluator; import org.springframework.security.core.Authentication; /** * @author Rob Winch * */ public class JpaPermissionEvaluator implements PermissionEvaluator { @Autowired private EntityManager entityManager; public JpaPermissionEvaluator() { System.out.println("initializing " + this); } @Override public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) { return true; } @Override public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) { return true; } } ================================================ FILE: config/src/test/java/org/springframework/security/config/method/sec2136/Sec2136Tests.java ================================================ /* * 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 org.springframework.security.config.method.sec2136; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit.jupiter.SpringExtension; /** * @author Rob Winch * @since 3.2 */ @ExtendWith(SpringExtension.class) @ContextConfiguration("sec2136.xml") public class Sec2136Tests { @Test public void configurationLoads() { } } ================================================ FILE: config/src/test/java/org/springframework/security/config/method/sec2499/Sec2499Tests.java ================================================ /* * 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 org.springframework.security.config.method.sec2499; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import org.springframework.context.support.GenericXmlApplicationContext; /** * @author Rob Winch * */ public class Sec2499Tests { private GenericXmlApplicationContext parent; private GenericXmlApplicationContext child; @AfterEach public void cleanup() { if (this.parent != null) { this.parent.close(); } if (this.child != null) { this.child.close(); } } @Test public void methodExpressionHandlerInParentContextLoads() { this.parent = new GenericXmlApplicationContext("org/springframework/security/config/method/sec2499/parent.xml"); this.child = new GenericXmlApplicationContext(); this.child.load("org/springframework/security/config/method/sec2499/child.xml"); this.child.setParent(this.parent); this.child.refresh(); } } ================================================ FILE: config/src/test/java/org/springframework/security/config/oauth2/client/ClientRegistrationsBeanDefinitionParserTests.java ================================================ /* * 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 org.springframework.security.config.oauth2.client; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.ClientRegistration.ProviderDetails; import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository; import org.springframework.security.oauth2.core.AuthenticationMethod; import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.core.ClientAuthenticationMethod; import org.springframework.util.StringUtils; import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link ClientRegistrationsBeanDefinitionParser}. * * @author Ruby Hartono * @author Evgeniy Cheban */ @ExtendWith(SpringTestContextExtension.class) public class ClientRegistrationsBeanDefinitionParserTests { private static final String CONFIG_LOCATION_PREFIX = "classpath:org/springframework/security/config/oauth2/client/ClientRegistrationsBeanDefinitionParserTests"; // @formatter:off private static final String ISSUER_URI_XML_CONFIG = "\n" + "\n" + " \n" + " \n" + " \n" + " \n" + "\n" + "\n"; // @formatter:on // @formatter:off private static final String OIDC_DISCOVERY_RESPONSE = "{\n" + " \"authorization_endpoint\": \"https://example.com/o/oauth2/v2/auth\", \n" + " \"claims_supported\": [\n" + " \"aud\", \n" + " \"email\", \n" + " \"email_verified\", \n" + " \"exp\", \n" + " \"family_name\", \n" + " \"given_name\", \n" + " \"iat\", \n" + " \"iss\", \n" + " \"locale\", \n" + " \"name\", \n" + " \"picture\", \n" + " \"sub\"\n" + " ], \n" + " \"code_challenge_methods_supported\": [\n" + " \"plain\", \n" + " \"S256\"\n" + " ], \n" + " \"id_token_signing_alg_values_supported\": [\n" + " \"RS256\"\n" + " ], \n" + " \"issuer\": \"${issuer-uri}\", \n" + " \"jwks_uri\": \"https://example.com/oauth2/v3/certs\", \n" + " \"response_types_supported\": [\n" + " \"code\", \n" + " \"token\", \n" + " \"id_token\", \n" + " \"code token\", \n" + " \"code id_token\", \n" + " \"token id_token\", \n" + " \"code token id_token\", \n" + " \"none\"\n" + " ], \n" + " \"revocation_endpoint\": \"https://example.com/o/oauth2/revoke\", \n" + " \"scopes_supported\": [\n" + " \"openid\", \n" + " \"email\", \n" + " \"profile\"\n" + " ], \n" + " \"subject_types_supported\": [\n" + " \"public\"\n" + " ], \n" + " \"grant_types_supported\" : [\"authorization_code\"], \n" + " \"token_endpoint\": \"https://example.com/oauth2/v4/token\", \n" + " \"token_endpoint_auth_methods_supported\": [\n" + " \"client_secret_post\", \n" + " \"client_secret_basic\", \n" + " \"none\"\n" + " ], \n" + " \"userinfo_endpoint\": \"https://example.com/oauth2/v3/userinfo\"\n" + "}"; // @formatter:on @Autowired private ClientRegistrationRepository clientRegistrationRepository; public final SpringTestContext spring = new SpringTestContext(this); private MockWebServer server; @AfterEach public void cleanup() throws Exception { if (this.server != null) { this.server.shutdown(); } } @Test public void parseWhenIssuerUriConfiguredThenRequestConfigFromIssuer() throws Exception { this.server = new MockWebServer(); this.server.start(); String serverUrl = this.server.url("/").toString(); String discoveryResponse = OIDC_DISCOVERY_RESPONSE.replace("${issuer-uri}", serverUrl); this.server.enqueue(jsonResponse(discoveryResponse)); String contextConfig = ISSUER_URI_XML_CONFIG.replace("${issuer-uri}", serverUrl); this.spring.context(contextConfig).autowire(); assertThat(this.clientRegistrationRepository).isInstanceOf(InMemoryClientRegistrationRepository.class); ClientRegistration googleRegistration = this.clientRegistrationRepository.findByRegistrationId("google-login"); assertThat(googleRegistration).isNotNull(); assertThat(googleRegistration.getRegistrationId()).isEqualTo("google-login"); assertThat(googleRegistration.getClientId()).isEqualTo("google-client-id"); assertThat(googleRegistration.getClientSecret()).isEqualTo("google-client-secret"); assertThat(googleRegistration.getClientAuthenticationMethod()) .isEqualTo(ClientAuthenticationMethod.CLIENT_SECRET_BASIC); assertThat(googleRegistration.getAuthorizationGrantType()).isEqualTo(AuthorizationGrantType.AUTHORIZATION_CODE); assertThat(googleRegistration.getRedirectUri()).isEqualTo("{baseUrl}/{action}/oauth2/code/{registrationId}"); assertThat(googleRegistration.getScopes()).isEmpty(); assertThat(googleRegistration.getClientName()).isEqualTo(serverUrl); ProviderDetails googleProviderDetails = googleRegistration.getProviderDetails(); assertThat(googleProviderDetails).isNotNull(); assertThat(googleProviderDetails.getAuthorizationUri()).isEqualTo("https://example.com/o/oauth2/v2/auth"); assertThat(googleProviderDetails.getTokenUri()).isEqualTo("https://example.com/oauth2/v4/token"); assertThat(googleProviderDetails.getUserInfoEndpoint().getUri()) .isEqualTo("https://example.com/oauth2/v3/userinfo"); assertThat(googleProviderDetails.getUserInfoEndpoint().getAuthenticationMethod()) .isEqualTo(AuthenticationMethod.HEADER); assertThat(googleProviderDetails.getUserInfoEndpoint().getUserNameAttributeName()).isEqualTo("sub"); assertThat(googleProviderDetails.getJwkSetUri()).isEqualTo("https://example.com/oauth2/v3/certs"); assertThat(googleProviderDetails.getIssuerUri()).isEqualTo(serverUrl); } @Test public void parseWhenMultipleClientsConfiguredThenAvailableInRepository() { this.spring.configLocations(ClientRegistrationsBeanDefinitionParserTests.xml("MultiClientRegistration")) .autowire(); assertThat(this.clientRegistrationRepository).isInstanceOf(InMemoryClientRegistrationRepository.class); ClientRegistration googleRegistration = this.clientRegistrationRepository.findByRegistrationId("google-login"); assertThat(googleRegistration).isNotNull(); assertThat(googleRegistration.getRegistrationId()).isEqualTo("google-login"); assertThat(googleRegistration.getClientId()).isEqualTo("google-client-id"); assertThat(googleRegistration.getClientSecret()).isEqualTo("google-client-secret"); assertThat(googleRegistration.getClientAuthenticationMethod()) .isEqualTo(ClientAuthenticationMethod.CLIENT_SECRET_BASIC); assertThat(googleRegistration.getAuthorizationGrantType()).isEqualTo(AuthorizationGrantType.AUTHORIZATION_CODE); assertThat(googleRegistration.getRedirectUri()).isEqualTo("{baseUrl}/login/oauth2/code/{registrationId}"); assertThat(googleRegistration.getScopes()) .isEqualTo(StringUtils.commaDelimitedListToSet("openid,profile,email")); assertThat(googleRegistration.getClientName()).isEqualTo("Google"); ProviderDetails googleProviderDetails = googleRegistration.getProviderDetails(); assertThat(googleProviderDetails).isNotNull(); assertThat(googleProviderDetails.getAuthorizationUri()) .isEqualTo("https://accounts.google.com/o/oauth2/v2/auth"); assertThat(googleProviderDetails.getTokenUri()).isEqualTo("https://www.googleapis.com/oauth2/v4/token"); assertThat(googleProviderDetails.getUserInfoEndpoint().getUri()) .isEqualTo("https://www.googleapis.com/oauth2/v3/userinfo"); assertThat(googleProviderDetails.getUserInfoEndpoint().getAuthenticationMethod()) .isEqualTo(AuthenticationMethod.HEADER); assertThat(googleProviderDetails.getUserInfoEndpoint().getUserNameAttributeName()).isEqualTo("sub"); assertThat(googleProviderDetails.getJwkSetUri()).isEqualTo("https://www.googleapis.com/oauth2/v3/certs"); assertThat(googleProviderDetails.getIssuerUri()).isEqualTo("https://accounts.google.com"); ClientRegistration githubRegistration = this.clientRegistrationRepository.findByRegistrationId("github-login"); assertThat(githubRegistration).isNotNull(); assertThat(githubRegistration.getRegistrationId()).isEqualTo("github-login"); assertThat(githubRegistration.getClientId()).isEqualTo("github-client-id"); assertThat(githubRegistration.getClientSecret()).isEqualTo("github-client-secret"); assertThat(githubRegistration.getClientAuthenticationMethod()) .isEqualTo(ClientAuthenticationMethod.CLIENT_SECRET_BASIC); assertThat(githubRegistration.getAuthorizationGrantType()).isEqualTo(AuthorizationGrantType.AUTHORIZATION_CODE); assertThat(githubRegistration.getRedirectUri()).isEqualTo("{baseUrl}/login/oauth2/code/{registrationId}"); assertThat(googleRegistration.getScopes()) .isEqualTo(StringUtils.commaDelimitedListToSet("openid,profile,email")); assertThat(githubRegistration.getClientName()).isEqualTo("Github"); ProviderDetails githubProviderDetails = githubRegistration.getProviderDetails(); assertThat(githubProviderDetails).isNotNull(); assertThat(githubProviderDetails.getAuthorizationUri()).isEqualTo("https://github.com/login/oauth/authorize"); assertThat(githubProviderDetails.getTokenUri()).isEqualTo("https://github.com/login/oauth/access_token"); assertThat(githubProviderDetails.getUserInfoEndpoint().getUri()).isEqualTo("https://api.github.com/user"); assertThat(githubProviderDetails.getUserInfoEndpoint().getAuthenticationMethod()) .isEqualTo(AuthenticationMethod.HEADER); assertThat(githubProviderDetails.getUserInfoEndpoint().getUserNameAttributeName()).isEqualTo("id"); } @Test public void parseWhenClientPlaceholdersThenResolvePlaceholders() { System.setProperty("oauth2.client.id", "github-client-id"); System.setProperty("oauth2.client.secret", "github-client-secret"); this.spring.configLocations(xml("ClientPlaceholders")).autowire(); assertThat(this.clientRegistrationRepository).isInstanceOf(InMemoryClientRegistrationRepository.class); ClientRegistration githubRegistration = this.clientRegistrationRepository.findByRegistrationId("github"); assertThat(githubRegistration.getClientId()).isEqualTo("github-client-id"); assertThat(githubRegistration.getClientSecret()).isEqualTo("github-client-secret"); } private static MockResponse jsonResponse(String json) { return new MockResponse().setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE).setBody(json); } private static String xml(String configName) { return CONFIG_LOCATION_PREFIX + "-" + configName + ".xml"; } } ================================================ FILE: config/src/test/java/org/springframework/security/config/oauth2/client/CommonOAuth2ProviderTests.java ================================================ /* * 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 org.springframework.security.config.oauth2.client; import org.junit.jupiter.api.Test; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.ClientRegistration.ProviderDetails; import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.core.ClientAuthenticationMethod; import org.springframework.security.oauth2.core.oidc.IdTokenClaimNames; import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link CommonOAuth2Provider}. * * @author Phillip Webb */ public class CommonOAuth2ProviderTests { private static final String DEFAULT_REDIRECT_URL = "{baseUrl}/{action}/oauth2/code/{registrationId}"; @Test public void getBuilderWhenGoogleShouldHaveGoogleSettings() { ClientRegistration registration = build(CommonOAuth2Provider.GOOGLE); ProviderDetails providerDetails = registration.getProviderDetails(); assertThat(providerDetails.getAuthorizationUri()).isEqualTo("https://accounts.google.com/o/oauth2/v2/auth"); assertThat(providerDetails.getTokenUri()).isEqualTo("https://www.googleapis.com/oauth2/v4/token"); assertThat(providerDetails.getUserInfoEndpoint().getUri()) .isEqualTo("https://www.googleapis.com/oauth2/v3/userinfo"); assertThat(providerDetails.getUserInfoEndpoint().getUserNameAttributeName()).isEqualTo(IdTokenClaimNames.SUB); assertThat(providerDetails.getJwkSetUri()).isEqualTo("https://www.googleapis.com/oauth2/v3/certs"); assertThat(providerDetails.getIssuerUri()).isEqualTo("https://accounts.google.com"); assertThat(registration.getClientAuthenticationMethod()) .isEqualTo(ClientAuthenticationMethod.CLIENT_SECRET_BASIC); assertThat(registration.getAuthorizationGrantType()).isEqualTo(AuthorizationGrantType.AUTHORIZATION_CODE); assertThat(registration.getRedirectUri()).isEqualTo(DEFAULT_REDIRECT_URL); assertThat(registration.getScopes()).containsOnly("openid", "profile", "email"); assertThat(registration.getClientName()).isEqualTo("Google"); assertThat(registration.getRegistrationId()).isEqualTo("123"); } @Test public void getBuilderWhenGitHubShouldHaveGitHubSettings() { ClientRegistration registration = build(CommonOAuth2Provider.GITHUB); ProviderDetails providerDetails = registration.getProviderDetails(); assertThat(providerDetails.getAuthorizationUri()).isEqualTo("https://github.com/login/oauth/authorize"); assertThat(providerDetails.getTokenUri()).isEqualTo("https://github.com/login/oauth/access_token"); assertThat(providerDetails.getUserInfoEndpoint().getUri()).isEqualTo("https://api.github.com/user"); assertThat(providerDetails.getUserInfoEndpoint().getUserNameAttributeName()).isEqualTo("id"); assertThat(providerDetails.getJwkSetUri()).isNull(); assertThat(registration.getClientAuthenticationMethod()) .isEqualTo(ClientAuthenticationMethod.CLIENT_SECRET_BASIC); assertThat(registration.getAuthorizationGrantType()).isEqualTo(AuthorizationGrantType.AUTHORIZATION_CODE); assertThat(registration.getRedirectUri()).isEqualTo(DEFAULT_REDIRECT_URL); assertThat(registration.getScopes()).containsOnly("read:user"); assertThat(registration.getClientName()).isEqualTo("GitHub"); assertThat(registration.getRegistrationId()).isEqualTo("123"); } @Test public void getBuilderWhenFacebookShouldHaveFacebookSettings() { ClientRegistration registration = build(CommonOAuth2Provider.FACEBOOK); ProviderDetails providerDetails = registration.getProviderDetails(); assertThat(providerDetails.getAuthorizationUri()).isEqualTo("https://www.facebook.com/v2.8/dialog/oauth"); assertThat(providerDetails.getTokenUri()).isEqualTo("https://graph.facebook.com/v2.8/oauth/access_token"); assertThat(providerDetails.getUserInfoEndpoint().getUri()) .isEqualTo("https://graph.facebook.com/me?fields=id,name,email"); assertThat(providerDetails.getUserInfoEndpoint().getUserNameAttributeName()).isEqualTo("id"); assertThat(providerDetails.getJwkSetUri()).isNull(); assertThat(registration.getClientAuthenticationMethod()) .isEqualTo(ClientAuthenticationMethod.CLIENT_SECRET_POST); assertThat(registration.getAuthorizationGrantType()).isEqualTo(AuthorizationGrantType.AUTHORIZATION_CODE); assertThat(registration.getRedirectUri()).isEqualTo(DEFAULT_REDIRECT_URL); assertThat(registration.getScopes()).containsOnly("public_profile", "email"); assertThat(registration.getClientName()).isEqualTo("Facebook"); assertThat(registration.getRegistrationId()).isEqualTo("123"); } @Test public void getBuilderWhenOktaShouldHaveOktaSettings() { ClientRegistration registration = builder(CommonOAuth2Provider.OKTA) .authorizationUri("https://example.com/auth") .tokenUri("https://example.com/token") .userInfoUri("https://example.com/info") .jwkSetUri("https://example.com/jwkset") .build(); ProviderDetails providerDetails = registration.getProviderDetails(); assertThat(providerDetails.getAuthorizationUri()).isEqualTo("https://example.com/auth"); assertThat(providerDetails.getTokenUri()).isEqualTo("https://example.com/token"); assertThat(providerDetails.getUserInfoEndpoint().getUri()).isEqualTo("https://example.com/info"); assertThat(providerDetails.getUserInfoEndpoint().getUserNameAttributeName()).isEqualTo(IdTokenClaimNames.SUB); assertThat(providerDetails.getJwkSetUri()).isEqualTo("https://example.com/jwkset"); assertThat(registration.getClientAuthenticationMethod()) .isEqualTo(ClientAuthenticationMethod.CLIENT_SECRET_BASIC); assertThat(registration.getAuthorizationGrantType()).isEqualTo(AuthorizationGrantType.AUTHORIZATION_CODE); assertThat(registration.getRedirectUri()).isEqualTo(DEFAULT_REDIRECT_URL); assertThat(registration.getScopes()).containsOnly("openid", "profile", "email"); assertThat(registration.getClientName()).isEqualTo("Okta"); assertThat(registration.getRegistrationId()).isEqualTo("123"); } @Test public void getBuilderWhenXShouldHaveXSettings() { ClientRegistration registration = build(CommonOAuth2Provider.X); ProviderDetails providerDetails = registration.getProviderDetails(); assertThat(providerDetails.getAuthorizationUri()).isEqualTo("https://x.com/i/oauth2/authorize"); assertThat(providerDetails.getTokenUri()).isEqualTo("https://api.x.com/2/oauth2/token"); assertThat(providerDetails.getUserInfoEndpoint().getUri()).isEqualTo("https://api.x.com/2/users/me"); assertThat(providerDetails.getUserInfoEndpoint().getUserNameAttributeName()).isEqualTo("username"); assertThat(providerDetails.getJwkSetUri()).isNull(); assertThat(registration.getClientAuthenticationMethod()) .isEqualTo(ClientAuthenticationMethod.CLIENT_SECRET_POST); assertThat(registration.getAuthorizationGrantType()).isEqualTo(AuthorizationGrantType.AUTHORIZATION_CODE); assertThat(registration.getRedirectUri()).isEqualTo(DEFAULT_REDIRECT_URL); assertThat(registration.getScopes()).containsOnly("users.read", "tweet.read"); assertThat(registration.getClientName()).isEqualTo("X"); assertThat(registration.getRegistrationId()).isEqualTo("123"); } private ClientRegistration build(CommonOAuth2Provider provider) { return builder(provider).build(); } private ClientRegistration.Builder builder(CommonOAuth2Provider provider) { return provider.getBuilder("123").clientId("abcd").clientSecret("secret"); } } ================================================ FILE: config/src/test/java/org/springframework/security/config/observation/SecurityObservationSettingsTests.java ================================================ /* * 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 org.springframework.security.config.observation; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link SecurityObservationSettings} */ public class SecurityObservationSettingsTests { @Test void withDefaultsThenFilterOffAuthenticationOnAuthorizationOn() { SecurityObservationSettings defaults = SecurityObservationSettings.withDefaults().build(); assertThat(defaults.shouldObserveRequests()).isFalse(); assertThat(defaults.shouldObserveAuthentications()).isTrue(); assertThat(defaults.shouldObserveAuthorizations()).isTrue(); } @Test void noObservationsWhenConstructedThenAllOff() { SecurityObservationSettings defaults = SecurityObservationSettings.noObservations(); assertThat(defaults.shouldObserveRequests()).isFalse(); assertThat(defaults.shouldObserveAuthentications()).isFalse(); assertThat(defaults.shouldObserveAuthorizations()).isFalse(); } @Test void withDefaultsWhenExclusionsThenInstanceReflects() { SecurityObservationSettings defaults = SecurityObservationSettings.withDefaults() .shouldObserveAuthentications(false) .shouldObserveAuthorizations(false) .shouldObserveRequests(true) .build(); assertThat(defaults.shouldObserveRequests()).isTrue(); assertThat(defaults.shouldObserveAuthentications()).isFalse(); assertThat(defaults.shouldObserveAuthorizations()).isFalse(); } } ================================================ FILE: config/src/test/java/org/springframework/security/config/provisioning/UserDetailsManagerResourceFactoryBeanPropertiesResourceITests.java ================================================ /* * 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 org.springframework.security.config.provisioning; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.provisioning.UserDetailsManager; import org.springframework.security.util.InMemoryResource; import org.springframework.test.context.junit.jupiter.SpringExtension; import static org.assertj.core.api.Assertions.assertThat; /** * @author Rob Winch * @since 5.0 */ @ExtendWith(SpringExtension.class) public class UserDetailsManagerResourceFactoryBeanPropertiesResourceITests { @Autowired UserDetailsManager users; @Test public void loadUserByUsernameWhenUserFoundThenNotNull() { assertThat(this.users.loadUserByUsername("user")).isNotNull(); } @Configuration static class Config { @Bean UserDetailsManagerResourceFactoryBean userDetailsService() { return UserDetailsManagerResourceFactoryBean.fromResource(new InMemoryResource("user=password,ROLE_USER")); } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/provisioning/UserDetailsManagerResourceFactoryBeanPropertiesResourceLocationITests.java ================================================ /* * 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 org.springframework.security.config.provisioning; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.provisioning.UserDetailsManager; import org.springframework.test.context.junit.jupiter.SpringExtension; import static org.assertj.core.api.Assertions.assertThat; /** * @author Rob Winch * @since 5.0 */ @ExtendWith(SpringExtension.class) public class UserDetailsManagerResourceFactoryBeanPropertiesResourceLocationITests { @Autowired UserDetailsManager users; @Test public void loadUserByUsernameWhenUserFoundThenNotNull() { assertThat(this.users.loadUserByUsername("user")).isNotNull(); } @Configuration static class Config { @Bean UserDetailsManagerResourceFactoryBean userDetailsService() { return UserDetailsManagerResourceFactoryBean.fromResourceLocation("classpath:users.properties"); } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/provisioning/UserDetailsManagerResourceFactoryBeanStringITests.java ================================================ /* * 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 org.springframework.security.config.provisioning; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.provisioning.UserDetailsManager; import org.springframework.test.context.junit.jupiter.SpringExtension; import static org.assertj.core.api.Assertions.assertThat; /** * @author Rob Winch * @since 5.0 */ @ExtendWith(SpringExtension.class) public class UserDetailsManagerResourceFactoryBeanStringITests { @Autowired UserDetailsManager users; @Test public void loadUserByUsernameWhenUserFoundThenNotNull() { assertThat(this.users.loadUserByUsername("user")).isNotNull(); } @Configuration static class Config { @Bean UserDetailsManagerResourceFactoryBean userDetailsService() { return UserDetailsManagerResourceFactoryBean.fromString("user=password,ROLE_USER"); } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/saml2/RelyingPartyRegistrationsBeanDefinitionParserTests.java ================================================ /* * 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 org.springframework.security.config.saml2; import jakarta.servlet.http.HttpServletRequest; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.core.convert.converter.Converter; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.saml2.provider.service.registration.AssertingPartyMetadata; import org.springframework.security.saml2.provider.service.registration.InMemoryRelyingPartyRegistrationRepository; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository; import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding; import org.springframework.security.saml2.provider.service.registration.TestRelyingPartyRegistrations; import org.springframework.security.saml2.provider.service.web.authentication.OpenSaml5AuthenticationRequestResolver; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.verify; import static org.springframework.security.web.servlet.TestMockHttpServletRequests.get; /** * Tests for {@link RelyingPartyRegistrationsBeanDefinitionParser}. * * @author Marcus da Coregio */ @ExtendWith(SpringTestContextExtension.class) public class RelyingPartyRegistrationsBeanDefinitionParserTests { private static final String CONFIG_LOCATION_PREFIX = "classpath:org/springframework/security/config/saml2/RelyingPartyRegistrationsBeanDefinitionParserTests"; // @formatter:off private static final String METADATA_LOCATION_XML_CONFIG = "\n" + " \n" + " \n" + " \n" + " \n" + "\n" + "\n"; // @formatter:on // @formatter:off private static final String METADATA_LOCATION_OVERRIDE_PROPERTIES_XML_CONFIG = "\n" + " \n" + " \n" + " " + " \n" + "\n" + "\n"; // @formatter:on // @formatter:off private static final String METADATA_RESPONSE = "\n" + "\n" + " \n" + " \n" + " qIGOB+m2Kuq9Vp6F9qs/EFvFzuo6qEGukjICPyVAkjk=NgKak4k9LBAqbi8Za8ALUXW1l4npZ4+MOf8jhmpePDP3msbzjeKkkWFgxx+ILLJYwZzVWd3l028xm2l+SBOwoYRKJ670NgcdSdj6plBTGiZ5NXsXrX5M0zmgvAShREgjth/BKTUct5UVJOTqIxOPwBuCnj+Nn1+QUtY9ekPLrM0O2i+g1wckKaP6D7N+uVBwNgZGoOj5bZ082G7QXRX6Jo0925uKczAIKdIiBbMeKa/0phS2L97AkgQRGi2+j8V66TaDWuDSwd9hA2qzCwjsNui4DVLBwP0/LvgUdcu8g7JBIZ1yTddfByefOTVsU7UuZXkYEn4jU2ouk+u5klSo3Q==\n" + "MIIEEzCCAvugAwIBAgIJAIc1qzLrv+5nMA0GCSqGSIb3DQEBCwUAMIGfMQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ08xFDASBgNVBAcMC0Nhc3RsZSBSb2NrMRwwGgYDVQQKDBNTYW1sIFRlc3RpbmcgU2VydmVyMQswCQYDVQQLDAJJVDEgMB4GA1UEAwwXc2ltcGxlc2FtbHBocC5jZmFwcHMuaW8xIDAeBgkqhkiG9w0BCQEWEWZoYW5pa0BwaXZvdGFsLmlvMB4XDTE1MDIyMzIyNDUwM1oXDTI1MDIyMjIyNDUwM1owgZ8xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDTzEUMBIGA1UEBwwLQ2FzdGxlIFJvY2sxHDAaBgNVBAoME1NhbWwgVGVzdGluZyBTZXJ2ZXIxCzAJBgNVBAsMAklUMSAwHgYDVQQDDBdzaW1wbGVzYW1scGhwLmNmYXBwcy5pbzEgMB4GCSqGSIb3DQEJARYRZmhhbmlrQHBpdm90YWwuaW8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC4cn62E1xLqpN34PmbrKBbkOXFjzWgJ9b+pXuaRft6A339uuIQeoeH5qeSKRVTl32L0gdz2ZivLwZXW+cqvftVW1tvEHvzJFyxeTW3fCUeCQsebLnA2qRa07RkxTo6Nf244mWWRDodcoHEfDUSbxfTZ6IExSojSIU2RnD6WllYWFdD1GFpBJOmQB8rAc8wJIBdHFdQnX8Ttl7hZ6rtgqEYMzYVMuJ2F2r1HSU1zSAvwpdYP6rRGFRJEfdA9mm3WKfNLSc5cljz0X/TXy0vVlAV95l9qcfFzPmrkNIst9FZSwpvB49LyAVke04FQPPwLgVH4gphiJH3jvZ7I+J5lS8VAgMBAAGjUDBOMB0GA1UdDgQWBBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAfBgNVHSMEGDAWgBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAvMS4EQeP/ipV4jOG5lO6/tYCb/iJeAduOnRhkJk0DbX329lDLZhTTL/x/w/9muCVcvLrzEp6PN+VWfw5E5FWtZN0yhGtP9R+vZnrV+oc2zGD+no1/ySFOe3EiJCO5dehxKjYEmBRv5sU/LZFKZpozKN/BMEa6CqLuxbzb7ykxVr7EVFXwltPxzE9TmL9OACNNyF5eJHWMRMllarUvkcXlh4pux4ks9e6zV9DQBy2zds9f1I3qxg0eX6JnGrXi/ZiCT+lJgVe3ZFXiejiLAiKB04sXW3ti0LW3lx13Y1YlQ4/tlpgTgfIJxKV6nyPiLoK0nywbMd+vpAirDt2Oc+hk\n" + " \n" + " \n" + " \n" + " \n" + " MIIEEzCCAvugAwIBAgIJAIc1qzLrv+5nMA0GCSqGSIb3DQEBCwUAMIGfMQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ08xFDASBgNVBAcMC0Nhc3RsZSBSb2NrMRwwGgYDVQQKDBNTYW1sIFRlc3RpbmcgU2VydmVyMQswCQYDVQQLDAJJVDEgMB4GA1UEAwwXc2ltcGxlc2FtbHBocC5jZmFwcHMuaW8xIDAeBgkqhkiG9w0BCQEWEWZoYW5pa0BwaXZvdGFsLmlvMB4XDTE1MDIyMzIyNDUwM1oXDTI1MDIyMjIyNDUwM1owgZ8xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDTzEUMBIGA1UEBwwLQ2FzdGxlIFJvY2sxHDAaBgNVBAoME1NhbWwgVGVzdGluZyBTZXJ2ZXIxCzAJBgNVBAsMAklUMSAwHgYDVQQDDBdzaW1wbGVzYW1scGhwLmNmYXBwcy5pbzEgMB4GCSqGSIb3DQEJARYRZmhhbmlrQHBpdm90YWwuaW8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC4cn62E1xLqpN34PmbrKBbkOXFjzWgJ9b+pXuaRft6A339uuIQeoeH5qeSKRVTl32L0gdz2ZivLwZXW+cqvftVW1tvEHvzJFyxeTW3fCUeCQsebLnA2qRa07RkxTo6Nf244mWWRDodcoHEfDUSbxfTZ6IExSojSIU2RnD6WllYWFdD1GFpBJOmQB8rAc8wJIBdHFdQnX8Ttl7hZ6rtgqEYMzYVMuJ2F2r1HSU1zSAvwpdYP6rRGFRJEfdA9mm3WKfNLSc5cljz0X/TXy0vVlAV95l9qcfFzPmrkNIst9FZSwpvB49LyAVke04FQPPwLgVH4gphiJH3jvZ7I+J5lS8VAgMBAAGjUDBOMB0GA1UdDgQWBBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAfBgNVHSMEGDAWgBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAvMS4EQeP/ipV4jOG5lO6/tYCb/iJeAduOnRhkJk0DbX329lDLZhTTL/x/w/9muCVcvLrzEp6PN+VWfw5E5FWtZN0yhGtP9R+vZnrV+oc2zGD+no1/ySFOe3EiJCO5dehxKjYEmBRv5sU/LZFKZpozKN/BMEa6CqLuxbzb7ykxVr7EVFXwltPxzE9TmL9OACNNyF5eJHWMRMllarUvkcXlh4pux4ks9e6zV9DQBy2zds9f1I3qxg0eX6JnGrXi/ZiCT+lJgVe3ZFXiejiLAiKB04sXW3ti0LW3lx13Y1YlQ4/tlpgTgfIJxKV6nyPiLoK0nywbMd+vpAirDt2Oc+hk\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " MIIEEzCCAvugAwIBAgIJAIc1qzLrv+5nMA0GCSqGSIb3DQEBCwUAMIGfMQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ08xFDASBgNVBAcMC0Nhc3RsZSBSb2NrMRwwGgYDVQQKDBNTYW1sIFRlc3RpbmcgU2VydmVyMQswCQYDVQQLDAJJVDEgMB4GA1UEAwwXc2ltcGxlc2FtbHBocC5jZmFwcHMuaW8xIDAeBgkqhkiG9w0BCQEWEWZoYW5pa0BwaXZvdGFsLmlvMB4XDTE1MDIyMzIyNDUwM1oXDTI1MDIyMjIyNDUwM1owgZ8xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDTzEUMBIGA1UEBwwLQ2FzdGxlIFJvY2sxHDAaBgNVBAoME1NhbWwgVGVzdGluZyBTZXJ2ZXIxCzAJBgNVBAsMAklUMSAwHgYDVQQDDBdzaW1wbGVzYW1scGhwLmNmYXBwcy5pbzEgMB4GCSqGSIb3DQEJARYRZmhhbmlrQHBpdm90YWwuaW8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC4cn62E1xLqpN34PmbrKBbkOXFjzWgJ9b+pXuaRft6A339uuIQeoeH5qeSKRVTl32L0gdz2ZivLwZXW+cqvftVW1tvEHvzJFyxeTW3fCUeCQsebLnA2qRa07RkxTo6Nf244mWWRDodcoHEfDUSbxfTZ6IExSojSIU2RnD6WllYWFdD1GFpBJOmQB8rAc8wJIBdHFdQnX8Ttl7hZ6rtgqEYMzYVMuJ2F2r1HSU1zSAvwpdYP6rRGFRJEfdA9mm3WKfNLSc5cljz0X/TXy0vVlAV95l9qcfFzPmrkNIst9FZSwpvB49LyAVke04FQPPwLgVH4gphiJH3jvZ7I+J5lS8VAgMBAAGjUDBOMB0GA1UdDgQWBBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAfBgNVHSMEGDAWgBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAvMS4EQeP/ipV4jOG5lO6/tYCb/iJeAduOnRhkJk0DbX329lDLZhTTL/x/w/9muCVcvLrzEp6PN+VWfw5E5FWtZN0yhGtP9R+vZnrV+oc2zGD+no1/ySFOe3EiJCO5dehxKjYEmBRv5sU/LZFKZpozKN/BMEa6CqLuxbzb7ykxVr7EVFXwltPxzE9TmL9OACNNyF5eJHWMRMllarUvkcXlh4pux4ks9e6zV9DQBy2zds9f1I3qxg0eX6JnGrXi/ZiCT+lJgVe3ZFXiejiLAiKB04sXW3ti0LW3lx13Y1YlQ4/tlpgTgfIJxKV6nyPiLoK0nywbMd+vpAirDt2Oc+hk\n" + " \n" + " \n" + " \n" + " \n" + " urn:oasis:names:tc:SAML:2.0:nameid-format:transient\n" + " \n" + " \n" + " \n" + " John\n" + " Doe\n" + " john@doe.com\n" + " \n" + "\n"; // @formatter:on @Autowired @Qualifier("registrations") private RelyingPartyRegistrationRepository relyingPartyRegistrationRepository; public final SpringTestContext spring = new SpringTestContext(this); private MockWebServer server; @AfterEach void cleanup() throws Exception { if (this.server != null) { this.server.shutdown(); } } @Test public void parseWhenMetadataLocationConfiguredThenRequestMetadataFromLocation() throws Exception { this.server = new MockWebServer(); this.server.start(); String serverUrl = this.server.url("/").toString(); this.server.enqueue(xmlResponse(METADATA_RESPONSE)); String metadataConfig = METADATA_LOCATION_XML_CONFIG.replace("${metadata-location}", serverUrl); this.spring.context(metadataConfig).autowire(); assertThat(this.relyingPartyRegistrationRepository) .isInstanceOf(InMemoryRelyingPartyRegistrationRepository.class); RelyingPartyRegistration relyingPartyRegistration = this.relyingPartyRegistrationRepository .findByRegistrationId("one"); AssertingPartyMetadata assertingPartyMetadata = relyingPartyRegistration.getAssertingPartyMetadata(); assertThat(relyingPartyRegistration).isNotNull(); assertThat(relyingPartyRegistration.getRegistrationId()).isEqualTo("one"); assertThat(relyingPartyRegistration.getEntityId()) .isEqualTo("{baseUrl}/saml2/service-provider-metadata/{registrationId}"); assertThat(relyingPartyRegistration.getAssertionConsumerServiceLocation()) .isEqualTo("{baseUrl}/login/saml2/sso/{registrationId}"); assertThat(relyingPartyRegistration.getAssertionConsumerServiceBinding()).isEqualTo(Saml2MessageBinding.POST); assertThat(assertingPartyMetadata.getEntityId()) .isEqualTo("https://simplesaml-for-spring-saml.apps.pcfone.io/saml2/idp/metadata.php"); assertThat(assertingPartyMetadata.getWantAuthnRequestsSigned()).isFalse(); assertThat(assertingPartyMetadata.getVerificationX509Credentials()).hasSize(1); assertThat(assertingPartyMetadata.getEncryptionX509Credentials()).hasSize(1); assertThat(assertingPartyMetadata.getSingleSignOnServiceLocation()) .isEqualTo("https://simplesaml-for-spring-saml.apps.pcfone.io/saml2/idp/SSOService.php"); assertThat(assertingPartyMetadata.getSingleSignOnServiceBinding()).isEqualTo(Saml2MessageBinding.REDIRECT); assertThat(assertingPartyMetadata.getSigningAlgorithms()) .containsExactly("http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"); } @Test public void parseWhenMetadataLocationConfiguredAndRegistrationHasPropertiesThenDoNotOverrideSpecifiedProperties() throws Exception { this.server = new MockWebServer(); this.server.start(); String serverUrl = this.server.url("/").toString(); this.server.enqueue(xmlResponse(METADATA_RESPONSE)); String metadataConfig = METADATA_LOCATION_OVERRIDE_PROPERTIES_XML_CONFIG.replace("${metadata-location}", serverUrl); this.spring.context(metadataConfig).autowire(); assertThat(this.relyingPartyRegistrationRepository) .isInstanceOf(InMemoryRelyingPartyRegistrationRepository.class); RelyingPartyRegistration relyingPartyRegistration = this.relyingPartyRegistrationRepository .findByRegistrationId("one"); AssertingPartyMetadata assertingPartyMetadata = relyingPartyRegistration.getAssertingPartyMetadata(); assertThat(relyingPartyRegistration).isNotNull(); assertThat(relyingPartyRegistration.getRegistrationId()).isEqualTo("one"); assertThat(relyingPartyRegistration.getEntityId()).isEqualTo("https://rp.example.org"); assertThat(relyingPartyRegistration.getAssertionConsumerServiceLocation()) .isEqualTo("https://rp.example.org/location"); assertThat(relyingPartyRegistration.getAssertionConsumerServiceBinding()) .isEqualTo(Saml2MessageBinding.REDIRECT); assertThat(assertingPartyMetadata.getEntityId()) .isEqualTo("https://simplesaml-for-spring-saml.apps.pcfone.io/saml2/idp/metadata.php"); assertThat(assertingPartyMetadata.getWantAuthnRequestsSigned()).isFalse(); assertThat(assertingPartyMetadata.getVerificationX509Credentials()).hasSize(1); assertThat(assertingPartyMetadata.getEncryptionX509Credentials()).hasSize(1); assertThat(assertingPartyMetadata.getSingleSignOnServiceLocation()) .isEqualTo("https://simplesaml-for-spring-saml.apps.pcfone.io/saml2/idp/SSOService.php"); assertThat(assertingPartyMetadata.getSingleSignOnServiceBinding()).isEqualTo(Saml2MessageBinding.REDIRECT); assertThat(assertingPartyMetadata.getSigningAlgorithms()) .containsExactly("http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"); } @Test public void parseWhenSingleRelyingPartyRegistrationThenAvailableInRepository() { this.spring.configLocations(xml("SingleRegistration")).autowire(); assertThat(this.relyingPartyRegistrationRepository) .isInstanceOf(InMemoryRelyingPartyRegistrationRepository.class); RelyingPartyRegistration relyingPartyRegistration = this.relyingPartyRegistrationRepository .findByRegistrationId("one"); AssertingPartyMetadata assertingPartyMetadata = relyingPartyRegistration.getAssertingPartyMetadata(); assertThat(relyingPartyRegistration).isNotNull(); assertThat(relyingPartyRegistration.getRegistrationId()).isEqualTo("one"); assertThat(relyingPartyRegistration.getEntityId()) .isEqualTo("{baseUrl}/saml2/service-provider-metadata/{registrationId}"); assertThat(relyingPartyRegistration.getAssertionConsumerServiceLocation()) .isEqualTo("{baseUrl}/login/saml2/sso/{registrationId}"); assertThat(relyingPartyRegistration.getAssertionConsumerServiceBinding()) .isEqualTo(Saml2MessageBinding.REDIRECT); assertThat(assertingPartyMetadata.getEntityId()).isEqualTo("https://accounts.google.com/o/saml2/idp/entity-id"); assertThat(assertingPartyMetadata.getWantAuthnRequestsSigned()).isTrue(); assertThat(assertingPartyMetadata.getSingleSignOnServiceLocation()) .isEqualTo("https://accounts.google.com/o/saml2/idp/sso-url"); assertThat(assertingPartyMetadata.getSingleSignOnServiceBinding()).isEqualTo(Saml2MessageBinding.POST); assertThat(assertingPartyMetadata.getVerificationX509Credentials()).hasSize(1); assertThat(assertingPartyMetadata.getEncryptionX509Credentials()).hasSize(1); assertThat(assertingPartyMetadata.getSigningAlgorithms()) .containsExactly("http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"); } @Test public void parseWhenMultiRelyingPartyRegistrationThenAvailableInRepository() { this.spring.configLocations(xml("MultiRegistration")).autowire(); assertThat(this.relyingPartyRegistrationRepository) .isInstanceOf(InMemoryRelyingPartyRegistrationRepository.class); RelyingPartyRegistration one = this.relyingPartyRegistrationRepository.findByRegistrationId("one"); AssertingPartyMetadata google = one.getAssertingPartyMetadata(); RelyingPartyRegistration two = this.relyingPartyRegistrationRepository.findByRegistrationId("two"); AssertingPartyMetadata simpleSaml = two.getAssertingPartyMetadata(); assertThat(one).isNotNull(); assertThat(one.getRegistrationId()).isEqualTo("one"); assertThat(one.getEntityId()).isEqualTo("{baseUrl}/saml2/service-provider-metadata/{registrationId}"); assertThat(one.getAssertionConsumerServiceLocation()).isEqualTo("{baseUrl}/login/saml2/sso/{registrationId}"); assertThat(one.getAssertionConsumerServiceBinding()).isEqualTo(Saml2MessageBinding.REDIRECT); assertThat(google.getEntityId()).isEqualTo("https://accounts.google.com/o/saml2/idp/entity-id"); assertThat(google.getWantAuthnRequestsSigned()).isTrue(); assertThat(google.getSingleSignOnServiceLocation()) .isEqualTo("https://accounts.google.com/o/saml2/idp/sso-url"); assertThat(google.getSingleSignOnServiceBinding()).isEqualTo(Saml2MessageBinding.POST); assertThat(google.getVerificationX509Credentials()).hasSize(1); assertThat(google.getEncryptionX509Credentials()).hasSize(1); assertThat(google.getSigningAlgorithms()).containsExactly("http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"); assertThat(two).isNotNull(); assertThat(two.getRegistrationId()).isEqualTo("two"); assertThat(two.getEntityId()).isEqualTo("{baseUrl}/saml2/service-provider-metadata/{registrationId}"); assertThat(two.getAssertionConsumerServiceLocation()).isEqualTo("{baseUrl}/login/saml2/sso/{registrationId}"); assertThat(two.getAssertionConsumerServiceBinding()).isEqualTo(Saml2MessageBinding.POST); assertThat(simpleSaml.getEntityId()) .isEqualTo("https://simplesaml-for-spring-saml.apps.pcfone.io/saml2/idp/metadata.php"); assertThat(simpleSaml.getWantAuthnRequestsSigned()).isFalse(); assertThat(simpleSaml.getSingleSignOnServiceLocation()) .isEqualTo("https://simplesaml-for-spring-saml.apps.pcfone.io/saml2/idp/SSOService.php"); assertThat(simpleSaml.getSingleSignOnServiceBinding()).isEqualTo(Saml2MessageBinding.POST); assertThat(simpleSaml.getVerificationX509Credentials()).hasSize(1); assertThat(simpleSaml.getEncryptionX509Credentials()).hasSize(1); assertThat(simpleSaml.getSigningAlgorithms()).containsExactly( "http://www.w3.org/2001/04/xmldsig-more#rsa-sha224", "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256", "http://www.w3.org/2001/04/xmldsig-more#rsa-sha384"); } @Test public void parseWhenRelayStateResolverThenUses() { this.spring.configLocations(xml("RelayStateResolver")).autowire(); Converter relayStateResolver = this.spring.getContext().getBean(Converter.class); OpenSaml5AuthenticationRequestResolver authenticationRequestResolver = this.spring.getContext() .getBean(OpenSaml5AuthenticationRequestResolver.class); MockHttpServletRequest request = get("/saml2/authenticate/one").build(); authenticationRequestResolver.resolve(request); verify(relayStateResolver).convert(request); } @Test public void parseWhenPlaceholdersThenResolves() throws Exception { RelyingPartyRegistration sample = TestRelyingPartyRegistrations.relyingPartyRegistration().build(); System.setProperty("registration-id", sample.getRegistrationId()); System.setProperty("entity-id", sample.getEntityId()); System.setProperty("acs-location", sample.getAssertionConsumerServiceLocation()); System.setProperty("slo-location", sample.getSingleLogoutServiceLocation()); System.setProperty("slo-response-location", sample.getSingleLogoutServiceResponseLocation()); try (MockWebServer web = new MockWebServer()) { web.start(); String serverUrl = web.url("/metadata").toString(); web.enqueue(xmlResponse(METADATA_RESPONSE)); System.setProperty("metadata-location", serverUrl); this.spring.configLocations(xml("PlaceholderRegistration")).autowire(); } RelyingPartyRegistration registration = this.relyingPartyRegistrationRepository .findByRegistrationId(sample.getRegistrationId()); assertThat(registration.getRegistrationId()).isEqualTo(sample.getRegistrationId()); assertThat(registration.getEntityId()).isEqualTo(sample.getEntityId()); assertThat(registration.getAssertionConsumerServiceLocation()) .isEqualTo(sample.getAssertionConsumerServiceLocation()); assertThat(registration.getSingleLogoutServiceLocation()).isEqualTo(sample.getSingleLogoutServiceLocation()); assertThat(registration.getSingleLogoutServiceResponseLocation()) .isEqualTo(sample.getSingleLogoutServiceResponseLocation()); } private static MockResponse xmlResponse(String xml) { return new MockResponse().setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE).setBody(xml); } private static String xml(String configName) { return CONFIG_LOCATION_PREFIX + "-" + configName + ".xml"; } } ================================================ FILE: config/src/test/java/org/springframework/security/config/test/SpringTestContext.java ================================================ /* * 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 org.springframework.security.config.test; import java.io.Closeable; import java.util.ArrayList; import java.util.List; import java.util.function.Consumer; import jakarta.servlet.Filter; import jakarta.servlet.FilterChain; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor; import org.springframework.mock.web.MockServletConfig; import org.springframework.security.config.BeanIds; import org.springframework.security.config.util.InMemoryXmlWebApplicationContext; import org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers; import org.springframework.security.test.web.reactive.server.WebTestClientBuilder; import org.springframework.security.web.servlet.MockServletContext; import org.springframework.test.context.web.GenericXmlWebContextLoader; import org.springframework.test.web.reactive.server.WebTestClient; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.request.RequestPostProcessor; import org.springframework.test.web.servlet.setup.ConfigurableMockMvcBuilder; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.test.web.servlet.setup.MockMvcConfigurer; import org.springframework.web.context.ConfigurableWebApplicationContext; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; import org.springframework.web.context.support.XmlWebApplicationContext; import org.springframework.web.filter.OncePerRequestFilter; import org.springframework.web.server.WebFilter; import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; /** * @author Rob Winch * @since 5.0 */ public class SpringTestContext implements Closeable { private Object test; private ConfigurableWebApplicationContext context; private List filters = new ArrayList<>(); private DeferAddFilter deferAddFilter = new DeferAddFilter(); private List> postProcessors = new ArrayList<>(); public SpringTestContext(Object test) { setTest(test); } public void setTest(Object test) { this.test = test; } @Override public void close() { try { this.context.close(); } catch (Exception ex) { } } public SpringTestContext context(ConfigurableWebApplicationContext context) { this.context = context; return this; } public SpringTestContext register(Class... classes) { AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext(); applicationContext.register(classes); this.context = applicationContext; return this; } public SpringTestContext testConfigLocations(String... configLocations) { GenericXmlWebContextLoader loader = new GenericXmlWebContextLoader(); String[] locations = loader.processLocations(this.test.getClass(), configLocations); return configLocations(locations); } public SpringTestContext configLocations(String... configLocations) { XmlWebApplicationContext context = new XmlWebApplicationContext(); context.setConfigLocations(configLocations); this.context = context; return this; } public SpringTestContext context(String configuration) { InMemoryXmlWebApplicationContext context = new InMemoryXmlWebApplicationContext(configuration); this.context = context; return this; } public SpringTestContext postProcessor(Consumer contextConsumer) { this.postProcessors.add(contextConsumer); return this; } public SpringTestContext mockMvcAfterSpringSecurityOk() { this.deferAddFilter.addFilter(new OncePerRequestFilter() { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) { response.setStatus(HttpServletResponse.SC_OK); } }); return this; } public SpringTestContext addFilter(Filter filter) { this.filters.add(filter); return this; } public ConfigurableWebApplicationContext getContext() { if (!this.context.isRunning()) { this.context.setServletContext(MockServletContext.mvc()); this.context.setServletConfig(new MockServletConfig()); this.context.refresh(); } return this.context; } public void autowire() { this.context.setServletContext(MockServletContext.mvc()); this.context.setServletConfig(new MockServletConfig()); for (Consumer postProcessor : this.postProcessors) { postProcessor.accept(this.context); } this.context.refresh(); if (this.context.containsBean(BeanIds.SPRING_SECURITY_FILTER_CHAIN)) { // @formatter:off MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context) .addFilters(this.filters.toArray(new Filter[0])) .apply(springSecurity()) .apply(this.deferAddFilter) .build(); // @formatter:on this.context.getBeanFactory().registerResolvableDependency(MockMvc.class, mockMvc); } String webFluxSecurityBean = "org.springframework.security.config.annotation.web.reactive.WebFluxSecurityConfiguration.WebFilterChainFilter"; if (this.context.containsBean(webFluxSecurityBean)) { WebFilter springSecurityFilter = this.context.getBean(webFluxSecurityBean, WebFilter.class); // @formatter:off WebTestClient webTest = WebTestClient .bindToController(new WebTestClientBuilder.Http200RestController()) .webFilter(springSecurityFilter) .apply(SecurityMockServerConfigurers.springSecurity()) .build(); // @formatter:on this.context.getBeanFactory().registerResolvableDependency(WebTestClient.class, webTest); } AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); bpp.setBeanFactory(this.context.getBeanFactory()); bpp.processInjection(this.test); } private static class DeferAddFilter implements MockMvcConfigurer { private List filters = new ArrayList<>(); void addFilter(Filter filter) { this.filters.add(filter); } @Override public RequestPostProcessor beforeMockMvcCreated(ConfigurableMockMvcBuilder builder, WebApplicationContext context) { builder.addFilters(this.filters.toArray(new Filter[0])); return null; } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/test/SpringTestContextExtension.java ================================================ /* * 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 org.springframework.security.config.test; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.List; import org.junit.jupiter.api.extension.AfterEachCallback; import org.junit.jupiter.api.extension.BeforeEachCallback; import org.junit.jupiter.api.extension.ExtensionContext; import org.springframework.security.test.context.TestSecurityContextHolder; public class SpringTestContextExtension implements BeforeEachCallback, AfterEachCallback { @Override public void afterEach(ExtensionContext context) throws Exception { TestSecurityContextHolder.clearContext(); getContexts(context.getRequiredTestInstance()).forEach(SpringTestContext::close); } @Override public void beforeEach(ExtensionContext context) throws Exception { Object testInstance = context.getRequiredTestInstance(); getContexts(testInstance).forEach((springTestContext) -> springTestContext.setTest(testInstance)); } private static List getContexts(Object test) throws IllegalAccessException { Field[] declaredFields = test.getClass().getDeclaredFields(); List result = new ArrayList<>(); for (Field field : declaredFields) { if (SpringTestContext.class.isAssignableFrom(field.getType())) { result.add((SpringTestContext) field.get(test)); } } return result; } } ================================================ FILE: config/src/test/java/org/springframework/security/config/test/SpringTestParentApplicationContextExecutionListener.java ================================================ /* * 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 org.springframework.security.config.test; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.List; import org.springframework.context.ApplicationContext; import org.springframework.test.context.TestContext; import org.springframework.test.context.TestExecutionListener; public class SpringTestParentApplicationContextExecutionListener implements TestExecutionListener { @Override public void beforeTestMethod(TestContext testContext) throws Exception { ApplicationContext parent = testContext.getApplicationContext(); Object testInstance = testContext.getTestInstance(); getContexts(testInstance).forEach((springTestContext) -> springTestContext .postProcessor((applicationContext) -> applicationContext.setParent(parent))); } private static List getContexts(Object test) throws IllegalAccessException { Field[] declaredFields = test.getClass().getDeclaredFields(); List result = new ArrayList<>(); for (Field field : declaredFields) { if (SpringTestContext.class.isAssignableFrom(field.getType())) { result.add((SpringTestContext) field.get(test)); } } return result; } } ================================================ FILE: config/src/test/java/org/springframework/security/config/users/AuthenticationTestConfiguration.java ================================================ /* * 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 org.springframework.security.config.users; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.core.userdetails.PasswordEncodedUser; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.provisioning.InMemoryUserDetailsManager; /** * @author Rob Winch * @since 5.0 */ @Configuration public class AuthenticationTestConfiguration { @Bean public static UserDetailsService userDetailsService() { return new InMemoryUserDetailsManager(PasswordEncodedUser.user(), PasswordEncodedUser.admin()); } } ================================================ FILE: config/src/test/java/org/springframework/security/config/users/ReactiveAuthenticationTestConfiguration.java ================================================ /* * 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 org.springframework.security.config.users; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.core.userdetails.MapReactiveUserDetailsService; import org.springframework.security.core.userdetails.PasswordEncodedUser; import org.springframework.security.core.userdetails.ReactiveUserDetailsService; /** * @author Rob Winch * @since 5.0 */ @Configuration public class ReactiveAuthenticationTestConfiguration { @Bean public static ReactiveUserDetailsService userDetailsService() { return new MapReactiveUserDetailsService(PasswordEncodedUser.user(), PasswordEncodedUser.admin()); } } ================================================ FILE: config/src/test/java/org/springframework/security/config/util/InMemoryXmlApplicationContext.java ================================================ /* * 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 org.springframework.security.config.util; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.support.AbstractXmlApplicationContext; import org.springframework.core.io.Resource; import org.springframework.security.util.InMemoryResource; /** * @author Luke Taylor * @author Eddú Meléndez * @author Emil Sierżęga */ public class InMemoryXmlApplicationContext extends AbstractXmlApplicationContext { static final String BEANS_OPENING = "\n" + xml + BEANS_CLOSE; this.inMemoryXml = new InMemoryResource(fullXml); setAllowBeanDefinitionOverriding(true); setParent(parent); refresh(); } @Override protected DefaultListableBeanFactory createBeanFactory() { return new DefaultListableBeanFactory(getInternalParentBeanFactory()) { @Override protected boolean allowAliasOverriding() { return true; } }; } @Override protected Resource[] getConfigResources() { return new Resource[] { this.inMemoryXml }; } } ================================================ FILE: config/src/test/java/org/springframework/security/config/util/InMemoryXmlWebApplicationContext.java ================================================ /* * 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 org.springframework.security.config.util; import org.springframework.beans.BeansException; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; import org.springframework.context.ApplicationContext; import org.springframework.core.io.Resource; import org.springframework.security.util.InMemoryResource; import org.springframework.web.context.support.AbstractRefreshableWebApplicationContext; /** * @author Joe Grandja */ public class InMemoryXmlWebApplicationContext extends AbstractRefreshableWebApplicationContext { private Resource inMemoryXml; public InMemoryXmlWebApplicationContext(String xml) { this(xml, InMemoryXmlApplicationContext.SPRING_SECURITY_VERSION, null); } public InMemoryXmlWebApplicationContext(String xml, ApplicationContext parent) { this(xml, InMemoryXmlApplicationContext.SPRING_SECURITY_VERSION, parent); } public InMemoryXmlWebApplicationContext(String xml, String secVersion, ApplicationContext parent) { String fullXml = InMemoryXmlApplicationContext.BEANS_OPENING + secVersion + ".xsd'>\n" + xml + InMemoryXmlApplicationContext.BEANS_CLOSE; this.inMemoryXml = new InMemoryResource(fullXml); setAllowBeanDefinitionOverriding(true); setParent(parent); } @Override protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException { XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory); reader.loadBeanDefinitions(new Resource[] { this.inMemoryXml }); } } ================================================ FILE: config/src/test/java/org/springframework/security/config/util/SpringSecurityVersions.java ================================================ /* * 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 org.springframework.security.config.util; import java.io.IOException; import java.io.InputStream; import java.util.Properties; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.springframework.security.core.SpringSecurityCoreVersion; /** * For computing different Spring Security versions */ public final class SpringSecurityVersions { static final Pattern SCHEMA_VERSION_PATTERN = Pattern.compile("\\d+\\.\\d+(\\.\\d+)?"); public static String getCurrentXsdVersionFromSpringSchemas() { Properties properties = new Properties(); try (InputStream is = SpringSecurityCoreVersion.class.getClassLoader() .getResourceAsStream("META-INF/spring.schemas")) { properties.load(is); } catch (IOException ex) { throw new RuntimeException("Could not read 'META-INF/spring.schemas'", ex); } String inPackageLocation = properties .getProperty("https://www.springframework.org/schema/security/spring-security.xsd"); Matcher matcher = SCHEMA_VERSION_PATTERN.matcher(inPackageLocation); if (matcher.find()) { return matcher.group(0); } throw new IllegalStateException("Failed to find version"); } private SpringSecurityVersions() { } } ================================================ FILE: config/src/test/java/org/springframework/security/config/web/PathPatternRequestMatcherBuilderFactoryBeanTests.java ================================================ /* * 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 org.springframework.security.config.web; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.context.support.GenericApplicationContext; import org.springframework.security.web.servlet.TestMockHttpServletRequests; import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher; import org.springframework.web.util.pattern.PathPatternParser; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @ExtendWith(MockitoExtension.class) class PathPatternRequestMatcherBuilderFactoryBeanTests { GenericApplicationContext context; @BeforeEach void setUp() { this.context = new GenericApplicationContext(); } @Test void getObjectWhenDefaultsThenBuilder() throws Exception { factoryBean().getObject(); } @Test void getObjectWhenMvcPatternParserThenUses() throws Exception { PathPatternParser mvc = registerMvcPatternParser(); PathPatternRequestMatcher.Builder builder = factoryBean().getObject(); builder.matcher("/path/**"); verify(mvc).parse("/path/**"); } @Test void getObjectWhenPathPatternParserThenUses() throws Exception { PathPatternParser parser = mock(PathPatternParser.class); PathPatternRequestMatcher.Builder builder = factoryBean(parser).getObject(); builder.matcher("/path/**"); verify(parser).parse("/path/**"); } @Test void getObjectWhenMvcAndPathPatternParserConflictThenIllegalArgument() { registerMvcPatternParser(); PathPatternParser parser = mock(PathPatternParser.class); assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() -> factoryBean(parser).getObject()); } @Test void getObjectWhenMvcAndPathPatternParserAgreeThenUses() throws Exception { PathPatternParser mvc = registerMvcPatternParser(); PathPatternRequestMatcher.Builder builder = factoryBean(mvc).getObject(); builder.matcher("/path/**"); verify(mvc).parse("/path/**"); } @Test void getObjectWhenBasePathThenUses() throws Exception { PathPatternRequestMatcherBuilderFactoryBean factoryBean = new PathPatternRequestMatcherBuilderFactoryBean(); factoryBean.setApplicationContext(this.context); factoryBean.setBasePath("/mvc"); PathPatternRequestMatcher.Builder builder = factoryBean.getObject(); PathPatternRequestMatcher matcher = builder.matcher("/path/**"); assertThat(matcher.matches(TestMockHttpServletRequests.get("/mvc/path/123").build())).isTrue(); assertThat(matcher.matches(TestMockHttpServletRequests.get("/path/123").build())).isFalse(); } PathPatternRequestMatcherBuilderFactoryBean factoryBean() { PathPatternRequestMatcherBuilderFactoryBean factoryBean = new PathPatternRequestMatcherBuilderFactoryBean(); factoryBean.setApplicationContext(this.context); return factoryBean; } PathPatternRequestMatcherBuilderFactoryBean factoryBean(PathPatternParser parser) { PathPatternRequestMatcherBuilderFactoryBean factoryBean = new PathPatternRequestMatcherBuilderFactoryBean( parser); factoryBean.setApplicationContext(this.context); return factoryBean; } PathPatternParser registerMvcPatternParser() { PathPatternParser mvc = mock(PathPatternParser.class); this.context.registerBean(PathPatternRequestMatcherBuilderFactoryBean.MVC_PATTERN_PARSER_BEAN_NAME, PathPatternParser.class, () -> mvc); this.context.refresh(); return mvc; } } ================================================ FILE: config/src/test/java/org/springframework/security/config/web/server/AuthorizeExchangeSpecTests.java ================================================ /* * 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 org.springframework.security.config.web.server; import org.junit.jupiter.api.Test; import org.springframework.http.HttpMethod; import org.springframework.security.config.annotation.web.reactive.ServerHttpSecurityConfigurationBuilder; import org.springframework.security.test.web.reactive.server.WebTestClientBuilder; import org.springframework.test.web.reactive.server.WebTestClient; import static org.assertj.core.api.Assertions.assertThatIllegalStateException; /** * @author Rob Winch * @since 5.0 */ public class AuthorizeExchangeSpecTests { ServerHttpSecurity http = ServerHttpSecurityConfigurationBuilder.httpWithDefaultAuthentication(); @Test public void antMatchersWhenMethodAndPatternsThenDiscriminatesByMethod() { this.http.csrf((csrf) -> csrf.disable()) .authorizeExchange((authorize) -> authorize.pathMatchers(HttpMethod.POST, "/a", "/b") .denyAll() .anyExchange() .permitAll()); WebTestClient client = buildClient(); // @formatter:off client.get() .uri("/a") .exchange() .expectStatus().isOk(); client.get() .uri("/b") .exchange() .expectStatus().isOk(); client.post() .uri("/a") .exchange() .expectStatus().isUnauthorized(); client.post() .uri("/b") .exchange() .expectStatus().isUnauthorized(); // @formatter:on } @Test public void antMatchersWhenPatternsThenAnyMethod() { this.http.csrf((csrf) -> csrf.disable()) .authorizeExchange((authorize) -> authorize.pathMatchers("/a", "/b").denyAll().anyExchange().permitAll()); WebTestClient client = buildClient(); // @formatter:off client.get() .uri("/a") .exchange() .expectStatus().isUnauthorized(); client.get() .uri("/b") .exchange() .expectStatus().isUnauthorized(); client.post() .uri("/a") .exchange() .expectStatus().isUnauthorized(); client.post() .uri("/b") .exchange() .expectStatus().isUnauthorized(); // @formatter:on } @Test public void antMatchersWhenPatternsInLambdaThenAnyMethod() { this.http.csrf(ServerHttpSecurity.CsrfSpec::disable) .authorizeExchange((authorize) -> authorize.pathMatchers("/a", "/b").denyAll().anyExchange().permitAll()); WebTestClient client = buildClient(); // @formatter:off client.get() .uri("/a") .exchange() .expectStatus().isUnauthorized(); client.get() .uri("/b") .exchange() .expectStatus().isUnauthorized(); client.post() .uri("/a") .exchange() .expectStatus().isUnauthorized(); client.post() .uri("/b") .exchange() .expectStatus().isUnauthorized(); // @formatter:on } @Test public void antMatchersWhenNoAccessAndAnotherMatcherThenThrowsException() { this.http.authorizeExchange((authorize) -> authorize.pathMatchers("/incomplete")); assertThatIllegalStateException() .isThrownBy(() -> this.http.authorizeExchange((authorize) -> authorize.pathMatchers("/throws-exception"))); } @Test public void anyExchangeWhenFollowedByMatcherThenThrowsException() { assertThatIllegalStateException().isThrownBy(() -> // @formatter:off this.http.authorizeExchange((authorize) -> authorize .anyExchange().denyAll() .pathMatchers("/never-reached")) // @formatter:on ); } @Test public void buildWhenMatcherDefinedWithNoAccessThenThrowsException() { this.http.authorizeExchange((authorize) -> authorize.pathMatchers("/incomplete")); assertThatIllegalStateException().isThrownBy(this.http::build); } @Test public void buildWhenMatcherDefinedWithNoAccessInLambdaThenThrowsException() { this.http.authorizeExchange((authorize) -> authorize.pathMatchers("/incomplete")); assertThatIllegalStateException().isThrownBy(this.http::build); } private WebTestClient buildClient() { return WebTestClientBuilder.bindToWebFilters(this.http.build()).build(); } } ================================================ FILE: config/src/test/java/org/springframework/security/config/web/server/CorsSpecTests.java ================================================ /* * 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 org.springframework.security.config.web.server; import java.util.Arrays; import java.util.HashSet; import java.util.Set; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.context.ApplicationContext; import org.springframework.context.support.GenericApplicationContext; import org.springframework.http.HttpHeaders; import org.springframework.security.test.web.reactive.server.WebTestClientBuilder; import org.springframework.test.web.reactive.server.FluxExchangeResult; import org.springframework.test.web.reactive.server.WebTestClient; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.reactive.CorsConfigurationSource; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; /** * @author Rob Winch * @since 5.0 */ @ExtendWith(MockitoExtension.class) public class CorsSpecTests { @Mock private CorsConfigurationSource source; private ApplicationContext context; ServerHttpSecurity http; HttpHeaders expectedHeaders = new HttpHeaders(); Set headerNamesNotPresent = new HashSet<>(); @BeforeEach public void setup() { this.context = new GenericApplicationContext(); ((GenericApplicationContext) this.context).refresh(); this.http = new TestingServerHttpSecurity().applicationContext(this.context); } private void givenGetCorsConfigurationWillReturnWildcard() { CorsConfiguration value = new CorsConfiguration(); value.setAllowedOrigins(Arrays.asList("*")); given(this.source.getCorsConfiguration(any())).willReturn(value); } @Test public void corsWhenEnabledThenAccessControlAllowOriginAndSecurityHeaders() { givenGetCorsConfigurationWillReturnWildcard(); this.http.cors((cors) -> cors.configurationSource(this.source)); this.expectedHeaders.set("Access-Control-Allow-Origin", "*"); this.expectedHeaders.set("X-Frame-Options", "DENY"); assertHeaders(); } @Test public void corsWhenEnabledInLambdaThenAccessControlAllowOriginAndSecurityHeaders() { givenGetCorsConfigurationWillReturnWildcard(); this.http.cors((cors) -> cors.configurationSource(this.source)); this.expectedHeaders.set("Access-Control-Allow-Origin", "*"); this.expectedHeaders.set("X-Frame-Options", "DENY"); assertHeaders(); } @Test public void corsWhenCorsConfigurationSourceBeanThenAccessControlAllowOriginAndSecurityHeaders() { givenGetCorsConfigurationWillReturnWildcard(); ((GenericApplicationContext) this.context).registerBean(CorsConfigurationSource.class, () -> this.source); this.expectedHeaders.set("Access-Control-Allow-Origin", "*"); this.expectedHeaders.set("X-Frame-Options", "DENY"); assertHeaders(); } @Test public void corsWhenNoConfigurationSourceThenNoCorsHeaders() { this.headerNamesNotPresent.add("Access-Control-Allow-Origin"); assertHeaders(); } private void assertHeaders() { WebTestClient client = buildClient(); // @formatter:off FluxExchangeResult response = client.get() .uri("https://example.com/") .headers((h) -> h.setOrigin("https://origin.example.com")) .exchange() .returnResult(String.class); // @formatter:on HttpHeaders responseHeaders = response.getResponseHeaders(); if (!this.expectedHeaders.isEmpty()) { this.expectedHeaders.forEach( (headerName, headerValues) -> assertThat(responseHeaders.get(headerName)).isEqualTo(headerValues)); } if (!this.headerNamesNotPresent.isEmpty()) { assertThat(responseHeaders.headerNames()).doesNotContainAnyElementsOf(this.headerNamesNotPresent); } } private WebTestClient buildClient() { // @formatter:off return WebTestClientBuilder.bindToWebFilters(this.http.build()) .build(); // @formatter:on } } ================================================ FILE: config/src/test/java/org/springframework/security/config/web/server/ExceptionHandlingSpecTests.java ================================================ /* * 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 org.springframework.security.config.web.server; import org.junit.jupiter.api.Test; import org.springframework.http.HttpStatus; import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.reactive.ServerHttpSecurityConfigurationBuilder; import org.springframework.security.test.web.reactive.server.WebTestClientBuilder; import org.springframework.security.web.server.SecurityWebFilterChain; import org.springframework.security.web.server.ServerAuthenticationEntryPoint; import org.springframework.security.web.server.authentication.RedirectServerAuthenticationEntryPoint; import org.springframework.security.web.server.authorization.HttpStatusServerAccessDeniedHandler; import org.springframework.security.web.server.authorization.ServerAccessDeniedHandler; import org.springframework.test.web.reactive.server.WebTestClient; import static org.springframework.security.config.Customizer.withDefaults; /** * @author Denys Ivano * @since 5.0.5 */ public class ExceptionHandlingSpecTests { private ServerHttpSecurity http = ServerHttpSecurityConfigurationBuilder.httpWithDefaultAuthentication(); @Test public void defaultAuthenticationEntryPoint() { // @formatter:off SecurityWebFilterChain securityWebFilter = this.http .csrf((csrf) -> csrf.disable()) .authorizeExchange((authorize) -> authorize .anyExchange().authenticated()) .exceptionHandling(withDefaults()) .build(); WebTestClient client = WebTestClientBuilder .bindToWebFilters(securityWebFilter) .build(); client.get() .uri("/test") .exchange() .expectStatus().isUnauthorized() .expectHeader().valueMatches("WWW-Authenticate", "Basic.*"); // @formatter:on } @Test public void requestWhenExceptionHandlingWithDefaultsInLambdaThenDefaultAuthenticationEntryPointUsed() { // @formatter:off SecurityWebFilterChain securityWebFilter = this.http .authorizeExchange((authorize) -> authorize .anyExchange().authenticated() ) .exceptionHandling(withDefaults()) .build(); WebTestClient client = WebTestClientBuilder .bindToWebFilters(securityWebFilter) .build(); client.get() .uri("/test") .exchange() .expectStatus().isUnauthorized() .expectHeader().valueMatches("WWW-Authenticate", "Basic.*"); // @formatter:on } @Test public void customAuthenticationEntryPoint() { // @formatter:off SecurityWebFilterChain securityWebFilter = this.http .csrf((csrf) -> csrf.disable()) .authorizeExchange((authorize) -> authorize .anyExchange().authenticated()) .exceptionHandling((handling) -> handling .authenticationEntryPoint(redirectServerAuthenticationEntryPoint("/auth"))) .build(); WebTestClient client = WebTestClientBuilder .bindToWebFilters(securityWebFilter) .build(); client.get() .uri("/test") .exchange() .expectStatus().isFound() .expectHeader().valueMatches("Location", ".*"); // @formatter:on } @Test public void requestWhenCustomAuthenticationEntryPointInLambdaThenCustomAuthenticationEntryPointUsed() { // @formatter:off SecurityWebFilterChain securityWebFilter = this.http .authorizeExchange((authorize) -> authorize .anyExchange().authenticated() ) .exceptionHandling((exceptionHandling) -> exceptionHandling .authenticationEntryPoint(redirectServerAuthenticationEntryPoint("/auth")) ) .build(); WebTestClient client = WebTestClientBuilder .bindToWebFilters(securityWebFilter) .build(); client.get() .uri("/test") .exchange() .expectStatus().isFound() .expectHeader().valueMatches("Location", ".*"); // @formatter:on } @Test public void defaultAccessDeniedHandler() { // @formatter:off SecurityWebFilterChain securityWebFilter = this.http .csrf((csrf) -> csrf.disable()) .httpBasic(Customizer.withDefaults()) .authorizeExchange((authorize) -> authorize .anyExchange().hasRole("ADMIN")) .exceptionHandling(withDefaults()) .build(); WebTestClient client = WebTestClientBuilder .bindToWebFilters(securityWebFilter) .build(); client.get() .uri("/admin") .headers((headers) -> headers.setBasicAuth("user", "password")) .exchange() .expectStatus().isForbidden(); // @formatter:on } @Test public void requestWhenExceptionHandlingWithDefaultsInLambdaThenDefaultAccessDeniedHandlerUsed() { // @formatter:off SecurityWebFilterChain securityWebFilter = this.http .httpBasic(withDefaults()) .authorizeExchange((authorize) -> authorize .anyExchange().hasRole("ADMIN") ) .exceptionHandling(withDefaults()) .build(); WebTestClient client = WebTestClientBuilder .bindToWebFilters(securityWebFilter) .build(); client.get() .uri("/admin") .headers((headers) -> headers.setBasicAuth("user", "password")) .exchange() .expectStatus().isForbidden(); // @formatter:on } @Test public void customAccessDeniedHandler() { // @formatter:off SecurityWebFilterChain securityWebFilter = this.http .csrf((csrf) -> csrf.disable()) .httpBasic(Customizer.withDefaults()) .authorizeExchange((authorize) -> authorize .anyExchange().hasRole("ADMIN")) .exceptionHandling((handling) -> handling .accessDeniedHandler(httpStatusServerAccessDeniedHandler(HttpStatus.BAD_REQUEST))) .build(); WebTestClient client = WebTestClientBuilder .bindToWebFilters(securityWebFilter) .build(); client.get() .uri("/admin") .headers((headers) -> headers.setBasicAuth("user", "password")) .exchange() .expectStatus().isBadRequest(); // @formatter:on } @Test public void requestWhenCustomAccessDeniedHandlerInLambdaThenCustomAccessDeniedHandlerUsed() { // @formatter:off SecurityWebFilterChain securityWebFilter = this.http .httpBasic(withDefaults()) .authorizeExchange((authorize) -> authorize .anyExchange().hasRole("ADMIN") ) .exceptionHandling((exceptionHandling) -> exceptionHandling .accessDeniedHandler(httpStatusServerAccessDeniedHandler(HttpStatus.BAD_REQUEST)) ) .build(); WebTestClient client = WebTestClientBuilder .bindToWebFilters(securityWebFilter) .build(); client.get() .uri("/admin") .headers((headers) -> headers.setBasicAuth("user", "password")) .exchange() .expectStatus().isBadRequest(); // @formatter:on } private ServerAuthenticationEntryPoint redirectServerAuthenticationEntryPoint(String location) { return new RedirectServerAuthenticationEntryPoint(location); } private ServerAccessDeniedHandler httpStatusServerAccessDeniedHandler(HttpStatus httpStatus) { return new HttpStatusServerAccessDeniedHandler(httpStatus); } } ================================================ FILE: config/src/test/java/org/springframework/security/config/web/server/FormLoginTests.java ================================================ /* * 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 org.springframework.security.config.web.server; import org.junit.jupiter.api.Test; import org.openqa.selenium.By; import org.openqa.selenium.NoSuchElementException; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; import org.openqa.selenium.support.FindBy; import org.openqa.selenium.support.PageFactory; import reactor.core.publisher.Mono; import org.springframework.security.authentication.ReactiveAuthenticationManager; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.config.annotation.web.reactive.ServerHttpSecurityConfigurationBuilder; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextImpl; import org.springframework.security.htmlunit.server.WebTestClientHtmlUnitDriverBuilder; import org.springframework.security.test.web.reactive.server.WebTestClientBuilder; import org.springframework.security.web.server.SecurityWebFilterChain; import org.springframework.security.web.server.WebFilterChainProxy; import org.springframework.security.web.server.authentication.RedirectServerAuthenticationFailureHandler; import org.springframework.security.web.server.authentication.RedirectServerAuthenticationSuccessHandler; import org.springframework.security.web.server.context.ServerSecurityContextRepository; import org.springframework.security.web.server.csrf.CsrfToken; import org.springframework.security.web.server.util.matcher.PathPatternParserServerWebExchangeMatcher; import org.springframework.stereotype.Controller; import org.springframework.test.web.reactive.server.WebTestClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.server.ServerWebExchange; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.springframework.security.config.Customizer.withDefaults; /** * @author Rob Winch * @author Eddú Meléndez * @since 5.0 */ public class FormLoginTests { private ServerHttpSecurity http = ServerHttpSecurityConfigurationBuilder.httpWithDefaultAuthentication(); @Test public void defaultLoginPage() { // @formatter:off SecurityWebFilterChain securityWebFilter = this.http .authorizeExchange((authorize) -> authorize .anyExchange().authenticated()) .formLogin(withDefaults()) .build(); WebTestClient webTestClient = WebTestClientBuilder .bindToWebFilters(securityWebFilter) .build(); WebDriver driver = WebTestClientHtmlUnitDriverBuilder .webTestClientSetup(webTestClient) .build(); // @formatter:on DefaultLoginPage loginPage = HomePage.to(driver, DefaultLoginPage.class).assertAt(); // @formatter:off loginPage = loginPage.loginForm() .username("user") .password("invalid") .submit(DefaultLoginPage.class) .assertError(); HomePage homePage = loginPage.loginForm() .username("user") .password("password") .submit(HomePage.class); // @formatter:on homePage.assertAt(); loginPage = DefaultLogoutPage.to(driver).assertAt().logout(); loginPage.assertAt().assertLogout(); } @Test public void formLoginWhenDefaultsInLambdaThenCreatesDefaultLoginPage() { SecurityWebFilterChain securityWebFilter = this.http .authorizeExchange((authorize) -> authorize.anyExchange().authenticated()) .formLogin(withDefaults()) .build(); WebTestClient webTestClient = WebTestClientBuilder.bindToWebFilters(securityWebFilter).build(); WebDriver driver = WebTestClientHtmlUnitDriverBuilder.webTestClientSetup(webTestClient).build(); DefaultLoginPage loginPage = HomePage.to(driver, DefaultLoginPage.class).assertAt(); // @formatter:off loginPage = loginPage .loginForm() .username("user") .password("invalid") .submit(DefaultLoginPage.class) .assertError(); HomePage homePage = loginPage.loginForm() .username("user") .password("password") .submit(HomePage.class); // @formatter:on homePage.assertAt(); loginPage = DefaultLogoutPage.to(driver).assertAt().logout(); loginPage.assertAt().assertLogout(); } @Test public void customLoginPage() { // @formatter:off SecurityWebFilterChain securityWebFilter = this.http .authorizeExchange((authorize) -> authorize .pathMatchers("/login").permitAll() .anyExchange().authenticated()) .formLogin((login) -> login .loginPage("/login")) .build(); WebTestClient webTestClient = WebTestClient .bindToController(new CustomLoginPageController(), new WebTestClientBuilder.Http200RestController()) .webFilter(new WebFilterChainProxy(securityWebFilter)) .build(); WebDriver driver = WebTestClientHtmlUnitDriverBuilder .webTestClientSetup(webTestClient) .build(); // @formatter:on CustomLoginPage loginPage = HomePage.to(driver, CustomLoginPage.class).assertAt(); // @formatter:off HomePage homePage = loginPage.loginForm() .username("user") .password("password") .submit(HomePage.class); // @formatter:on homePage.assertAt(); } @Test public void formLoginWhenCustomLoginPageInLambdaThenUsed() { // @formatter:off SecurityWebFilterChain securityWebFilter = this.http .authorizeExchange((authorize) -> authorize .pathMatchers("/login").permitAll() .anyExchange().authenticated() ) .formLogin((formLogin) -> formLogin .loginPage("/login") ) .build(); WebTestClient webTestClient = WebTestClient .bindToController(new CustomLoginPageController(), new WebTestClientBuilder.Http200RestController()) .webFilter(new WebFilterChainProxy(securityWebFilter)) .build(); WebDriver driver = WebTestClientHtmlUnitDriverBuilder .webTestClientSetup(webTestClient) .build(); // @formatter:on CustomLoginPage loginPage = HomePage.to(driver, CustomLoginPage.class).assertAt(); // @formatter:off HomePage homePage = loginPage.loginForm() .username("user") .password("password") .submit(HomePage.class); // @formatter:on homePage.assertAt(); } @Test public void formLoginWhenCustomAuthenticationFailureHandlerThenUsed() { // @formatter:off SecurityWebFilterChain securityWebFilter = this.http .authorizeExchange((authorize) -> authorize .pathMatchers("/login", "/failure").permitAll() .anyExchange().authenticated()) .formLogin((login) -> login .authenticationFailureHandler(new RedirectServerAuthenticationFailureHandler("/failure"))) .build(); WebTestClient webTestClient = WebTestClientBuilder .bindToWebFilters(securityWebFilter) .build(); WebDriver driver = WebTestClientHtmlUnitDriverBuilder .webTestClientSetup(webTestClient) .build(); // @formatter:on DefaultLoginPage loginPage = HomePage.to(driver, DefaultLoginPage.class).assertAt(); // @formatter:off loginPage.loginForm() .username("invalid") .password("invalid") .submit(HomePage.class); // @formatter:on assertThat(driver.getCurrentUrl()).endsWith("/failure"); } @Test public void formLoginWhenCustomRequiresAuthenticationMatcherThenUsed() { // @formatter:off SecurityWebFilterChain securityWebFilter = this.http .authorizeExchange((authorize) -> authorize .pathMatchers("/login", "/sign-in").permitAll() .anyExchange().authenticated()) .formLogin((login) -> login .requiresAuthenticationMatcher(new PathPatternParserServerWebExchangeMatcher("/sign-in"))) .build(); WebTestClient webTestClient = WebTestClientBuilder .bindToWebFilters(securityWebFilter) .build(); WebDriver driver = WebTestClientHtmlUnitDriverBuilder .webTestClientSetup(webTestClient) .build(); // @formatter:on driver.get("http://localhost/sign-in"); assertThat(driver.getCurrentUrl()).endsWith("/login?error"); } @Test public void authenticationSuccess() { // @formatter:off SecurityWebFilterChain securityWebFilter = this.http .authorizeExchange((authorize) -> authorize .anyExchange().authenticated()) .formLogin((login) -> login .authenticationSuccessHandler(new RedirectServerAuthenticationSuccessHandler("/custom"))) .build(); WebTestClient webTestClient = WebTestClientBuilder .bindToWebFilters(securityWebFilter) .build(); WebDriver driver = WebTestClientHtmlUnitDriverBuilder .webTestClientSetup(webTestClient) .build(); // @formatter:on DefaultLoginPage loginPage = DefaultLoginPage.to(driver).assertAt(); // @formatter:off HomePage homePage = loginPage.loginForm() .username("user") .password("password") .submit(HomePage.class); // @formatter:on assertThat(driver.getCurrentUrl()).endsWith("/custom"); } @Test public void customAuthenticationManager() { ReactiveAuthenticationManager defaultAuthenticationManager = mock(ReactiveAuthenticationManager.class); ReactiveAuthenticationManager customAuthenticationManager = mock(ReactiveAuthenticationManager.class); given(defaultAuthenticationManager.authenticate(any())) .willThrow(new RuntimeException("should not interact with default auth manager")); given(customAuthenticationManager.authenticate(any())) .willReturn(Mono.just(new TestingAuthenticationToken("user", "password", "ROLE_USER", "ROLE_ADMIN"))); // @formatter:off SecurityWebFilterChain securityWebFilter = this.http .authenticationManager(defaultAuthenticationManager) .formLogin((login) -> login .authenticationManager(customAuthenticationManager)) .build(); WebTestClient webTestClient = WebTestClientBuilder .bindToWebFilters(securityWebFilter) .build(); WebDriver driver = WebTestClientHtmlUnitDriverBuilder .webTestClientSetup(webTestClient) .build(); // @formatter:on DefaultLoginPage loginPage = DefaultLoginPage.to(driver).assertAt(); // @formatter:off HomePage homePage = loginPage.loginForm() .username("user") .password("password") .submit(HomePage.class); // @formatter:on homePage.assertAt(); verifyNoMoreInteractions(defaultAuthenticationManager); } @Test public void formLoginSecurityContextRepository() { ServerSecurityContextRepository defaultSecContextRepository = mock(ServerSecurityContextRepository.class); ServerSecurityContextRepository formLoginSecContextRepository = mock(ServerSecurityContextRepository.class); TestingAuthenticationToken token = new TestingAuthenticationToken("rob", "rob", "ROLE_USER"); given(defaultSecContextRepository.save(any(), any())).willReturn(Mono.empty()); given(defaultSecContextRepository.load(any())).willReturn(authentication(token)); given(formLoginSecContextRepository.save(any(), any())).willReturn(Mono.empty()); given(formLoginSecContextRepository.load(any())).willReturn(authentication(token)); // @formatter:off SecurityWebFilterChain securityWebFilter = this.http .authorizeExchange((authorize) -> authorize .anyExchange().authenticated()) .securityContextRepository(defaultSecContextRepository) .formLogin((login) -> login .securityContextRepository(formLoginSecContextRepository)) .build(); WebTestClient webTestClient = WebTestClientBuilder .bindToWebFilters(securityWebFilter) .build(); WebDriver driver = WebTestClientHtmlUnitDriverBuilder .webTestClientSetup(webTestClient) .build(); // @formatter:on DefaultLoginPage loginPage = DefaultLoginPage.to(driver).assertAt(); // @formatter:off HomePage homePage = loginPage.loginForm() .username("user") .password("password") .submit(HomePage.class); // @formatter:on homePage.assertAt(); verify(defaultSecContextRepository, atLeastOnce()).load(any()); verify(formLoginSecContextRepository).save(any(), any()); } Mono authentication(Authentication authentication) { SecurityContext context = new SecurityContextImpl(); context.setAuthentication(authentication); return Mono.just(context); } public static class CustomLoginPage { private WebDriver driver; private LoginForm loginForm; public CustomLoginPage(WebDriver webDriver) { this.driver = webDriver; this.loginForm = PageFactory.initElements(webDriver, LoginForm.class); } public CustomLoginPage assertAt() { assertThat(this.driver.getTitle()).isEqualTo("Custom Log In Page"); return this; } public LoginForm loginForm() { return this.loginForm; } public static class LoginForm { private WebDriver driver; private WebElement username; private WebElement password; @FindBy(css = "button[type=submit]") private WebElement submit; public LoginForm(WebDriver driver) { this.driver = driver; } public LoginForm username(String username) { this.username.sendKeys(username); return this; } public LoginForm password(String password) { this.password.sendKeys(password); return this; } public T submit(Class page) { this.submit.click(); return PageFactory.initElements(this.driver, page); } } } public static class DefaultLoginPage { private WebDriver driver; @FindBy(css = "div[role=alert]") private WebElement alert; private LoginForm loginForm; private OAuth2Login oauth2Login = new OAuth2Login(); public DefaultLoginPage(WebDriver webDriver) { this.driver = webDriver; } static DefaultLoginPage create(WebDriver driver) { return PageFactory.initElements(driver, DefaultLoginPage.class); } public DefaultLoginPage assertAt() { assertThat(this.driver.getTitle()).isEqualTo("Please sign in"); return this; } public DefaultLoginPage assertError() { assertThat(this.alert.getText()).isEqualTo("Invalid credentials"); return this; } public DefaultLoginPage assertLogout() { assertThat(this.alert.getText()).isEqualTo("You have been signed out"); return this; } public DefaultLoginPage assertLoginFormNotPresent() { assertThatExceptionOfType(NoSuchElementException.class).isThrownBy(() -> loginForm().username("")); return this; } public DefaultLoginPage assertLoginFormPresent() { loginForm().username(""); return this; } public LoginForm loginForm() { if (this.loginForm == null) { this.loginForm = PageFactory.initElements(this.driver, LoginForm.class); } return this.loginForm; } public OAuth2Login oauth2Login() { return this.oauth2Login; } static DefaultLoginPage to(WebDriver driver) { driver.get("http://localhost/login"); return PageFactory.initElements(driver, DefaultLoginPage.class); } public static class LoginForm { private WebDriver driver; private WebElement username; private WebElement password; @FindBy(css = "button[type=submit]") private WebElement submit; public LoginForm(WebDriver driver) { this.driver = driver; } public LoginForm username(String username) { this.username.sendKeys(username); return this; } public LoginForm password(String password) { this.password.sendKeys(password); return this; } public T submit(Class page) { this.submit.click(); return PageFactory.initElements(this.driver, page); } } public class OAuth2Login { public WebElement findClientRegistrationByName(String clientName) { return DefaultLoginPage.this.driver.findElement(By.linkText(clientName)); } public OAuth2Login assertClientRegistrationByName(String clientName) { findClientRegistrationByName(clientName); return this; } public DefaultLoginPage and() { return DefaultLoginPage.this; } } } public static class DefaultLogoutPage { private WebDriver driver; @FindBy(css = "button[type=submit]") private WebElement submit; public DefaultLogoutPage(WebDriver webDriver) { this.driver = webDriver; } public DefaultLogoutPage assertAt() { assertThat(this.driver.getTitle()).isEqualTo("Confirm Log Out?"); return this; } public DefaultLoginPage logout() { this.submit.click(); return DefaultLoginPage.create(this.driver); } static DefaultLogoutPage to(WebDriver driver) { driver.get("http://localhost/logout"); return PageFactory.initElements(driver, DefaultLogoutPage.class); } } public static class HomePage { private WebDriver driver; @FindBy(tagName = "body") WebElement body; public HomePage(WebDriver driver) { this.driver = driver; } public void assertAt() { assertThat(this.body.getText()).isEqualToIgnoringWhitespace("ok"); } static T to(WebDriver driver, Class page) { driver.get("http://localhost/"); return PageFactory.initElements(driver, page); } } @Controller public static class CustomLoginPageController { @ResponseBody @GetMapping("/login") public Mono login(ServerWebExchange exchange) { Mono token = exchange.getAttributeOrDefault(CsrfToken.class.getName(), Mono.empty()); // @formatter:off return token.map((t) -> "\n" + "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " Custom Log In Page\n" + " \n" + " \n" + "
\n" + "
\n" + "

Please sign in

\n" + "

\n" + " \n" + " \n" + "

\n" + "

\n" + " \n" + " \n" + "

\n" + " \n" + " \n" + "
\n" + "
\n" + " \n" + ""); // @formatter:on } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/web/server/HeaderSpecTests.java ================================================ /* * 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 org.springframework.security.config.web.server; import java.time.Duration; import java.util.HashSet; import java.util.Set; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import reactor.core.publisher.Mono; import org.springframework.http.HttpHeaders; import org.springframework.security.config.Customizer; import org.springframework.security.test.web.reactive.server.WebTestClientBuilder; import org.springframework.security.web.server.header.ContentSecurityPolicyServerHttpHeadersWriter; import org.springframework.security.web.server.header.ContentTypeOptionsServerHttpHeadersWriter; import org.springframework.security.web.server.header.CrossOriginEmbedderPolicyServerHttpHeadersWriter; import org.springframework.security.web.server.header.CrossOriginOpenerPolicyServerHttpHeadersWriter; import org.springframework.security.web.server.header.CrossOriginResourcePolicyServerHttpHeadersWriter; import org.springframework.security.web.server.header.FeaturePolicyServerHttpHeadersWriter; import org.springframework.security.web.server.header.ReferrerPolicyServerHttpHeadersWriter; import org.springframework.security.web.server.header.ReferrerPolicyServerHttpHeadersWriter.ReferrerPolicy; import org.springframework.security.web.server.header.StrictTransportSecurityServerHttpHeadersWriter; import org.springframework.security.web.server.header.XFrameOptionsServerHttpHeadersWriter; import org.springframework.security.web.server.header.XXssProtectionServerHttpHeadersWriter; import org.springframework.test.web.reactive.server.FluxExchangeResult; import org.springframework.test.web.reactive.server.WebTestClient; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.security.config.Customizer.withDefaults; /** * Tests for {@link ServerHttpSecurity.HeaderSpec}. * * @author Rob Winch * @author Vedran Pavic * @author Ankur Pathak * @author Marcus Da Coregio * @since 5.0 */ public class HeaderSpecTests { private static final String CUSTOM_HEADER = "CUSTOM-HEADER"; private static final String CUSTOM_VALUE = "CUSTOM-VALUE"; private ServerHttpSecurity http = ServerHttpSecurity.http(); private HttpHeaders expectedHeaders = new HttpHeaders(); private Set headerNamesNotPresent = new HashSet<>(); @BeforeEach public void setup() { this.expectedHeaders.add(StrictTransportSecurityServerHttpHeadersWriter.STRICT_TRANSPORT_SECURITY, "max-age=31536000 ; includeSubDomains"); this.expectedHeaders.add(HttpHeaders.CACHE_CONTROL, "no-cache, no-store, max-age=0, must-revalidate"); this.expectedHeaders.add(HttpHeaders.PRAGMA, "no-cache"); this.expectedHeaders.add(HttpHeaders.EXPIRES, "0"); this.expectedHeaders.add(ContentTypeOptionsServerHttpHeadersWriter.X_CONTENT_OPTIONS, "nosniff"); this.expectedHeaders.add(XFrameOptionsServerHttpHeadersWriter.X_FRAME_OPTIONS, "DENY"); this.expectedHeaders.add(XXssProtectionServerHttpHeadersWriter.X_XSS_PROTECTION, "0"); } @Test public void headersWhenDisableThenNoSecurityHeaders() { new HashSet<>(this.expectedHeaders.headerNames()).forEach(this::expectHeaderNamesNotPresent); this.http.headers((headers) -> headers.disable()); assertHeaders(); } @Test public void headersWhenDisableInLambdaThenNoSecurityHeaders() { new HashSet<>(this.expectedHeaders.headerNames()).forEach(this::expectHeaderNamesNotPresent); this.http.headers((headers) -> headers.disable()); assertHeaders(); } @Test public void headersWhenDisableAndInvokedExplicitlyThenDefautsUsed() { this.http.headers((headers) -> headers.disable().headers(Customizer.withDefaults())); assertHeaders(); } @Test public void headersWhenDefaultsThenAllDefaultsWritten() { this.http.headers(withDefaults()); assertHeaders(); } @Test public void headersWhenDefaultsInLambdaThenAllDefaultsWritten() { this.http.headers(withDefaults()); assertHeaders(); } @Test public void headersWhenCacheDisableThenCacheNotWritten() { expectHeaderNamesNotPresent(HttpHeaders.CACHE_CONTROL, HttpHeaders.PRAGMA, HttpHeaders.EXPIRES); this.http.headers((headers) -> headers.cache((cache) -> cache.disable())); assertHeaders(); } @Test public void headersWhenCacheDisableInLambdaThenCacheNotWritten() { expectHeaderNamesNotPresent(HttpHeaders.CACHE_CONTROL, HttpHeaders.PRAGMA, HttpHeaders.EXPIRES); // @formatter:off this.http.headers((headers) -> headers .cache((cache) -> cache.disable()) ); // @formatter:on assertHeaders(); } @Test public void headersWhenContentOptionsDisableThenContentTypeOptionsNotWritten() { expectHeaderNamesNotPresent(ContentTypeOptionsServerHttpHeadersWriter.X_CONTENT_OPTIONS); this.http.headers((headers) -> headers.contentTypeOptions((options) -> options.disable())); assertHeaders(); } @Test public void headersWhenContentOptionsDisableInLambdaThenContentTypeOptionsNotWritten() { expectHeaderNamesNotPresent(ContentTypeOptionsServerHttpHeadersWriter.X_CONTENT_OPTIONS); // @formatter:off this.http .headers((headers) -> headers .contentTypeOptions((contentTypeOptions) -> contentTypeOptions.disable() )); // @formatter:on assertHeaders(); } @Test public void headersWhenHstsDisableThenHstsNotWritten() { expectHeaderNamesNotPresent(StrictTransportSecurityServerHttpHeadersWriter.STRICT_TRANSPORT_SECURITY); this.http.headers((headers) -> headers.hsts((hsts) -> hsts.disable())); assertHeaders(); } @Test public void headersWhenHstsDisableInLambdaThenHstsNotWritten() { expectHeaderNamesNotPresent(StrictTransportSecurityServerHttpHeadersWriter.STRICT_TRANSPORT_SECURITY); // @formatter:off this.http.headers((headers) -> headers .hsts((hsts) -> hsts.disable()) ); // @formatter:on assertHeaders(); } @Test public void headersWhenHstsCustomThenCustomHstsWritten() { this.expectedHeaders.remove(StrictTransportSecurityServerHttpHeadersWriter.STRICT_TRANSPORT_SECURITY); this.expectedHeaders.add(StrictTransportSecurityServerHttpHeadersWriter.STRICT_TRANSPORT_SECURITY, "max-age=60"); // @formatter:off this.http.headers((headers) -> headers .hsts((hsts) -> hsts .maxAge(Duration.ofSeconds(60)) .includeSubdomains(false))); // @formatter:on assertHeaders(); } @Test public void headersWhenHstsCustomInLambdaThenCustomHstsWritten() { this.expectedHeaders.remove(StrictTransportSecurityServerHttpHeadersWriter.STRICT_TRANSPORT_SECURITY); this.expectedHeaders.add(StrictTransportSecurityServerHttpHeadersWriter.STRICT_TRANSPORT_SECURITY, "max-age=60"); // @formatter:off this.http.headers( (headers) -> headers .hsts((hsts) -> hsts .maxAge(Duration.ofSeconds(60)) .includeSubdomains(false) ) ); // @formatter:on assertHeaders(); } @Test public void headersWhenHstsCustomWithPreloadThenCustomHstsWritten() { this.expectedHeaders.remove(StrictTransportSecurityServerHttpHeadersWriter.STRICT_TRANSPORT_SECURITY); this.expectedHeaders.add(StrictTransportSecurityServerHttpHeadersWriter.STRICT_TRANSPORT_SECURITY, "max-age=60 ; includeSubDomains ; preload"); // @formatter:off this.http.headers((headers) -> headers .hsts((hsts) -> hsts .maxAge(Duration.ofSeconds(60)) .preload(true))); // @formatter:on assertHeaders(); } @Test public void headersWhenHstsCustomWithPreloadInLambdaThenCustomHstsWritten() { this.expectedHeaders.remove(StrictTransportSecurityServerHttpHeadersWriter.STRICT_TRANSPORT_SECURITY); this.expectedHeaders.add(StrictTransportSecurityServerHttpHeadersWriter.STRICT_TRANSPORT_SECURITY, "max-age=60 ; includeSubDomains ; preload"); // @formatter:off this.http.headers((headers) -> headers .hsts((hsts) -> hsts .maxAge(Duration.ofSeconds(60)) .preload(true) ) ); // @formatter:on assertHeaders(); } @Test public void headersWhenFrameOptionsDisableThenFrameOptionsNotWritten() { expectHeaderNamesNotPresent(XFrameOptionsServerHttpHeadersWriter.X_FRAME_OPTIONS); // @formatter:off this.http.headers((headers) -> headers .frameOptions((options) -> options.disable())); // @formatter:on assertHeaders(); } @Test public void headersWhenFrameOptionsDisableInLambdaThenFrameOptionsNotWritten() { expectHeaderNamesNotPresent(XFrameOptionsServerHttpHeadersWriter.X_FRAME_OPTIONS); // @formatter:off this.http.headers((headers) -> headers .frameOptions((frameOptions) -> frameOptions .disable() ) ); // @formatter:on assertHeaders(); } @Test public void headersWhenFrameOptionsModeThenFrameOptionsCustomMode() { this.expectedHeaders.set(XFrameOptionsServerHttpHeadersWriter.X_FRAME_OPTIONS, "SAMEORIGIN"); // @formatter:off this.http.headers((headers) -> headers .frameOptions((frameOptions) -> frameOptions .mode(XFrameOptionsServerHttpHeadersWriter.Mode.SAMEORIGIN))); // @formatter:on assertHeaders(); } @Test public void headersWhenFrameOptionsModeInLambdaThenFrameOptionsCustomMode() { this.expectedHeaders.set(XFrameOptionsServerHttpHeadersWriter.X_FRAME_OPTIONS, "SAMEORIGIN"); // @formatter:off this.http.headers((headers) -> headers .frameOptions((frameOptions) -> frameOptions .mode(XFrameOptionsServerHttpHeadersWriter.Mode.SAMEORIGIN) ) ); // @formatter:on assertHeaders(); } @Test public void headersWhenXssProtectionDisableThenXssProtectionNotWritten() { expectHeaderNamesNotPresent("X-Xss-Protection"); // @formatter:off this.http.headers((headers) -> headers .xssProtection((xss) -> xss.disable())); // @formatter:on assertHeaders(); } @Test public void headersWhenXssProtectionDisableInLambdaThenXssProtectionNotWritten() { expectHeaderNamesNotPresent("X-Xss-Protection"); // @formatter:off this.http.headers((headers) -> headers .xssProtection((xssProtection) -> xssProtection .disable() ) ); // @formatter:on assertHeaders(); } @Test public void headersWhenXssProtectionValueDisabledThenXssProtectionWritten() { this.expectedHeaders.set(XXssProtectionServerHttpHeadersWriter.X_XSS_PROTECTION, "0"); // @formatter:off this.http.headers((headers) -> headers .xssProtection((xss) -> xss .headerValue(XXssProtectionServerHttpHeadersWriter.HeaderValue.DISABLED))); // @formatter:on assertHeaders(); } @Test public void headersWhenXssProtectionValueEnabledThenXssProtectionWritten() { this.expectedHeaders.set(XXssProtectionServerHttpHeadersWriter.X_XSS_PROTECTION, "1"); // @formatter:off this.http.headers((headers) -> headers .xssProtection((xss) -> xss .headerValue(XXssProtectionServerHttpHeadersWriter.HeaderValue.ENABLED))); // @formatter:on assertHeaders(); } @Test public void headersWhenXssProtectionValueEnabledModeBlockThenXssProtectionWritten() { this.expectedHeaders.set(XXssProtectionServerHttpHeadersWriter.X_XSS_PROTECTION, "1; mode=block"); // @formatter:off this.http.headers((headers) -> headers .xssProtection((xss) -> xss .headerValue(XXssProtectionServerHttpHeadersWriter.HeaderValue.ENABLED_MODE_BLOCK))); // @formatter:on assertHeaders(); } @Test public void headersWhenXssProtectionValueDisabledInLambdaThenXssProtectionWritten() { this.expectedHeaders.set(XXssProtectionServerHttpHeadersWriter.X_XSS_PROTECTION, "0"); // @formatter:off this.http.headers((headers) -> headers .xssProtection((xssProtection) -> xssProtection.headerValue(XXssProtectionServerHttpHeadersWriter.HeaderValue.DISABLED) )); // @formatter:on assertHeaders(); } @Test public void headersWhenFeaturePolicyEnabledThenFeaturePolicyWritten() { String policyDirectives = "Feature-Policy"; this.expectedHeaders.add(FeaturePolicyServerHttpHeadersWriter.FEATURE_POLICY, policyDirectives); // @formatter:off this.http.headers((headers) -> headers .featurePolicy(policyDirectives)); // @formatter:on assertHeaders(); } @Test public void headersWhenContentSecurityPolicyEnabledThenFeaturePolicyWritten() { String policyDirectives = "default-src 'self'"; this.expectedHeaders.add(ContentSecurityPolicyServerHttpHeadersWriter.CONTENT_SECURITY_POLICY, policyDirectives); // @formatter:off this.http.headers((headers) -> headers .contentSecurityPolicy((csp) -> csp.policyDirectives(policyDirectives))); // @formatter:on assertHeaders(); } @Test public void headersWhenContentSecurityPolicyEnabledWithDefaultsInLambdaThenDefaultPolicyWritten() { String expectedPolicyDirectives = "default-src 'self'"; this.expectedHeaders.add(ContentSecurityPolicyServerHttpHeadersWriter.CONTENT_SECURITY_POLICY, expectedPolicyDirectives); // @formatter:off this.http.headers((headers) -> headers .contentSecurityPolicy(withDefaults()) ); // @formatter:on assertHeaders(); } @Test public void headersWhenContentSecurityPolicyEnabledInLambdaThenContentSecurityPolicyWritten() { String policyDirectives = "default-src 'self' *.trusted.com"; this.expectedHeaders.add(ContentSecurityPolicyServerHttpHeadersWriter.CONTENT_SECURITY_POLICY, policyDirectives); // @formatter:off this.http.headers((headers) -> headers .contentSecurityPolicy((csp) -> csp .policyDirectives(policyDirectives) ) ); // @formatter:on assertHeaders(); } @Test public void headersWhenReferrerPolicyEnabledThenFeaturePolicyWritten() { this.expectedHeaders.add(ReferrerPolicyServerHttpHeadersWriter.REFERRER_POLICY, ReferrerPolicy.NO_REFERRER.getPolicy()); // @formatter:off this.http.headers((headers) -> headers .referrerPolicy(Customizer.withDefaults())); // @formatter:on assertHeaders(); } @Test public void headersWhenReferrerPolicyEnabledInLambdaThenReferrerPolicyWritten() { this.expectedHeaders.add(ReferrerPolicyServerHttpHeadersWriter.REFERRER_POLICY, ReferrerPolicy.NO_REFERRER.getPolicy()); // @formatter:off this.http.headers((headers) -> headers .referrerPolicy(withDefaults() ) ); // @formatter:on assertHeaders(); } @Test public void headersWhenReferrerPolicyCustomEnabledThenFeaturePolicyCustomWritten() { this.expectedHeaders.add(ReferrerPolicyServerHttpHeadersWriter.REFERRER_POLICY, ReferrerPolicy.NO_REFERRER_WHEN_DOWNGRADE.getPolicy()); // @formatter:off this.http.headers((headers) -> headers .referrerPolicy((referrer) -> referrer.policy(ReferrerPolicy.NO_REFERRER_WHEN_DOWNGRADE))); // @formatter:on assertHeaders(); } @Test public void headersWhenReferrerPolicyCustomEnabledInLambdaThenCustomReferrerPolicyWritten() { this.expectedHeaders.add(ReferrerPolicyServerHttpHeadersWriter.REFERRER_POLICY, ReferrerPolicy.NO_REFERRER_WHEN_DOWNGRADE.getPolicy()); // @formatter:off this.http.headers((headers) -> headers .referrerPolicy((referrerPolicy) -> referrerPolicy .policy(ReferrerPolicy.NO_REFERRER_WHEN_DOWNGRADE) ) ); // @formatter:on assertHeaders(); } @Test public void headersWhenCustomHeadersWriter() { this.expectedHeaders.add(CUSTOM_HEADER, CUSTOM_VALUE); // @formatter:off this.http.headers((headers) -> headers .writer((exchange) -> Mono.just(exchange) .doOnNext((it) -> it.getResponse().getHeaders().add(CUSTOM_HEADER, CUSTOM_VALUE)) .then() ) ); // @formatter:on assertHeaders(); } @Test public void headersWhenCrossOriginPoliciesCustomEnabledThenCustomCrossOriginPoliciesWritten() { this.expectedHeaders.add(CrossOriginOpenerPolicyServerHttpHeadersWriter.OPENER_POLICY, CrossOriginOpenerPolicyServerHttpHeadersWriter.CrossOriginOpenerPolicy.SAME_ORIGIN_ALLOW_POPUPS .getPolicy()); this.expectedHeaders.add(CrossOriginEmbedderPolicyServerHttpHeadersWriter.EMBEDDER_POLICY, CrossOriginEmbedderPolicyServerHttpHeadersWriter.CrossOriginEmbedderPolicy.REQUIRE_CORP.getPolicy()); this.expectedHeaders.add(CrossOriginResourcePolicyServerHttpHeadersWriter.RESOURCE_POLICY, CrossOriginResourcePolicyServerHttpHeadersWriter.CrossOriginResourcePolicy.SAME_ORIGIN.getPolicy()); // @formatter:off this.http.headers((headers) -> headers .crossOriginOpenerPolicy((opener) -> opener .policy(CrossOriginOpenerPolicyServerHttpHeadersWriter.CrossOriginOpenerPolicy.SAME_ORIGIN_ALLOW_POPUPS)) .crossOriginEmbedderPolicy((embedder) -> embedder .policy(CrossOriginEmbedderPolicyServerHttpHeadersWriter.CrossOriginEmbedderPolicy.REQUIRE_CORP)) .crossOriginResourcePolicy((resource) -> resource .policy(CrossOriginResourcePolicyServerHttpHeadersWriter.CrossOriginResourcePolicy.SAME_ORIGIN))); // @formatter:on assertHeaders(); } @Test public void headersWhenCrossOriginPoliciesCustomEnabledInLambdaThenCustomCrossOriginPoliciesWritten() { this.expectedHeaders.add(CrossOriginOpenerPolicyServerHttpHeadersWriter.OPENER_POLICY, CrossOriginOpenerPolicyServerHttpHeadersWriter.CrossOriginOpenerPolicy.SAME_ORIGIN_ALLOW_POPUPS .getPolicy()); this.expectedHeaders.add(CrossOriginEmbedderPolicyServerHttpHeadersWriter.EMBEDDER_POLICY, CrossOriginEmbedderPolicyServerHttpHeadersWriter.CrossOriginEmbedderPolicy.REQUIRE_CORP.getPolicy()); this.expectedHeaders.add(CrossOriginResourcePolicyServerHttpHeadersWriter.RESOURCE_POLICY, CrossOriginResourcePolicyServerHttpHeadersWriter.CrossOriginResourcePolicy.SAME_ORIGIN.getPolicy()); // @formatter:off this.http.headers((headers) -> headers .crossOriginOpenerPolicy((policy) -> policy .policy(CrossOriginOpenerPolicyServerHttpHeadersWriter.CrossOriginOpenerPolicy.SAME_ORIGIN_ALLOW_POPUPS) ) .crossOriginEmbedderPolicy((policy) -> policy .policy(CrossOriginEmbedderPolicyServerHttpHeadersWriter.CrossOriginEmbedderPolicy.REQUIRE_CORP) ) .crossOriginResourcePolicy((policy) -> policy .policy(CrossOriginResourcePolicyServerHttpHeadersWriter.CrossOriginResourcePolicy.SAME_ORIGIN) )); // @formatter:on assertHeaders(); } private void expectHeaderNamesNotPresent(String... headerNames) { for (String headerName : headerNames) { this.expectedHeaders.remove(headerName); this.headerNamesNotPresent.add(headerName); } } private void assertHeaders() { WebTestClient client = buildClient(); FluxExchangeResult response = client.get() .uri("https://example.com/") .exchange() .returnResult(String.class); HttpHeaders responseHeaders = response.getResponseHeaders(); if (!this.expectedHeaders.isEmpty()) { this.expectedHeaders.forEach( (headerName, headerValues) -> assertThat(responseHeaders.get(headerName)).isEqualTo(headerValues)); } if (!this.headerNamesNotPresent.isEmpty()) { assertThat(responseHeaders.headerNames()).doesNotContainAnyElementsOf(this.headerNamesNotPresent); } } private WebTestClient buildClient() { return WebTestClientBuilder.bindToWebFilters(this.http.build()).build(); } } ================================================ FILE: config/src/test/java/org/springframework/security/config/web/server/HttpsRedirectSpecTests.java ================================================ /* * 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 org.springframework.security.config.web.server; import org.apache.http.HttpHeaders; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.web.PortMapper; import org.springframework.security.web.server.SecurityWebFilterChain; import org.springframework.security.web.server.util.matcher.PathPatternParserServerWebExchangeMatcher; import org.springframework.test.web.reactive.server.WebTestClient; import org.springframework.web.reactive.config.EnableWebFlux; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; import static org.springframework.security.config.Customizer.withDefaults; /** * Tests for {@link HttpsRedirectSpecTests} * * @author Josh Cummings */ @ExtendWith(SpringTestContextExtension.class) public class HttpsRedirectSpecTests { public final SpringTestContext spring = new SpringTestContext(this); WebTestClient client; @Autowired public void setApplicationContext(ApplicationContext context) { // @formatter:off this.client = WebTestClient .bindToApplicationContext(context) .build(); // @formatter:on } @Test public void getWhenSecureThenDoesNotRedirect() { this.spring.register(RedirectToHttpConfig.class).autowire(); // @formatter:off this.client.get() .uri("https://localhost") .exchange() .expectStatus().isNotFound(); // @formatter:on } @Test public void getWhenInsecureThenRespondsWithRedirectToSecure() { this.spring.register(RedirectToHttpConfig.class).autowire(); // @formatter:off this.client.get() .uri("http://localhost") .exchange() .expectStatus().isFound() .expectHeader().valueEquals(HttpHeaders.LOCATION, "https://localhost"); // @formatter:on } @Test public void getWhenInsecureAndRedirectConfiguredInLambdaThenRespondsWithRedirectToSecure() { this.spring.register(RedirectToHttpsInLambdaConfig.class).autowire(); // @formatter:off this.client.get() .uri("http://localhost") .exchange() .expectStatus().isFound() .expectHeader().valueEquals(HttpHeaders.LOCATION, "https://localhost"); // @formatter:on } @Test public void getWhenInsecureAndPathRequiresTransportSecurityThenRedirects() { this.spring.register(SometimesRedirectToHttpsConfig.class).autowire(); // @formatter:off this.client.get() .uri("http://localhost:8080") .exchange() .expectStatus().isNotFound(); this.client.get() .uri("http://localhost:8080/secure") .exchange() .expectStatus().isFound() .expectHeader().valueEquals(HttpHeaders.LOCATION, "https://localhost:8443/secure"); // @formatter:on } @Test public void getWhenInsecureAndPathRequiresTransportSecurityInLambdaThenRedirects() { this.spring.register(SometimesRedirectToHttpsInLambdaConfig.class).autowire(); // @formatter:off this.client.get() .uri("http://localhost:8080") .exchange() .expectStatus().isNotFound(); this.client.get() .uri("http://localhost:8080/secure") .exchange() .expectStatus().isFound() .expectHeader().valueEquals(HttpHeaders.LOCATION, "https://localhost:8443/secure"); // @formatter:on } @Test public void getWhenInsecureAndUsingCustomPortMapperThenRespondsWithRedirectToSecurePort() { this.spring.register(RedirectToHttpsViaCustomPortsConfig.class).autowire(); PortMapper portMapper = this.spring.getContext().getBean(PortMapper.class); given(portMapper.lookupHttpsPort(4080)).willReturn(4443); // @formatter:off this.client.get() .uri("http://localhost:4080") .exchange() .expectStatus().isFound() .expectHeader().valueEquals(HttpHeaders.LOCATION, "https://localhost:4443"); // @formatter:on } @Test public void getWhenInsecureAndUsingCustomPortMapperInLambdaThenRespondsWithRedirectToSecurePort() { this.spring.register(RedirectToHttpsViaCustomPortsInLambdaConfig.class).autowire(); PortMapper portMapper = this.spring.getContext().getBean(PortMapper.class); given(portMapper.lookupHttpsPort(4080)).willReturn(4443); // @formatter:off this.client.get() .uri("http://localhost:4080") .exchange() .expectStatus().isFound() .expectHeader().valueEquals(HttpHeaders.LOCATION, "https://localhost:4443"); // @formatter:on } @Configuration @EnableWebFlux @EnableWebFluxSecurity static class RedirectToHttpConfig { @Bean SecurityWebFilterChain springSecurity(ServerHttpSecurity http) { // @formatter:off http .redirectToHttps(withDefaults()); // @formatter:on return http.build(); } } @Configuration @EnableWebFlux @EnableWebFluxSecurity static class RedirectToHttpsInLambdaConfig { @Bean SecurityWebFilterChain springSecurity(ServerHttpSecurity http) { // @formatter:off http .redirectToHttps(withDefaults()); // @formatter:on return http.build(); } } @Configuration @EnableWebFlux @EnableWebFluxSecurity static class SometimesRedirectToHttpsConfig { @Bean SecurityWebFilterChain springSecurity(ServerHttpSecurity http) { // @formatter:off http .redirectToHttps((https) -> https .httpsRedirectWhen(new PathPatternParserServerWebExchangeMatcher("/secure"))); // @formatter:on return http.build(); } } @Configuration @EnableWebFlux @EnableWebFluxSecurity static class SometimesRedirectToHttpsInLambdaConfig { @Bean SecurityWebFilterChain springSecurity(ServerHttpSecurity http) { // @formatter:off http .redirectToHttps((redirectToHttps) -> redirectToHttps .httpsRedirectWhen(new PathPatternParserServerWebExchangeMatcher("/secure")) ); // @formatter:on return http.build(); } } @Configuration @EnableWebFlux @EnableWebFluxSecurity static class RedirectToHttpsViaCustomPortsConfig { @Bean SecurityWebFilterChain springSecurity(ServerHttpSecurity http) { // @formatter:off http .redirectToHttps((https) -> https .portMapper(portMapper())); // @formatter:on return http.build(); } @Bean PortMapper portMapper() { return mock(PortMapper.class); } } @Configuration @EnableWebFlux @EnableWebFluxSecurity static class RedirectToHttpsViaCustomPortsInLambdaConfig { @Bean SecurityWebFilterChain springSecurity(ServerHttpSecurity http) { // @formatter:off http .redirectToHttps((redirectToHttps) -> redirectToHttps .portMapper(portMapper()) ); // @formatter:on return http.build(); } @Bean PortMapper portMapper() { return mock(PortMapper.class); } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/web/server/LogoutSpecTests.java ================================================ /* * 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 org.springframework.security.config.web.server; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; import org.openqa.selenium.WebDriver; import reactor.core.publisher.Mono; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.config.annotation.web.reactive.ServerHttpSecurityConfigurationBuilder; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.htmlunit.server.WebTestClientHtmlUnitDriverBuilder; import org.springframework.security.test.web.reactive.server.WebTestClientBuilder; import org.springframework.security.web.server.SecurityWebFilterChain; import org.springframework.security.web.server.authentication.logout.ServerLogoutHandler; import org.springframework.security.web.server.context.ServerSecurityContextRepository; import org.springframework.security.web.server.context.WebSessionServerSecurityContextRepository; import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers; import org.springframework.test.web.reactive.server.WebTestClient; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.server.ServerWebExchange; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.security.config.Customizer.withDefaults; /** * @author Shazin Sadakath * @since 5.0 */ public class LogoutSpecTests { private ServerHttpSecurity http = ServerHttpSecurityConfigurationBuilder.httpWithDefaultAuthentication(); @Test public void defaultLogout() { // @formatter:off SecurityWebFilterChain securityWebFilter = this.http .authorizeExchange((authorize) -> authorize .anyExchange().authenticated()) .formLogin(withDefaults()) .build(); WebTestClient webTestClient = WebTestClientBuilder .bindToWebFilters(securityWebFilter) .build(); WebDriver driver = WebTestClientHtmlUnitDriverBuilder .webTestClientSetup(webTestClient) .build(); // @formatter:on FormLoginTests.DefaultLoginPage loginPage = FormLoginTests.HomePage .to(driver, FormLoginTests.DefaultLoginPage.class) .assertAt(); // @formatter:off loginPage = loginPage.loginForm() .username("user") .password("invalid") .submit(FormLoginTests.DefaultLoginPage.class) .assertError(); FormLoginTests.HomePage homePage = loginPage.loginForm() .username("user") .password("password") .submit(FormLoginTests.HomePage.class); // @formatter:on homePage.assertAt(); loginPage = FormLoginTests.DefaultLogoutPage.to(driver).assertAt().logout(); loginPage.assertAt().assertLogout(); } @Test public void customLogout() { // @formatter:off SecurityWebFilterChain securityWebFilter = this.http .authorizeExchange((authorize) -> authorize .anyExchange().authenticated()) .formLogin(withDefaults()) .logout((logout) -> logout .requiresLogout(ServerWebExchangeMatchers.pathMatchers("/custom-logout"))) .build(); WebTestClient webTestClient = WebTestClientBuilder .bindToWebFilters(securityWebFilter) .build(); WebDriver driver = WebTestClientHtmlUnitDriverBuilder .webTestClientSetup(webTestClient) .build(); // @formatter:on FormLoginTests.DefaultLoginPage loginPage = FormLoginTests.HomePage .to(driver, FormLoginTests.DefaultLoginPage.class) .assertAt(); // @formatter:off loginPage = loginPage.loginForm() .username("user") .password("invalid") .submit(FormLoginTests.DefaultLoginPage.class) .assertError(); FormLoginTests.HomePage homePage = loginPage.loginForm() .username("user") .password("password") .submit(FormLoginTests.HomePage.class); homePage.assertAt(); // @formatter:on driver.get("http://localhost/custom-logout"); FormLoginTests.DefaultLoginPage.create(driver).assertAt().assertLogout(); } @Test public void logoutWhenCustomLogoutInLambdaThenCustomLogoutUsed() { // @formatter:off SecurityWebFilterChain securityWebFilter = this.http .authorizeExchange((authorize) -> authorize .anyExchange().authenticated() ) .formLogin(withDefaults()) .logout((logout) -> logout .requiresLogout(ServerWebExchangeMatchers.pathMatchers("/custom-logout")) ) .build(); WebTestClient webTestClient = WebTestClientBuilder .bindToWebFilters(securityWebFilter) .build(); WebDriver driver = WebTestClientHtmlUnitDriverBuilder .webTestClientSetup(webTestClient) .build(); // @formatter:on FormLoginTests.DefaultLoginPage loginPage = FormLoginTests.HomePage .to(driver, FormLoginTests.DefaultLoginPage.class) .assertAt(); // @formatter:off loginPage = loginPage.loginForm() .username("user") .password("invalid") .submit(FormLoginTests.DefaultLoginPage.class) .assertError(); FormLoginTests.HomePage homePage = loginPage.loginForm() .username("user").password("password") .submit(FormLoginTests.HomePage.class); // @formatter:on homePage.assertAt(); driver.get("http://localhost/custom-logout"); FormLoginTests.DefaultLoginPage.create(driver).assertAt().assertLogout(); } @Test public void logoutWhenDisabledThenDefaultLogoutPageDoesNotExist() { // @formatter:off SecurityWebFilterChain securityWebFilter = this.http .authorizeExchange((authorize) -> authorize .anyExchange().authenticated()) .formLogin(withDefaults()) .logout((logout) -> logout.disable()) .build(); WebTestClient webTestClient = WebTestClientBuilder .bindToControllerAndWebFilters(HomeController.class, securityWebFilter) .build(); WebDriver driver = WebTestClientHtmlUnitDriverBuilder .webTestClientSetup(webTestClient) .build(); // @formatter:on FormLoginTests.DefaultLoginPage loginPage = FormLoginTests.HomePage .to(driver, FormLoginTests.DefaultLoginPage.class) .assertAt(); // @formatter:off FormLoginTests.HomePage homePage = loginPage.loginForm() .username("user") .password("password") .submit(FormLoginTests.HomePage.class); // @formatter:on homePage.assertAt(); FormLoginTests.DefaultLogoutPage.to(driver); assertThat(driver.getPageSource()).isEmpty(); } @Test public void logoutWhenCustomSecurityContextRepositoryThenLogsOut() { WebSessionServerSecurityContextRepository repository = new WebSessionServerSecurityContextRepository(); repository.setSpringSecurityContextAttrName("CUSTOM_CONTEXT_ATTR"); // @formatter:off SecurityWebFilterChain securityWebFilter = this.http .securityContextRepository(repository) .authorizeExchange((authorize) -> authorize .anyExchange().authenticated()) .formLogin(withDefaults()) .logout(withDefaults()) .build(); WebTestClient webTestClient = WebTestClientBuilder .bindToWebFilters(securityWebFilter) .build(); WebDriver driver = WebTestClientHtmlUnitDriverBuilder .webTestClientSetup(webTestClient) .build(); // @formatter:on FormLoginTests.DefaultLoginPage loginPage = FormLoginTests.HomePage .to(driver, FormLoginTests.DefaultLoginPage.class) .assertAt(); // @formatter:off FormLoginTests.HomePage homePage = loginPage.loginForm() .username("user") .password("password") .submit(FormLoginTests.HomePage.class); // @formatter:on homePage.assertAt(); FormLoginTests.DefaultLogoutPage.to(driver).assertAt().logout(); FormLoginTests.HomePage.to(driver, FormLoginTests.DefaultLoginPage.class).assertAt(); } @Test public void multipleLogoutHandlers() { InMemorySecurityContextRepository repository = new InMemorySecurityContextRepository(); MultiValueMap logoutData = new LinkedMultiValueMap<>(); ServerLogoutHandler handler1 = (exchange, authentication) -> { logoutData.add("handler-header", "value1"); return Mono.empty(); }; ServerLogoutHandler handler2 = (exchange, authentication) -> { logoutData.add("handler-header", "value2"); return Mono.empty(); }; // @formatter:off SecurityWebFilterChain securityWebFilter = this.http .securityContextRepository(repository) .authorizeExchange((authorize) -> authorize .anyExchange().authenticated()) .formLogin(withDefaults()) .logout((logoutSpec) -> logoutSpec.logoutHandler((handlers) -> { handlers.add(handler1); handlers.add(0, handler2); })) .build(); WebTestClient webTestClient = WebTestClientBuilder .bindToWebFilters(securityWebFilter) .build(); WebDriver driver = WebTestClientHtmlUnitDriverBuilder .webTestClientSetup(webTestClient) .build(); // @formatter:on FormLoginTests.DefaultLoginPage loginPage = FormLoginTests.HomePage .to(driver, FormLoginTests.DefaultLoginPage.class) .assertAt(); // @formatter:off loginPage = loginPage.loginForm() .username("user") .password("invalid") .submit(FormLoginTests.DefaultLoginPage.class) .assertError(); FormLoginTests.HomePage homePage = loginPage.loginForm() .username("user") .password("password") .submit(FormLoginTests.HomePage.class); // @formatter:on homePage.assertAt(); SecurityContext savedContext = repository.getSavedContext(); assertThat(savedContext).isNotNull(); assertThat(savedContext.getAuthentication()).isInstanceOf(UsernamePasswordAuthenticationToken.class); loginPage = FormLoginTests.DefaultLogoutPage.to(driver).assertAt().logout(); loginPage.assertAt().assertLogout(); assertThat(logoutData).hasSize(1); assertThat(logoutData.get("handler-header")).containsExactly("value2", "value1"); savedContext = repository.getSavedContext(); assertThat(savedContext).isNull(); } private static class InMemorySecurityContextRepository implements ServerSecurityContextRepository { private @Nullable SecurityContext savedContext; @Override public Mono save(ServerWebExchange exchange, SecurityContext context) { this.savedContext = context; return Mono.empty(); } @Override public Mono load(ServerWebExchange exchange) { return Mono.justOrEmpty(this.savedContext); } private @Nullable SecurityContext getSavedContext() { return this.savedContext; } } @RestController public static class HomeController { @GetMapping("/") public String ok() { return "ok"; } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/web/server/OAuth2ClientSpecTests.java ================================================ /* * 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 org.springframework.security.config.web.server; import java.net.URI; import java.util.Set; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; import reactor.core.publisher.Mono; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.ReactiveAuthenticationManager; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; import org.springframework.security.oauth2.client.annotation.RegisteredOAuth2AuthorizedClient; import org.springframework.security.oauth2.client.authentication.OAuth2AuthorizationCodeAuthenticationToken; import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest; import org.springframework.security.oauth2.client.endpoint.ReactiveOAuth2AccessTokenResponseClient; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.InMemoryReactiveClientRegistrationRepository; import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository; import org.springframework.security.oauth2.client.registration.TestClientRegistrations; import org.springframework.security.oauth2.client.web.server.ServerAuthorizationRequestRepository; import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizationRequestResolver; import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository; import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.core.OAuth2AccessToken; import org.springframework.security.oauth2.core.TestOAuth2AccessTokens; import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationExchange; import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponse; import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; import org.springframework.security.oauth2.core.endpoint.TestOAuth2AuthorizationRequests; import org.springframework.security.oauth2.core.endpoint.TestOAuth2AuthorizationResponses; import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners; import org.springframework.security.test.context.support.WithMockUser; import org.springframework.security.web.server.SecurityWebFilterChain; import org.springframework.security.web.server.authentication.ServerAuthenticationConverter; import org.springframework.security.web.server.savedrequest.ServerRequestCache; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.reactive.server.WebTestClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.reactive.config.EnableWebFlux; import org.springframework.web.server.ServerWebExchange; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.springframework.security.config.Customizer.withDefaults; /** * @author Rob Winch * @author Parikshit Dutta * @since 5.1 */ @ExtendWith({ SpringExtension.class, SpringTestContextExtension.class }) @SecurityTestExecutionListeners public class OAuth2ClientSpecTests { public final SpringTestContext spring = new SpringTestContext(this); private WebTestClient client; private ClientRegistration registration = TestClientRegistrations.clientRegistration().build(); @Autowired public void setApplicationContext(ApplicationContext context) { // @formatter:off this.client = WebTestClient .bindToApplicationContext(context) .build(); // @formatter:on } @Test @WithMockUser public void registeredOAuth2AuthorizedClientWhenAuthenticatedThenRedirects() { this.spring.register(Config.class, AuthorizedClientController.class).autowire(); ReactiveClientRegistrationRepository repository = this.spring.getContext() .getBean(ReactiveClientRegistrationRepository.class); ServerOAuth2AuthorizedClientRepository authorizedClientRepository = this.spring.getContext() .getBean(ServerOAuth2AuthorizedClientRepository.class); given(repository.findByRegistrationId(any())) .willReturn(Mono.just(TestClientRegistrations.clientRegistration().build())); given(authorizedClientRepository.loadAuthorizedClient(any(), any(), any())).willReturn(Mono.empty()); // @formatter:off this.client.get() .uri("/") .exchange() .expectStatus().is3xxRedirection(); // @formatter:on } @Test public void registeredOAuth2AuthorizedClientWhenAnonymousThenRedirects() { this.spring.register(Config.class, AuthorizedClientController.class).autowire(); ReactiveClientRegistrationRepository repository = this.spring.getContext() .getBean(ReactiveClientRegistrationRepository.class); ServerOAuth2AuthorizedClientRepository authorizedClientRepository = this.spring.getContext() .getBean(ServerOAuth2AuthorizedClientRepository.class); given(repository.findByRegistrationId(any())) .willReturn(Mono.just(TestClientRegistrations.clientRegistration().build())); given(authorizedClientRepository.loadAuthorizedClient(any(), any(), any())).willReturn(Mono.empty()); // @formatter:off this.client.get() .uri("/") .exchange() .expectStatus().is3xxRedirection(); // @formatter:on } @Test public void oauth2ClientWhenCustomObjectsThenUsed() { this.spring .register(ClientRegistrationConfig.class, OAuth2ClientCustomConfig.class, AuthorizedClientController.class) .autowire(); OAuth2ClientCustomConfig config = this.spring.getContext().getBean(OAuth2ClientCustomConfig.class); ServerAuthenticationConverter converter = config.authenticationConverter; ReactiveAuthenticationManager manager = config.manager; ServerAuthorizationRequestRepository authorizationRequestRepository = config.authorizationRequestRepository; ServerOAuth2AuthorizationRequestResolver resolver = config.resolver; ServerRequestCache requestCache = config.requestCache; OAuth2AuthorizationRequest authorizationRequest = TestOAuth2AuthorizationRequests.request() .redirectUri("/authorize/oauth2/code/registration-id") .build(); OAuth2AuthorizationResponse authorizationResponse = TestOAuth2AuthorizationResponses.success() .redirectUri("/authorize/oauth2/code/registration-id") .build(); OAuth2AuthorizationExchange authorizationExchange = new OAuth2AuthorizationExchange(authorizationRequest, authorizationResponse); OAuth2AccessToken accessToken = TestOAuth2AccessTokens.noScopes(); OAuth2AuthorizationCodeAuthenticationToken result = new OAuth2AuthorizationCodeAuthenticationToken( this.registration, authorizationExchange, accessToken); given(authorizationRequestRepository.loadAuthorizationRequest(any())) .willReturn(Mono.just(authorizationRequest)); given(resolver.resolve(any())).willReturn(Mono.empty()); given(converter.convert(any())).willReturn(Mono.just(new TestingAuthenticationToken("a", "b", "c"))); given(manager.authenticate(any())).willReturn(Mono.just(result)); given(requestCache.getRedirectUri(any())).willReturn(Mono.just(URI.create("/saved-request"))); // @formatter:off this.client.get() .uri((uriBuilder) -> uriBuilder .path("/authorize/oauth2/code/registration-id") .queryParam(OAuth2ParameterNames.CODE, "code") .queryParam(OAuth2ParameterNames.STATE, "state") .build() ) .exchange() .expectStatus().is3xxRedirection(); // @formatter:on verify(converter).convert(any()); verify(manager).authenticate(any()); verify(requestCache).getRedirectUri(any()); verify(resolver).resolve(any()); } @Test public void oauth2ClientWhenCustomObjectsInLambdaThenUsed() { this.spring .register(ClientRegistrationConfig.class, OAuth2ClientInLambdaCustomConfig.class, AuthorizedClientController.class) .autowire(); OAuth2ClientInLambdaCustomConfig config = this.spring.getContext() .getBean(OAuth2ClientInLambdaCustomConfig.class); ServerAuthenticationConverter converter = config.authenticationConverter; ReactiveAuthenticationManager manager = config.manager; ServerAuthorizationRequestRepository authorizationRequestRepository = config.authorizationRequestRepository; ServerRequestCache requestCache = config.requestCache; OAuth2AuthorizationRequest authorizationRequest = TestOAuth2AuthorizationRequests.request() .redirectUri("/authorize/oauth2/code/registration-id") .build(); OAuth2AuthorizationResponse authorizationResponse = TestOAuth2AuthorizationResponses.success() .redirectUri("/authorize/oauth2/code/registration-id") .build(); OAuth2AuthorizationExchange authorizationExchange = new OAuth2AuthorizationExchange(authorizationRequest, authorizationResponse); OAuth2AccessToken accessToken = TestOAuth2AccessTokens.noScopes(); OAuth2AuthorizationCodeAuthenticationToken result = new OAuth2AuthorizationCodeAuthenticationToken( this.registration, authorizationExchange, accessToken); given(authorizationRequestRepository.loadAuthorizationRequest(any())) .willReturn(Mono.just(authorizationRequest)); given(converter.convert(any())).willReturn(Mono.just(new TestingAuthenticationToken("a", "b", "c"))); given(manager.authenticate(any())).willReturn(Mono.just(result)); given(requestCache.getRedirectUri(any())).willReturn(Mono.just(URI.create("/saved-request"))); // @formatter:off this.client.get() .uri((uriBuilder) -> uriBuilder .path("/authorize/oauth2/code/registration-id") .queryParam(OAuth2ParameterNames.CODE, "code") .queryParam(OAuth2ParameterNames.STATE, "state") .build() ) .exchange() .expectStatus().is3xxRedirection(); // @formatter:on verify(converter).convert(any()); verify(manager).authenticate(any()); verify(requestCache).getRedirectUri(any()); } @Test @SuppressWarnings("unchecked") public void oauth2ClientWhenCustomAccessTokenResponseClientThenUsed() { this.spring.register(OAuth2ClientBeanConfig.class, AuthorizedClientController.class).autowire(); ReactiveClientRegistrationRepository clientRegistrationRepository = this.spring.getContext() .getBean(ReactiveClientRegistrationRepository.class); given(clientRegistrationRepository.findByRegistrationId(any())).willReturn(Mono.just(this.registration)); ServerOAuth2AuthorizedClientRepository authorizedClientRepository = this.spring.getContext() .getBean(ServerOAuth2AuthorizedClientRepository.class); given(authorizedClientRepository.saveAuthorizedClient(any(OAuth2AuthorizedClient.class), any(Authentication.class), any(ServerWebExchange.class))) .willReturn(Mono.empty()); ServerAuthorizationRequestRepository authorizationRequestRepository = this.spring .getContext() .getBean(ServerAuthorizationRequestRepository.class); OAuth2AuthorizationRequest authorizationRequest = TestOAuth2AuthorizationRequests.request() .redirectUri("/authorize/oauth2/code/registration-id") .build(); given(authorizationRequestRepository.loadAuthorizationRequest(any(ServerWebExchange.class))) .willReturn(Mono.just(authorizationRequest)); given(authorizationRequestRepository.removeAuthorizationRequest(any(ServerWebExchange.class))) .willReturn(Mono.just(authorizationRequest)); ReactiveOAuth2AccessTokenResponseClient accessTokenResponseClient = this.spring .getContext() .getBean(ReactiveOAuth2AccessTokenResponseClient.class); OAuth2AccessTokenResponse accessTokenResponse = OAuth2AccessTokenResponse.withToken("token") .tokenType(OAuth2AccessToken.TokenType.BEARER) .scopes(Set.of()) .expiresIn(300) .build(); given(accessTokenResponseClient.getTokenResponse(any(OAuth2AuthorizationCodeGrantRequest.class))) .willReturn(Mono.just(accessTokenResponse)); // @formatter:off this.client.get() .uri((uriBuilder) -> uriBuilder .path("/authorize/oauth2/code/registration-id") .queryParam(OAuth2ParameterNames.CODE, "code") .queryParam(OAuth2ParameterNames.STATE, "state") .build() ) .exchange() .expectStatus().is3xxRedirection(); // @formatter:on ArgumentCaptor grantRequestArgumentCaptor = ArgumentCaptor .forClass(OAuth2AuthorizationCodeGrantRequest.class); verify(accessTokenResponseClient).getTokenResponse(grantRequestArgumentCaptor.capture()); OAuth2AuthorizationCodeGrantRequest grantRequest = grantRequestArgumentCaptor.getValue(); assertThat(grantRequest.getClientRegistration()).isEqualTo(this.registration); assertThat(grantRequest.getGrantType()).isEqualTo(AuthorizationGrantType.AUTHORIZATION_CODE); assertThat(grantRequest.getAuthorizationExchange().getAuthorizationRequest()).isEqualTo(authorizationRequest); assertThat(grantRequest.getAuthorizationExchange().getAuthorizationResponse().getCode()).isEqualTo("code"); assertThat(grantRequest.getAuthorizationExchange().getAuthorizationResponse().getState()).isEqualTo("state"); assertThat(grantRequest.getAuthorizationExchange().getAuthorizationResponse().getRedirectUri()) .startsWith("/authorize/oauth2/code/registration-id"); } @Configuration @EnableWebFlux @EnableWebFluxSecurity static class Config { @Bean SecurityWebFilterChain springSecurity(ServerHttpSecurity http) { // @formatter:off http .oauth2Client(withDefaults()); // @formatter:on return http.build(); } @Bean ReactiveClientRegistrationRepository clientRegistrationRepository() { return mock(ReactiveClientRegistrationRepository.class); } @Bean ServerOAuth2AuthorizedClientRepository authorizedClientRepository() { return mock(ServerOAuth2AuthorizedClientRepository.class); } } @RestController static class AuthorizedClientController { @GetMapping("/") String home(@RegisteredOAuth2AuthorizedClient("github") OAuth2AuthorizedClient authorizedClient) { return "home"; } } @Configuration @EnableWebFlux @EnableWebFluxSecurity static class ClientRegistrationConfig { private ClientRegistration clientRegistration = TestClientRegistrations.clientRegistration().build(); @Bean InMemoryReactiveClientRegistrationRepository clientRegistrationRepository() { return new InMemoryReactiveClientRegistrationRepository(this.clientRegistration); } } @Configuration static class OAuth2ClientCustomConfig { ReactiveAuthenticationManager manager = mock(ReactiveAuthenticationManager.class); ServerAuthenticationConverter authenticationConverter = mock(ServerAuthenticationConverter.class); ServerAuthorizationRequestRepository authorizationRequestRepository = mock( ServerAuthorizationRequestRepository.class); ServerOAuth2AuthorizationRequestResolver resolver = mock(ServerOAuth2AuthorizationRequestResolver.class); ServerRequestCache requestCache = mock(ServerRequestCache.class); @Bean SecurityWebFilterChain springSecurityFilter(ServerHttpSecurity http) { // @formatter:off http .oauth2Client((client) -> client .authenticationConverter(this.authenticationConverter) .authenticationManager(this.manager) .authorizationRequestRepository(this.authorizationRequestRepository) .authorizationRequestResolver(this.resolver)) .requestCache((c) -> c.requestCache(this.requestCache)); // @formatter:on return http.build(); } } @Configuration static class OAuth2ClientInLambdaCustomConfig { ReactiveAuthenticationManager manager = mock(ReactiveAuthenticationManager.class); ServerAuthenticationConverter authenticationConverter = mock(ServerAuthenticationConverter.class); ServerAuthorizationRequestRepository authorizationRequestRepository = mock( ServerAuthorizationRequestRepository.class); ServerRequestCache requestCache = mock(ServerRequestCache.class); @Bean SecurityWebFilterChain springSecurityFilter(ServerHttpSecurity http) { // @formatter:off http .oauth2Client((oauth2Client) -> oauth2Client .authenticationConverter(this.authenticationConverter) .authenticationManager(this.manager) .authorizationRequestRepository(this.authorizationRequestRepository)) .requestCache((c) -> c.requestCache(this.requestCache)); // @formatter:on return http.build(); } } @Configuration @EnableWebFlux @EnableWebFluxSecurity static class OAuth2ClientBeanConfig { @Bean SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) { // @formatter:off http .oauth2Client((oauth2Client) -> oauth2Client .authorizationRequestRepository(authorizationRequestRepository()) ); // @formatter:on return http.build(); } @Bean @SuppressWarnings("unchecked") ServerAuthorizationRequestRepository authorizationRequestRepository() { return mock(ServerAuthorizationRequestRepository.class); } @Bean @SuppressWarnings("unchecked") ReactiveOAuth2AccessTokenResponseClient authorizationCodeAccessTokenResponseClient() { return mock(ReactiveOAuth2AccessTokenResponseClient.class); } @Bean ReactiveClientRegistrationRepository clientRegistrationRepository() { return mock(ReactiveClientRegistrationRepository.class); } @Bean ServerOAuth2AuthorizedClientRepository authorizedClientRepository() { return mock(ServerOAuth2AuthorizedClientRepository.class); } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/web/server/OAuth2LoginTests.java ================================================ /* * 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 org.springframework.security.config.web.server; import java.util.Collections; import java.util.HashMap; import java.util.Map; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.stubbing.Answer; import org.openqa.selenium.WebDriver; import reactor.core.publisher.Mono; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.security.authentication.ReactiveAuthenticationManager; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.authentication.UserDetailsRepositoryReactiveAuthenticationManager; import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; import org.springframework.security.config.oauth2.client.CommonOAuth2Provider; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.config.users.ReactiveAuthenticationTestConfiguration; import org.springframework.security.config.web.server.ServerHttpSecurity.OAuth2LoginSpec.OidcSessionRegistryAuthenticationWebFilter; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextImpl; import org.springframework.security.core.userdetails.ReactiveUserDetailsService; import org.springframework.security.htmlunit.server.WebTestClientHtmlUnitDriverBuilder; import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; import org.springframework.security.oauth2.client.annotation.RegisteredOAuth2AuthorizedClient; import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; import org.springframework.security.oauth2.client.authentication.OAuth2AuthorizationCodeAuthenticationToken; import org.springframework.security.oauth2.client.authentication.OAuth2LoginAuthenticationToken; import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest; import org.springframework.security.oauth2.client.endpoint.ReactiveOAuth2AccessTokenResponseClient; import org.springframework.security.oauth2.client.oidc.authentication.OidcAuthorizationCodeReactiveAuthenticationManager; import org.springframework.security.oauth2.client.oidc.server.session.ReactiveOidcSessionRegistry; import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest; import org.springframework.security.oauth2.client.oidc.web.server.logout.OidcClientInitiatedServerLogoutSuccessHandler; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.InMemoryReactiveClientRegistrationRepository; import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository; import org.springframework.security.oauth2.client.registration.TestClientRegistrations; import org.springframework.security.oauth2.client.userinfo.DefaultReactiveOAuth2UserService; import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; import org.springframework.security.oauth2.client.userinfo.ReactiveOAuth2UserService; import org.springframework.security.oauth2.client.web.server.DefaultServerOAuth2AuthorizationRequestResolver; import org.springframework.security.oauth2.client.web.server.ServerAuthorizationRequestRepository; import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizationRequestResolver; import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository; import org.springframework.security.oauth2.core.OAuth2AccessToken; import org.springframework.security.oauth2.core.OAuth2AuthenticationException; import org.springframework.security.oauth2.core.OAuth2Error; import org.springframework.security.oauth2.core.TestOAuth2AccessTokens; import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationExchange; import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponse; import org.springframework.security.oauth2.core.endpoint.TestOAuth2AuthorizationExchanges; import org.springframework.security.oauth2.core.endpoint.TestOAuth2AuthorizationRequests; import org.springframework.security.oauth2.core.endpoint.TestOAuth2AuthorizationResponses; import org.springframework.security.oauth2.core.oidc.IdTokenClaimNames; import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames; import org.springframework.security.oauth2.core.oidc.user.OidcUser; import org.springframework.security.oauth2.core.oidc.user.TestOidcUsers; import org.springframework.security.oauth2.core.user.DefaultOAuth2User; import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.security.oauth2.core.user.TestOAuth2Users; import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.security.oauth2.jwt.JwtValidationException; import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder; import org.springframework.security.oauth2.jwt.ReactiveJwtDecoderFactory; import org.springframework.security.oauth2.jwt.TestJwts; import org.springframework.security.test.web.reactive.server.WebTestClientBuilder; import org.springframework.security.web.server.SecurityWebFilterChain; import org.springframework.security.web.server.WebFilterChainProxy; import org.springframework.security.web.server.WebFilterExchange; import org.springframework.security.web.server.authentication.RedirectServerAuthenticationFailureHandler; import org.springframework.security.web.server.authentication.RedirectServerAuthenticationSuccessHandler; import org.springframework.security.web.server.authentication.ServerAuthenticationConverter; import org.springframework.security.web.server.authentication.ServerAuthenticationFailureHandler; import org.springframework.security.web.server.authentication.ServerAuthenticationSuccessHandler; import org.springframework.security.web.server.authentication.logout.SecurityContextServerLogoutHandler; import org.springframework.security.web.server.context.ServerSecurityContextRepository; import org.springframework.security.web.server.savedrequest.ServerRequestCache; import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher; import org.springframework.test.web.reactive.server.WebTestClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.reactive.config.EnableWebFlux; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.WebFilter; import org.springframework.web.server.WebFilterChain; import org.springframework.web.server.WebHandler; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.springframework.security.config.Customizer.withDefaults; /** * @author Rob Winch * @author Eddú Meléndez * @since 5.1 */ @ExtendWith(SpringTestContextExtension.class) public class OAuth2LoginTests { public final SpringTestContext spring = new SpringTestContext(this); private WebTestClient client; @Autowired private WebFilterChainProxy springSecurity; private static ClientRegistration github = CommonOAuth2Provider.GITHUB.getBuilder("github") .clientId("client") .clientSecret("secret") .build(); private static ClientRegistration google = CommonOAuth2Provider.GOOGLE.getBuilder("google") .clientId("client") .clientSecret("secret") .build(); // @formatter:off private static ClientRegistration clientCredentials = TestClientRegistrations.clientCredentials() .build(); // @formatter:on @Autowired public void setApplicationContext(ApplicationContext context) { if (context.getBeanNamesForType(WebHandler.class).length > 0) { // @formatter:off this.client = WebTestClient .bindToApplicationContext(context) .build(); // @formatter:on } } @Test public void defaultLoginPageWithMultipleClientRegistrationsThenLinks() { this.spring.register(OAuth2LoginWithMultipleClientRegistrations.class).autowire(); // @formatter:off WebTestClient webTestClient = WebTestClientBuilder .bindToWebFilters(this.springSecurity) .build(); WebDriver driver = WebTestClientHtmlUnitDriverBuilder .webTestClientSetup(webTestClient) .build(); FormLoginTests.DefaultLoginPage loginPage = FormLoginTests.HomePage.to(driver, FormLoginTests.DefaultLoginPage.class) .assertAt() .assertLoginFormNotPresent() .oauth2Login() .assertClientRegistrationByName(OAuth2LoginTests.github.getClientName()) .and(); // @formatter:on } @Test public void defaultLoginPageWithSingleClientRegistrationThenRedirect() { this.spring.register(OAuth2LoginWithSingleClientRegistrations.class).autowire(); // @formatter:off WebTestClient webTestClient = WebTestClientBuilder .bindToWebFilters(new GitHubWebFilter(), this.springSecurity) .build(); WebDriver driver = WebTestClientHtmlUnitDriverBuilder .webTestClientSetup(webTestClient) .build(); // @formatter:on driver.get("http://localhost/"); assertThat(driver.getCurrentUrl()).startsWith("https://github.com/login/oauth/authorize"); } // gh-9457 @Test public void defaultLoginPageWithAuthorizationCodeAndClientCredentialsClientRegistrationThenRedirect() { this.spring.register(OAuth2LoginWithAuthorizationCodeAndClientCredentialsClientRegistration.class).autowire(); // @formatter:off WebTestClient webTestClient = WebTestClientBuilder .bindToWebFilters(new GitHubWebFilter(), this.springSecurity) .build(); WebDriver driver = WebTestClientHtmlUnitDriverBuilder .webTestClientSetup(webTestClient) .build(); // @formatter:on driver.get("http://localhost/"); assertThat(driver.getCurrentUrl()).startsWith("https://github.com/login/oauth/authorize"); } @Test public void defaultLoginPageWithSingleClientRegistrationAndFormLoginThenLinks() { this.spring.register(OAuth2LoginWithSingleClientRegistrations.class, OAuth2LoginWithFormLogin.class).autowire(); // @formatter:off WebTestClient webTestClient = WebTestClientBuilder .bindToWebFilters(new GitHubWebFilter(), this.springSecurity) .build(); WebDriver driver = WebTestClientHtmlUnitDriverBuilder .webTestClientSetup(webTestClient) .build(); FormLoginTests.HomePage.to(driver, FormLoginTests.DefaultLoginPage.class) .assertAt() .assertLoginFormPresent() .oauth2Login() .assertClientRegistrationByName(OAuth2LoginTests.github.getClientName()); // @formatter:on } // gh-8118 @Test public void defaultLoginPageWithSingleClientRegistrationAndXhrRequestThenDoesNotRedirectForAuthorization() { this.spring.register(OAuth2LoginWithSingleClientRegistrations.class, WebFluxConfig.class).autowire(); // @formatter:off this.client.get() .uri("/") .header("X-Requested-With", "XMLHttpRequest") .exchange() .expectStatus().is3xxRedirection() .expectHeader().valueEquals(HttpHeaders.LOCATION, "/login"); // @formatter:on } @Test public void defaultLoginPageWithOAuth2LoginHttpBasicAndXhrRequestThenUnauthorized() { this.spring .register(OAuth2LoginWithSingleClientRegistrations.class, OAuth2LoginWithHttpBasic.class, WebFluxConfig.class) .autowire(); // @formatter:off this.client.get() .uri("/") .header("X-Requested-With", "XMLHttpRequest") .exchange() .expectStatus().isUnauthorized(); // @formatter:on } @Test public void defaultLoginPageWhenCustomLoginPageThenGeneratedLoginPageDoesNotExist() { this.spring .register(OAuth2LoginWithSingleClientRegistrations.class, OAuth2LoginWithCustomLoginPage.class, WebFluxConfig.class) .autowire(); // @formatter:off this.client.get() .uri("/login") .exchange() .expectStatus().isNotFound(); // @formatter:on } @Test public void oauth2LoginWhenCustomLoginPageAndSingleClientRegistrationThenRedirectsToLoginPage() { this.spring .register(OAuth2LoginWithSingleClientRegistrations.class, OAuth2LoginWithCustomLoginPage.class, WebFluxConfig.class) .autowire(); // @formatter:off this.client.get() .uri("/") .exchange() .expectStatus().is3xxRedirection() .expectHeader().valueEquals(HttpHeaders.LOCATION, "/login"); // @formatter:on } @Test public void oauth2LoginWhenCustomLoginPageAndMultipleClientRegistrationsThenRedirectsToLoginPage() { this.spring .register(OAuth2LoginWithMultipleClientRegistrations.class, OAuth2LoginWithCustomLoginPage.class, WebFluxConfig.class) .autowire(); // @formatter:off this.client.get() .uri("/") .exchange() .expectStatus().is3xxRedirection() .expectHeader().valueEquals(HttpHeaders.LOCATION, "/login"); // @formatter:on } @Test public void oauth2LoginWhenProviderLoginPageAndMultipleClientRegistrationsThenRedirectsToProvider() { this.spring .register(OAuth2LoginWithMultipleClientRegistrations.class, OAuth2LoginWithProviderLoginPage.class, WebFluxConfig.class) .autowire(); // @formatter:off this.client.get() .uri("/") .exchange() .expectStatus().is3xxRedirection() .expectHeader().valueEquals(HttpHeaders.LOCATION, "/oauth2/authorization/github"); // @formatter:on } @Test public void oauth2AuthorizeWhenCustomObjectsThenUsed() { this.spring .register(OAuth2LoginWithSingleClientRegistrations.class, OAuth2AuthorizeWithMockObjectsConfig.class, AuthorizedClientController.class) .autowire(); OAuth2AuthorizeWithMockObjectsConfig config = this.spring.getContext() .getBean(OAuth2AuthorizeWithMockObjectsConfig.class); ServerOAuth2AuthorizedClientRepository authorizedClientRepository = config.authorizedClientRepository; ServerAuthorizationRequestRepository authorizationRequestRepository = config.authorizationRequestRepository; ServerRequestCache requestCache = config.requestCache; given(authorizedClientRepository.loadAuthorizedClient(any(), any(), any())).willReturn(Mono.empty()); given(authorizationRequestRepository.saveAuthorizationRequest(any(), any())).willReturn(Mono.empty()); given(requestCache.removeMatchingRequest(any())).willReturn(Mono.empty()); given(requestCache.saveRequest(any())).willReturn(Mono.empty()); // @formatter:off this.client.get() .uri("/") .exchange() .expectStatus().is3xxRedirection(); // @formatter:on verify(authorizedClientRepository).loadAuthorizedClient(any(), any(), any()); verify(authorizationRequestRepository).saveAuthorizationRequest(any(), any()); verify(requestCache).saveRequest(any()); } @Test public void oauth2LoginWhenCustomObjectsThenUsed() { this.spring .register(OAuth2LoginWithSingleClientRegistrations.class, OAuth2LoginMockAuthenticationManagerConfig.class) .autowire(); String redirectLocation = "/custom-redirect-location"; WebTestClient webTestClient = WebTestClientBuilder.bindToWebFilters(this.springSecurity).build(); OAuth2LoginMockAuthenticationManagerConfig config = this.spring.getContext() .getBean(OAuth2LoginMockAuthenticationManagerConfig.class); ServerAuthenticationConverter converter = config.authenticationConverter; ReactiveAuthenticationManager manager = config.manager; ServerWebExchangeMatcher matcher = config.matcher; ServerOAuth2AuthorizationRequestResolver resolver = config.resolver; ServerAuthenticationSuccessHandler successHandler = config.successHandler; OAuth2AuthorizationExchange exchange = TestOAuth2AuthorizationExchanges.success(); OAuth2User user = TestOAuth2Users.create(); OAuth2AccessToken accessToken = TestOAuth2AccessTokens.noScopes(); OAuth2LoginAuthenticationToken result = new OAuth2LoginAuthenticationToken(github, exchange, user, user.getAuthorities(), accessToken); given(converter.convert(any())).willReturn(Mono.just(new TestingAuthenticationToken("a", "b", "c"))); given(manager.authenticate(any())).willReturn(Mono.just(result)); given(matcher.matches(any())).willReturn(ServerWebExchangeMatcher.MatchResult.match()); given(resolver.resolve(any())).willReturn(Mono.empty()); given(successHandler.onAuthenticationSuccess(any(), any())).willAnswer((Answer>) (invocation) -> { WebFilterExchange webFilterExchange = invocation.getArgument(0); Authentication authentication = invocation.getArgument(1); return new RedirectServerAuthenticationSuccessHandler(redirectLocation) .onAuthenticationSuccess(webFilterExchange, authentication); }); // @formatter:off webTestClient.get() .uri("/login/oauth2/code/github") .exchange() .expectStatus().is3xxRedirection() .expectHeader().valueEquals("Location", redirectLocation); // @formatter:on verify(converter).convert(any()); verify(manager).authenticate(any()); verify(matcher).matches(any()); verify(resolver).resolve(any()); verify(successHandler).onAuthenticationSuccess(any(), any()); } @Test public void oauth2LoginFailsWhenCustomObjectsThenUsed() { this.spring .register(OAuth2LoginWithSingleClientRegistrations.class, OAuth2LoginMockAuthenticationManagerConfig.class) .autowire(); String redirectLocation = "/custom-redirect-location"; String failureRedirectLocation = "/failure-redirect-location"; // @formatter:off WebTestClient webTestClient = WebTestClientBuilder .bindToWebFilters(this.springSecurity) .build(); // @formatter:on OAuth2LoginMockAuthenticationManagerConfig config = this.spring.getContext() .getBean(OAuth2LoginMockAuthenticationManagerConfig.class); ServerAuthenticationConverter converter = config.authenticationConverter; ReactiveAuthenticationManager manager = config.manager; ServerWebExchangeMatcher matcher = config.matcher; ServerOAuth2AuthorizationRequestResolver resolver = config.resolver; ServerAuthenticationSuccessHandler successHandler = config.successHandler; ServerAuthenticationFailureHandler failureHandler = config.failureHandler; given(converter.convert(any())).willReturn(Mono.just(new TestingAuthenticationToken("a", "b", "c"))); given(manager.authenticate(any())) .willReturn(Mono.error(new OAuth2AuthenticationException(new OAuth2Error("error"), "message"))); given(matcher.matches(any())).willReturn(ServerWebExchangeMatcher.MatchResult.match()); given(resolver.resolve(any())).willReturn(Mono.empty()); given(successHandler.onAuthenticationSuccess(any(), any())).willAnswer((Answer>) (invocation) -> { WebFilterExchange webFilterExchange = invocation.getArgument(0); Authentication authentication = invocation.getArgument(1); return new RedirectServerAuthenticationSuccessHandler(redirectLocation) .onAuthenticationSuccess(webFilterExchange, authentication); }); given(failureHandler.onAuthenticationFailure(any(), any())).willAnswer((Answer>) (invocation) -> { WebFilterExchange webFilterExchange = invocation.getArgument(0); AuthenticationException authenticationException = invocation.getArgument(1); return new RedirectServerAuthenticationFailureHandler(failureRedirectLocation) .onAuthenticationFailure(webFilterExchange, authenticationException); }); // @formatter:off webTestClient.get() .uri("/login/oauth2/code/github") .exchange() .expectStatus().is3xxRedirection() .expectHeader().valueEquals("Location", failureRedirectLocation); // @formatter:on verify(converter).convert(any()); verify(manager).authenticate(any()); verify(matcher).matches(any()); verify(resolver).resolve(any()); verify(failureHandler).onAuthenticationFailure(any(), any()); } @Test public void oauth2LoginWhenCustomObjectsInLambdaThenUsed() { this.spring .register(OAuth2LoginWithSingleClientRegistrations.class, OAuth2LoginMockAuthenticationManagerInLambdaConfig.class) .autowire(); String redirectLocation = "/custom-redirect-location"; WebTestClient webTestClient = WebTestClientBuilder.bindToWebFilters(this.springSecurity).build(); OAuth2LoginMockAuthenticationManagerInLambdaConfig config = this.spring.getContext() .getBean(OAuth2LoginMockAuthenticationManagerInLambdaConfig.class); ServerAuthenticationConverter converter = config.authenticationConverter; ReactiveAuthenticationManager manager = config.manager; ServerWebExchangeMatcher matcher = config.matcher; ServerOAuth2AuthorizationRequestResolver resolver = config.resolver; ServerAuthenticationSuccessHandler successHandler = config.successHandler; OAuth2AuthorizationExchange exchange = TestOAuth2AuthorizationExchanges.success(); OAuth2User user = TestOAuth2Users.create(); OAuth2AccessToken accessToken = TestOAuth2AccessTokens.noScopes(); OAuth2LoginAuthenticationToken result = new OAuth2LoginAuthenticationToken(github, exchange, user, user.getAuthorities(), accessToken); given(converter.convert(any())).willReturn(Mono.just(new TestingAuthenticationToken("a", "b", "c"))); given(manager.authenticate(any())).willReturn(Mono.just(result)); given(matcher.matches(any())).willReturn(ServerWebExchangeMatcher.MatchResult.match()); given(resolver.resolve(any())).willReturn(Mono.empty()); given(successHandler.onAuthenticationSuccess(any(), any())).willAnswer((Answer>) (invocation) -> { WebFilterExchange webFilterExchange = invocation.getArgument(0); Authentication authentication = invocation.getArgument(1); return new RedirectServerAuthenticationSuccessHandler(redirectLocation) .onAuthenticationSuccess(webFilterExchange, authentication); }); // @formatter:off webTestClient.get() .uri("/login/oauth2/code/github") .exchange() .expectStatus().is3xxRedirection() .expectHeader().valueEquals("Location", redirectLocation); // @formatter:on verify(converter).convert(any()); verify(manager).authenticate(any()); verify(matcher).matches(any()); verify(resolver).resolve(any()); verify(successHandler).onAuthenticationSuccess(any(), any()); } @Test public void oauth2LoginWhenCustomBeansThenUsed() { this.spring.register(OAuth2LoginWithMultipleClientRegistrations.class, OAuth2LoginWithCustomBeansConfig.class) .autowire(); // @formatter:off WebTestClient webTestClient = WebTestClientBuilder .bindToWebFilters(this.springSecurity) .build(); // @formatter:on OAuth2LoginWithCustomBeansConfig config = this.spring.getContext() .getBean(OAuth2LoginWithCustomBeansConfig.class); OAuth2AuthorizationRequest request = TestOAuth2AuthorizationRequests.request().scope("openid").build(); OAuth2AuthorizationResponse response = TestOAuth2AuthorizationResponses.success().build(); OAuth2AuthorizationExchange exchange = new OAuth2AuthorizationExchange(request, response); OAuth2AccessToken accessToken = TestOAuth2AccessTokens.scopes("openid"); OAuth2AuthorizationCodeAuthenticationToken token = new OAuth2AuthorizationCodeAuthenticationToken(google, exchange, accessToken); ServerAuthenticationConverter converter = config.authenticationConverter; given(converter.convert(any())).willReturn(Mono.just(token)); ServerSecurityContextRepository securityContextRepository = config.securityContextRepository; given(securityContextRepository.save(any(), any())).willReturn(Mono.empty()); given(securityContextRepository.load(any())).willReturn(authentication(token)); Map additionalParameters = new HashMap<>(); additionalParameters.put(OidcParameterNames.ID_TOKEN, "id-token"); // @formatter:off OAuth2AccessTokenResponse accessTokenResponse = OAuth2AccessTokenResponse .withToken(accessToken.getTokenValue()) .tokenType(accessToken.getTokenType()) .scopes(accessToken.getScopes()) .additionalParameters(additionalParameters) .build(); // @formatter:on ReactiveOAuth2AccessTokenResponseClient tokenResponseClient = config.tokenResponseClient; given(tokenResponseClient.getTokenResponse(any())).willReturn(Mono.just(accessTokenResponse)); OidcUser user = TestOidcUsers.create(); ReactiveOAuth2UserService userService = config.userService; given(userService.loadUser(any())).willReturn(Mono.just(user)); ServerOAuth2AuthorizationRequestResolver authorizationRequestResolver = config.authorizationRequestResolver; // @formatter:off webTestClient.get() .uri("/login/oauth2/code/google") .exchange() .expectStatus().is3xxRedirection(); // @formatter:on verify(config.jwtDecoderFactory).createDecoder(any()); verify(tokenResponseClient).getTokenResponse(any()); verify(securityContextRepository).save(any(), any()); verify(authorizationRequestResolver).resolve(any()); } // gh-5562 @Test public void oauth2LoginWhenAccessTokenRequestFailsThenDefaultRedirectToLogin() { this.spring.register(OAuth2LoginWithMultipleClientRegistrations.class, OAuth2LoginWithCustomBeansConfig.class) .autowire(); // @formatter:off WebTestClient webTestClient = WebTestClientBuilder .bindToWebFilters(this.springSecurity) .build(); OAuth2AuthorizationRequest request = TestOAuth2AuthorizationRequests .request() .scope("openid") .build(); // @formatter:on OAuth2AuthorizationResponse response = TestOAuth2AuthorizationResponses.success().build(); OAuth2AuthorizationExchange exchange = new OAuth2AuthorizationExchange(request, response); OAuth2AccessToken accessToken = TestOAuth2AccessTokens.scopes("openid"); OAuth2AuthorizationCodeAuthenticationToken authenticationToken = new OAuth2AuthorizationCodeAuthenticationToken( google, exchange, accessToken); OAuth2LoginWithCustomBeansConfig config = this.spring.getContext() .getBean(OAuth2LoginWithCustomBeansConfig.class); ServerAuthenticationConverter converter = config.authenticationConverter; given(converter.convert(any())).willReturn(Mono.just(authenticationToken)); ReactiveOAuth2AccessTokenResponseClient tokenResponseClient = config.tokenResponseClient; OAuth2Error oauth2Error = new OAuth2Error("invalid_request", "Invalid request", null); given(tokenResponseClient.getTokenResponse(any())).willThrow(new OAuth2AuthenticationException(oauth2Error)); // @formatter:off webTestClient.get() .uri("/login/oauth2/code/google") .exchange() .expectStatus().is3xxRedirection() .expectHeader().valueEquals("Location", "/login?error"); // @formatter:on } // gh-6484 @Test public void oauth2LoginWhenIdTokenValidationFailsThenDefaultRedirectToLogin() { this.spring.register(OAuth2LoginWithMultipleClientRegistrations.class, OAuth2LoginWithCustomBeansConfig.class) .autowire(); WebTestClient webTestClient = WebTestClientBuilder.bindToWebFilters(this.springSecurity).build(); OAuth2LoginWithCustomBeansConfig config = this.spring.getContext() .getBean(OAuth2LoginWithCustomBeansConfig.class); // @formatter:off OAuth2AuthorizationRequest request = TestOAuth2AuthorizationRequests .request() .scope("openid") .build(); OAuth2AuthorizationResponse response = TestOAuth2AuthorizationResponses .success() .build(); // @formatter:on OAuth2AuthorizationExchange exchange = new OAuth2AuthorizationExchange(request, response); OAuth2AccessToken accessToken = TestOAuth2AccessTokens.scopes("openid"); OAuth2AuthorizationCodeAuthenticationToken authenticationToken = new OAuth2AuthorizationCodeAuthenticationToken( google, exchange, accessToken); ServerAuthenticationConverter converter = config.authenticationConverter; given(converter.convert(any())).willReturn(Mono.just(authenticationToken)); Map additionalParameters = new HashMap<>(); additionalParameters.put(OidcParameterNames.ID_TOKEN, "id-token"); // @formatter:off OAuth2AccessTokenResponse accessTokenResponse = OAuth2AccessTokenResponse .withToken(accessToken.getTokenValue()) .tokenType(accessToken.getTokenType()) .scopes(accessToken.getScopes()) .additionalParameters(additionalParameters) .build(); // @formatter:on ReactiveOAuth2AccessTokenResponseClient tokenResponseClient = config.tokenResponseClient; given(tokenResponseClient.getTokenResponse(any())).willReturn(Mono.just(accessTokenResponse)); ReactiveJwtDecoderFactory jwtDecoderFactory = config.jwtDecoderFactory; OAuth2Error oauth2Error = new OAuth2Error("invalid_id_token", "Invalid ID Token", null); given(jwtDecoderFactory.createDecoder(any())).willReturn((token) -> Mono .error(new JwtValidationException("ID Token validation failed", Collections.singleton(oauth2Error)))); // @formatter:off webTestClient.get() .uri("/login/oauth2/code/google") .exchange() .expectStatus().is3xxRedirection() .expectHeader().valueEquals("Location", "/login?error"); // @formatter:on } @Test public void logoutWhenUsingOidcLogoutHandlerThenRedirects() { this.spring.register(OAuth2LoginConfigWithOidcLogoutSuccessHandler.class).autowire(); OAuth2AuthenticationToken token = new OAuth2AuthenticationToken(TestOidcUsers.create(), AuthorityUtils.NO_AUTHORITIES, getBean(ClientRegistration.class).getRegistrationId()); ServerSecurityContextRepository repository = getBean(ServerSecurityContextRepository.class); given(repository.load(any())).willReturn(authentication(token)); // @formatter:off this.client.post() .uri("/logout") .exchange() .expectHeader().valueEquals("Location", "https://logout?id_token_hint=id-token"); // @formatter:on } // gh-8609 @Test public void oauth2LoginWhenAuthenticationConverterFailsThenDefaultRedirectToLogin() { this.spring.register(OAuth2LoginWithMultipleClientRegistrations.class).autowire(); WebTestClient webTestClient = WebTestClientBuilder.bindToWebFilters(this.springSecurity).build(); // @formatter:off webTestClient.get() .uri("/login/oauth2/code/google") .exchange() .expectStatus().is3xxRedirection() .expectHeader().valueEquals("Location", "/login?error"); // @formatter:on } @Test public void oauth2LoginWhenOidcSessionRegistryThenUses() { this.spring.register(OAuth2LoginWithOidcSessionRegistry.class).autowire(); SecurityWebFilterChain chain = this.spring.getContext().getBean(SecurityWebFilterChain.class); assertThat(chain.getWebFilters() .filter((filter) -> filter instanceof OidcSessionRegistryAuthenticationWebFilter) .collectList() .block()).isNotEmpty(); } // gh-14558 @Test public void oauth2LoginWhenDefaultsThenNoOidcSessionRegistry() { this.spring.register(OAuth2LoginWithSingleClientRegistrations.class, OAuth2LoginConfig.class).autowire(); SecurityWebFilterChain chain = this.spring.getContext().getBean(SecurityWebFilterChain.class); assertThat(chain.getWebFilters() .filter((filter) -> filter instanceof OidcSessionRegistryAuthenticationWebFilter) .collectList() .block()).isEmpty(); } @Test public void oauth2LoginWhenOauth2UserServiceBeanPresent() { this.spring.register(OAuth2LoginWithMultipleClientRegistrations.class, OAuth2LoginWithOauth2UserService.class) .autowire(); WebTestClient webTestClient = WebTestClientBuilder.bindToWebFilters(this.springSecurity).build(); OAuth2LoginWithOauth2UserService config = this.spring.getContext() .getBean(OAuth2LoginWithOauth2UserService.class); OAuth2AuthorizationRequest request = TestOAuth2AuthorizationRequests.request().scope("openid").build(); OAuth2AuthorizationResponse response = TestOAuth2AuthorizationResponses.success().build(); OAuth2AuthorizationExchange exchange = new OAuth2AuthorizationExchange(request, response); OAuth2AccessToken accessToken = TestOAuth2AccessTokens.scopes("openid"); OAuth2AuthorizationCodeAuthenticationToken token = new OAuth2AuthorizationCodeAuthenticationToken(google, exchange, accessToken); ServerAuthenticationConverter converter = config.authenticationConverter; given(converter.convert(any())).willReturn(Mono.just(token)); ServerSecurityContextRepository securityContextRepository = config.securityContextRepository; given(securityContextRepository.save(any(), any())).willReturn(Mono.empty()); given(securityContextRepository.load(any())).willReturn(authentication(token)); Map additionalParameters = new HashMap<>(); additionalParameters.put(OidcParameterNames.ID_TOKEN, "id-token"); OAuth2AccessTokenResponse accessTokenResponse = OAuth2AccessTokenResponse.withToken(accessToken.getTokenValue()) .tokenType(accessToken.getTokenType()) .scopes(accessToken.getScopes()) .additionalParameters(additionalParameters) .build(); ReactiveOAuth2AccessTokenResponseClient tokenResponseClient = config.tokenResponseClient; given(tokenResponseClient.getTokenResponse(any())).willReturn(Mono.just(accessTokenResponse)); ReactiveOAuth2UserService userService = config.reactiveOAuth2UserService; given(userService.loadUser(any())).willReturn(Mono .just(new DefaultOAuth2User(AuthorityUtils.createAuthorityList("USER"), Map.of("sub", "subject"), "sub"))); webTestClient.get().uri("/login/oauth2/code/google").exchange().expectStatus().is3xxRedirection(); verify(userService).loadUser(any()); } Mono authentication(Authentication authentication) { SecurityContext context = new SecurityContextImpl(); context.setAuthentication(authentication); return Mono.just(context); } T getBean(Class beanClass) { return this.spring.getContext().getBean(beanClass); } @Configuration static class OAuth2LoginWithOauth2UserService { ReactiveOAuth2AccessTokenResponseClient tokenResponseClient = mock( ReactiveOAuth2AccessTokenResponseClient.class); ReactiveOAuth2UserService reactiveOAuth2UserService = mock( DefaultReactiveOAuth2UserService.class); ServerAuthenticationConverter authenticationConverter = mock(ServerAuthenticationConverter.class); ServerSecurityContextRepository securityContextRepository = mock(ServerSecurityContextRepository.class); @Bean SecurityWebFilterChain springSecurity(ServerHttpSecurity http) { http.authorizeExchange((authorize) -> authorize.anyExchange().authenticated()) .oauth2Login((c) -> c.authenticationConverter(this.authenticationConverter) .securityContextRepository(this.securityContextRepository)); return http.build(); } @Bean ReactiveOAuth2UserService customOAuth2UserService() { return this.reactiveOAuth2UserService; } @Bean ReactiveJwtDecoderFactory jwtDecoderFactory() { return (clientRegistration) -> (token) -> { Map claims = new HashMap<>(); claims.put(IdTokenClaimNames.SUB, "subject"); claims.put(IdTokenClaimNames.ISS, "http://localhost/issuer"); claims.put(IdTokenClaimNames.AUD, Collections.singletonList("client")); claims.put(IdTokenClaimNames.AZP, "client"); return Mono.just(TestJwts.jwt().claims((c) -> c.putAll(claims)).build()); }; } @Bean ReactiveOAuth2AccessTokenResponseClient requestReactiveOAuth2AccessTokenResponseClient() { return this.tokenResponseClient; } } @Configuration @EnableWebFluxSecurity static class OAuth2LoginWithMultipleClientRegistrations { @Bean InMemoryReactiveClientRegistrationRepository clientRegistrationRepository() { return new InMemoryReactiveClientRegistrationRepository(github, google); } } @EnableWebFlux static class WebFluxConfig { } @Configuration @EnableWebFluxSecurity static class OAuth2LoginWithSingleClientRegistrations { @Bean InMemoryReactiveClientRegistrationRepository clientRegistrationRepository() { return new InMemoryReactiveClientRegistrationRepository(github); } } @Configuration @EnableWebFluxSecurity static class OAuth2LoginWithAuthorizationCodeAndClientCredentialsClientRegistration { @Bean InMemoryReactiveClientRegistrationRepository clientRegistrationRepository() { return new InMemoryReactiveClientRegistrationRepository(github, clientCredentials); } } @EnableWebFlux static class OAuth2LoginConfig { @Bean SecurityWebFilterChain springSecurity(ServerHttpSecurity http) { // @formatter:off http .authorizeExchange((authorize) -> authorize.anyExchange().authenticated()) .oauth2Login(Customizer.withDefaults()); // @formatter:on return http.build(); } } @EnableWebFlux static class OAuth2AuthorizeWithMockObjectsConfig { ServerOAuth2AuthorizedClientRepository authorizedClientRepository = mock( ServerOAuth2AuthorizedClientRepository.class); ServerAuthorizationRequestRepository authorizationRequestRepository = mock( ServerAuthorizationRequestRepository.class); ServerRequestCache requestCache = mock(ServerRequestCache.class); @Bean SecurityWebFilterChain springSecurity(ServerHttpSecurity http) { // @formatter:off http .requestCache((cache) -> cache .requestCache(this.requestCache)) .oauth2Login((login) -> login .authorizationRequestRepository(this.authorizationRequestRepository)); // @formatter:on return http.build(); } @Bean ServerOAuth2AuthorizedClientRepository authorizedClientRepository() { return this.authorizedClientRepository; } } @RestController static class AuthorizedClientController { @GetMapping("/") String home(@RegisteredOAuth2AuthorizedClient("github") OAuth2AuthorizedClient authorizedClient) { return "home"; } } @Configuration static class OAuth2LoginWithFormLogin { @Bean SecurityWebFilterChain springSecurityFilter(ServerHttpSecurity http) { ReactiveUserDetailsService reactiveUserDetailsService = ReactiveAuthenticationTestConfiguration .userDetailsService(); ReactiveAuthenticationManager authenticationManager = new UserDetailsRepositoryReactiveAuthenticationManager( reactiveUserDetailsService); http.authenticationManager(authenticationManager); // @formatter:off http .authorizeExchange((authorize) -> authorize .anyExchange().authenticated()) .oauth2Login(withDefaults()) .formLogin(withDefaults()); // @formatter:on return http.build(); } } @Configuration static class OAuth2LoginWithHttpBasic { @Bean SecurityWebFilterChain springSecurityFilter(ServerHttpSecurity http) { ReactiveUserDetailsService reactiveUserDetailsService = ReactiveAuthenticationTestConfiguration .userDetailsService(); ReactiveAuthenticationManager authenticationManager = new UserDetailsRepositoryReactiveAuthenticationManager( reactiveUserDetailsService); http.authenticationManager(authenticationManager); // @formatter:off http .authorizeExchange((authorize) -> authorize .anyExchange().authenticated()) .oauth2Login(withDefaults()) .httpBasic(withDefaults()); // @formatter:on return http.build(); } } @Configuration @EnableWebFluxSecurity static class OAuth2LoginWithCustomLoginPage { @Bean SecurityWebFilterChain filterChain(ServerHttpSecurity http) { // @formatter:off http .authorizeExchange((authorize) -> authorize .pathMatchers(HttpMethod.GET, "/login").permitAll() .anyExchange().authenticated() ) .oauth2Login((oauth2) -> oauth2 .loginPage("/login") ); // @formatter:on return http.build(); } } @Configuration @EnableWebFluxSecurity static class OAuth2LoginWithProviderLoginPage { @Bean SecurityWebFilterChain filterChain(ServerHttpSecurity http) { // @formatter:off http.authorizeExchange((authorize) -> authorize .anyExchange().authenticated() ) .oauth2Login((oauth2) -> oauth2 .loginPage("/oauth2/authorization/github") ); // @formatter:on return http.build(); } } @Configuration static class OAuth2LoginMockAuthenticationManagerConfig { ReactiveAuthenticationManager manager = mock(ReactiveAuthenticationManager.class); ServerAuthenticationConverter authenticationConverter = mock(ServerAuthenticationConverter.class); ServerWebExchangeMatcher matcher = mock(ServerWebExchangeMatcher.class); ServerOAuth2AuthorizationRequestResolver resolver = mock(ServerOAuth2AuthorizationRequestResolver.class); ServerAuthenticationSuccessHandler successHandler = mock(ServerAuthenticationSuccessHandler.class); ServerAuthenticationFailureHandler failureHandler = mock(ServerAuthenticationFailureHandler.class); @Bean SecurityWebFilterChain springSecurityFilter(ServerHttpSecurity http) { // @formatter:off http .authorizeExchange((authorize) -> authorize .anyExchange().authenticated()) .oauth2Login((login) -> login .authenticationConverter(this.authenticationConverter) .authenticationManager(this.manager) .authenticationMatcher(this.matcher) .authorizationRequestResolver(this.resolver) .authenticationSuccessHandler(this.successHandler) .authenticationFailureHandler(this.failureHandler)); // @formatter:on return http.build(); } } @Configuration static class OAuth2LoginMockAuthenticationManagerInLambdaConfig { ReactiveAuthenticationManager manager = mock(ReactiveAuthenticationManager.class); ServerAuthenticationConverter authenticationConverter = mock(ServerAuthenticationConverter.class); ServerWebExchangeMatcher matcher = mock(ServerWebExchangeMatcher.class); ServerOAuth2AuthorizationRequestResolver resolver = mock(ServerOAuth2AuthorizationRequestResolver.class); ServerAuthenticationSuccessHandler successHandler = mock(ServerAuthenticationSuccessHandler.class); @Bean SecurityWebFilterChain springSecurityFilter(ServerHttpSecurity http) { // @formatter:off http .authorizeExchange((authorize) -> authorize .anyExchange().authenticated() ) .oauth2Login((oauth2) -> oauth2 .authenticationConverter(this.authenticationConverter) .authenticationManager(this.manager) .authenticationMatcher(this.matcher) .authorizationRequestResolver(this.resolver) .authenticationSuccessHandler(this.successHandler) ); // @formatter:on return http.build(); } } @Configuration static class OAuth2LoginWithCustomBeansConfig { ServerAuthenticationConverter authenticationConverter = mock(ServerAuthenticationConverter.class); ReactiveOAuth2AccessTokenResponseClient tokenResponseClient = mock( ReactiveOAuth2AccessTokenResponseClient.class); ReactiveOAuth2UserService userService = mock(ReactiveOAuth2UserService.class); ReactiveJwtDecoderFactory jwtDecoderFactory = spy(new JwtDecoderFactory()); ServerSecurityContextRepository securityContextRepository = mock(ServerSecurityContextRepository.class); ServerOAuth2AuthorizationRequestResolver authorizationRequestResolver = spy( new DefaultServerOAuth2AuthorizationRequestResolver(new InMemoryReactiveClientRegistrationRepository( TestClientRegistrations.clientRegistration().build()))); @Bean SecurityWebFilterChain springSecurityFilter(ServerHttpSecurity http) { // @formatter:off http .authorizeExchange((authorize) -> authorize .anyExchange().authenticated()) .oauth2Login((login) -> login .authenticationConverter(this.authenticationConverter) .authenticationManager(authenticationManager()) .securityContextRepository(this.securityContextRepository)); return http.build(); // @formatter:on } private ReactiveAuthenticationManager authenticationManager() { OidcAuthorizationCodeReactiveAuthenticationManager oidc = new OidcAuthorizationCodeReactiveAuthenticationManager( this.tokenResponseClient, this.userService); oidc.setJwtDecoderFactory(jwtDecoderFactory()); return oidc; } @Bean ReactiveJwtDecoderFactory jwtDecoderFactory() { return this.jwtDecoderFactory; } @Bean ServerOAuth2AuthorizationRequestResolver authorizationRequestResolver() { return this.authorizationRequestResolver; } @Bean ReactiveOAuth2AccessTokenResponseClient accessTokenResponseClient() { return this.tokenResponseClient; } private static class JwtDecoderFactory implements ReactiveJwtDecoderFactory { @Override public ReactiveJwtDecoder createDecoder(ClientRegistration clientRegistration) { return getJwtDecoder(); } private ReactiveJwtDecoder getJwtDecoder() { return (token) -> { Map claims = new HashMap<>(); claims.put(IdTokenClaimNames.SUB, "subject"); claims.put(IdTokenClaimNames.ISS, "http://localhost/issuer"); claims.put(IdTokenClaimNames.AUD, Collections.singletonList("client")); claims.put(IdTokenClaimNames.AZP, "client"); Jwt jwt = TestJwts.jwt().claims((c) -> c.putAll(claims)).build(); return Mono.just(jwt); }; } } } @EnableWebFlux @Configuration @EnableWebFluxSecurity static class OAuth2LoginConfigWithOidcLogoutSuccessHandler { private final ServerSecurityContextRepository repository = mock(ServerSecurityContextRepository.class); private final ClientRegistration withLogout = TestClientRegistrations.clientRegistration() .providerConfigurationMetadata(Collections.singletonMap("end_session_endpoint", "https://logout")) .build(); @Bean SecurityWebFilterChain springSecurity(ServerHttpSecurity http) { // @formatter:off http .csrf((csrf) -> csrf.disable()) .logout((logout) -> logout // avoid using mock ServerSecurityContextRepository for logout .logoutHandler(new SecurityContextServerLogoutHandler()) .logoutSuccessHandler( new OidcClientInitiatedServerLogoutSuccessHandler( new InMemoryReactiveClientRegistrationRepository(this.withLogout)))) .securityContextRepository(this.repository); // @formatter:on return http.build(); } @Bean ServerSecurityContextRepository securityContextRepository() { return this.repository; } @Bean ClientRegistration clientRegistration() { return this.withLogout; } } @Configuration @EnableWebFluxSecurity static class OAuth2LoginWithOidcSessionRegistry { private final ReactiveOidcSessionRegistry registry = mock(ReactiveOidcSessionRegistry.class); private final ReactiveClientRegistrationRepository clients = new InMemoryReactiveClientRegistrationRepository( TestClientRegistrations.clientRegistration().build()); @Bean SecurityWebFilterChain filterChain(ServerHttpSecurity http) { // @formatter:off http .authorizeExchange((authorize) -> authorize.anyExchange().authenticated()) .oauth2Login((oauth2) -> oauth2 .clientRegistrationRepository(this.clients) .oidcSessionRegistry(this.registry) ); // @formatter:on return http.build(); } @Bean ReactiveOidcSessionRegistry oidcSessionRegistry() { return this.registry; } } static class GitHubWebFilter implements WebFilter { @Override public Mono filter(ServerWebExchange exchange, WebFilterChain chain) { if (exchange.getRequest().getURI().getHost().equals("github.com")) { return exchange.getResponse().setComplete(); } return chain.filter(exchange); } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/web/server/OAuth2ResourceServerSpecTests.java ================================================ /* * 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 org.springframework.security.config.web.server; import java.io.IOException; import java.math.BigInteger; import java.security.KeyFactory; import java.security.NoSuchAlgorithmException; import java.security.interfaces.RSAPublicKey; import java.security.spec.InvalidKeySpecException; import java.security.spec.RSAPublicKeySpec; import java.util.Base64; import java.util.Collections; import java.util.Optional; import java.util.stream.Collectors; import java.util.stream.Stream; import jakarta.annotation.PreDestroy; import okhttp3.mockwebserver.Dispatcher; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; import okhttp3.mockwebserver.RecordedRequest; import org.apache.http.HttpHeaders; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import reactor.core.publisher.Mono; import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.NoUniqueBeanDefinitionException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.convert.converter.Converter; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.ReactiveAuthenticationManager; import org.springframework.security.authentication.ReactiveAuthenticationManagerResolver; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.core.Authentication; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal; import org.springframework.security.oauth2.core.OAuth2AuthenticationException; import org.springframework.security.oauth2.core.OAuth2Error; import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder; import org.springframework.security.oauth2.jwt.TestJwts; import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthenticationToken; import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter; import org.springframework.security.oauth2.server.resource.authentication.ReactiveJwtAuthenticationConverter; import org.springframework.security.oauth2.server.resource.authentication.ReactiveJwtAuthenticationConverterAdapter; import org.springframework.security.oauth2.server.resource.introspection.ReactiveOpaqueTokenAuthenticationConverter; import org.springframework.security.web.server.SecurityWebFilterChain; import org.springframework.security.web.server.authentication.HttpStatusServerEntryPoint; import org.springframework.security.web.server.authentication.ServerAuthenticationConverter; import org.springframework.security.web.server.authentication.ServerAuthenticationFailureHandler; import org.springframework.security.web.server.authorization.HttpStatusServerAccessDeniedHandler; import org.springframework.test.web.reactive.server.WebTestClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.context.support.GenericWebApplicationContext; import org.springframework.web.reactive.DispatcherHandler; import org.springframework.web.reactive.config.EnableWebFlux; import org.springframework.web.server.ServerWebExchange; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.hamcrest.CoreMatchers.startsWith; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; /** * Tests for * {@link org.springframework.security.config.web.server.ServerHttpSecurity.OAuth2ResourceServerSpec} */ @ExtendWith({ SpringTestContextExtension.class }) @SuppressWarnings("removal") public class OAuth2ResourceServerSpecTests { private String expired = "eyJhbGciOiJSUzI1NiJ9.eyJleHAiOjE1MzUwMzc4OTd9.jqZDDjfc2eysX44lHXEIr9XFd2S8vjIZHCccZU-dRWMRJNsQ1QN5VNnJGklqJBXJR4qgla6cmVqPOLkUHDb0sL0nxM5XuzQaG5ZzKP81RV88shFyAiT0fD-6nl1k-Fai-Fu-VkzSpNXgeONoTxDaYhdB-yxmgrgsApgmbOTE_9AcMk-FQDXQ-pL9kynccFGV0lZx4CA7cyknKN7KBxUilfIycvXODwgKCjj_1WddLTCNGYogJJSg__7NoxzqbyWd3udbHVjqYq7GsMMrGB4_2kBD4CkghOSNcRHbT_DIXowxfAVT7PAg7Q0E5ruZsr2zPZacEUDhJ6-wbvlA0FAOUg"; private String messageReadToken = "eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJtb2NrLXN1YmplY3QiLCJzY29wZSI6Im1lc3NhZ2U6cmVhZCIsImV4cCI6NDY4ODY0MTQxM30.cRl1bv_dDYcAN5U4NlIVKj8uu4mLMwjABF93P4dShiq-GQ-owzaqTSlB4YarNFgV3PKQvT9wxN1jBpGribvISljakoC0E8wDV-saDi8WxN-qvImYsn1zLzYFiZXCfRIxCmonJpydeiAPRxMTPtwnYDS9Ib0T_iA80TBGd-INhyxUUfrwRW5sqKRbjUciRJhpp7fW2ZYXmi9iPt3HDjRQA4IloJZ7f4-spt5Q9wl5HcQTv1t4XrX4eqhVbE5cCoIkFQnKPOc-jhVM44_eazLU6Xk-CCXP8C_UT5pX0luRS2cJrVFfHp2IR_AWxC-shItg6LNEmNFD4Zc-JLZcr0Q86Q"; private String messageReadTokenWithKid = "eyJraWQiOiJvbmUiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJtb2NrLXN1YmplY3QiLCJzY29wZSI6Im1lc3NhZ2U6cmVhZCIsImV4cCI6NDY4ODY0MTQ2MX0.Arg3IjlNb_nkEIZpcWAQquvoiaeF_apJzO5ZxSzUQEWixH1Y7yrsW2uco452a7OtAKDNT09IplK8126z_hdI_RRk0CXVsGZYe1qppNIVLEPGv4rHxND4bPv1YA91Q8vG-vDk9rod7EvAuZU1tEP_pWkSkZVAmfuP43bP5FQcO6Q31Aba7Yb7O5qWn9U2MjruPSFvTsIx3hSXgTuJxhNCKeHnTCmv2WdjYWatR7-VujBlHd-ZolysXm7-JPz3kI75omnomG2UqnKkI76sczIpm4ieOp3fSyv-QR-i-3Z_eJ9hS3Ox46Y9NJS6Z-y1g3X0fjVyhLiIJkFV3VA5HrSf_A"; private String unsignedToken = "eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJleHAiOi0yMDMzMjI0OTcsImp0aSI6IjEyMyIsInR5cCI6IkpXVCJ9."; // @formatter:off private String jwkSet = "{\n" + " \"keys\":[\n" + " {\n" + " \"kty\":\"RSA\",\n" + " \"e\":\"AQAB\",\n" + " \"use\":\"sig\",\n" + " \"kid\":\"one\",\n" + " \"n\":\"0IUjrPZDz-3z0UE4ppcKU36v7hnh8FJjhu3lbJYj0qj9eZiwEJxi9HHUfSK1DhUQG7mJBbYTK1tPYCgre5EkfKh-64VhYUa-vz17zYCmuB8fFj4XHE3MLkWIG-AUn8hNbPzYYmiBTjfGnMKxLHjsbdTiF4mtn-85w366916R6midnAuiPD4HjZaZ1PAsuY60gr8bhMEDtJ8unz81hoQrozpBZJ6r8aR1PrsWb1OqPMloK9kAIutJNvWYKacp8WYAp2WWy72PxQ7Fb0eIA1br3A5dnp-Cln6JROJcZUIRJ-QvS6QONWeS2407uQmS-i-lybsqaH0ldYC7NBEBA5inPQ\"\n" + " }\n" + " ]\n" + "}\n"; // @formatter:on private Jwt jwt = TestJwts.jwt().build(); private String clientId = "client"; private String clientSecret = "secret"; // @formatter:off private String active = "{\n" + " \"active\": true,\n" + " \"client_id\": \"l238j323ds-23ij4\",\n" + " \"username\": \"jdoe\",\n" + " \"scope\": \"read write dolphin\",\n" + " \"sub\": \"Z5O3upPC88QrAjx00dis\",\n" + " \"aud\": \"https://protected.example.net/resource\",\n" + " \"iss\": \"https://server.example.com/\",\n" + " \"exp\": 1419356238,\n" + " \"iat\": 1419350238,\n" + " \"extension_field\": \"twenty-seven\"\n" + " }"; // @formatter:on public final SpringTestContext spring = new SpringTestContext(this); WebTestClient client; @Autowired public void setApplicationContext(ApplicationContext context) { this.client = WebTestClient.bindToApplicationContext(context).build(); } @Test public void getWhenValidThenReturnsOk() { this.spring.register(PublicKeyConfig.class, RootController.class).autowire(); // @formatter:off this.client.get() .headers((headers) -> headers .setBearerAuth(this.messageReadToken)) .exchange() .expectStatus().isOk(); // @formatter:on } @Test public void getWhenExpiredThenReturnsInvalidToken() { this.spring.register(PublicKeyConfig.class).autowire(); // @formatter:off this.client.get() .headers((headers) -> headers .setBearerAuth(this.expired)) .exchange() .expectStatus().isUnauthorized() .expectHeader().value(HttpHeaders.WWW_AUTHENTICATE, startsWith("Bearer error=\"invalid_token\"")); // @formatter:on } @Test public void getWhenUnsignedThenReturnsInvalidToken() { this.spring.register(PublicKeyConfig.class).autowire(); // @formatter:off this.client.get() .headers((headers) -> headers .setBearerAuth(this.unsignedToken)) .exchange() .expectStatus().isUnauthorized() .expectHeader().value(HttpHeaders.WWW_AUTHENTICATE, startsWith("Bearer error=\"invalid_token\"")); // @formatter:on } @Test public void getWhenEmptyBearerTokenThenReturnsInvalidToken() { this.spring.register(PublicKeyConfig.class).autowire(); // @formatter:off this.client.get() .headers((headers) -> headers .add("Authorization", "Bearer ") ) .exchange() .expectStatus().isUnauthorized() .expectHeader().value(HttpHeaders.WWW_AUTHENTICATE, startsWith("Bearer error=\"invalid_token\"")); // @formatter:on } @Test public void getWhenValidTokenAndPublicKeyInLambdaThenReturnsOk() { this.spring.register(PublicKeyInLambdaConfig.class, RootController.class).autowire(); // @formatter:off this.client.get() .headers((headers) -> headers .setBearerAuth(this.messageReadToken) ) .exchange() .expectStatus().isOk(); // @formatter:on } @Test public void getWhenExpiredTokenAndPublicKeyInLambdaThenReturnsInvalidToken() { this.spring.register(PublicKeyInLambdaConfig.class).autowire(); // @formatter:off this.client.get() .headers((headers) -> headers .setBearerAuth(this.expired) ) .exchange() .expectStatus().isUnauthorized() .expectHeader().value(HttpHeaders.WWW_AUTHENTICATE, startsWith("Bearer error=\"invalid_token\"")); // @formatter:on } @Test public void getWhenValidUsingPlaceholderThenReturnsOk() { this.spring.register(PlaceholderConfig.class, RootController.class).autowire(); // @formatter:off this.client.get() .headers((headers) -> headers .setBearerAuth(this.messageReadToken) ) .exchange() .expectStatus().isOk(); // @formatter:on } @Test public void getWhenCustomDecoderThenAuthenticatesAccordingly() { this.spring.register(CustomDecoderConfig.class, RootController.class).autowire(); ReactiveJwtDecoder jwtDecoder = this.spring.getContext().getBean(ReactiveJwtDecoder.class); given(jwtDecoder.decode(anyString())).willReturn(Mono.just(this.jwt)); // @formatter:off this.client.get() .headers((headers) -> headers .setBearerAuth("token") ) .exchange() .expectStatus().isOk(); // @formatter:on verify(jwtDecoder).decode(anyString()); } @Test public void getWhenUsingJwkSetUriThenConsultsAccordingly() { this.spring.register(JwkSetUriConfig.class, RootController.class).autowire(); MockWebServer mockWebServer = this.spring.getContext().getBean(MockWebServer.class); mockWebServer.enqueue(new MockResponse().setBody(this.jwkSet)); // @formatter:off this.client.get() .headers((headers) -> headers .setBearerAuth(this.messageReadTokenWithKid) ) .exchange() .expectStatus().isOk(); // @formatter:on } @Test public void getWhenUsingJwkSetUriInLambdaThenConsultsAccordingly() { this.spring.register(JwkSetUriInLambdaConfig.class, RootController.class).autowire(); MockWebServer mockWebServer = this.spring.getContext().getBean(MockWebServer.class); mockWebServer.enqueue(new MockResponse().setBody(this.jwkSet)); // @formatter:off this.client.get() .headers((headers) -> headers .setBearerAuth(this.messageReadTokenWithKid) ) .exchange() .expectStatus().isOk(); // @formatter:on } @Test public void getWhenUsingCustomAuthenticationManagerThenUsesItAccordingly() { this.spring.register(CustomAuthenticationManagerConfig.class).autowire(); ReactiveAuthenticationManager authenticationManager = this.spring.getContext() .getBean(ReactiveAuthenticationManager.class); given(authenticationManager.authenticate(any(Authentication.class))) .willReturn(Mono.error(new OAuth2AuthenticationException(new OAuth2Error("mock-failure")))); // @formatter:off this.client.get() .headers((headers) -> headers .setBearerAuth(this.messageReadToken) ) .exchange() .expectStatus().isUnauthorized() .expectHeader().value(HttpHeaders.WWW_AUTHENTICATE, startsWith("Bearer error=\"mock-failure\"")); // @formatter:on } @Test public void getWhenUsingCustomAuthenticationManagerInLambdaThenUsesItAccordingly() { this.spring.register(CustomAuthenticationManagerInLambdaConfig.class).autowire(); ReactiveAuthenticationManager authenticationManager = this.spring.getContext() .getBean(ReactiveAuthenticationManager.class); given(authenticationManager.authenticate(any(Authentication.class))) .willReturn(Mono.error(new OAuth2AuthenticationException(new OAuth2Error("mock-failure")))); // @formatter:off this.client.get() .headers((headers) -> headers .setBearerAuth(this.messageReadToken) ) .exchange() .expectStatus().isUnauthorized() .expectHeader().value(HttpHeaders.WWW_AUTHENTICATE, startsWith("Bearer error=\"mock-failure\"")); // @formatter:on } @Test public void getWhenUsingCustomAuthenticationManagerResolverThenUsesItAccordingly() { this.spring.register(CustomAuthenticationManagerResolverConfig.class).autowire(); ReactiveAuthenticationManagerResolver authenticationManagerResolver = this.spring .getContext() .getBean(ReactiveAuthenticationManagerResolver.class); ReactiveAuthenticationManager authenticationManager = this.spring.getContext() .getBean(ReactiveAuthenticationManager.class); given(authenticationManagerResolver.resolve(any(ServerWebExchange.class))) .willReturn(Mono.just(authenticationManager)); given(authenticationManager.authenticate(any(Authentication.class))) .willReturn(Mono.error(new OAuth2AuthenticationException(new OAuth2Error("mock-failure")))); // @formatter:off this.client.get() .headers((headers) -> headers .setBearerAuth(this.messageReadToken) ) .exchange() .expectStatus().isUnauthorized() .expectHeader().value(HttpHeaders.WWW_AUTHENTICATE, startsWith("Bearer error=\"mock-failure\"")); // @formatter:on } @Test public void getWhenUsingCustomAuthenticationFailureHandlerThenUsesIsAccordingly() { this.spring.register(CustomAuthenticationFailureHandlerConfig.class).autowire(); ServerAuthenticationFailureHandler handler = this.spring.getContext() .getBean(ServerAuthenticationFailureHandler.class); ReactiveAuthenticationManager authenticationManager = this.spring.getContext() .getBean(ReactiveAuthenticationManager.class); given(authenticationManager.authenticate(any())) .willReturn(Mono.error(() -> new BadCredentialsException("bad"))); given(handler.onAuthenticationFailure(any(), any())).willReturn(Mono.empty()); // @formatter:off this.client.get() .headers((headers) -> headers.setBearerAuth(this.messageReadToken)) .exchange() .expectStatus().isOk(); // @formatter:on verify(handler).onAuthenticationFailure(any(), any()); } @Test public void postWhenSignedThenReturnsOk() { this.spring.register(PublicKeyConfig.class, RootController.class).autowire(); // @formatter:off this.client.post() .headers((headers) -> headers .setBearerAuth(this.messageReadToken) ) .exchange() .expectStatus().isOk(); // @formatter:on } @Test public void getWhenTokenHasInsufficientScopeThenReturnsInsufficientScope() { this.spring.register(DenyAllConfig.class, RootController.class).autowire(); // @formatter:off this.client.get() .headers((headers) -> headers .setBearerAuth(this.messageReadToken) ) .exchange() .expectStatus().isForbidden() .expectHeader().value(HttpHeaders.WWW_AUTHENTICATE, startsWith("Bearer error=\"insufficient_scope\"")); // @formatter:on } @Test public void postWhenMissingTokenThenReturnsForbidden() { this.spring.register(PublicKeyConfig.class, RootController.class).autowire(); // @formatter:off this.client.post() .exchange() .expectStatus().isForbidden(); // @formatter:on } @Test public void getWhenCustomBearerTokenServerAuthenticationConverterThenResponds() { this.spring.register(CustomBearerTokenServerAuthenticationConverter.class, RootController.class).autowire(); // @formatter:off this.client.get() .cookie("TOKEN", this.messageReadToken) .exchange() .expectStatus().isOk(); // @formatter:on } @Test public void getWhenSignedAndCustomConverterThenConverts() { this.spring.register(CustomJwtAuthenticationConverterConfig.class, RootController.class).autowire(); // @formatter:off this.client.get() .headers((headers) -> headers .setBearerAuth(this.messageReadToken) ) .exchange() .expectStatus().isOk(); // @formatter:on } @Test public void getWhenCustomBearerTokenEntryPointThenResponds() { this.spring.register(CustomErrorHandlingConfig.class).autowire(); // @formatter:off this.client.get() .uri("/authenticated") .exchange() .expectStatus().isEqualTo(HttpStatus.I_AM_A_TEAPOT); // @formatter:on } @Test public void getWhenCustomBearerTokenDeniedHandlerThenResponds() { this.spring.register(CustomErrorHandlingConfig.class).autowire(); // @formatter:off this.client.get() .uri("/unobtainable") .headers((headers) -> headers .setBearerAuth(this.messageReadToken) ) .exchange() .expectStatus().isEqualTo(HttpStatus.BANDWIDTH_LIMIT_EXCEEDED); // @formatter:on } @Test public void getJwtDecoderWhenBeanWiredAndDslWiredThenDslTakesPrecedence() { GenericWebApplicationContext context = autowireWebServerGenericWebApplicationContext(); ServerHttpSecurity http = new ServerHttpSecurity(); http.setApplicationContext(context); ReactiveJwtDecoder beanWiredJwtDecoder = mock(ReactiveJwtDecoder.class); ReactiveJwtDecoder dslWiredJwtDecoder = mock(ReactiveJwtDecoder.class); context.registerBean(ReactiveJwtDecoder.class, () -> beanWiredJwtDecoder); http.oauth2ResourceServer((server) -> server.jwt((jwt) -> { jwt.jwtDecoder(dslWiredJwtDecoder); assertThat(jwt.getJwtDecoder()).isEqualTo(dslWiredJwtDecoder); })); } @Test public void getJwtDecoderWhenTwoBeansWiredAndDslWiredThenDslTakesPrecedence() { GenericWebApplicationContext context = autowireWebServerGenericWebApplicationContext(); ServerHttpSecurity http = new ServerHttpSecurity(); http.setApplicationContext(context); ReactiveJwtDecoder beanWiredJwtDecoder = mock(ReactiveJwtDecoder.class); ReactiveJwtDecoder dslWiredJwtDecoder = mock(ReactiveJwtDecoder.class); context.registerBean("firstJwtDecoder", ReactiveJwtDecoder.class, () -> beanWiredJwtDecoder); context.registerBean("secondJwtDecoder", ReactiveJwtDecoder.class, () -> beanWiredJwtDecoder); http.oauth2ResourceServer((server) -> server.jwt((jwt) -> { jwt.jwtDecoder(dslWiredJwtDecoder); assertThat(jwt.getJwtDecoder()).isEqualTo(dslWiredJwtDecoder); })); } @Test public void getJwtDecoderWhenTwoBeansWiredThenThrowsWiringException() { GenericWebApplicationContext context = autowireWebServerGenericWebApplicationContext(); ServerHttpSecurity http = new ServerHttpSecurity(); http.setApplicationContext(context); ReactiveJwtDecoder beanWiredJwtDecoder = mock(ReactiveJwtDecoder.class); context.registerBean("firstJwtDecoder", ReactiveJwtDecoder.class, () -> beanWiredJwtDecoder); context.registerBean("secondJwtDecoder", ReactiveJwtDecoder.class, () -> beanWiredJwtDecoder); http.oauth2ResourceServer( (server) -> server.jwt((jwt) -> assertThatExceptionOfType(NoUniqueBeanDefinitionException.class) .isThrownBy(jwt::getJwtDecoder))); } @Test public void getJwtDecoderWhenNoBeansAndNoDslWiredThenWiringException() { GenericWebApplicationContext context = autowireWebServerGenericWebApplicationContext(); ServerHttpSecurity http = new ServerHttpSecurity(); http.setApplicationContext(context); http.oauth2ResourceServer( (server) -> server.jwt((jwt) -> assertThatExceptionOfType(NoSuchBeanDefinitionException.class) .isThrownBy(jwt::getJwtDecoder))); } @Test public void getJwtAuthenticationConverterWhenBeanWiredAndDslWiredThenDslTakesPrecedence() { GenericWebApplicationContext context = autowireWebServerGenericWebApplicationContext(); ServerHttpSecurity http = new ServerHttpSecurity(); http.setApplicationContext(context); ReactiveJwtAuthenticationConverter beanWiredJwtAuthenticationConverter = new ReactiveJwtAuthenticationConverter(); ReactiveJwtAuthenticationConverter dslWiredJwtAuthenticationConverter = new ReactiveJwtAuthenticationConverter(); context.registerBean(ReactiveJwtAuthenticationConverter.class, () -> beanWiredJwtAuthenticationConverter); http.oauth2ResourceServer((server) -> server.jwt((jwt) -> { jwt.jwtAuthenticationConverter(dslWiredJwtAuthenticationConverter); assertThat(jwt.getJwtAuthenticationConverter()).isEqualTo(dslWiredJwtAuthenticationConverter); })); } @Test public void getJwtAuthenticationConverterWhenTwoBeansWiredAndDslWiredThenDslTakesPrecedence() { GenericWebApplicationContext context = autowireWebServerGenericWebApplicationContext(); ServerHttpSecurity http = new ServerHttpSecurity(); http.setApplicationContext(context); ReactiveJwtAuthenticationConverter beanWiredJwtAuthenticationConverter = new ReactiveJwtAuthenticationConverter(); ReactiveJwtAuthenticationConverter dslWiredJwtAuthenticationConverter = new ReactiveJwtAuthenticationConverter(); context.registerBean("firstJwtAuthenticationConverter", ReactiveJwtAuthenticationConverter.class, () -> beanWiredJwtAuthenticationConverter); context.registerBean("secondJwtAuthenticationConverter", ReactiveJwtAuthenticationConverter.class, () -> beanWiredJwtAuthenticationConverter); http.oauth2ResourceServer((server) -> server.jwt((jwt) -> { jwt.jwtAuthenticationConverter(dslWiredJwtAuthenticationConverter); assertThat(jwt.getJwtAuthenticationConverter()).isEqualTo(dslWiredJwtAuthenticationConverter); })); } @Test public void getJwtAuthenticationConverterWhenTwoBeansWiredThenThrowsWiringException() { GenericWebApplicationContext context = autowireWebServerGenericWebApplicationContext(); ServerHttpSecurity http = new ServerHttpSecurity(); http.setApplicationContext(context); ReactiveJwtAuthenticationConverter beanWiredJwtAuthenticationConverter = new ReactiveJwtAuthenticationConverter(); context.registerBean("firstJwtAuthenticationConverter", ReactiveJwtAuthenticationConverter.class, () -> beanWiredJwtAuthenticationConverter); context.registerBean("secondJwtAuthenticationConverter", ReactiveJwtAuthenticationConverter.class, () -> beanWiredJwtAuthenticationConverter); http.oauth2ResourceServer( (server) -> server.jwt((jwt) -> assertThatExceptionOfType(NoUniqueBeanDefinitionException.class) .isThrownBy(jwt::getJwtAuthenticationConverter))); } @Test public void getJwtAuthenticationConverterWhenNoBeansAndNoDslWiredThenDefaultConverter() { GenericWebApplicationContext context = autowireWebServerGenericWebApplicationContext(); ServerHttpSecurity http = new ServerHttpSecurity(); http.setApplicationContext(context); http.oauth2ResourceServer((server) -> server.jwt((jwt) -> assertThat(jwt.getJwtAuthenticationConverter()) .isInstanceOf(ReactiveJwtAuthenticationConverter.class))); } @Test public void introspectWhenValidThenReturnsOk() { this.spring.register(IntrospectionConfig.class, RootController.class).autowire(); this.spring.getContext() .getBean(MockWebServer.class) .setDispatcher(requiresAuth(this.clientId, this.clientSecret, this.active)); // @formatter:off this.client.get() .headers((headers) -> headers .setBearerAuth(this.messageReadToken) ) .exchange() .expectStatus().isOk(); // @formatter:on } @Test public void introspectWhenValidAndIntrospectionInLambdaThenReturnsOk() { this.spring.register(IntrospectionInLambdaConfig.class, RootController.class).autowire(); this.spring.getContext() .getBean(MockWebServer.class) .setDispatcher(requiresAuth(this.clientId, this.clientSecret, this.active)); // @formatter:off this.client.get() .headers((headers) -> headers .setBearerAuth(this.messageReadToken) ) .exchange() .expectStatus().isOk(); // @formatter:on } @Test public void configureWhenUsingBothAuthenticationManagerResolverAndOpaqueThenWiringException() { assertThatExceptionOfType(BeanCreationException.class) .isThrownBy(() -> this.spring.register(AuthenticationManagerResolverPlusOtherConfig.class).autowire()) .withMessageContaining("authenticationManagerResolver"); } @Test public void getWhenCustomAuthenticationConverterThenConverts() { this.spring.register(ReactiveOpaqueTokenAuthenticationConverterConfig.class, RootController.class).autowire(); this.spring.getContext() .getBean(MockWebServer.class) .setDispatcher(requiresAuth(this.clientId, this.clientSecret, this.active)); ReactiveOpaqueTokenAuthenticationConverter authenticationConverter = this.spring.getContext() .getBean(ReactiveOpaqueTokenAuthenticationConverter.class); given(authenticationConverter.convert(anyString(), any(OAuth2AuthenticatedPrincipal.class))) .willReturn(Mono.just(new TestingAuthenticationToken("jdoe", null, Collections.emptyList()))); // @formatter:off this.client.get() .headers((headers) -> headers .setBearerAuth(this.messageReadToken) ) .exchange() .expectStatus().isOk(); // @formatter:on } private static Dispatcher requiresAuth(String username, String password, String response) { return new Dispatcher() { @Override public MockResponse dispatch(RecordedRequest request) { String authorization = request.getHeader(org.springframework.http.HttpHeaders.AUTHORIZATION); // @formatter:off return Optional.ofNullable(authorization) .filter((a) -> isAuthorized(authorization, username, password)) .map((a) -> ok(response)) .orElse(unauthorized()); // @formatter:on } }; } private static boolean isAuthorized(String authorization, String username, String password) { String[] values = new String(Base64.getDecoder().decode(authorization.substring(6))).split(":"); return username.equals(values[0]) && password.equals(values[1]); } private static MockResponse ok(String response) { return new MockResponse().setBody(response) .setHeader(org.springframework.http.HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE); } private static MockResponse unauthorized() { return new MockResponse().setResponseCode(401); } private static RSAPublicKey publicKey() { String modulus = "26323220897278656456354815752829448539647589990395639665273015355787577386000316054335559633864476469390247312823732994485311378484154955583861993455004584140858982659817218753831620205191028763754231454775026027780771426040997832758235764611119743390612035457533732596799927628476322029280486807310749948064176545712270582940917249337311592011920620009965129181413510845780806191965771671528886508636605814099711121026468495328702234901200169245493126030184941412539949521815665744267183140084667383643755535107759061065656273783542590997725982989978433493861515415520051342321336460543070448417126615154138673620797"; String exponent = "65537"; RSAPublicKeySpec spec = new RSAPublicKeySpec(new BigInteger(modulus), new BigInteger(exponent)); RSAPublicKey rsaPublicKey = null; try { KeyFactory factory = KeyFactory.getInstance("RSA"); rsaPublicKey = (RSAPublicKey) factory.generatePublic(spec); } catch (NoSuchAlgorithmException | InvalidKeySpecException ex) { ex.printStackTrace(); } return rsaPublicKey; } private GenericWebApplicationContext autowireWebServerGenericWebApplicationContext() { GenericWebApplicationContext context = new GenericWebApplicationContext(); context.registerBean("webHandler", DispatcherHandler.class); this.spring.context(context).autowire(); return (GenericWebApplicationContext) this.spring.getContext(); } @Configuration @EnableWebFlux @EnableWebFluxSecurity static class PublicKeyConfig { @Bean SecurityWebFilterChain springSecurity(ServerHttpSecurity http) { // @formatter:off http .authorizeExchange((authorize) -> authorize .anyExchange().hasAuthority("SCOPE_message:read")) .oauth2ResourceServer((server) -> server .jwt((jwt) -> jwt.publicKey(publicKey()))); // @formatter:on return http.build(); } } @Configuration @EnableWebFlux @EnableWebFluxSecurity static class PublicKeyInLambdaConfig { @Bean SecurityWebFilterChain springSecurity(ServerHttpSecurity http) { // @formatter:off http .authorizeExchange((authorize) -> authorize .anyExchange().hasAuthority("SCOPE_message:read") ) .oauth2ResourceServer((oauth2) -> oauth2 .jwt((jwt) -> jwt .publicKey(publicKey()) ) ); // @formatter:on return http.build(); } } @Configuration @EnableWebFlux @EnableWebFluxSecurity static class PlaceholderConfig { @Value("classpath:org/springframework/security/config/web/server/OAuth2ResourceServerSpecTests-simple.pub") RSAPublicKey key; @Bean SecurityWebFilterChain springSecurity(ServerHttpSecurity http) { // @formatter:off http .authorizeExchange((authorize) -> authorize .anyExchange().hasAuthority("SCOPE_message:read")) .oauth2ResourceServer((server) -> server .jwt((jwt) -> jwt.publicKey(this.key))); // @formatter:on return http.build(); } } @Configuration @EnableWebFlux @EnableWebFluxSecurity static class JwkSetUriConfig { private MockWebServer mockWebServer = new MockWebServer(); @Bean SecurityWebFilterChain springSecurity(ServerHttpSecurity http) { String jwkSetUri = mockWebServer().url("/.well-known/jwks.json").toString(); // @formatter:off http .oauth2ResourceServer((server) -> server .jwt((jwt) -> jwt.jwkSetUri(jwkSetUri))); // @formatter:on return http.build(); } @Bean MockWebServer mockWebServer() { return this.mockWebServer; } @PreDestroy void shutdown() throws IOException { this.mockWebServer.shutdown(); } } @Configuration @EnableWebFlux @EnableWebFluxSecurity static class JwkSetUriInLambdaConfig { private MockWebServer mockWebServer = new MockWebServer(); @Bean SecurityWebFilterChain springSecurity(ServerHttpSecurity http) { String jwkSetUri = mockWebServer().url("/.well-known/jwks.json").toString(); // @formatter:off http .oauth2ResourceServer((oauth2) -> oauth2 .jwt((jwt) -> jwt .jwkSetUri(jwkSetUri) ) ); // @formatter:on return http.build(); } @Bean MockWebServer mockWebServer() { return this.mockWebServer; } @PreDestroy void shutdown() throws IOException { this.mockWebServer.shutdown(); } } @Configuration @EnableWebFlux @EnableWebFluxSecurity static class CustomDecoderConfig { ReactiveJwtDecoder jwtDecoder = mock(ReactiveJwtDecoder.class); @Bean SecurityWebFilterChain springSecurity(ServerHttpSecurity http) { // @formatter:off http .oauth2ResourceServer((server) -> server .jwt(Customizer.withDefaults())); // @formatter:on return http.build(); } @Bean ReactiveJwtDecoder jwtDecoder() { return this.jwtDecoder; } } @Configuration @EnableWebFlux @EnableWebFluxSecurity static class DenyAllConfig { @Bean SecurityWebFilterChain authorization(ServerHttpSecurity http) { // @formatter:off http .authorizeExchange((authorize) -> authorize .anyExchange().denyAll()) .oauth2ResourceServer((server) -> server .jwt((jwt) -> jwt.publicKey(publicKey()))); // @formatter:on return http.build(); } } @Configuration @EnableWebFlux @EnableWebFluxSecurity static class CustomAuthenticationManagerConfig { @Bean SecurityWebFilterChain springSecurity(ServerHttpSecurity http) { // @formatter:off http .oauth2ResourceServer((server) -> server .jwt((jwt) -> jwt.authenticationManager(authenticationManager()))); // @formatter:on return http.build(); } @Bean ReactiveAuthenticationManager authenticationManager() { return mock(ReactiveAuthenticationManager.class); } } @Configuration @EnableWebFlux @EnableWebFluxSecurity static class CustomAuthenticationManagerInLambdaConfig { @Bean SecurityWebFilterChain springSecurity(ServerHttpSecurity http) { // @formatter:off http .oauth2ResourceServer((oauth2) -> oauth2 .jwt((jwt) -> jwt .authenticationManager(authenticationManager()) ) ); // @formatter:on return http.build(); } @Bean ReactiveAuthenticationManager authenticationManager() { return mock(ReactiveAuthenticationManager.class); } } @Configuration @EnableWebFlux @EnableWebFluxSecurity static class CustomAuthenticationManagerResolverConfig { @Bean SecurityWebFilterChain springSecurity(ServerHttpSecurity http) { // @formatter:off http .authorizeExchange((authorize) -> authorize .pathMatchers("/*/message/**").hasAnyAuthority("SCOPE_message:read")) .oauth2ResourceServer((server) -> server .authenticationManagerResolver(authenticationManagerResolver())); // @formatter:on return http.build(); } @Bean ReactiveAuthenticationManagerResolver authenticationManagerResolver() { return mock(ReactiveAuthenticationManagerResolver.class); } @Bean ReactiveAuthenticationManager authenticationManager() { return mock(ReactiveAuthenticationManager.class); } } @Configuration @EnableWebFlux @EnableWebFluxSecurity static class CustomAuthenticationFailureHandlerConfig { @Bean SecurityWebFilterChain springSecurity(ServerHttpSecurity http) { // @formatter:off http .authorizeExchange((authorize) -> authorize.anyExchange().authenticated()) .oauth2ResourceServer((oauth2) -> oauth2 .authenticationFailureHandler(authenticationFailureHandler()) .jwt((jwt) -> jwt.authenticationManager(authenticationManager())) ); // @formatter:on return http.build(); } @Bean ReactiveAuthenticationManager authenticationManager() { return mock(ReactiveAuthenticationManager.class); } @Bean ServerAuthenticationFailureHandler authenticationFailureHandler() { return mock(ServerAuthenticationFailureHandler.class); } } @EnableWebFlux @EnableWebFluxSecurity static class CustomBearerTokenServerAuthenticationConverter { @Bean SecurityWebFilterChain springSecurity(ServerHttpSecurity http) { // @formatter:off http .authorizeExchange((authorize) -> authorize .anyExchange().hasAuthority("SCOPE_message:read")) .oauth2ResourceServer((server) -> server .bearerTokenConverter(bearerTokenAuthenticationConverter()) .jwt((jwt) -> jwt.publicKey(publicKey()))); // @formatter:on return http.build(); } @Bean ServerAuthenticationConverter bearerTokenAuthenticationConverter() { return (exchange) -> Mono.justOrEmpty(exchange.getRequest().getCookies().getFirst("TOKEN").getValue()) .map(BearerTokenAuthenticationToken::new); } } @Configuration @EnableWebFlux @EnableWebFluxSecurity static class CustomJwtAuthenticationConverterConfig { @Bean SecurityWebFilterChain springSecurity(ServerHttpSecurity http) { // @formatter:off http .authorizeExchange((authorize) -> authorize .anyExchange().hasAuthority("message:read")) .oauth2ResourceServer((server) -> server .jwt((jwt) -> jwt .jwtAuthenticationConverter(jwtAuthenticationConverter()) .publicKey(publicKey()))); // @formatter:on return http.build(); } @Bean Converter> jwtAuthenticationConverter() { JwtAuthenticationConverter converter = new JwtAuthenticationConverter(); converter.setJwtGrantedAuthoritiesConverter((jwt) -> { String[] claims = ((String) jwt.getClaims().get("scope")).split(" "); return Stream.of(claims).map(SimpleGrantedAuthority::new).collect(Collectors.toList()); }); return new ReactiveJwtAuthenticationConverterAdapter(converter); } } @Configuration @EnableWebFlux @EnableWebFluxSecurity static class CustomErrorHandlingConfig { @Bean SecurityWebFilterChain springSecurity(ServerHttpSecurity http) { // @formatter:off http .authorizeExchange((authorize) -> authorize .pathMatchers("/authenticated").authenticated() .pathMatchers("/unobtainable").hasAuthority("unobtainable")) .oauth2ResourceServer((server) -> server .accessDeniedHandler(new HttpStatusServerAccessDeniedHandler(HttpStatus.BANDWIDTH_LIMIT_EXCEEDED)) .authenticationEntryPoint(new HttpStatusServerEntryPoint(HttpStatus.I_AM_A_TEAPOT)) .jwt((jwt) -> jwt.publicKey(publicKey()))); // @formatter:on return http.build(); } } @Configuration @EnableWebFlux @EnableWebFluxSecurity static class IntrospectionConfig { private MockWebServer mockWebServer = new MockWebServer(); @Bean SecurityWebFilterChain springSecurity(ServerHttpSecurity http) { String introspectionUri = mockWebServer().url("/introspect").toString(); // @formatter:off http .oauth2ResourceServer((server) -> server .opaqueToken((opaqueToken) -> opaqueToken .introspectionUri(introspectionUri) .introspectionClientCredentials("client", "secret"))); // @formatter:on return http.build(); } @Bean MockWebServer mockWebServer() { return this.mockWebServer; } @PreDestroy void shutdown() throws IOException { this.mockWebServer.shutdown(); } } @Configuration @EnableWebFlux @EnableWebFluxSecurity static class IntrospectionInLambdaConfig { private MockWebServer mockWebServer = new MockWebServer(); @Bean SecurityWebFilterChain springSecurity(ServerHttpSecurity http) { String introspectionUri = mockWebServer().url("/introspect").toString(); // @formatter:off http .oauth2ResourceServer((oauth2) -> oauth2 .opaqueToken((opaqueToken) -> opaqueToken .introspectionUri(introspectionUri) .introspectionClientCredentials("client", "secret") ) ); // @formatter:on return http.build(); } @Bean MockWebServer mockWebServer() { return this.mockWebServer; } @PreDestroy void shutdown() throws IOException { this.mockWebServer.shutdown(); } } @Configuration @EnableWebFlux @EnableWebFluxSecurity static class AuthenticationManagerResolverPlusOtherConfig { @Bean SecurityWebFilterChain springSecurity(ServerHttpSecurity http) { // @formatter:off http .authorizeExchange((authorize) -> authorize .anyExchange().authenticated()) .oauth2ResourceServer((server) -> server .authenticationManagerResolver(mock(ReactiveAuthenticationManagerResolver.class)) .opaqueToken(Customizer.withDefaults())); // @formatter:on return http.build(); } } @Configuration @EnableWebFlux @EnableWebFluxSecurity static class ReactiveOpaqueTokenAuthenticationConverterConfig { private MockWebServer mockWebServer = new MockWebServer(); @Bean SecurityWebFilterChain springSecurity(ServerHttpSecurity http) { String introspectionUri = mockWebServer().url("/introspect").toString(); // @formatter:off http .oauth2ResourceServer((server) -> server .opaqueToken((opaqueToken) -> opaqueToken .introspectionUri(introspectionUri) .introspectionClientCredentials("client", "secret") .authenticationConverter(authenticationConverter()))); // @formatter:on return http.build(); } @Bean ReactiveOpaqueTokenAuthenticationConverter authenticationConverter() { return mock(ReactiveOpaqueTokenAuthenticationConverter.class); } @Bean MockWebServer mockWebServer() { return this.mockWebServer; } @PreDestroy void shutdown() throws IOException { this.mockWebServer.shutdown(); } } @RestController static class RootController { @GetMapping Mono get() { return Mono.just("ok"); } @PostMapping Mono post() { return Mono.just("ok"); } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/web/server/OidcBackChannelServerLogoutHandlerTests.java ================================================ /* * 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 org.springframework.security.config.web.server; import org.junit.jupiter.api.Test; import org.springframework.mock.http.server.reactive.MockServerHttpRequest; import org.springframework.security.oauth2.client.oidc.authentication.logout.TestOidcLogoutTokens; import org.springframework.security.oauth2.client.oidc.server.session.InMemoryReactiveOidcSessionRegistry; import org.springframework.security.oauth2.client.oidc.server.session.ReactiveOidcSessionRegistry; import org.springframework.security.oauth2.client.registration.TestClientRegistrations; import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link OidcBackChannelServerLogoutHandler} */ public class OidcBackChannelServerLogoutHandlerTests { private final ReactiveOidcSessionRegistry sessionRegistry = new InMemoryReactiveOidcSessionRegistry(); private final OidcBackChannelLogoutAuthentication token = new OidcBackChannelLogoutAuthentication( TestOidcLogoutTokens.withSubject("issuer", "subject").build(), TestClientRegistrations.clientRegistration().build()); // gh-14553 @Test public void computeLogoutEndpointWhenDifferentHostnameThenLocalhost() { OidcBackChannelServerLogoutHandler logoutHandler = new OidcBackChannelServerLogoutHandler(this.sessionRegistry); logoutHandler.setLogoutUri("{baseScheme}://localhost{basePort}/logout"); MockServerHttpRequest request = MockServerHttpRequest .get("https://host.docker.internal:8090/back-channel/logout") .build(); String endpoint = logoutHandler.computeLogoutEndpoint(request, this.token); assertThat(endpoint).startsWith("https://localhost:8090/logout"); } @Test public void computeLogoutEndpointWhenUsingBaseUrlTemplateThenServerName() { OidcBackChannelServerLogoutHandler logoutHandler = new OidcBackChannelServerLogoutHandler(this.sessionRegistry); logoutHandler.setLogoutUri("{baseUrl}/logout"); MockServerHttpRequest request = MockServerHttpRequest .get("http://host.docker.internal:8090/back-channel/logout") .build(); String endpoint = logoutHandler.computeLogoutEndpoint(request, this.token); assertThat(endpoint).startsWith("http://host.docker.internal:8090/logout"); } // gh-14609 @Test public void computeLogoutEndpointWhenLogoutUriThenUses() { OidcBackChannelServerLogoutHandler logoutHandler = new OidcBackChannelServerLogoutHandler(this.sessionRegistry); logoutHandler.setLogoutUri("http://localhost:8090/logout"); MockServerHttpRequest request = MockServerHttpRequest.get("https://server-one.com/back-channel/logout").build(); String endpoint = logoutHandler.computeLogoutEndpoint(request, this.token); assertThat(endpoint).startsWith("http://localhost:8090/logout"); } } ================================================ FILE: config/src/test/java/org/springframework/security/config/web/server/OidcLogoutSpecTests.java ================================================ /* * 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 org.springframework.security.config.web.server; import java.io.IOException; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.interfaces.RSAPublicKey; import java.time.Duration; import java.time.Instant; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.function.Consumer; import com.nimbusds.jose.jwk.JWKSet; import com.nimbusds.jose.jwk.RSAKey; import com.nimbusds.jose.jwk.source.ImmutableJWKSet; import com.nimbusds.jose.jwk.source.JWKSource; import com.nimbusds.jose.proc.SecurityContext; import com.nimbusds.oauth2.sdk.Scope; import com.nimbusds.oauth2.sdk.token.BearerAccessToken; import com.nimbusds.openid.connect.sdk.token.OIDCTokens; import jakarta.annotation.PreDestroy; import okhttp3.mockwebserver.Dispatcher; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; import okhttp3.mockwebserver.RecordedRequest; import org.htmlunit.util.UrlUtils; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import reactor.core.publisher.Mono; import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.core.ParameterizedTypeReference; import org.springframework.core.annotation.Order; import org.springframework.http.ResponseCookie; import org.springframework.http.client.reactive.ClientHttpConnector; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.core.userdetails.MapReactiveUserDetailsService; import org.springframework.security.core.userdetails.ReactiveUserDetailsService; import org.springframework.security.core.userdetails.User; import org.springframework.security.oauth2.client.oidc.authentication.logout.LogoutTokenClaimNames; import org.springframework.security.oauth2.client.oidc.authentication.logout.OidcLogoutToken; import org.springframework.security.oauth2.client.oidc.authentication.logout.TestOidcLogoutTokens; import org.springframework.security.oauth2.client.oidc.server.session.InMemoryReactiveOidcSessionRegistry; import org.springframework.security.oauth2.client.oidc.server.session.ReactiveOidcSessionRegistry; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.InMemoryReactiveClientRegistrationRepository; import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository; import org.springframework.security.oauth2.client.registration.TestClientRegistrations; import org.springframework.security.oauth2.core.oidc.OidcIdToken; import org.springframework.security.oauth2.core.oidc.TestOidcIdTokens; import org.springframework.security.oauth2.core.oidc.user.OidcUser; import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm; import org.springframework.security.oauth2.jwt.JwsHeader; import org.springframework.security.oauth2.jwt.JwtClaimsSet; import org.springframework.security.oauth2.jwt.JwtEncoder; import org.springframework.security.oauth2.jwt.JwtEncoderParameters; import org.springframework.security.oauth2.jwt.NimbusJwtEncoder; import org.springframework.security.web.server.SecurityWebFilterChain; import org.springframework.security.web.server.authentication.logout.ServerLogoutHandler; import org.springframework.security.web.server.util.matcher.OrServerWebExchangeMatcher; import org.springframework.security.web.server.util.matcher.PathPatternParserServerWebExchangeMatcher; import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher; import org.springframework.test.web.reactive.server.FluxExchangeResult; import org.springframework.test.web.reactive.server.WebTestClient; import org.springframework.test.web.reactive.server.WebTestClientConfigurer; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.context.ConfigurableWebApplicationContext; import org.springframework.web.reactive.config.EnableWebFlux; import org.springframework.web.reactive.function.BodyInserters; import org.springframework.web.server.WebSession; import org.springframework.web.server.adapter.WebHttpHandlerBuilder; import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.hasValue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.csrf; import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.mockAuthentication; import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.springSecurity; /** * Tests for {@link ServerHttpSecurity.OAuth2ResourceServerSpec} */ @ExtendWith({ SpringTestContextExtension.class }) public class OidcLogoutSpecTests { private static final String SESSION_COOKIE_NAME = "SESSION"; private WebTestClient test; @Autowired(required = false) private MockWebServer web; @Autowired private ClientRegistration clientRegistration; public final SpringTestContext spring = new SpringTestContext(this); @Autowired public void setApplicationContext(ApplicationContext context) { this.test = WebTestClient.bindToApplicationContext(context) .apply(springSecurity()) .configureClient() .responseTimeout(Duration.ofDays(1)) .build(); if (context instanceof ConfigurableWebApplicationContext configurable) { configurable.getBeanFactory().registerResolvableDependency(WebTestClient.class, this.test); } } @Test void logoutWhenDefaultsThenRemotelyInvalidatesSessions() { this.spring.register(WebServerConfig.class, OidcProviderConfig.class, DefaultConfig.class).autowire(); String registrationId = this.clientRegistration.getRegistrationId(); String session = login(); String logoutToken = this.test.mutateWith(session(session)) .get() .uri("/token/logout") .exchange() .expectStatus() .isOk() .returnResult(String.class) .getResponseBody() .blockFirst(); this.test.post() .uri(this.web.url("/logout/connect/back-channel/" + registrationId).toString()) .body(BodyInserters.fromFormData("logout_token", logoutToken)) .exchange() .expectStatus() .isOk(); this.test.mutateWith(session(session)).get().uri("/token/logout").exchange().expectStatus().isUnauthorized(); } @Test @SuppressWarnings("removal") void logoutWhenInvalidLogoutTokenThenBadRequest() { this.spring.register(WebServerConfig.class, OidcProviderConfig.class, DefaultConfig.class).autowire(); this.test.get().uri("/token/logout").exchange().expectStatus().isUnauthorized(); String registrationId = this.clientRegistration.getRegistrationId(); FluxExchangeResult result = this.test.get() .uri("/oauth2/authorization/" + registrationId) .exchange() .expectStatus() .isFound() .returnResult(String.class); String session = sessionId(result); String redirectUrl = UrlUtils.decode(result.getResponseHeaders().getLocation().toString()); String state = this.test .mutateWith(mockAuthentication(new TestingAuthenticationToken(this.clientRegistration.getClientId(), this.clientRegistration.getClientSecret(), "APP"))) .get() .uri(redirectUrl) .exchange() .returnResult(String.class) .getResponseBody() .blockFirst(); result = this.test.get() .uri("/login/oauth2/code/" + registrationId + "?code=code&state=" + state) .cookie("SESSION", session) .exchange() .expectStatus() .isFound() .returnResult(String.class); session = sessionId(result); this.test.post() .uri(this.web.url("/logout/connect/back-channel/" + registrationId).toString()) .body(BodyInserters.fromFormData("logout_token", "invalid")) .exchange() .expectStatus() .isBadRequest() .expectBody(new ParameterizedTypeReference>() { }) .value(hasValue("invalid_request")); this.test.get().uri("/token/logout").cookie("SESSION", session).exchange().expectStatus().isOk(); } @Test @SuppressWarnings("removal") void logoutWhenLogoutTokenSpecifiesOneSessionThenRemotelyInvalidatesOnlyThatSession() throws Exception { this.spring.register(WebServerConfig.class, OidcProviderConfig.class, DefaultConfig.class).autowire(); String registrationId = this.clientRegistration.getRegistrationId(); String one = login(); String two = login(); String three = login(); String logoutToken = this.test.get() .uri("/token/logout") .cookie("SESSION", one) .exchange() .expectStatus() .isOk() .returnResult(String.class) .getResponseBody() .blockFirst(); this.test.post() .uri(this.web.url("/logout/connect/back-channel/" + registrationId).toString()) .body(BodyInserters.fromFormData("logout_token", logoutToken)) .exchange() .expectStatus() .isOk(); this.test.get().uri("/token/logout").cookie("SESSION", one).exchange().expectStatus().isUnauthorized(); this.test.get().uri("/token/logout").cookie("SESSION", two).exchange().expectStatus().isOk(); logoutToken = this.test.get() .uri("/token/logout/all") .cookie("SESSION", three) .exchange() .expectStatus() .isOk() .returnResult(String.class) .getResponseBody() .blockFirst(); this.test.post() .uri(this.web.url("/logout/connect/back-channel/" + registrationId).toString()) .body(BodyInserters.fromFormData("logout_token", logoutToken)) .exchange() .expectStatus() .isOk(); this.test.get().uri("/token/logout").cookie("SESSION", two).exchange().expectStatus().isUnauthorized(); this.test.get().uri("/token/logout").cookie("SESSION", three).exchange().expectStatus().isUnauthorized(); } @Test @SuppressWarnings("removal") void logoutWhenRemoteLogoutUriThenUses() { this.spring.register(WebServerConfig.class, OidcProviderConfig.class, LogoutUriConfig.class).autowire(); String registrationId = this.clientRegistration.getRegistrationId(); String one = login(); String logoutToken = this.test.get() .uri("/token/logout/all") .cookie("SESSION", one) .exchange() .expectStatus() .isOk() .returnResult(String.class) .getResponseBody() .blockFirst(); this.test.post() .uri(this.web.url("/logout/connect/back-channel/" + registrationId).toString()) .body(BodyInserters.fromFormData("logout_token", logoutToken)) .exchange() .expectStatus() .isBadRequest() .expectBody(new ParameterizedTypeReference>() { }) .value(hasValue("partial_logout")) .value(hasValue(containsString("not all sessions were terminated"))); this.test.get().uri("/token/logout").cookie("SESSION", one).exchange().expectStatus().isOk(); } @Test void logoutWhenSelfRemoteLogoutUriThenUses() { this.spring.register(WebServerConfig.class, OidcProviderConfig.class, SelfLogoutUriConfig.class).autowire(); String registrationId = this.clientRegistration.getRegistrationId(); String sessionId = login(); String logoutToken = this.test.get() .uri("/token/logout") .cookie("SESSION", sessionId) .exchange() .expectStatus() .isOk() .returnResult(String.class) .getResponseBody() .blockFirst(); this.test.post() .uri(this.web.url("/logout/connect/back-channel/" + registrationId).toString()) .body(BodyInserters.fromFormData("logout_token", logoutToken)) .exchange() .expectStatus() .isOk(); this.test.get().uri("/token/logout").cookie("SESSION", sessionId).exchange().expectStatus().isUnauthorized(); } @Test @SuppressWarnings("removal") void logoutWhenDifferentCookieNameThenUses() { this.spring.register(OidcProviderConfig.class, CookieConfig.class).autowire(); String registrationId = this.clientRegistration.getRegistrationId(); String sessionId = login(); String logoutToken = this.test.get() .uri("/token/logout") .cookie("SESSION", sessionId) .exchange() .expectStatus() .isOk() .returnResult(String.class) .getResponseBody() .blockFirst(); this.test.post() .uri(this.web.url("/logout/connect/back-channel/" + registrationId).toString()) .body(BodyInserters.fromFormData("logout_token", logoutToken)) .exchange() .expectStatus() .isOk(); this.test.get().uri("/token/logout").cookie("SESSION", sessionId).exchange().expectStatus().isUnauthorized(); } @Test @SuppressWarnings("removal") void logoutWhenRemoteLogoutFailsThenReportsPartialLogout() { this.spring.register(WebServerConfig.class, OidcProviderConfig.class, WithBrokenLogoutConfig.class).autowire(); ServerLogoutHandler logoutHandler = this.spring.getContext().getBean(ServerLogoutHandler.class); given(logoutHandler.logout(any(), any())).willReturn(Mono.error(() -> new IllegalStateException("illegal"))); String registrationId = this.clientRegistration.getRegistrationId(); String one = login(); String logoutToken = this.test.get() .uri("/token/logout/all") .cookie("SESSION", one) .exchange() .expectStatus() .isOk() .returnResult(String.class) .getResponseBody() .blockFirst(); this.test.post() .uri(this.web.url("/logout/connect/back-channel/" + registrationId).toString()) .body(BodyInserters.fromFormData("logout_token", logoutToken)) .exchange() .expectStatus() .isBadRequest() .expectBody(String.class) .value(containsString("partial_logout")); this.test.get().uri("/token/logout").cookie("SESSION", one).exchange().expectStatus().isOk(); } @Test void logoutWhenCustomComponentsThenUses() { this.spring.register(WebServerConfig.class, OidcProviderConfig.class, WithCustomComponentsConfig.class) .autowire(); String registrationId = this.clientRegistration.getRegistrationId(); String sessionId = login(); String logoutToken = this.test.get() .uri("/token/logout") .cookie("SESSION", sessionId) .exchange() .expectStatus() .isOk() .returnResult(String.class) .getResponseBody() .blockFirst(); this.test.post() .uri(this.web.url("/logout/connect/back-channel/" + registrationId).toString()) .body(BodyInserters.fromFormData("logout_token", logoutToken)) .exchange() .expectStatus() .isOk(); this.test.get().uri("/token/logout").cookie("SESSION", sessionId).exchange().expectStatus().isUnauthorized(); ReactiveOidcSessionRegistry sessionRegistry = this.spring.getContext() .getBean(ReactiveOidcSessionRegistry.class); verify(sessionRegistry, atLeastOnce()).saveSessionInformation(any()); verify(sessionRegistry, atLeastOnce()).removeSessionInformation(any(OidcLogoutToken.class)); } @Test void logoutWhenProviderIssuerMissingThen5xxServerError() { this.spring.register(WebServerConfig.class, OidcProviderConfig.class, ProviderIssuerMissingConfig.class) .autowire(); String registrationId = this.clientRegistration.getRegistrationId(); String session = login(); String logoutToken = this.test.mutateWith(session(session)) .get() .uri("/token/logout") .exchange() .expectStatus() .isOk() .returnResult(String.class) .getResponseBody() .blockFirst(); this.test.post() .uri(this.web.url("/logout/connect/back-channel/" + registrationId).toString()) .body(BodyInserters.fromFormData("logout_token", logoutToken)) .exchange() .expectStatus() .is5xxServerError(); this.test.mutateWith(session(session)).get().uri("/token/logout").exchange().expectStatus().isOk(); } private String login() { this.test.get().uri("/token/logout").exchange().expectStatus().isUnauthorized(); String registrationId = this.clientRegistration.getRegistrationId(); FluxExchangeResult result = this.test.get() .uri("/oauth2/authorization/" + registrationId) .exchange() .expectStatus() .isFound() .returnResult(String.class); String sessionId = sessionId(result); String redirectUrl = UrlUtils.decode(result.getResponseHeaders().getLocation().toString()); result = this.test .mutateWith(mockAuthentication(new TestingAuthenticationToken(this.clientRegistration.getClientId(), this.clientRegistration.getClientSecret(), "APP"))) .get() .uri(redirectUrl) .exchange() .returnResult(String.class); String state = result.getResponseBody().blockFirst(); result = this.test.mutateWith(session(sessionId)) .get() .uri("/login/oauth2/code/" + registrationId + "?code=code&state=" + state) .exchange() .expectStatus() .isFound() .returnResult(String.class); return sessionId(result); } private String sessionId(FluxExchangeResult result) { List cookies = result.getResponseCookies().get(SESSION_COOKIE_NAME); if (cookies == null || cookies.isEmpty()) { return null; } return cookies.get(0).getValue(); } static SessionMutator session(String session) { return new SessionMutator(session); } private record SessionMutator(String session) implements WebTestClientConfigurer { @Override public void afterConfigurerAdded(WebTestClient.Builder builder, WebHttpHandlerBuilder httpHandlerBuilder, ClientHttpConnector connector) { builder.defaultCookie(SESSION_COOKIE_NAME, this.session); } } @Configuration static class RegistrationConfig { @Autowired(required = false) MockWebServer web; @Bean ClientRegistration clientRegistration() { if (this.web == null) { return TestClientRegistrations.clientRegistration().build(); } String issuer = this.web.url("/").toString(); return TestClientRegistrations.clientRegistration() .issuerUri(issuer) .jwkSetUri(issuer + "jwks") .tokenUri(issuer + "token") .userInfoUri(issuer + "user") .scope("openid") .build(); } @Bean ReactiveClientRegistrationRepository clientRegistrationRepository(ClientRegistration clientRegistration) { return new InMemoryReactiveClientRegistrationRepository(clientRegistration); } } @Configuration @EnableWebFluxSecurity @Import(RegistrationConfig.class) static class DefaultConfig { @Bean @Order(1) SecurityWebFilterChain filters(ServerHttpSecurity http) throws Exception { // @formatter:off http .authorizeExchange((authorize) -> authorize.anyExchange().authenticated()) .oauth2Login(Customizer.withDefaults()) .oidcLogout((oidc) -> oidc.backChannel(Customizer.withDefaults())); // @formatter:on return http.build(); } } @Configuration @EnableWebFluxSecurity @Import(RegistrationConfig.class) static class LogoutUriConfig { @Bean @Order(1) SecurityWebFilterChain filters(ServerHttpSecurity http) throws Exception { // @formatter:off http .authorizeExchange((authorize) -> authorize.anyExchange().authenticated()) .oauth2Login(Customizer.withDefaults()) .oidcLogout((oidc) -> oidc .backChannel((backchannel) -> backchannel.logoutUri("http://localhost/wrong")) ); // @formatter:on return http.build(); } } @Configuration @EnableWebFluxSecurity @Import(RegistrationConfig.class) static class SelfLogoutUriConfig { @Bean @Order(1) SecurityWebFilterChain filters(ServerHttpSecurity http) throws Exception { // @formatter:off http .authorizeExchange((authorize) -> authorize.anyExchange().authenticated()) .oauth2Login(Customizer.withDefaults()) .oidcLogout((oidc) -> oidc .backChannel(Customizer.withDefaults()) ); // @formatter:on return http.build(); } } @Configuration @EnableWebFluxSecurity @Import(RegistrationConfig.class) static class CookieConfig { private final MockWebServer server = new MockWebServer(); @Bean @Order(1) SecurityWebFilterChain filters(ServerHttpSecurity http) throws Exception { // @formatter:off http .authorizeExchange((authorize) -> authorize.anyExchange().authenticated()) .oauth2Login(Customizer.withDefaults()) .oidcLogout((oidc) -> oidc .backChannel(Customizer.withDefaults()) ); // @formatter:on return http.build(); } @Bean ReactiveOidcSessionRegistry oidcSessionRegistry() { return new InMemoryReactiveOidcSessionRegistry(); } @Bean OidcBackChannelServerLogoutHandler oidcLogoutHandler(ReactiveOidcSessionRegistry sessionRegistry) { OidcBackChannelServerLogoutHandler logoutHandler = new OidcBackChannelServerLogoutHandler(sessionRegistry); logoutHandler.setSessionCookieName("JSESSIONID"); return logoutHandler; } @Bean MockWebServer web(ObjectProvider web) { WebTestClientDispatcher dispatcher = new WebTestClientDispatcher(web); dispatcher.setAssertion((rr) -> { String cookie = rr.getHeaders().get("Cookie"); if (cookie == null) { return; } assertThat(cookie).contains("JSESSIONID"); }); this.server.setDispatcher(dispatcher); return this.server; } @PreDestroy void shutdown() throws IOException { this.server.shutdown(); } } @Configuration @EnableWebFluxSecurity @Import(RegistrationConfig.class) static class WithCustomComponentsConfig { ReactiveOidcSessionRegistry sessionRegistry = spy(new InMemoryReactiveOidcSessionRegistry()); @Bean @Order(1) SecurityWebFilterChain filters(ServerHttpSecurity http) throws Exception { // @formatter:off http .authorizeExchange((authorize) -> authorize.anyExchange().authenticated()) .oauth2Login(Customizer.withDefaults()) .oidcLogout((oidc) -> oidc.backChannel(Customizer.withDefaults())); // @formatter:on return http.build(); } @Bean ReactiveOidcSessionRegistry sessionRegistry() { return this.sessionRegistry; } } @Configuration @EnableWebFluxSecurity @Import(RegistrationConfig.class) static class WithBrokenLogoutConfig { private final ServerLogoutHandler logoutHandler = mock(ServerLogoutHandler.class); @Bean @Order(1) SecurityWebFilterChain filters(ServerHttpSecurity http) throws Exception { // @formatter:off http .authorizeExchange((authorize) -> authorize.anyExchange().authenticated()) .logout((logout) -> logout.logoutHandler(this.logoutHandler)) .oauth2Login(Customizer.withDefaults()) .oidcLogout((oidc) -> oidc.backChannel(Customizer.withDefaults())); // @formatter:on return http.build(); } @Bean ServerLogoutHandler logoutHandler() { return this.logoutHandler; } } @Configuration static class ProviderIssuerMissingRegistrationConfig { @Autowired(required = false) MockWebServer web; @Bean ClientRegistration clientRegistration() { if (this.web == null) { return TestClientRegistrations.clientRegistration().issuerUri(null).build(); } String issuer = this.web.url("/").toString(); return TestClientRegistrations.clientRegistration() .issuerUri(null) .jwkSetUri(issuer + "jwks") .tokenUri(issuer + "token") .userInfoUri(issuer + "user") .scope("openid") .build(); } @Bean ReactiveClientRegistrationRepository clientRegistrationRepository(ClientRegistration clientRegistration) { return new InMemoryReactiveClientRegistrationRepository(clientRegistration); } } @Configuration @EnableWebFluxSecurity @Import(ProviderIssuerMissingRegistrationConfig.class) static class ProviderIssuerMissingConfig { @Bean @Order(1) SecurityWebFilterChain filters(ServerHttpSecurity http) throws Exception { // @formatter:off http .authorizeExchange((authorize) -> authorize.anyExchange().authenticated()) .oauth2Login(Customizer.withDefaults()) .oidcLogout((oidc) -> oidc.backChannel(Customizer.withDefaults())); // @formatter:on return http.build(); } } @Configuration @EnableWebFluxSecurity @EnableWebFlux @RestController static class OidcProviderConfig { private static final RSAKey key = key(); private static final JWKSource jwks = jwks(key); private static RSAKey key() { try { KeyPair pair = KeyPairGenerator.getInstance("RSA").generateKeyPair(); return new RSAKey.Builder((RSAPublicKey) pair.getPublic()).privateKey(pair.getPrivate()).build(); } catch (Exception ex) { throw new RuntimeException(ex); } } private static JWKSource jwks(RSAKey key) { try { return new ImmutableJWKSet<>(new JWKSet(key)); } catch (Exception ex) { throw new RuntimeException(ex); } } private final String username = "user"; private final JwtEncoder encoder = new NimbusJwtEncoder(jwks); private String nonce; @Autowired ClientRegistration registration; @Autowired(required = false) MockWebServer web; static ServerWebExchangeMatcher or(String... patterns) { List matchers = new ArrayList<>(); for (String pattern : patterns) { matchers.add(new PathPatternParserServerWebExchangeMatcher(pattern)); } return new OrServerWebExchangeMatcher(matchers); } @Bean @Order(0) SecurityWebFilterChain authorizationServer(ServerHttpSecurity http, ClientRegistration registration) throws Exception { // @formatter:off http .securityMatcher(or("/jwks", "/login/oauth/authorize", "/nonce", "/token", "/token/logout", "/user")) .authorizeExchange((authorize) -> authorize .pathMatchers("/jwks").permitAll() .anyExchange().authenticated() ) .httpBasic(Customizer.withDefaults()) .oauth2ResourceServer((oauth2) -> oauth2 .jwt((jwt) -> jwt.jwkSetUri(registration.getProviderDetails().getJwkSetUri())) ); // @formatter:off return http.build(); } @Bean ReactiveUserDetailsService users(ClientRegistration registration) { return new MapReactiveUserDetailsService(User.withUsername(registration.getClientId()) .password("{noop}" + registration.getClientSecret()).authorities("APP").build()); } @GetMapping("/login/oauth/authorize") String nonce(@RequestParam("nonce") String nonce, @RequestParam("state") String state) { this.nonce = nonce; return state; } @PostMapping("/token") Map accessToken(WebSession session) { JwtEncoderParameters parameters = JwtEncoderParameters .from(JwtClaimsSet.builder().id("id").subject(this.username) .issuer(getIssuerUri()).issuedAt(Instant.now()) .expiresAt(Instant.now().plusSeconds(86400)).claim("scope", "openid").build()); String token = this.encoder.encode(parameters).getTokenValue(); return new OIDCTokens(idToken(session.getId()), new BearerAccessToken(token, 86400, new Scope("openid")), null) .toJSONObject(); } String idToken(String sessionId) { OidcIdToken token = TestOidcIdTokens.idToken().issuer(getIssuerUri()) .subject(this.username).expiresAt(Instant.now().plusSeconds(86400)) .audience(List.of(this.registration.getClientId())).nonce(this.nonce) .claim(LogoutTokenClaimNames.SID, sessionId).build(); JwtEncoderParameters parameters = JwtEncoderParameters .from(JwtClaimsSet.builder().claims((claims) -> claims.putAll(token.getClaims())).build()); return this.encoder.encode(parameters).getTokenValue(); } private String getIssuerUri() { if (this.web == null) { return TestClientRegistrations.clientRegistration().build().getProviderDetails().getIssuerUri(); } return this.web.url("/").toString(); } @GetMapping("/user") Map userinfo() { return Map.of("sub", this.username, "id", this.username); } @GetMapping("/jwks") String jwks() { return new JWKSet(key).toString(); } @GetMapping("/token/logout") String logoutToken(@AuthenticationPrincipal OidcUser user) { OidcLogoutToken token = TestOidcLogoutTokens.withUser(user) .audience(List.of(this.registration.getClientId())).build(); JwsHeader header = JwsHeader.with(SignatureAlgorithm.RS256).type("logout+jwt").build(); JwtClaimsSet claims = JwtClaimsSet.builder().claims((c) -> c.putAll(token.getClaims())).build(); JwtEncoderParameters parameters = JwtEncoderParameters.from(header, claims); return this.encoder.encode(parameters).getTokenValue(); } @GetMapping("/token/logout/all") String logoutTokenAll(@AuthenticationPrincipal OidcUser user) { OidcLogoutToken token = TestOidcLogoutTokens.withUser(user) .audience(List.of(this.registration.getClientId())) .claims((claims) -> claims.remove(LogoutTokenClaimNames.SID)).build(); JwsHeader header = JwsHeader.with(SignatureAlgorithm.RS256).type("JWT").build(); JwtClaimsSet claims = JwtClaimsSet.builder().claims((c) -> c.putAll(token.getClaims())).build(); JwtEncoderParameters parameters = JwtEncoderParameters.from(header, claims); return this.encoder.encode(parameters).getTokenValue(); } } @Configuration static class WebServerConfig { private final MockWebServer server = new MockWebServer(); @Bean MockWebServer web(ObjectProvider web) { this.server.setDispatcher(new WebTestClientDispatcher(web)); return this.server; } @PreDestroy void shutdown() throws IOException { this.server.shutdown(); } } private static class WebTestClientDispatcher extends Dispatcher { private final ObjectProvider webProvider; private WebTestClient web; private Consumer assertion = (rr) -> { }; WebTestClientDispatcher(ObjectProvider web) { this.webProvider = web; } @Override public MockResponse dispatch(RecordedRequest request) throws InterruptedException { this.assertion.accept(request); this.web = this.webProvider.getObject(); String method = request.getMethod(); String path = request.getPath(); String csrf = request.getHeader("X-CSRF-TOKEN"); String sessionId = session(request); WebTestClient.RequestHeadersSpec r; if ("GET".equals(method)) { r = this.web.get().uri(path); } else { WebTestClient.RequestBodySpec body; if (csrf == null) { body = this.web.mutateWith(csrf()).post().uri(path); } else { body = this.web.post().uri(path).header("X-CSRF-TOKEN", csrf); } body.body(BodyInserters.fromValue(request.getBody().readUtf8())); r = body; } for (Map.Entry> header : request.getHeaders().toMultimap().entrySet()) { if (header.getKey().equalsIgnoreCase("Cookie")) { continue; } r.header(header.getKey(), header.getValue().iterator().next()); } if (sessionId != null) { r.cookie(SESSION_COOKIE_NAME, sessionId); } try { FluxExchangeResult result = r.exchange().returnResult(String.class); return toMockResponse(result); } catch (Exception ex) { MockResponse response = new MockResponse(); response.setResponseCode(500); response.setBody(ex.getMessage()); return response; } } void setAssertion(Consumer assertion) { this.assertion = assertion; } private String session(RecordedRequest request) { String cookieHeaderValue = request.getHeader("Cookie"); if (cookieHeaderValue == null) { return null; } String[] cookies = cookieHeaderValue.split(";"); for (String cookie : cookies) { String[] parts = cookie.split("="); if (SESSION_COOKIE_NAME.equals(parts[0])) { return parts[1]; } if ("JSESSIONID".equals(parts[0])) { return parts[1]; } } return null; } private MockResponse toMockResponse(FluxExchangeResult result) { MockResponse response = new MockResponse(); response.setResponseCode(result.getStatus().value()); for (String name : result.getResponseHeaders().headerNames()) { response.addHeader(name, result.getResponseHeaders().getFirst(name)); } String body = result.getResponseBody().blockFirst(); if (body != null) { response.setBody(body); } return response; } } } ================================================ FILE: config/src/test/java/org/springframework/security/config/web/server/OneTimeTokenLoginSpecTests.java ================================================ /* * 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 org.springframework.security.config.web.server; import java.util.Collections; import java.util.Map; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentMatchers; import org.mockito.Mockito; import reactor.core.publisher.Mono; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.http.MediaType; import org.springframework.security.authentication.ott.OneTimeToken; import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.core.userdetails.MapReactiveUserDetailsService; import org.springframework.security.core.userdetails.ReactiveUserDetailsService; import org.springframework.security.core.userdetails.User; import org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers; import org.springframework.security.web.server.SecurityWebFilterChain; import org.springframework.security.web.server.authentication.RedirectServerAuthenticationSuccessHandler; import org.springframework.security.web.server.authentication.ott.DefaultServerGenerateOneTimeTokenRequestResolver; import org.springframework.security.web.server.authentication.ott.ServerGenerateOneTimeTokenRequestResolver; import org.springframework.security.web.server.authentication.ott.ServerOneTimeTokenGenerationSuccessHandler; import org.springframework.security.web.server.authentication.ott.ServerRedirectOneTimeTokenGenerationSuccessHandler; import org.springframework.test.web.reactive.server.WebTestClient; import org.springframework.web.reactive.config.EnableWebFlux; import org.springframework.web.reactive.function.BodyInserters; import org.springframework.web.server.ServerWebExchange; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatException; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; /** * Tests for {@link ServerHttpSecurity.OneTimeTokenLoginSpec} * * @author Max Batischev */ @ExtendWith(SpringTestContextExtension.class) public class OneTimeTokenLoginSpecTests { public final SpringTestContext spring = new SpringTestContext(this); private WebTestClient client; private static final String EXPECTED_HTML_HEAD = """ Please sign in """; private static final String LOGIN_PART = """