Compare commits

..

No commits in common. "main" and "7.1.0-M3" have entirely different histories.

573 changed files with 1915 additions and 7123 deletions

View File

@ -5,7 +5,6 @@ on:
branches:
- main
- '*.x'
- 'docs-build'
run-name: Merge Dependabot PR ${{ github.ref_name }}

View File

@ -14,7 +14,7 @@ permissions:
jobs:
snapshot-test:
name: Test Against Snapshots
uses: spring-io/spring-security-release-tools/.github/workflows/test.yml@b92832ecbc7cbe969201e6beafbde0ee400cf095 # v1.0.15
uses: spring-io/spring-security-release-tools/.github/workflows/test.yml@729fed56d42122f88583aff1be35c0800b7d77e9 # v1.0.14
strategy:
matrix:
include:
@ -31,6 +31,6 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Send Notification
uses: spring-io/spring-security-release-tools/.github/actions/send-notification@b92832ecbc7cbe969201e6beafbde0ee400cf095 # v1.0.15
uses: spring-io/spring-security-release-tools/.github/actions/send-notification@729fed56d42122f88583aff1be35c0800b7d77e9 # v1.0.14
with:
webhook-url: ${{ secrets.SPRING_SECURITY_CI_GCHAT_WEBHOOK_URL }}

View File

@ -16,7 +16,7 @@ permissions:
jobs:
perform-release:
name: Perform Release
uses: spring-io/spring-security-release-tools/.github/workflows/perform-release.yml@b92832ecbc7cbe969201e6beafbde0ee400cf095 # v1.0.15
uses: spring-io/spring-security-release-tools/.github/workflows/perform-release.yml@729fed56d42122f88583aff1be35c0800b7d77e9 # v1.0.14
with:
should-perform-release: true
project-version: ${{ inputs.version }}

View File

@ -30,6 +30,6 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Send Notification
uses: spring-io/spring-security-release-tools/.github/actions/send-notification@b92832ecbc7cbe969201e6beafbde0ee400cf095 # v1.0.15
uses: spring-io/spring-security-release-tools/.github/actions/send-notification@729fed56d42122f88583aff1be35c0800b7d77e9 # v1.0.14
with:
webhook-url: ${{ secrets.SPRING_SECURITY_CI_GCHAT_WEBHOOK_URL }}

View File

@ -13,7 +13,7 @@ jobs:
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up gradle
uses: spring-io/spring-gradle-build-action@c8668747d7c264864c8c7f7026d0d277d14a78dc # v2.0.6
uses: spring-io/spring-gradle-build-action@efc55f07f4dfa22f2afd97f9ea1be4212eeed737 # v2.0.5
with:
java-version: '25'
distribution: 'temurin'
@ -26,7 +26,7 @@ jobs:
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up gradle
uses: spring-io/spring-gradle-build-action@c8668747d7c264864c8c7f7026d0d277d14a78dc # v2.0.6
uses: spring-io/spring-gradle-build-action@efc55f07f4dfa22f2afd97f9ea1be4212eeed737 # v2.0.5
with:
java-version: '25'
distribution: 'temurin'
@ -46,6 +46,6 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Send Notification
uses: spring-io/spring-security-release-tools/.github/actions/send-notification@b92832ecbc7cbe969201e6beafbde0ee400cf095 # v1.0.15
uses: spring-io/spring-security-release-tools/.github/actions/send-notification@729fed56d42122f88583aff1be35c0800b7d77e9 # v1.0.14
with:
webhook-url: ${{ secrets.SPRING_SECURITY_CI_GCHAT_WEBHOOK_URL }}

View File

@ -9,7 +9,7 @@ permissions:
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
uses: spring-io/spring-security-release-tools/.github/workflows/update-scheduled-release-version.yml@729fed56d42122f88583aff1be35c0800b7d77e9 # v1.0.14
secrets: inherit
send-notification:
name: Send Notification
@ -18,6 +18,6 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Send Notification
uses: spring-io/spring-security-release-tools/.github/actions/send-notification@b92832ecbc7cbe969201e6beafbde0ee400cf095 # v1.0.15
uses: spring-io/spring-security-release-tools/.github/actions/send-notification@729fed56d42122f88583aff1be35c0800b7d77e9 # v1.0.14
with:
webhook-url: ${{ secrets.SPRING_SECURITY_CI_GCHAT_WEBHOOK_URL }}

View File

@ -20,8 +20,6 @@ import java.io.Serial;
import java.util.ArrayList;
import java.util.List;
import org.jspecify.annotations.Nullable;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.core.annotation.SecurityAnnotationScanner;
import org.springframework.util.Assert;
@ -52,7 +50,7 @@ public class SecurityConfig implements ConfigAttribute {
}
@Override
public boolean equals(@Nullable Object obj) {
public boolean equals(Object obj) {
if (obj instanceof ConfigAttribute attr) {
return this.attrib.equals(attr.getAttribute());
}

View File

@ -114,10 +114,8 @@ public final class DelegatingMethodSecurityMetadataSource extends AbstractMethod
}
@Override
public boolean equals(@Nullable Object other) {
if (!(other instanceof DefaultCacheKey otherKey)) {
return false;
}
public boolean equals(Object other) {
DefaultCacheKey otherKey = (DefaultCacheKey) other;
return (this.method.equals(otherKey.method)
&& ObjectUtils.nullSafeEquals(this.targetClass, otherKey.targetClass));
}

View File

@ -265,7 +265,7 @@ public class MapBasedMethodSecurityMetadataSource extends AbstractFallbackMethod
}
@Override
public boolean equals(@Nullable Object obj) {
public boolean equals(Object obj) {
if (this == obj) {
return true;
}

View File

@ -145,7 +145,6 @@ public class PrePostAdviceReactiveMethodInterceptor implements MethodInterceptor
.map((r) -> (attr != null) ? this.postAdvice.after(auth, invocation, attr, r) : r));
}
@SuppressWarnings("unchecked")
private static <T extends Publisher<?>> @Nullable T proceed(final MethodInvocation invocation) {
try {
return (T) invocation.proceed();

View File

@ -111,7 +111,6 @@ public class AclEntryAfterInvocationCollectionFilteringProvider extends Abstract
return returnedObject;
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private Filterer getFilterer(Object returnedObject) {
if (returnedObject instanceof Collection) {
return new CollectionFilterer((Collection) returnedObject);

View File

@ -50,7 +50,6 @@ class MessageExpressionConfigAttribute implements ConfigAttribute, EvaluationCon
* @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");

View File

@ -41,7 +41,6 @@ public class DefaultWebSecurityExpressionHandler extends AbstractSecurityExpress
private String defaultRolePrefix = DEFAULT_ROLE_PREFIX;
@Override
@SuppressWarnings("deprecation")
protected SecurityExpressionOperations createSecurityExpressionRoot(@Nullable Authentication authentication,
FilterInvocation fi) {
FilterInvocationExpressionRoot root = new FilterInvocationExpressionRoot(() -> authentication, fi);

View File

@ -29,7 +29,6 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException
*
* @author Ben Alex
*/
@SuppressWarnings("deprecation")
public class AuthenticationCredentialsNotFoundEventTests {
@Test

View File

@ -32,7 +32,6 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException
*
* @author Ben Alex
*/
@SuppressWarnings("deprecation")
public class AuthorizationFailureEventTests {
private final UsernamePasswordAuthenticationToken foo = UsernamePasswordAuthenticationToken.unauthenticated("foo",

View File

@ -29,7 +29,6 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException
*
* @author Ben Alex
*/
@SuppressWarnings("deprecation")
public class AuthorizedEventTests {
@Test

View File

@ -27,7 +27,6 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException
*
* @author Ben Alex
*/
@SuppressWarnings("deprecation")
public class SecurityConfigTests {
@Test

View File

@ -32,7 +32,6 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Luke Taylor
* @author Ben Alex
*/
@SuppressWarnings("deprecation")
public class Jsr250MethodSecurityMetadataSourceTests {
Jsr250MethodSecurityMetadataSource mds;

View File

@ -31,7 +31,6 @@ import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Luke Taylor
*/
@SuppressWarnings("deprecation")
public class Jsr250VoterTests {
// SEC-1443

View File

@ -46,7 +46,6 @@ import static org.assertj.core.api.Assertions.fail;
* @author Ben Alex
* @author Luke Taylor
*/
@SuppressWarnings("deprecation")
public class SecuredAnnotationSecurityMetadataSourceTests {
private SecuredAnnotationSecurityMetadataSource mds = new SecuredAnnotationSecurityMetadataSource();

View File

@ -79,13 +79,11 @@ public class DefaultMethodSecurityExpressionHandlerTests {
}
@Test
@SuppressWarnings("deprecation")
public void setTrustResolverNull() {
assertThatIllegalArgumentException().isThrownBy(() -> this.handler.setTrustResolver(null));
}
@Test
@SuppressWarnings("deprecation")
public void createEvaluationContextCustomTrustResolver() {
setupMocks();
this.handler.setTrustResolver(this.trustResolver);
@ -177,7 +175,7 @@ public class DefaultMethodSecurityExpressionHandlerTests {
@Test
public void createEvaluationContextSupplierAuthentication() {
setupMocks();
Supplier<Authentication> mockAuthenticationSupplier = mock();
Supplier<Authentication> mockAuthenticationSupplier = mock(Supplier.class);
given(mockAuthenticationSupplier.get()).willReturn(this.authentication);
EvaluationContext context = this.handler.createEvaluationContext(mockAuthenticationSupplier,
this.methodInvocation);

View File

@ -39,7 +39,6 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException
* @since 5.2
*/
@ExtendWith(MockitoExtension.class)
@SuppressWarnings("deprecation")
public class ExpressionBasedPreInvocationAdviceTests {
@Mock

View File

@ -34,7 +34,7 @@ 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" })
@SuppressWarnings("unchecked")
public class MethodExpressionVoterTests {
private TestingAuthenticationToken joe = new TestingAuthenticationToken("joe", "joespass", "ROLE_blah");

View File

@ -27,7 +27,6 @@ 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;
@ -59,9 +58,7 @@ public class MethodSecurityExpressionRootTests {
this.ctx = new StandardEvaluationContext();
this.ctx.setRootObject(this.root);
this.trustResolver = mock(AuthenticationTrustResolver.class);
DefaultAuthorizationManagerFactory<MethodInvocation> authorizationManagerFactory = new DefaultAuthorizationManagerFactory<>();
authorizationManagerFactory.setTrustResolver(this.trustResolver);
this.root.setAuthorizationManagerFactory(authorizationManagerFactory);
this.root.setTrustResolver(this.trustResolver);
}
@Test

View File

@ -44,7 +44,6 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Luke Taylor
* @since 3.0
*/
@SuppressWarnings("deprecation")
public class PrePostAnnotationSecurityMetadataSourceTests {
private PrePostAnnotationSecurityMetadataSource mds = new PrePostAnnotationSecurityMetadataSource(

View File

@ -32,7 +32,6 @@ import static org.mockito.Mockito.mock;
*
* @author Ben Alex
*/
@SuppressWarnings("deprecation")
public class AbstractSecurityInterceptorTests {
@Test

View File

@ -38,7 +38,7 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException
*
* @author Ben Alex
*/
@SuppressWarnings({ "unchecked", "deprecation" })
@SuppressWarnings("unchecked")
public class AfterInvocationProviderManagerTests {
@Test

View File

@ -34,7 +34,6 @@ import static org.assertj.core.api.Assertions.assertThat;
*
* @author Ben Alex
*/
@SuppressWarnings("deprecation")
public class InterceptorStatusTokenTests {
@Test

View File

@ -27,7 +27,6 @@ import static org.assertj.core.api.Assertions.assertThat;
*
* @author Ben Alex
*/
@SuppressWarnings("deprecation")
public class NullRunAsManagerTests {
@Test

View File

@ -31,7 +31,6 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException
/**
* Tests {@link RunAsImplAuthenticationProvider}.
*/
@SuppressWarnings("deprecation")
public class RunAsImplAuthenticationProviderTests {
@Test

View File

@ -34,7 +34,6 @@ import static org.assertj.core.api.Assertions.fail;
*
* @author Ben Alex
*/
@SuppressWarnings("deprecation")
public class RunAsManagerImplTests {
@Test

View File

@ -29,7 +29,6 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
*
* @author Ben Alex
*/
@SuppressWarnings("deprecation")
public class RunAsUserTokenTests {
@Test

View File

@ -65,7 +65,7 @@ import static org.mockito.Mockito.verifyNoMoreInteractions;
* @author Ben Alex
* @author Rob Winch
*/
@SuppressWarnings({ "unchecked", "deprecation" })
@SuppressWarnings("unchecked")
public class MethodSecurityInterceptorTests {
private TestingAuthenticationToken token;

View File

@ -33,7 +33,6 @@ import static org.mockito.Mockito.mock;
*
* @author Ben Alex
*/
@SuppressWarnings("deprecation")
public class MethodSecurityMetadataSourceAdvisorTests {
@Test

View File

@ -62,7 +62,6 @@ import static org.mockito.Mockito.verifyNoMoreInteractions;
* @author Luke Taylor
* @author Rob Winch
*/
@SuppressWarnings("deprecation")
public class AspectJMethodSecurityInterceptorTests {
private TestingAuthenticationToken token;

View File

@ -34,7 +34,6 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Luke Taylor
* @since 2.0.4
*/
@SuppressWarnings("deprecation")
public class MapBasedMethodSecurityMetadataSourceTests {
private final List<ConfigAttribute> ROLE_A = SecurityConfig.createList("ROLE_A");

View File

@ -49,7 +49,6 @@ import static org.mockito.Mockito.mock;
*
* @author Ben Alex
*/
@SuppressWarnings("deprecation")
public class MethodInvocationPrivilegeEvaluatorTests {
private TestingAuthenticationToken token;

View File

@ -36,7 +36,7 @@ import static org.mockito.Mockito.mock;
/**
* @author Luke Taylor
*/
@SuppressWarnings({ "unchecked", "deprecation" })
@SuppressWarnings({ "unchecked" })
public class DelegatingMethodSecurityMetadataSourceTests {
DelegatingMethodSecurityMetadataSource mds;

View File

@ -29,7 +29,6 @@ import org.springframework.security.access.intercept.aspectj.MethodInvocationAda
import static org.assertj.core.api.Assertions.assertThat;
@ExtendWith(MockitoExtension.class)
@SuppressWarnings("deprecation")
public class PostInvocationAdviceProviderTests {
@Mock

View File

@ -29,7 +29,6 @@ import org.springframework.security.access.intercept.aspectj.MethodInvocationAda
import static org.assertj.core.api.Assertions.assertThat;
@ExtendWith(MockitoExtension.class)
@SuppressWarnings("deprecation")
public class PreInvocationAuthorizationAdviceVoterTests {
@Mock

View File

@ -36,7 +36,7 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException
*
* @author Ben Alex
*/
@SuppressWarnings({ "unchecked", "deprecation" })
@SuppressWarnings("unchecked")
public class AbstractAccessDecisionManagerTests {
@Test

View File

@ -31,7 +31,6 @@ import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Luke Taylor
*/
@SuppressWarnings("deprecation")
public class AbstractAclVoterTests {
private AbstractAclVoter voter = new AbstractAclVoter() {

View File

@ -40,7 +40,6 @@ import static org.mockito.Mockito.mock;
*
* @author Ben Alex
*/
@SuppressWarnings("deprecation")
public class AffirmativeBasedTests {
private final List<ConfigAttribute> attrs = new ArrayList<>();

View File

@ -37,7 +37,6 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException
*
* @author Ben Alex
*/
@SuppressWarnings("deprecation")
public class AuthenticatedVoterTests {
private Authentication createAnonymous() {

View File

@ -35,7 +35,6 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
*
* @author Ben Alex
*/
@SuppressWarnings("deprecation")
public class ConsensusBasedTests {
@Test

View File

@ -32,7 +32,6 @@ import org.springframework.security.core.Authentication;
*
* @author Ben Alex
*/
@SuppressWarnings("deprecation")
public class DenyAgainVoter implements AccessDecisionVoter<Object> {
@Override

View File

@ -34,7 +34,6 @@ import org.springframework.security.core.Authentication;
*
* @author Ben Alex
*/
@SuppressWarnings("deprecation")
public class DenyVoter implements AccessDecisionVoter<Object> {
@Override

View File

@ -25,7 +25,6 @@ import org.springframework.security.authentication.TestingAuthenticationToken;
import static org.assertj.core.api.Assertions.assertThat;
@SuppressWarnings("deprecation")
public class RoleHierarchyVoterTests {
@Test

View File

@ -28,7 +28,6 @@ import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Luke Taylor
*/
@SuppressWarnings("deprecation")
public class RoleVoterTests {
@Test

View File

@ -35,7 +35,6 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
*
* @author Ben Alex
*/
@SuppressWarnings("deprecation")
public class UnanimousBasedTests {
private UnanimousBased makeDecisionManager() {

View File

@ -44,7 +44,7 @@ import static org.mockito.Mockito.verify;
/**
* @author Luke Taylor
*/
@SuppressWarnings({ "unchecked", "deprecation" })
@SuppressWarnings({ "unchecked" })
public class AclEntryAfterInvocationCollectionFilteringProviderTests {
@Test

View File

@ -48,7 +48,7 @@ import static org.mockito.Mockito.verify;
/**
* @author Luke Taylor
*/
@SuppressWarnings({ "unchecked", "deprecation" })
@SuppressWarnings({ "unchecked" })
public class AclEntryAfterInvocationProviderTests {
@Test

View File

@ -35,7 +35,6 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
@ExtendWith(MockitoExtension.class)
@SuppressWarnings("deprecation")
public class ExpressionBasedMessageSecurityMetadataSourceFactoryTests {
@Mock
@ -58,7 +57,7 @@ public class ExpressionBasedMessageSecurityMetadataSourceFactoryTests {
MessageSecurityMetadataSource source;
MessageSecurityExpressionRoot<Object> rootObject;
MessageSecurityExpressionRoot rootObject;
@BeforeEach
public void setup() {
@ -69,7 +68,7 @@ public class ExpressionBasedMessageSecurityMetadataSourceFactoryTests {
this.matcherToExpression.put(this.matcher2, this.expression2);
this.source = ExpressionBasedMessageSecurityMetadataSourceFactory
.createExpressionMessageMetadataSource(this.matcherToExpression);
this.rootObject = new MessageSecurityExpressionRoot<>(this.authentication, this.message);
this.rootObject = new MessageSecurityExpressionRoot(this.authentication, this.message);
}
@Test

View File

@ -37,7 +37,6 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
@ExtendWith(MockitoExtension.class)
@SuppressWarnings("deprecation")
public class MessageExpressionConfigAttributeTests {
@Mock

View File

@ -44,7 +44,6 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
@ExtendWith(MockitoExtension.class)
@SuppressWarnings("deprecation")
public class MessageExpressionVoterTests {
@Mock
@ -77,7 +76,6 @@ public class MessageExpressionVoterTests {
}
@Test
@SuppressWarnings("unchecked")
public void voteGranted() {
given(this.expression.getValue(any(EvaluationContext.class), eq(Boolean.class))).willReturn(true);
given(this.matcher.matcher(any())).willCallRealMethod();
@ -86,7 +84,6 @@ public class MessageExpressionVoterTests {
}
@Test
@SuppressWarnings("unchecked")
public void voteDenied() {
given(this.expression.getValue(any(EvaluationContext.class), eq(Boolean.class))).willReturn(false);
given(this.matcher.matcher(any())).willCallRealMethod();
@ -95,7 +92,6 @@ public class MessageExpressionVoterTests {
}
@Test
@SuppressWarnings("unchecked")
public void voteAbstain() {
this.attributes = Arrays.<ConfigAttribute>asList(new SecurityConfig("ROLE_USER"));
assertThat(this.voter.vote(this.authentication, this.message, this.attributes))
@ -103,13 +99,11 @@ public class MessageExpressionVoterTests {
}
@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();
}
@ -125,13 +119,11 @@ public class MessageExpressionVoterTests {
}
@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))
@ -144,7 +136,6 @@ public class MessageExpressionVoterTests {
}
@Test
@SuppressWarnings("unchecked")
public void postProcessEvaluationContext() {
final MessageExpressionConfigAttribute configAttribute = mock(MessageExpressionConfigAttribute.class);
this.voter.setExpressionHandler(this.expressionHandler);

View File

@ -47,7 +47,6 @@ import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.willThrow;
@ExtendWith(MockitoExtension.class)
@SuppressWarnings("deprecation")
public class ChannelSecurityInterceptorTests {
@Mock

View File

@ -36,7 +36,6 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
@ExtendWith(MockitoExtension.class)
@SuppressWarnings("deprecation")
public class DefaultMessageSecurityMetadataSourceTests {
@Mock

View File

@ -43,7 +43,6 @@ import org.springframework.security.web.access.intercept.FilterSecurityIntercept
*
* @author Ben Alex
*/
@SuppressWarnings("deprecation")
public class DefaultWebInvocationPrivilegeEvaluatorTests {
private AccessDecisionManager adm;

View File

@ -42,7 +42,7 @@ import static org.mockito.Mockito.mock;
*
* @author Ben Alex
*/
@SuppressWarnings({ "unchecked", "deprecation" })
@SuppressWarnings("unchecked")
public class ChannelDecisionManagerImplTests {
@Test

View File

@ -39,7 +39,6 @@ import static org.mockito.Mockito.mock;
*
* @author Ben Alex
*/
@SuppressWarnings("deprecation")
public class ChannelProcessingFilterTests {
@Test

View File

@ -35,7 +35,6 @@ import static org.mockito.Mockito.mock;
*
* @author Ben Alex
*/
@SuppressWarnings("deprecation")
public class InsecureChannelProcessorTests {
@Test

View File

@ -36,7 +36,6 @@ import static org.mockito.Mockito.mock;
*
* @author Ben Alex
*/
@SuppressWarnings("deprecation")
public class RetryWithHttpEntryPointTests {
@Test

View File

@ -33,7 +33,6 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException
*
* @author Ben Alex
*/
@SuppressWarnings("deprecation")
public class RetryWithHttpsEntryPointTests {
@Test

View File

@ -35,7 +35,6 @@ import static org.mockito.Mockito.mock;
*
* @author Ben Alex
*/
@SuppressWarnings("deprecation")
public class SecureChannelProcessorTests {
@Test

View File

@ -64,7 +64,6 @@ public class DefaultWebSecurityExpressionHandlerTests {
}
@Test
@SuppressWarnings("deprecation")
public void expressionPropertiesAreResolvedAgainstAppContextBeans() {
StaticApplicationContext appContext = new StaticApplicationContext();
RootBeanDefinition bean = new RootBeanDefinition(SecurityConfig.class);
@ -79,13 +78,11 @@ public class DefaultWebSecurityExpressionHandlerTests {
}
@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");

View File

@ -33,7 +33,6 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException
/**
* @author Luke Taylor
*/
@SuppressWarnings("deprecation")
public class ExpressionBasedFilterInvocationSecurityMetadataSourceTests {
@Test

View File

@ -41,7 +41,7 @@ import static org.mockito.Mockito.mock;
/**
* @author Luke Taylor
*/
@SuppressWarnings({ "unchecked", "deprecation" })
@SuppressWarnings({ "unchecked" })
public class WebExpressionVoterTests {
private Authentication user = new TestingAuthenticationToken("user", "pass", "X");

View File

@ -40,7 +40,6 @@ import static org.mockito.Mockito.mock;
*
* @author Ben Alex
*/
@SuppressWarnings("deprecation")
public class DefaultFilterInvocationSecurityMetadataSourceTests {
private DefaultFilterInvocationSecurityMetadataSource fids;

View File

@ -62,7 +62,6 @@ import static org.mockito.Mockito.verifyNoMoreInteractions;
* @author Luke Taylor
* @author Rob Winch
*/
@SuppressWarnings("deprecation")
public class FilterSecurityInterceptorTests {
private AuthenticationManager am;

View File

@ -16,8 +16,6 @@
package org.springframework.security.acls.domain;
import org.jspecify.annotations.Nullable;
import org.springframework.security.acls.model.Permission;
/**
@ -54,7 +52,7 @@ public abstract class AbstractPermission implements Permission {
}
@Override
public final boolean equals(@Nullable Object obj) {
public final boolean equals(Object obj) {
if (obj == null) {
return false;
}

View File

@ -63,7 +63,7 @@ public class AccessControlEntryImpl implements AccessControlEntry, AuditableAcce
}
@Override
public boolean equals(@Nullable Object arg0) {
public boolean equals(Object arg0) {
if (!(arg0 instanceof AccessControlEntryImpl)) {
return false;
}

View File

@ -278,7 +278,7 @@ public class AclImpl implements Acl, MutableAcl, AuditableAcl, OwnershipAcl {
}
@Override
public boolean equals(@Nullable Object obj) {
public boolean equals(Object obj) {
if (obj == this) {
return true;
}

View File

@ -16,8 +16,6 @@
package org.springframework.security.acls.domain;
import org.jspecify.annotations.Nullable;
import org.springframework.security.acls.model.Sid;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.util.Assert;
@ -49,7 +47,7 @@ public class GrantedAuthoritySid implements Sid {
}
@Override
public boolean equals(@Nullable Object object) {
public boolean equals(Object object) {
if ((object == null) || !(object instanceof GrantedAuthoritySid)) {
return false;
}

View File

@ -19,8 +19,6 @@ package org.springframework.security.acls.domain;
import java.io.Serializable;
import java.lang.reflect.Method;
import org.jspecify.annotations.Nullable;
import org.springframework.security.acls.model.ObjectIdentity;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
@ -99,7 +97,7 @@ public class ObjectIdentityImpl implements ObjectIdentity {
* @return <code>true</code> if the presented object matches this object
*/
@Override
public boolean equals(@Nullable Object obj) {
public boolean equals(Object obj) {
if (obj == null || !(obj instanceof ObjectIdentityImpl)) {
return false;
}

View File

@ -16,8 +16,6 @@
package org.springframework.security.acls.domain;
import org.jspecify.annotations.Nullable;
import org.springframework.security.acls.model.Sid;
import org.springframework.security.core.Authentication;
import org.springframework.util.Assert;
@ -48,7 +46,7 @@ public class PrincipalSid implements Sid {
}
@Override
public boolean equals(@Nullable Object object) {
public boolean equals(Object object) {
if ((object == null) || !(object instanceof PrincipalSid)) {
return false;
}

View File

@ -18,8 +18,6 @@ package org.springframework.security.acls.model;
import java.io.Serializable;
import org.jspecify.annotations.Nullable;
/**
* Represents the identity of an individual domain object instance.
*
@ -42,7 +40,7 @@ public interface ObjectIdentity extends Serializable {
* @see Object#equals(Object)
*/
@Override
boolean equals(@Nullable Object obj);
boolean equals(Object obj);
/**
* Obtains the actual identifier. This identifier must not be reused to represent

View File

@ -18,8 +18,6 @@ package org.springframework.security.acls.model;
import java.io.Serializable;
import org.jspecify.annotations.Nullable;
/**
* A security identity recognised by the ACL system.
*
@ -42,7 +40,7 @@ public interface Sid extends Serializable {
* @return <code>true</code> if the objects are equal, <code>false</code> otherwise
*/
@Override
boolean equals(@Nullable Object obj);
boolean equals(Object obj);
/**
* Refer to the <code>java.lang.Object</code> documentation for the interface

View File

@ -80,7 +80,7 @@ develocity {
}
nohttp {
source.exclude "buildSrc/build/**", "**/build/**", "**/target/**", "javascript/.gradle/**", "javascript/package-lock.json", "javascript/node_modules/**", "javascript/build/**", "javascript/dist/**"
source.exclude "buildSrc/build/**", "javascript/.gradle/**", "javascript/package-lock.json", "javascript/node_modules/**", "javascript/build/**", "javascript/dist/**"
source.builtBy(project(':spring-security-config').tasks.withType(RncToXsd))
}

View File

@ -3,7 +3,6 @@ plugins {
id "groovy-gradle-plugin"
id "java"
id "groovy"
id "org.jetbrains.dokka" version "2.2.0"
}
java {
@ -80,7 +79,6 @@ dependencies {
implementation libs.io.spring.javaformat.spring.javaformat.gradle.plugin
implementation libs.io.spring.nohttp.nohttp.gradle
implementation libs.org.jetbrains.kotlin.kotlin.gradle.plugin
implementation libs.org.jetbrains.dokka.dokka.gradle.plugin
implementation (libs.net.sourceforge.htmlunit) {
exclude group: 'org.eclipse.jetty.websocket', module: 'websocket-client'
}

View File

@ -5,7 +5,6 @@ import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.plugins.BasePlugin
import org.gradle.api.plugins.PluginManager
import org.gradle.api.tasks.Sync
import org.gradle.api.tasks.bundling.Zip
/**
@ -19,37 +18,6 @@ public class DocsPlugin implements Plugin<Project> {
PluginManager pluginManager = project.getPluginManager();
pluginManager.apply(BasePlugin);
pluginManager.apply(JavadocApiPlugin);
pluginManager.apply("org.jetbrains.dokka");
project.rootProject.subprojects { subproject ->
subproject.pluginManager.withPlugin("security-kotlin") {
subproject.pluginManager.apply("org.jetbrains.dokka")
configureDokka(subproject)
project.dependencies.add("dokka", subproject)
}
}
project.extensions.configure("dokka") { dokka ->
dokka.moduleName.set(Utils.getProjectName(project) + " Kotlin API")
}
project.tasks.named("dokkaGeneratePublicationHtml").configure { it.dependsOn("api") }
project.tasks.register("syncAntoraAttachments", Sync) { sync ->
sync.group = 'Documentation'
sync.description = 'Syncs the Antora attachments'
sync.into(project.layout.buildDirectory.dir('generated-antora-resources/modules/ROOT/assets/attachments/api'))
sync.from(project.provider({ project.tasks.api.outputs })) { copy ->
copy.into('java')
}
sync.from(project.tasks.named("dokkaGeneratePublicationHtml")) { copy ->
copy.into('kotlin')
}
}
project.tasks.register("generateAntoraResources") {
it.dependsOn 'generateAntoraYml', 'syncAntoraAttachments'
}
Task docsZip = project.tasks.create('docsZip', Zip) {
dependsOn 'api'
@ -73,16 +41,4 @@ public class DocsPlugin implements Plugin<Project> {
}
project.tasks.assemble.dependsOn docs
}
void configureDokka(Project project) {
project.extensions.configure("dokka") { dokka ->
dokka.dokkaSourceSets.configureEach { spec ->
spec.suppressedFiles.from(project.fileTree("src/main/java"))
spec.externalDocumentationLinks.register("javadoc") {
it.url.set(new URI("https://docs.spring.io/${Utils.getProjectName(project)}/reference/${project.rootProject.version}/api/java/"))
it.packageListUrl.set(project.layout.buildDirectory.file("api/element-list").get().asFile.toURI())
}
}
}
}
}

View File

@ -21,12 +21,8 @@ import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.Task;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.logging.Logger;
import org.gradle.api.plugins.JavaBasePlugin;
import org.gradle.api.provider.Property;
import org.gradle.api.provider.Provider;
import org.gradle.api.provider.ProviderFactory;
import org.gradle.api.specs.Spec;
import org.gradle.api.tasks.CacheableTask;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.OutputFile;
@ -34,7 +30,6 @@ import org.gradle.api.tasks.TaskAction;
import org.gradle.api.tasks.TaskExecutionException;
import org.gradle.api.tasks.TaskProvider;
import org.gradle.api.tasks.VerificationException;
import org.gradle.process.ExecOutput;
import java.io.IOException;
import java.nio.file.Files;
@ -49,53 +44,21 @@ public class CheckExpectedBranchVersionPlugin implements Plugin<Project> {
TaskProvider<CheckExpectedBranchVersionTask> checkExpectedBranchVersionTask = project.getTasks().register("checkExpectedBranchVersion", CheckExpectedBranchVersionTask.class, (task) -> {
task.setGroup("Build");
task.setDescription("Check if the project version matches the branch version");
task.onlyIf("Property 'skipCheckExpectedBranchVersion' is false or not present", skipPropertyFalseOrNotPresent(project.getProviders()));
task.onlyIf("Branch name matches expected version pattern *.x", CheckExpectedBranchVersionPlugin::isVersionBranch);
task.onlyIf("skipCheckExpectedBranchVersion property is false or not present", CheckExpectedBranchVersionPlugin::skipPropertyFalseOrNotPresent);
task.getVersion().convention(project.provider(() -> project.getVersion().toString()));
task.getBranchName().convention(getBranchName(project.getProviders(), project.getLogger()));
task.getBranchName().convention(project.getProviders().exec((execSpec) -> execSpec.setCommandLine("git", "symbolic-ref", "--short", "HEAD")).getStandardOutput().getAsText());
task.getOutputFile().convention(project.getLayout().getBuildDirectory().file("check-expected-branch-version"));
});
project.getTasks().named(JavaBasePlugin.CHECK_TASK_NAME, checkTask -> checkTask.dependsOn(checkExpectedBranchVersionTask));
}
private static Spec<Task> skipPropertyFalseOrNotPresent(ProviderFactory providers) {
Provider<Boolean> skipPropertyFalseOrNotPresent = providers
private static boolean skipPropertyFalseOrNotPresent(Task task) {
return task.getProject()
.getProviders()
.gradleProperty("skipCheckExpectedBranchVersion")
.orElse("false")
.map("false"::equalsIgnoreCase);
return (task) -> skipPropertyFalseOrNotPresent.get();
}
private static boolean isVersionBranch(Task task) {
return isVersionBranch((CheckExpectedBranchVersionTask) task);
}
private static boolean isVersionBranch(CheckExpectedBranchVersionTask task) {
String branchName = task.getBranchName().getOrNull();
if (branchName == null) {
return false;
}
return branchName.matches("^[0-9]+\\.[0-9]+\\.x$");
}
private static Provider<String> getBranchName(ProviderFactory providers, Logger logger) {
ExecOutput execOutput = providers.exec((execSpec) -> {
execSpec.setCommandLine("git", "symbolic-ref", "--short", "HEAD");
execSpec.setIgnoreExitValue(true);
});
return providers.provider(() -> {
int exitValue = execOutput.getResult().get().getExitValue();
if (exitValue != 0) {
logger.warn("Unable to determine branch name. Received exit code '{}' from `git`.", exitValue);
logger.warn(execOutput.getStandardError().getAsText().getOrNull());
return null;
}
String branchName = execOutput.getStandardOutput().getAsText().get().trim();
logger.info("Git branch name is '{}'.", branchName);
return branchName;
});
.map("false"::equalsIgnoreCase)
.get();
}
@CacheableTask
@ -114,10 +77,15 @@ public class CheckExpectedBranchVersionPlugin implements Plugin<Project> {
public void run() {
String version = getVersion().get();
String branchVersion = getBranchName().map(String::trim).get();
if (!branchVersion.matches("^[0-9]+\\.[0-9]+\\.x$")) {
String msg = String.format("Branch version [%s] does not match *.x, ignoring", branchVersion);
getLogger().warn(msg);
writeExpectedVersionOutput(msg);
return;
}
if (!versionsMatch(version, branchVersion)) {
String msg = String.format("Project version [%s] does not match branch version [%s]. " +
"Please verify that the branch contains the right version. " +
"To bypass this check, run the build with -PskipCheckExpectedBranchVersion.", version, branchVersion);
"Please verify that the branch contains the right version.", version, branchVersion);
writeExpectedVersionOutput(msg);
throw new VerificationException(msg);
}

View File

@ -124,7 +124,7 @@ public class CasAuthenticationToken extends AbstractAuthenticationToken implemen
}
@Override
public boolean equals(@Nullable final Object obj) {
public boolean equals(final Object obj) {
if (!super.equals(obj)) {
return false;
}

View File

@ -71,7 +71,7 @@ final class DefaultServiceAuthenticationDetails extends WebAuthenticationDetails
}
@Override
public boolean equals(@Nullable Object obj) {
public boolean equals(Object obj) {
if (this == obj) {
return true;
}

View File

@ -16,16 +16,15 @@
package org.springframework.security.config.annotation.authorization;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ImportAware;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
import org.springframework.security.authorization.AuthorizationManagerFactories;
import org.springframework.security.authorization.AuthorizationManagerFactories.AdditionalRequiredFactorsBuilder;
import org.springframework.security.authorization.DefaultAuthorizationManagerFactory;
import org.springframework.security.config.Customizer;
/**
* Uses {@link EnableMultiFactorAuthentication} to configure a
@ -40,13 +39,10 @@ class AuthorizationManagerFactoryConfiguration implements ImportAware {
private String[] authorities;
@Bean
DefaultAuthorizationManagerFactory authorizationManagerFactory(
List<Customizer<AdditionalRequiredFactorsBuilder<Object>>> additionalRequiredFactorsCustomizers) {
AdditionalRequiredFactorsBuilder<Object> builder = AuthorizationManagerFactories.multiFactor()
DefaultAuthorizationManagerFactory authorizationManagerFactory(ObjectProvider<RoleHierarchy> roleHierarchy) {
AuthorizationManagerFactories.AdditionalRequiredFactorsBuilder<Object> builder = AuthorizationManagerFactories
.multiFactor()
.requireFactors(this.authorities);
for (Customizer<AdditionalRequiredFactorsBuilder<Object>> customizer : additionalRequiredFactorsCustomizers) {
customizer.customize(builder);
}
return builder.build();
}

View File

@ -30,12 +30,8 @@ import org.springframework.security.authorization.DefaultAuthorizationManagerFac
*
* When {@link #authorities()} is specified creates a
* {@link DefaultAuthorizationManagerFactory} as a Bean with the {@link #authorities()}
* specified as additional required authorities. When {@link #when()} is
* {@link MultiFactorCondition#WEBAUTHN_REGISTERED}, {@link #authorities()} must include
* {@link org.springframework.security.core.authority.FactorGrantedAuthority#WEBAUTHN_AUTHORITY};
* otherwise an {@link IllegalArgumentException} is thrown during configuration
* processing. When {@link #when()} is not specified (default is an empty array), no such
* requirement applies. The configuration will be picked up by both
* specified as additional required authorities. The configuration will be picked up by
* both
* {@link org.springframework.security.config.annotation.web.configuration.EnableWebSecurity}
* and
* {@link org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity}.
@ -49,19 +45,6 @@ import org.springframework.security.authorization.DefaultAuthorizationManagerFac
* }
* </pre>
*
* <p>
* You can also publish one or more
* {@code Customizer<AdditionalRequiredFactorsBuilder<Object>>} beans to further customize
* the {@link DefaultAuthorizationManagerFactory}. For example, conditionally applying MFA
* for specific users:
*
* <pre>
* &#64;Bean
* Customizer&lt;AuthorizationManagerFactories.AdditionalRequiredFactorsBuilder&lt;Object&gt;&gt; additionalRequiredFactorsCustomizer() {
* return (builder) -&gt; builder.when((auth) -&gt; "admin".equals(auth.getName()));
* }
* </pre>
*
* NOTE: At this time reactive applications do not support MFA and thus are not impacted.
* This will likely be enhanced in the future.
*
@ -84,15 +67,4 @@ public @interface EnableMultiFactorAuthentication {
*/
String[] authorities();
/**
* The conditions under which multi-factor authentication is required.
* <p>
* When multiple conditions are specified, they are applied as an AND (all conditions
* must be met).
* @return the conditions (default is an empty array, which requires MFA
* unconditionally)
* @since 7.1
*/
MultiFactorCondition[] when() default {};
}

View File

@ -17,24 +17,16 @@
package org.springframework.security.config.annotation.authorization;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.security.authorization.DefaultAuthorizationManagerFactory;
import org.springframework.security.core.authority.FactorGrantedAuthority;
/**
* Uses {@link EnableMultiFactorAuthentication} to configure a
* {@link DefaultAuthorizationManagerFactory}.
* <p>
* When {@link EnableMultiFactorAuthentication#when()} includes
* {@link MultiFactorCondition#WEBAUTHN_REGISTERED}, validates that
* {@link EnableMultiFactorAuthentication#authorities()} includes
* {@link org.springframework.security.core.authority.FactorGrantedAuthority#WEBAUTHN_AUTHORITY}
* and throws an {@link IllegalArgumentException} if not.
*
* @author Rob Winch
* @since 7.0
@ -47,19 +39,9 @@ class MultiFactorAuthenticationSelector implements ImportSelector {
Map<String, Object> multiFactorAuthenticationAttrs = metadata
.getAnnotationAttributes(EnableMultiFactorAuthentication.class.getName());
String[] authorities = (String[]) multiFactorAuthenticationAttrs.getOrDefault("authorities", new String[0]);
MultiFactorCondition[] when = (MultiFactorCondition[]) multiFactorAuthenticationAttrs.getOrDefault("when",
new MultiFactorCondition[0]);
boolean hasWebAuthn = Arrays.asList(when).contains(MultiFactorCondition.WEBAUTHN_REGISTERED);
if (hasWebAuthn && !Arrays.asList(authorities).contains(FactorGrantedAuthority.WEBAUTHN_AUTHORITY)) {
throw new IllegalArgumentException("When when() includes " + MultiFactorCondition.WEBAUTHN_REGISTERED
+ ", authorities() must include " + FactorGrantedAuthority.WEBAUTHN_AUTHORITY);
}
List<String> imports = new ArrayList<>(3);
List<String> imports = new ArrayList<>(2);
if (authorities.length > 0) {
imports.add(AuthorizationManagerFactoryConfiguration.class.getName());
if (hasWebAuthn) {
imports.add(WhenWebAuthnRegisteredMfaConfiguration.class.getName());
}
}
imports.add(EnableMfaFiltersConfiguration.class.getName());
return imports.toArray(new String[imports.size()]);

View File

@ -1,58 +0,0 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF 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.authorization;
/**
* Condition under which multi-factor authentication is required.
*
* @author Rob Winch
* @since 7.1
* @see EnableMultiFactorAuthentication#when()
*/
public enum MultiFactorCondition {
/**
* Require multi-factor authentication only when the user has a WebAuthn credential
* record registered.
* <p>
* When this condition is specified,
* {@link EnableMultiFactorAuthentication#authorities()} must include
* {@link org.springframework.security.core.authority.FactorGrantedAuthority#WEBAUTHN_AUTHORITY}.
* Failing to include it results in an {@link IllegalArgumentException} when the
* configuration is processed.
* <p>
* Using this condition also requires both a
* {@link org.springframework.security.web.webauthn.management.PublicKeyCredentialUserEntityRepository}
* Bean and a
* {@link org.springframework.security.web.webauthn.management.UserCredentialRepository}
* Bean to be published.
*
* <pre>
* &#64;Bean
* public PublicKeyCredentialUserEntityRepository userEntityRepository() {
* return new InMemoryPublicKeyCredentialUserEntityRepository();
* }
*
* &#64;Bean
* public UserCredentialRepository userCredentialRepository() {
* return new InMemoryUserCredentialRepository();
* }
* </pre>
*/
WEBAUTHN_REGISTERED
}

View File

@ -1,90 +0,0 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF 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.authorization;
import java.util.function.Predicate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authorization.AuthorizationManagerFactories.AdditionalRequiredFactorsBuilder;
import org.springframework.security.config.Customizer;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.webauthn.api.PublicKeyCredentialUserEntity;
import org.springframework.security.web.webauthn.management.PublicKeyCredentialUserEntityRepository;
import org.springframework.security.web.webauthn.management.UserCredentialRepository;
/**
* Configuration that provides a
* {@link Customizer}&lt;{@link AdditionalRequiredFactorsBuilder}&gt; for
* {@link MultiFactorCondition#WEBAUTHN_REGISTERED}, requiring multi-factor authentication
* only when the user has a WebAuthn credential record.
*
* @author Rob Winch
* @since 7.1
* @see EnableMultiFactorAuthentication#when()
* @see MultiFactorCondition#WEBAUTHN_REGISTERED
*/
@Configuration(proxyBeanMethods = false)
class WhenWebAuthnRegisteredMfaConfiguration {
@Bean
Customizer<AdditionalRequiredFactorsBuilder<Object>> additionalRequiredFactorsCustomizer(
PublicKeyCredentialUserEntityRepository userEntityRepository,
UserCredentialRepository userCredentialRepository) {
return (builder) -> builder.withWhen((current) -> {
Predicate<Authentication> webAuthnRegisteredPredicate = new WebAuthnRegisteredPredicate(
userEntityRepository, userCredentialRepository);
if (current == null) {
return webAuthnRegisteredPredicate;
}
return current.and(webAuthnRegisteredPredicate);
});
}
private static final class WebAuthnRegisteredPredicate implements Predicate<Authentication> {
private final PublicKeyCredentialUserEntityRepository userEntityRepository;
private final UserCredentialRepository userCredentialRepository;
private WebAuthnRegisteredPredicate(PublicKeyCredentialUserEntityRepository userEntityRepository,
UserCredentialRepository userCredentialRepository) {
this.userEntityRepository = userEntityRepository;
this.userCredentialRepository = userCredentialRepository;
}
@Override
public boolean test(Authentication authentication) {
if (authentication == null || authentication.getName() == null) {
return false;
}
PublicKeyCredentialUserEntity userEntity = this.userEntityRepository
.findByUsername(authentication.getName());
if (userEntity == null) {
return false;
}
return !this.userCredentialRepository.findByUserId(userEntity.getId()).isEmpty();
}
@Override
public String toString() {
return "WEBAUTHN_REGISTERED";
}
}
}

View File

@ -27,7 +27,6 @@ import java.util.function.Supplier;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.jspecify.annotations.Nullable;
import org.reactivestreams.Publisher;
import org.reactivestreams.Subscription;
import reactor.core.CoreSubscriber;
@ -287,7 +286,7 @@ class SecurityReactorContextConfiguration {
}
@Override
public boolean equals(@Nullable Object o) {
public boolean equals(Object o) {
if (this == o) {
return true;
}

View File

@ -21,18 +21,15 @@ import org.springframework.context.ApplicationContext;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.util.Assert;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.PreFlightRequestHandler;
import org.springframework.web.filter.CorsFilter;
import org.springframework.web.filter.PreFlightRequestFilter;
/**
* Adds {@link CorsFilter} or {@link PreFlightRequestFilter} to the Spring Security filter
* chain. If a bean by the name of corsFilter is provided, that {@link CorsFilter} is
* used. Else if corsConfigurationSource is defined, then that
* {@link CorsConfigurationSource} is used. If a {@link PreFlightRequestHandler} is set on
* this configurer, {@link CorsFilter} is not used and {@link PreFlightRequestFilter} is
* registered instead.
* Adds {@link CorsFilter} to the Spring Security filter chain. If a bean by the name of
* corsFilter is provided, that {@link CorsFilter} is used. Else if
* corsConfigurationSource is defined, then that {@link CorsConfiguration} is used.
*
* @param <H> the builder to return.
* @author Rob Winch
@ -46,8 +43,6 @@ public class CorsConfigurer<H extends HttpSecurityBuilder<H>> extends AbstractHt
private CorsConfigurationSource configurationSource;
private PreFlightRequestHandler preFlightRequestHandler;
/**
* Creates a new instance
*
@ -61,85 +56,30 @@ public class CorsConfigurer<H extends HttpSecurityBuilder<H>> extends AbstractHt
return this;
}
/**
* Use the given {@link PreFlightRequestHandler} for CORS preflight requests. When
* set, {@link CorsFilter} is not used. Cannot be combined with
* {@link #configurationSource(CorsConfigurationSource)}.
* @param preFlightRequestHandler the handler to use
* @return the {@link CorsConfigurer} for additional configuration
*/
public CorsConfigurer<H> preFlightRequestHandler(PreFlightRequestHandler preFlightRequestHandler) {
this.preFlightRequestHandler = preFlightRequestHandler;
return this;
}
@Override
public void configure(H http) {
ApplicationContext context = http.getSharedObject(ApplicationContext.class);
if (this.configurationSource != null && this.preFlightRequestHandler != null) {
throw new IllegalStateException(
"Cannot configure both a CorsConfigurationSource and a PreFlightRequestHandler on CorsConfigurer");
}
CorsFilter corsFilter = getCorsFilter(context);
if (corsFilter != null) {
http.addFilter(corsFilter);
return;
}
PreFlightRequestHandler preFlightRequestHandlerBean = getPreFlightRequestHandler(context);
if (preFlightRequestHandlerBean != null) {
http.addFilterBefore(new PreFlightRequestFilter(preFlightRequestHandlerBean), CorsFilter.class);
return;
}
throw new NoSuchBeanDefinitionException(CorsConfigurationSource.class,
"Failed to find a bean that implements `CorsConfigurationSource`. Please ensure that you are using "
+ "`@EnableWebMvc`, are publishing a `WebMvcConfigurer`, or are publishing a `CorsConfigurationSource` bean.");
}
private PreFlightRequestHandler getPreFlightRequestHandler(ApplicationContext context) {
if (this.configurationSource != null) {
return null;
}
if (this.preFlightRequestHandler != null) {
return this.preFlightRequestHandler;
}
if (context == null) {
return null;
}
if (context.getBeanNamesForType(PreFlightRequestHandler.class).length > 0) {
return context.getBean(PreFlightRequestHandler.class);
}
return null;
}
private CorsConfigurationSource getCorsConfigurationSource(ApplicationContext context) {
if (context == null) {
return null;
}
boolean containsCorsSource = context.containsBeanDefinition(CORS_CONFIGURATION_SOURCE_BEAN_NAME);
if (containsCorsSource) {
return context.getBean(CORS_CONFIGURATION_SOURCE_BEAN_NAME, CorsConfigurationSource.class);
}
return MvcCorsFilter.getMvcCorsConfigurationSource(context);
Assert.state(corsFilter != null, () -> "Please configure either a " + CORS_FILTER_BEAN_NAME + " bean or a "
+ CORS_CONFIGURATION_SOURCE_BEAN_NAME + "bean.");
http.addFilter(corsFilter);
}
private CorsFilter getCorsFilter(ApplicationContext context) {
if (this.preFlightRequestHandler != null) {
return null;
}
if (this.configurationSource != null) {
return new CorsFilter(this.configurationSource);
}
boolean containsCorsFilter = context != null && context.containsBeanDefinition(CORS_FILTER_BEAN_NAME);
boolean containsCorsFilter = context.containsBeanDefinition(CORS_FILTER_BEAN_NAME);
if (containsCorsFilter) {
return context.getBean(CORS_FILTER_BEAN_NAME, CorsFilter.class);
}
CorsConfigurationSource corsConfigurationSource = getCorsConfigurationSource(context);
if (corsConfigurationSource != null) {
return new CorsFilter(corsConfigurationSource);
boolean containsCorsSource = context.containsBean(CORS_CONFIGURATION_SOURCE_BEAN_NAME);
if (containsCorsSource) {
CorsConfigurationSource configurationSource = context.getBean(CORS_CONFIGURATION_SOURCE_BEAN_NAME,
CorsConfigurationSource.class);
return new CorsFilter(configurationSource);
}
return null;
return MvcCorsFilter.getMvcCorsFilter(context);
}
static class MvcCorsFilter {
@ -152,11 +92,15 @@ public class CorsConfigurer<H extends HttpSecurityBuilder<H>> extends AbstractHt
* @param context
* @return
*/
private static CorsConfigurationSource getMvcCorsConfigurationSource(ApplicationContext context) {
private static CorsFilter getMvcCorsFilter(ApplicationContext context) {
if (context.containsBean(HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME)) {
return context.getBean(HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME, CorsConfigurationSource.class);
CorsConfigurationSource corsConfigurationSource = context
.getBean(HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME, CorsConfigurationSource.class);
return new CorsFilter(corsConfigurationSource);
}
return null;
throw new NoSuchBeanDefinitionException(CorsConfigurationSource.class,
"Failed to find a bean that implements `CorsConfigurationSource`. Please ensure that you are using "
+ "`@EnableWebMvc`, are publishing a `WebMvcConfigurer`, or are publishing a `CorsConfigurationSource` bean.");
}
}

View File

@ -39,7 +39,6 @@ import org.springframework.security.web.webauthn.api.PublicKeyCredentialRpEntity
import org.springframework.security.web.webauthn.authentication.PublicKeyCredentialRequestOptionsFilter;
import org.springframework.security.web.webauthn.authentication.WebAuthnAuthenticationFilter;
import org.springframework.security.web.webauthn.authentication.WebAuthnAuthenticationProvider;
import org.springframework.security.web.webauthn.management.CredentialRecordOwnerAuthorizationManager;
import org.springframework.security.web.webauthn.management.MapPublicKeyCredentialUserEntityRepository;
import org.springframework.security.web.webauthn.management.MapUserCredentialRepository;
import org.springframework.security.web.webauthn.management.PublicKeyCredentialUserEntityRepository;
@ -181,8 +180,6 @@ public class WebAuthnConfigurer<H extends HttpSecurityBuilder<H>>
webAuthnAuthnFilter = postProcess(webAuthnAuthnFilter);
WebAuthnRegistrationFilter webAuthnRegistrationFilter = new WebAuthnRegistrationFilter(userCredentials,
rpOperations);
webAuthnRegistrationFilter.setDeleteCredentialAuthorizationManager(
new CredentialRecordOwnerAuthorizationManager(userCredentials, userEntities));
PublicKeyCredentialCreationOptionsFilter creationOptionsFilter = new PublicKeyCredentialCreationOptionsFilter(
rpOperations);
if (creationOptionsRepository != null) {

View File

@ -19,7 +19,6 @@ package org.springframework.security.config.annotation.web
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configurers.CorsConfigurer
import org.springframework.web.cors.CorsConfigurationSource
import org.springframework.web.cors.PreFlightRequestHandler
/**
* A Kotlin DSL to configure [HttpSecurity] CORS using idiomatic Kotlin code.
@ -27,14 +26,11 @@ import org.springframework.web.cors.PreFlightRequestHandler
* @author Eleftheria Stein
* @since 5.3
* @property configurationSource the [CorsConfigurationSource] to use.
* @property preFlightRequestHandler the [PreFlightRequestHandler] to use instead of [CorsFilter].
*/
@SecurityMarker
class CorsDsl {
var configurationSource: CorsConfigurationSource? = null
var preFlightRequestHandler: PreFlightRequestHandler? = null
private var disabled = false
/**
@ -46,8 +42,7 @@ class CorsDsl {
internal fun get(): (CorsConfigurer<HttpSecurity>) -> Unit {
return { cors ->
configurationSource?.also { cors.configurationSource(it) }
preFlightRequestHandler?.also { cors.preFlightRequestHandler(it) }
configurationSource?.also { cors.configurationSource(configurationSource) }
if (disabled) {
cors.disable()
}

View File

@ -1,122 +0,0 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF 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.authorization;
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.authorization.AuthorizationManagerFactories;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.authority.FactorGrantedAuthority;
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.support.WithMockUser;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.web.WebAppConfiguration;
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.WebApplicationContext;
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 EnableMultiFactorAuthentication} with
* {@link Customizer}&lt;{@link AuthorizationManagerFactories.AdditionalRequiredFactorsBuilder}&gt;.
*/
@ExtendWith(SpringExtension.class)
@WebAppConfiguration
@ContextConfiguration(classes = EnableMultiFactorAuthenticationCustomizerTests.ConfigWithCustomizer.class)
class EnableMultiFactorAuthenticationCustomizerTests {
@Autowired
MockMvc mvc;
@Test
@WithMockUser(username = "user", authorities = "ROLE_USER")
void whenCustomizerAppliedThenConditionalMfaUsed() throws Exception {
this.mvc.perform(get("/")).andExpect(status().isOk());
}
@Test
@WithMockUser(username = "admin", authorities = "ROLE_USER")
void whenCustomizerAppliedAndConditionTrueThenMfaRequired() throws Exception {
this.mvc.perform(get("/")).andExpect(status().isUnauthorized());
}
@Test
@WithMockUser(username = "admin", authorities = { "ROLE_USER", FactorGrantedAuthority.PASSWORD_AUTHORITY,
FactorGrantedAuthority.OTT_AUTHORITY })
void whenCustomizerAppliedAndConditionTrueWithMfaThenAuthorized() throws Exception {
this.mvc.perform(get("/")).andExpect(status().isOk());
}
@EnableWebSecurity
@Configuration
@EnableMultiFactorAuthentication(
authorities = { FactorGrantedAuthority.OTT_AUTHORITY, FactorGrantedAuthority.PASSWORD_AUTHORITY })
static class ConfigWithCustomizer {
@Bean
Customizer<AuthorizationManagerFactories.AdditionalRequiredFactorsBuilder<Object>> additionalRequiredFactorsCustomizer() {
return (builder) -> builder.when((auth) -> "admin".equals(auth.getName()));
}
@Bean
MockMvc mvc(WebApplicationContext context) {
return MockMvcBuilders.webAppContextSetup(context).apply(springSecurity()).build();
}
@Bean
@SuppressWarnings("deprecation")
UserDetailsService userDetailsService() {
UserDetails user = User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("USER")
.build();
UserDetails admin = User.withDefaultPasswordEncoder()
.username("admin")
.password("password")
.roles("USER")
.build();
return new InMemoryUserDetailsManager(user, admin);
}
@RestController
static class OkController {
@GetMapping("/")
String ok() {
return "ok";
}
}
}
}

View File

@ -1,145 +0,0 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF 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.authorization;
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.security.authorization.AuthorizationManagerFactories;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.authority.FactorGrantedAuthority;
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.support.WithMockUser;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.web.WebAppConfiguration;
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.WebApplicationContext;
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 EnableMultiFactorAuthentication} with multiple
* {@link Customizer}&lt;{@link AuthorizationManagerFactories.AdditionalRequiredFactorsBuilder}&gt;
* beans.
*/
@ExtendWith(SpringExtension.class)
@WebAppConfiguration
@ContextConfiguration(
classes = EnableMultiFactorAuthenticationMultipleCustomizersTests.ConfigWithMultipleCustomizers.class)
class EnableMultiFactorAuthenticationMultipleCustomizersTests {
@Autowired
MockMvc mvc;
@Test
@WithMockUser(username = "user", authorities = "ROLE_USER")
void whenCustomizerAppliedThenConditionalMfaUsed() throws Exception {
this.mvc.perform(get("/")).andExpect(status().isOk());
}
@Test
@WithMockUser(username = "admin", authorities = "ROLE_USER")
void whenCustomizersAppliedAndConditionTrueThenMfaRequired() throws Exception {
this.mvc.perform(get("/")).andExpect(status().isUnauthorized());
}
@Test
@WithMockUser(username = "admin", authorities = { "ROLE_USER", FactorGrantedAuthority.PASSWORD_AUTHORITY,
FactorGrantedAuthority.OTT_AUTHORITY })
void whenCustomizersAppliedAndConditionTrueWithMfaThenAuthorized() throws Exception {
this.mvc.perform(get("/")).andExpect(status().isOk());
}
@Test
@WithMockUser(username = "manager", authorities = "ROLE_USER")
void whenSecondCustomizerAppliedAndConditionTrueThenMfaRequired() throws Exception {
this.mvc.perform(get("/")).andExpect(status().isUnauthorized());
}
@EnableWebSecurity
@Configuration
@EnableMultiFactorAuthentication(
authorities = { FactorGrantedAuthority.OTT_AUTHORITY, FactorGrantedAuthority.PASSWORD_AUTHORITY })
static class ConfigWithMultipleCustomizers {
@Bean
@Order(1)
Customizer<AuthorizationManagerFactories.AdditionalRequiredFactorsBuilder<Object>> adminCustomizer() {
return (builder) -> builder.withWhen(
(current) -> (auth) -> "admin".equals(auth.getName()) || (current != null && current.test(auth)));
}
@Bean
@Order(2)
Customizer<AuthorizationManagerFactories.AdditionalRequiredFactorsBuilder<Object>> managerCustomizer() {
return (builder) -> builder.withWhen(
(current) -> (auth) -> "manager".equals(auth.getName()) || (current != null && current.test(auth)));
}
@Bean
MockMvc mvc(WebApplicationContext context) {
return MockMvcBuilders.webAppContextSetup(context).apply(springSecurity()).build();
}
@Bean
@SuppressWarnings("deprecation")
UserDetailsService userDetailsService() {
UserDetails user = User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("USER")
.build();
UserDetails admin = User.withDefaultPasswordEncoder()
.username("admin")
.password("password")
.roles("USER")
.build();
UserDetails manager = User.withDefaultPasswordEncoder()
.username("manager")
.password("password")
.roles("USER")
.build();
return new InMemoryUserDetailsManager(user, admin, manager);
}
@RestController
static class OkController {
@GetMapping("/")
String ok() {
return "ok";
}
}
}
}

View File

@ -1,108 +0,0 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF 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.authorization;
import java.util.HashMap;
import java.util.Map;
import org.junit.jupiter.api.Test;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.security.core.authority.FactorGrantedAuthority;
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 MultiFactorAuthenticationSelector}.
*
* @author Rob Winch
*/
class MultiFactorAuthenticationSelectorTests {
private final MultiFactorAuthenticationSelector selector = new MultiFactorAuthenticationSelector();
@Test
void selectImportsWhenWhenIsEmptyAndAuthoritiesSpecifiedThenReturnsImportsWithoutWebAuthnConfig() {
AnnotationMetadata metadata = metadata(new MultiFactorCondition[0], FactorGrantedAuthority.OTT_AUTHORITY,
FactorGrantedAuthority.PASSWORD_AUTHORITY);
String[] imports = this.selector.selectImports(metadata);
assertThat(imports).isNotEmpty();
assertThat(imports).doesNotContain(WhenWebAuthnRegisteredMfaConfiguration.class.getName());
}
@Test
void selectImportsWhenWhenOmittedThenDefaultsToEmptyAndReturnsImports() {
AnnotationMetadata metadata = metadataWithoutWhen(FactorGrantedAuthority.OTT_AUTHORITY,
FactorGrantedAuthority.PASSWORD_AUTHORITY);
String[] imports = this.selector.selectImports(metadata);
assertThat(imports).isNotEmpty();
assertThat(imports).doesNotContain(WhenWebAuthnRegisteredMfaConfiguration.class.getName());
}
@Test
void selectImportsWhenHasWebAuthnConditionAndAuthoritiesIncludesWebAuthnThenReturnsImports() {
AnnotationMetadata metadata = metadata(new MultiFactorCondition[] { MultiFactorCondition.WEBAUTHN_REGISTERED },
FactorGrantedAuthority.OTT_AUTHORITY, FactorGrantedAuthority.PASSWORD_AUTHORITY,
FactorGrantedAuthority.WEBAUTHN_AUTHORITY);
String[] imports = this.selector.selectImports(metadata);
assertThat(imports).isNotEmpty();
}
@Test
void selectImportsWhenHasWebAuthnConditionAndAuthoritiesOnlyWebAuthnThenReturnsImports() {
AnnotationMetadata metadata = metadata(new MultiFactorCondition[] { MultiFactorCondition.WEBAUTHN_REGISTERED },
FactorGrantedAuthority.WEBAUTHN_AUTHORITY);
String[] imports = this.selector.selectImports(metadata);
assertThat(imports).isNotEmpty();
}
@Test
void selectImportsWhenHasWebAuthnConditionAndAuthoritiesEmptyThenThrowsException() {
AnnotationMetadata metadata = metadata(new MultiFactorCondition[] { MultiFactorCondition.WEBAUTHN_REGISTERED });
assertThatIllegalArgumentException().isThrownBy(() -> this.selector.selectImports(metadata))
.withMessageContaining("authorities() must include " + FactorGrantedAuthority.WEBAUTHN_AUTHORITY);
}
@Test
void selectImportsWhenHasWebAuthnConditionAndAuthoritiesExcludesWebAuthnThenThrowsException() {
AnnotationMetadata metadata = metadata(new MultiFactorCondition[] { MultiFactorCondition.WEBAUTHN_REGISTERED },
FactorGrantedAuthority.OTT_AUTHORITY, FactorGrantedAuthority.PASSWORD_AUTHORITY);
assertThatIllegalArgumentException().isThrownBy(() -> this.selector.selectImports(metadata))
.withMessageContaining("authorities() must include " + FactorGrantedAuthority.WEBAUTHN_AUTHORITY);
}
private static AnnotationMetadata metadata(MultiFactorCondition[] when, String... authorities) {
AnnotationMetadata metadata = mock(AnnotationMetadata.class);
Map<String, Object> attrs = new HashMap<>();
attrs.put("authorities", authorities);
attrs.put("when", when);
given(metadata.getAnnotationAttributes(EnableMultiFactorAuthentication.class.getName())).willReturn(attrs);
return metadata;
}
private static AnnotationMetadata metadataWithoutWhen(String... authorities) {
AnnotationMetadata metadata = mock(AnnotationMetadata.class);
Map<String, Object> attrs = new HashMap<>();
attrs.put("authorities", authorities);
given(metadata.getAnnotationAttributes(EnableMultiFactorAuthentication.class.getName())).willReturn(attrs);
return metadata;
}
}

View File

@ -40,7 +40,6 @@ 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.CorsConfigurationSource;
import org.springframework.web.cors.PreFlightRequestHandler;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
@ -197,73 +196,6 @@ public class CorsConfigurerTests {
.andExpect(header().exists("X-Content-Type-Options"));
}
@Test
public void optionsWhenPreFlightRequestHandlerBeanThenHandled() throws Exception {
this.spring.register(PreFlightRequestHandlerConfig.class).autowire();
this.mvc
.perform(options("/")
.header(org.springframework.http.HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, HttpMethod.POST.name())
.header(HttpHeaders.ORIGIN, "https://example.com"))
.andExpect(status().isOk())
.andExpect(header().exists("X-Pre-Flight"));
}
@Test
public void optionsWhenNoPreFlightRequestHandlerBeanThenCorsFilterUsed() throws Exception {
this.spring.register(NoPreFlightRequestHandlerConfig.class).autowire();
this.mvc
.perform(options("/")
.header(org.springframework.http.HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, HttpMethod.POST.name())
.header(HttpHeaders.ORIGIN, "https://example.com"))
.andExpect(status().isOk())
.andExpect(header().exists("Access-Control-Allow-Origin"))
.andExpect(header().doesNotExist("X-Pre-Flight"));
}
@Test
public void optionsWhenExplicitConfigurationSourceThenPreFlightRequestHandlerBeanIgnored() throws Exception {
this.spring.register(ExplicitConfigurationSourceWithPreFlightRequestHandlerBeanConfig.class).autowire();
this.mvc
.perform(options("/")
.header(org.springframework.http.HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, HttpMethod.POST.name())
.header(HttpHeaders.ORIGIN, "https://example.com"))
.andExpect(status().isOk())
.andExpect(header().exists("Access-Control-Allow-Origin"))
.andExpect(header().doesNotExist("X-Pre-Flight"));
}
@Test
public void optionsWhenPreFlightRequestHandlerMemberThenHandled() throws Exception {
this.spring.register(PreFlightRequestHandlerMemberConfig.class).autowire();
this.mvc
.perform(options("/")
.header(org.springframework.http.HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, HttpMethod.POST.name())
.header(HttpHeaders.ORIGIN, "https://example.com"))
.andExpect(status().isOk())
.andExpect(header().exists("X-Pre-Flight"));
}
@Test
public void configureWhenBothConfigurationSourceAndPreFlightRequestHandlerMemberThenIllegalState() {
assertThatExceptionOfType(BeanCreationException.class)
.isThrownBy(() -> this.spring.register(BothCorsConfigurerMembersConfig.class).autowire())
.havingRootCause()
.isInstanceOf(IllegalStateException.class)
.withMessageContaining("Cannot configure both");
}
@Test
public void optionsWhenPreFlightRequestHandlerMemberThenCorsFilterBeanIgnored() throws Exception {
this.spring.register(PreFlightRequestHandlerMemberWithCorsFilterBeanConfig.class).autowire();
this.mvc
.perform(options("/")
.header(org.springframework.http.HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, HttpMethod.POST.name())
.header(HttpHeaders.ORIGIN, "https://example.com"))
.andExpect(status().isOk())
.andExpect(header().exists("X-Pre-Flight"))
.andExpect(header().doesNotExist("Access-Control-Allow-Origin"));
}
@Configuration
@EnableWebSecurity
static class DefaultCorsConfig {
@ -450,150 +382,4 @@ public class CorsConfigurerTests {
}
@Configuration
@EnableWebSecurity
static class PreFlightRequestHandlerConfig {
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeHttpRequests((requests) -> requests
.anyRequest().authenticated())
.cors(withDefaults());
return http.build();
// @formatter:on
}
@Bean
PreFlightRequestHandler preFlightRequestHandler() {
return (request, response) -> response.addHeader("X-Pre-Flight", "Handled");
}
}
@Configuration
@EnableWebSecurity
static class NoPreFlightRequestHandlerConfig {
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeHttpRequests((requests) -> requests
.anyRequest().authenticated())
.cors(withDefaults());
return http.build();
// @formatter:on
}
@Bean
CorsConfigurationSource corsConfigurationSource() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.setAllowedOrigins(Collections.singletonList("*"));
corsConfiguration.setAllowedMethods(Arrays.asList(RequestMethod.GET.name(), RequestMethod.POST.name()));
source.registerCorsConfiguration("/**", corsConfiguration);
return source;
}
}
@Configuration
@EnableWebSecurity
static class ExplicitConfigurationSourceWithPreFlightRequestHandlerBeanConfig {
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.setAllowedOrigins(Collections.singletonList("*"));
corsConfiguration.setAllowedMethods(Arrays.asList(RequestMethod.GET.name(), RequestMethod.POST.name()));
source.registerCorsConfiguration("/**", corsConfiguration);
// @formatter:off
http
.authorizeHttpRequests((requests) -> requests
.anyRequest().authenticated())
.cors((cors) -> cors.configurationSource(source));
return http.build();
// @formatter:on
}
@Bean
PreFlightRequestHandler preFlightRequestHandler() {
return (request, response) -> response.addHeader("X-Pre-Flight", "Handled");
}
}
@Configuration
@EnableWebSecurity
static class PreFlightRequestHandlerMemberConfig {
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeHttpRequests((requests) -> requests
.anyRequest().authenticated())
.cors((cors) -> cors.preFlightRequestHandler(
(request, response) -> response.addHeader("X-Pre-Flight", "Member")));
return http.build();
// @formatter:on
}
}
@Configuration
@EnableWebSecurity
static class BothCorsConfigurerMembersConfig {
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.setAllowedOrigins(Collections.singletonList("*"));
corsConfiguration.setAllowedMethods(Arrays.asList(RequestMethod.GET.name(), RequestMethod.POST.name()));
source.registerCorsConfiguration("/**", corsConfiguration);
// @formatter:off
http
.authorizeHttpRequests((requests) -> requests
.anyRequest().authenticated())
.cors((cors) -> cors
.configurationSource(source)
.preFlightRequestHandler((request, response) -> response.addHeader("X-Pre-Flight", "Handled")));
return http.build();
// @formatter:on
}
}
@Configuration
@EnableWebSecurity
static class PreFlightRequestHandlerMemberWithCorsFilterBeanConfig {
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeHttpRequests((requests) -> requests
.anyRequest().authenticated())
.cors((cors) -> cors.preFlightRequestHandler(
(request, response) -> response.addHeader("X-Pre-Flight", "Member")));
return http.build();
// @formatter:on
}
@Bean
CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.setAllowedOrigins(Collections.singletonList("*"));
corsConfiguration.setAllowedMethods(Arrays.asList(RequestMethod.GET.name(), RequestMethod.POST.name()));
source.registerCorsConfiguration("/**", corsConfiguration);
return new CorsFilter(source);
}
}
}

View File

@ -43,16 +43,9 @@ 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.Bytes;
import org.springframework.security.web.webauthn.api.ImmutablePublicKeyCredentialUserEntity;
import org.springframework.security.web.webauthn.api.PublicKeyCredentialCreationOptions;
import org.springframework.security.web.webauthn.api.TestCredentialRecords;
import org.springframework.security.web.webauthn.api.TestPublicKeyCredentialCreationOptions;
import org.springframework.security.web.webauthn.authentication.WebAuthnAuthenticationFilter;
import org.springframework.security.web.webauthn.management.MapPublicKeyCredentialUserEntityRepository;
import org.springframework.security.web.webauthn.management.MapUserCredentialRepository;
import org.springframework.security.web.webauthn.management.PublicKeyCredentialUserEntityRepository;
import org.springframework.security.web.webauthn.management.UserCredentialRepository;
import org.springframework.security.web.webauthn.management.WebAuthnRelyingPartyOperations;
import org.springframework.security.web.webauthn.registration.HttpSessionPublicKeyCredentialCreationOptionsRepository;
import org.springframework.test.web.servlet.MockMvc;
@ -65,7 +58,6 @@ 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.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;
@ -265,24 +257,6 @@ public class WebAuthnConfigurerTests {
.andExpect(content().string(expectedBody));
}
@Test
void webauthnWhenDeleteAndCredentialBelongsToUserThenNoContent() throws Exception {
this.spring.register(DeleteCredentialConfiguration.class).autowire();
this.mvc
.perform(delete("/webauthn/register/" + DeleteCredentialConfiguration.CREDENTIAL_ID_BASE64URL)
.with(authentication(new TestingAuthenticationToken("user", "password", "ROLE_USER"))))
.andExpect(status().isNoContent());
}
@Test
void webauthnWhenDeleteAndCredentialBelongsToDifferentUserThenForbidden() throws Exception {
this.spring.register(DeleteCredentialConfiguration.class).autowire();
this.mvc
.perform(delete("/webauthn/register/" + DeleteCredentialConfiguration.CREDENTIAL_ID_BASE64URL)
.with(authentication(new TestingAuthenticationToken("other-user", "password", "ROLE_USER"))))
.andExpect(status().isForbidden());
}
@Configuration
@EnableWebSecurity
static class ConfigCredentialCreationOptionsRepository {
@ -501,47 +475,4 @@ public class WebAuthnConfigurerTests {
}
@Configuration
@EnableWebSecurity
static class DeleteCredentialConfiguration {
static final String CREDENTIAL_ID_BASE64URL = "NauGCN7bZ5jEBwThcde51g";
static final Bytes USER_ENTITY_ID = Bytes.fromBase64("vKBFhsWT3gQnn-gHdT4VXIvjDkVXVYg5w8CLGHPunMM");
@Bean
UserDetailsService userDetailsService() {
return new InMemoryUserDetailsManager();
}
@Bean
WebAuthnRelyingPartyOperations webAuthnRelyingPartyOperations() {
return mock(WebAuthnRelyingPartyOperations.class);
}
@Bean
UserCredentialRepository userCredentialRepository() {
MapUserCredentialRepository repository = new MapUserCredentialRepository();
repository.save(TestCredentialRecords.userCredential().build());
return repository;
}
@Bean
PublicKeyCredentialUserEntityRepository userEntityRepository() {
MapPublicKeyCredentialUserEntityRepository repository = new MapPublicKeyCredentialUserEntityRepository();
repository.save(ImmutablePublicKeyCredentialUserEntity.builder()
.name("user")
.id(USER_ENTITY_ID)
.displayName("User")
.build());
return repository;
}
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http.csrf(AbstractHttpConfigurer::disable).webAuthn(Customizer.withDefaults()).build();
}
}
}

View File

@ -157,7 +157,7 @@ public class ClientRegistrationsBeanDefinitionParserTests {
.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.getScopes()).isNull();
assertThat(googleRegistration.getClientName()).isEqualTo(serverUrl);
ProviderDetails googleProviderDetails = googleRegistration.getProviderDetails();
assertThat(googleProviderDetails).isNotNull();

View File

@ -16,10 +16,7 @@
package org.springframework.security.config.annotation.web
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.assertj.core.api.Assertions.catchThrowable
import org.assertj.core.util.Throwables
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.springframework.beans.factory.BeanCreationException
@ -38,14 +35,9 @@ import org.springframework.test.web.servlet.get
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMethod
import org.springframework.web.bind.annotation.RestController
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.options
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.header
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
import org.springframework.web.cors.CorsConfiguration
import org.springframework.web.cors.CorsConfigurationSource
import org.springframework.web.cors.PreFlightRequestHandler
import org.springframework.web.cors.UrlBasedCorsConfigurationSource
import org.springframework.web.filter.CorsFilter
import org.springframework.web.servlet.config.annotation.EnableWebMvc
/**
@ -161,7 +153,7 @@ class CorsDslTests {
@Test
fun `CORS when CORS configuration source dsl then responds with CORS header`() {
this.spring.register(CorsCrossOriginSourceConfig::class.java, HomeController::class.java).autowire()
this.spring.register(CorsCrossOriginBeanConfig::class.java, HomeController::class.java).autowire()
this.mockMvc.get("/")
{
@ -193,117 +185,6 @@ class CorsDslTests {
}
}
@Test
fun `CORS when preFlight request handler dsl then OPTIONS uses handler`() {
this.spring.register(PreFlightRequestHandlerDslConfig::class.java).autowire()
this.mockMvc.perform(options("/")
.header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, RequestMethod.POST.name)
.header(HttpHeaders.ORIGIN, "https://example.com"))
.andExpect(status().isOk)
.andExpect(header().exists("X-Pre-Flight"))
}
@Configuration
@EnableWebSecurity
open class PreFlightRequestHandlerDslConfig {
@Bean
open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
http {
authorizeHttpRequests {
authorize(anyRequest, authenticated)
}
cors {
preFlightRequestHandler = PreFlightRequestHandler { _, response ->
response.addHeader("X-Pre-Flight", "Dsl")
}
}
}
return http.build()
}
}
@Test
fun `CORS when configuration source and preFlight handler dsl then illegal state`() {
val thrown = catchThrowable {
this.spring.register(BothCorsDslMembersConfig::class.java).autowire()
}
assertThat(thrown).isInstanceOf(BeanCreationException::class.java)
assertThat(Throwables.getRootCause(thrown))
.isInstanceOf(IllegalStateException::class.java)
.hasMessageContaining("Cannot configure both")
}
@Configuration
@EnableWebSecurity
open class BothCorsDslMembersConfig {
@Bean
open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
val source = UrlBasedCorsConfigurationSource()
val corsConfiguration = CorsConfiguration()
corsConfiguration.allowedOrigins = listOf("*")
corsConfiguration.allowedMethods = listOf(
RequestMethod.GET.name,
RequestMethod.POST.name)
source.registerCorsConfiguration("/**", corsConfiguration)
http {
authorizeHttpRequests {
authorize(anyRequest, authenticated)
}
cors {
configurationSource = source
preFlightRequestHandler = PreFlightRequestHandler { _, response ->
response.addHeader("X-Pre-Flight", "Dsl")
}
}
}
return http.build()
}
}
@Test
fun `CORS when preFlight handler dsl then CorsFilter bean ignored on OPTIONS`() {
this.spring.register(PreFlightRequestHandlerDslWithCorsFilterBeanConfig::class.java).autowire()
this.mockMvc.perform(options("/")
.header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, RequestMethod.POST.name)
.header(HttpHeaders.ORIGIN, "https://example.com"))
.andExpect(status().isOk)
.andExpect(header().exists("X-Pre-Flight"))
.andExpect(header().doesNotExist("Access-Control-Allow-Origin"))
}
@Configuration
@EnableWebSecurity
open class PreFlightRequestHandlerDslWithCorsFilterBeanConfig {
@Bean
open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
http {
authorizeHttpRequests {
authorize(anyRequest, authenticated)
}
cors {
preFlightRequestHandler = PreFlightRequestHandler { _, response ->
response.addHeader("X-Pre-Flight", "Dsl")
}
}
}
return http.build()
}
@Bean
open fun corsFilter(): CorsFilter {
val source = UrlBasedCorsConfigurationSource()
val corsConfiguration = CorsConfiguration()
corsConfiguration.allowedOrigins = listOf("*")
corsConfiguration.allowedMethods = listOf(
RequestMethod.GET.name,
RequestMethod.POST.name)
source.registerCorsConfiguration("/**", corsConfiguration)
return CorsFilter(source)
}
}
@RestController
private class HomeController {
@GetMapping("/")

View File

@ -73,11 +73,13 @@ class AuthorizationEndpointDslTests {
companion object {
val RESOLVER: OAuth2AuthorizationRequestResolver = object : OAuth2AuthorizationRequestResolver {
override fun resolve(request: HttpServletRequest) =
OAuth2AuthorizationRequest.authorizationCode().build()
override fun resolve(
request: HttpServletRequest?
) = OAuth2AuthorizationRequest.authorizationCode().build()
override fun resolve(request: HttpServletRequest, clientRegistrationId: String) =
OAuth2AuthorizationRequest.authorizationCode().build()
override fun resolve(
request: HttpServletRequest?, clientRegistrationId: String?
) = OAuth2AuthorizationRequest.authorizationCode().build()
}
}

Some files were not shown because too many files have changed in this diff Show More