Compare commits
95 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5b759a3e43 | |||
| f1824f8a5d | |||
| 36d83f863a | |||
| 334354f6dd | |||
| 97a7b4627c | |||
| bf0ddd8ad6 | |||
| ad22ef71d3 | |||
| 8a30e08349 | |||
| 4507072b69 | |||
| e1d61aff62 | |||
| c1fc51c2c0 | |||
| 38571d7ce9 | |||
| 9d18e9f616 | |||
| a3c3b627e4 | |||
| 6e1909ec72 | |||
| d0f338c92a | |||
| 5818201e9e | |||
| 2461d00f4d | |||
| eec6729125 | |||
| 6bb36f1a54 | |||
| ed657a8fac | |||
| edd1915d1b | |||
| 582c9c6438 | |||
| ae22b7537e | |||
| fc9c2513d8 | |||
| f75d35f5a4 | |||
| 195695fb57 | |||
| 143944619b | |||
| aaa611c5b0 | |||
| d2659ddf82 | |||
| 1f3c4707f3 | |||
| 579fc565ae | |||
| 5cf23585c0 | |||
| 50d793eab2 | |||
| c715df687a | |||
| 4ddecdbe36 | |||
| 94c8830f76 | |||
| 885f1d3165 | |||
| e414d2bd5c | |||
| dbcc3130ee | |||
| ac9c398fea | |||
| 7c428fb75e | |||
| aa1934ee95 | |||
| 58e8fe837f | |||
| 7080ea652f | |||
| 898c36287c | |||
| 99d6d21554 | |||
| 8ba7c0d00d | |||
| 88d50a531b | |||
| f1698ec188 | |||
| 2fdf762726 | |||
| 7aaa25b88e | |||
| fc25b87967 | |||
| f39f215140 | |||
| 626e53d121 | |||
| a5464ed819 | |||
| f487f859ed | |||
| 7439d5d2de | |||
| 4ee6149a4b | |||
| 707f8286f8 | |||
| 42a00e2003 | |||
| d2b33a2583 | |||
| 813179931a | |||
| de933f5bee | |||
| 74e8fa10a2 | |||
| b22bc42bb0 | |||
| 29c00905ce | |||
| 667cab6cda | |||
| 52c7141aac | |||
| a76b1f7a51 | |||
| 68a344d238 | |||
| e23c1cf7a7 | |||
| 14a48ea939 | |||
| 709de43e89 | |||
| c60c10792c | |||
| 701f754e37 | |||
| 162ed5c61c | |||
| 50da5b6498 | |||
| 5955e89dff | |||
| 08afa1782c | |||
| c2e86ad067 | |||
| 9c6d90490d | |||
| 3e0e532ed7 | |||
| 5db7ac4ce3 | |||
| 9b3f834bff | |||
| 70bfc39418 | |||
| 3f2bade4f1 | |||
| 2d567cdb09 | |||
| 87c074fc26 | |||
| 621889fa18 | |||
| fe252f5057 | |||
| d4afc68abc | |||
| 413c47311e | |||
| 457c45878c | |||
| b873b24413 |
@@ -1,5 +0,0 @@
|
||||
REPOSITORY_REF="$1"
|
||||
TOKEN="$2"
|
||||
|
||||
curl -H "Accept: application/vnd.github.everest-preview+json" -H "Authorization: token ${TOKEN}" --request POST --data '{"event_type": "request-build-reference"}' https://api.github.com/repos/${REPOSITORY_REF}/dispatches
|
||||
echo "Requested Build for $REPOSITORY_REF"
|
||||
@@ -1,41 +0,0 @@
|
||||
name: Generate Antora Files and Request Build
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches-ignore:
|
||||
- 'gh-pages'
|
||||
tags: '**'
|
||||
|
||||
env:
|
||||
GH_ACTIONS_REPO_TOKEN: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.repository == 'spring-projects/spring-security' }}
|
||||
steps:
|
||||
- name: Checkout Source
|
||||
uses: actions/checkout@v2
|
||||
- name: Set up gradle
|
||||
uses: spring-io/spring-gradle-build-action@v1
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'temurin'
|
||||
- name: Generate antora.yml
|
||||
run: ./gradlew :spring-security-docs:generateAntora
|
||||
- name: Extract Branch Name
|
||||
id: extract_branch_name
|
||||
run: echo "##[set-output name=generated_branch_name;]$(echo ${GITHUB_REPOSITORY}/${GITHUB_REF##*/})"
|
||||
- name: Push generated antora files to the spring-generated-docs
|
||||
uses: JamesIves/github-pages-deploy-action@4.1.4
|
||||
with:
|
||||
branch: ${{ steps.extract_branch_name.outputs.generated_branch_name }} # The branch the action should deploy to.
|
||||
folder: "docs/build/generateAntora" # The folder the action should deploy.
|
||||
repository-name: "spring-io/spring-generated-docs"
|
||||
token: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
|
||||
- name: Dispatch Build Request
|
||||
run: ${GITHUB_WORKSPACE}/.github/actions/dispatch.sh 'spring-projects/spring-security' "$GH_ACTIONS_REPO_TOKEN"
|
||||
@@ -31,15 +31,15 @@ jobs:
|
||||
runjobs: ${{ steps.continue.outputs.runjobs }}
|
||||
project_version: ${{ steps.continue.outputs.project_version }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- id: continue
|
||||
name: Determine if should continue
|
||||
run: |
|
||||
# Run jobs if in upstream repository
|
||||
echo "::set-output name=runjobs::true"
|
||||
echo "runjobs=true" >>$GITHUB_OUTPUT
|
||||
# Extract version from gradle.properties
|
||||
version=$(cat gradle.properties | grep "version=" | awk -F'=' '{print $2}')
|
||||
echo "::set-output name=project_version::$version"
|
||||
echo "project_version=$version" >>$GITHUB_OUTPUT
|
||||
build_jdk_17:
|
||||
name: Build JDK 17
|
||||
needs: [prerequisites]
|
||||
@@ -49,9 +49,9 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
if: needs.prerequisites.outputs.runjobs
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v2
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'temurin'
|
||||
@@ -71,7 +71,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
if: needs.prerequisites.outputs.runjobs
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set up gradle
|
||||
uses: spring-io/spring-gradle-build-action@v1
|
||||
with:
|
||||
@@ -89,7 +89,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
if: needs.prerequisites.outputs.runjobs
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set up gradle
|
||||
uses: spring-io/spring-gradle-build-action@v1
|
||||
with:
|
||||
@@ -113,7 +113,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
if: needs.prerequisites.outputs.runjobs
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set up gradle
|
||||
uses: spring-io/spring-gradle-build-action@v1
|
||||
with:
|
||||
@@ -130,7 +130,7 @@ jobs:
|
||||
needs: [build_jdk_17, snapshot_tests, check_samples, check_tangles]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set up gradle
|
||||
uses: spring-io/spring-gradle-build-action@v1
|
||||
with:
|
||||
@@ -154,7 +154,7 @@ jobs:
|
||||
needs: [build_jdk_17, snapshot_tests, check_samples, check_tangles]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set up gradle
|
||||
uses: spring-io/spring-gradle-build-action@v1
|
||||
with:
|
||||
@@ -175,7 +175,7 @@ jobs:
|
||||
needs: [build_jdk_17, snapshot_tests, check_samples, check_tangles]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set up gradle
|
||||
uses: spring-io/spring-gradle-build-action@v1
|
||||
with:
|
||||
@@ -205,7 +205,7 @@ jobs:
|
||||
TOKEN: ${{ github.token }}
|
||||
VERSION: ${{ needs.prerequisites.outputs.project_version }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
token: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
|
||||
- name: Set up gradle
|
||||
@@ -286,7 +286,7 @@ jobs:
|
||||
TOKEN: ${{ github.token }}
|
||||
VERSION: ${{ needs.prerequisites.outputs.project_version }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set up gradle
|
||||
uses: spring-io/spring-gradle-build-action@v1
|
||||
with:
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
name: Deploy Docs
|
||||
on:
|
||||
push:
|
||||
branches-ignore: [ gh-pages ]
|
||||
tags: '**'
|
||||
repository_dispatch:
|
||||
types: request-build-reference # legacy
|
||||
schedule:
|
||||
|
||||
@@ -11,7 +11,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.repository == 'spring-projects/spring-security' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set up gradle
|
||||
uses: spring-io/spring-gradle-build-action@v1
|
||||
with:
|
||||
|
||||
@@ -6,15 +6,15 @@ on:
|
||||
permissions: read-all
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository_owner == 'spring-projects'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ref: docs-build
|
||||
fetch-depth: 1
|
||||
- name: Dispatch
|
||||
run: gh workflow run --ref $(git rev-parse --abbrev-ref HEAD) rebuild-search-index.yml
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
|
||||
run: gh workflow run rebuild-search-index.yml -r $(git rev-parse --abbrev-ref HEAD)
|
||||
|
||||
@@ -1,32 +1,24 @@
|
||||
name: Release Scheduler
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '15 15 * * MON' # Every Monday at 3:15pm UTC
|
||||
- cron: '15 15 * * MON' # Every Monday at 3:15pm UTC
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
permissions: read-all
|
||||
jobs:
|
||||
dispatch_scheduled_releases:
|
||||
name: Dispatch scheduled releases
|
||||
if: ${{ github.repository == 'spring-projects/spring-security' }}
|
||||
if: github.repository_owner == 'spring-projects'
|
||||
strategy:
|
||||
matrix:
|
||||
#
|
||||
# List of active maintenance branches.
|
||||
#
|
||||
branch:
|
||||
- 'main'
|
||||
- '5.8.x'
|
||||
- '5.7.x'
|
||||
- '5.6.x'
|
||||
branch: [ main, 6.0.x, 5.8.x, 5.7.x, 5.6.x ]
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
TOKEN: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: spring-io/spring-gradle-build-action@v1
|
||||
- name: Trigger release workflow
|
||||
run: ./gradlew dispatchGitHubWorkflow -Pbranch=${{ matrix.branch }} -PworkflowId=update-scheduled-release-version.yml -PgitHubAccessToken=$TOKEN
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
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 }}
|
||||
|
||||
@@ -23,7 +23,7 @@ jobs:
|
||||
steps:
|
||||
- id: checkout-source
|
||||
name: Checkout Source Code
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
token: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
|
||||
- name: Set up gradle
|
||||
@@ -38,7 +38,7 @@ jobs:
|
||||
export GRADLE_ENTERPRISE_CACHE_PASSWORD="$GRADLE_ENTERPRISE_CACHE_PASSWORD"
|
||||
export GRADLE_ENTERPRISE_ACCESS_KEY="$GRADLE_ENTERPRISE_SECRET_ACCESS_KEY"
|
||||
./gradlew gitHubCheckNextVersionDueToday
|
||||
echo "::set-output name=is_due_today::$(cat build/github/milestones/is-due-today)"
|
||||
echo "is_due_today=$(cat build/github/milestones/is-due-today)" >>$GITHUB_OUTPUT
|
||||
- id: check-open-issues
|
||||
name: Check for open issues
|
||||
if: steps.check-release-due.outputs.is_due_today == 'true'
|
||||
@@ -47,7 +47,7 @@ jobs:
|
||||
export GRADLE_ENTERPRISE_CACHE_PASSWORD="$GRADLE_ENTERPRISE_CACHE_PASSWORD"
|
||||
export GRADLE_ENTERPRISE_ACCESS_KEY="$GRADLE_ENTERPRISE_SECRET_ACCESS_KEY"
|
||||
./gradlew gitHubCheckMilestoneHasNoOpenIssues
|
||||
echo "::set-output name=is_open_issues::$(cat build/github/milestones/is-open-issues)"
|
||||
echo "is_open_issues=$(cat build/github/milestones/is-open-issues)" >>$GITHUB_OUTPUT
|
||||
- id: validate-release-state
|
||||
name: Validate State of Release
|
||||
if: steps.check-release-due.outputs.is_due_today == 'true' && steps.check-open-issues.outputs.is_open_issues == 'true'
|
||||
|
||||
@@ -87,7 +87,7 @@ dependencies {
|
||||
implementation localGroovy()
|
||||
|
||||
implementation 'io.github.gradle-nexus:publish-plugin:1.1.0'
|
||||
implementation 'io.projectreactor:reactor-core:3.5.0-M6'
|
||||
implementation 'io.projectreactor:reactor-core:3.5.1'
|
||||
implementation 'org.gretty:gretty:3.0.9'
|
||||
implementation 'com.apollographql.apollo:apollo-runtime:2.4.5'
|
||||
implementation 'com.github.ben-manes:gradle-versions-plugin:0.38.0'
|
||||
|
||||
@@ -93,6 +93,7 @@ dependencies {
|
||||
testImplementation 'org.springframework:spring-jdbc'
|
||||
testImplementation 'org.springframework:spring-orm'
|
||||
testImplementation 'org.springframework:spring-tx'
|
||||
testImplementation 'org.springframework:spring-core-test'
|
||||
testImplementation ('org.springframework.data:spring-data-jpa') {
|
||||
exclude group: 'org.aspectj', module: 'aspectjrt'
|
||||
}
|
||||
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF 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.authentication.configuration;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.aop.framework.AopProxyUtils;
|
||||
import org.springframework.aot.generate.GenerationContext;
|
||||
import org.springframework.aot.hint.ProxyHints;
|
||||
import org.springframework.beans.factory.aot.BeanRegistrationAotContribution;
|
||||
import org.springframework.beans.factory.aot.BeanRegistrationAotProcessor;
|
||||
import org.springframework.beans.factory.aot.BeanRegistrationCode;
|
||||
import org.springframework.beans.factory.support.RegisteredBean;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* AOT {@code BeanRegistrationAotProcessor} that detects beans that implement
|
||||
* {@link AuthenticationManager} creates the required proxy hints.
|
||||
*
|
||||
* @author Marcus da Coregio
|
||||
* @since 6.0.1
|
||||
* @see AuthenticationConfiguration#getAuthenticationManager()
|
||||
*/
|
||||
class AuthenticationManagerBeanRegistrationAotProcessor implements BeanRegistrationAotProcessor {
|
||||
|
||||
@Override
|
||||
public BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registeredBean) {
|
||||
Class<?> beanClass = registeredBean.getBeanClass();
|
||||
Set<Class<?>> allInterfacesForClass = ClassUtils.getAllInterfacesForClassAsSet(beanClass);
|
||||
if (allInterfacesForClass.contains(AuthenticationManager.class)) {
|
||||
return new AuthenticationManagerBeanRegistrationAotContribution();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static class AuthenticationManagerBeanRegistrationAotContribution
|
||||
implements BeanRegistrationAotContribution {
|
||||
|
||||
@Override
|
||||
public void applyTo(GenerationContext generationContext, BeanRegistrationCode beanRegistrationCode) {
|
||||
ProxyHints proxyHints = generationContext.getRuntimeHints().proxies();
|
||||
proxyHints.registerJdkProxy(AopProxyUtils.completeJdkProxyInterfaces(AuthenticationManager.class));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+2
-1
@@ -50,7 +50,8 @@ import org.springframework.security.web.csrf.CsrfToken;
|
||||
*
|
||||
* <h2>Shared Objects Created</h2>
|
||||
*
|
||||
* No shared objects are created. isLogoutRequest
|
||||
* No shared objects are created.
|
||||
*
|
||||
* <h2>Shared Objects Used</h2>
|
||||
*
|
||||
* The following shared objects are used:
|
||||
|
||||
+3
-2
@@ -48,8 +48,9 @@ import org.springframework.util.Assert;
|
||||
* </p>
|
||||
*
|
||||
* <pre>
|
||||
* protected void configure(HttpSecurity http) throws Exception {
|
||||
* http.apply(new UrlAuthorizationConfigurer<HttpSecurity>()).getRegistry()
|
||||
* @Bean
|
||||
* public SecurityFilterChain filterChain(HttpSecurity http, ApplicationContext context) throws Exception {
|
||||
* http.apply(new UrlAuthorizationConfigurer<HttpSecurity>(context)).getRegistry()
|
||||
* .requestMatchers("/users**", "/sessions/**").hasRole("USER")
|
||||
* .requestMatchers("/signup").hasRole("ANONYMOUS").anyRequest().hasRole("USER");
|
||||
* }
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
org.springframework.beans.factory.aot.BeanRegistrationAotProcessor=\
|
||||
org.springframework.security.config.annotation.authentication.configuration.AuthenticationManagerBeanRegistrationAotProcessor
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF 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.authentication.configuration;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.aop.SpringProxy;
|
||||
import org.springframework.aop.framework.Advised;
|
||||
import org.springframework.aot.generate.GenerationContext;
|
||||
import org.springframework.aot.hint.predicate.RuntimeHintsPredicates;
|
||||
import org.springframework.aot.test.generate.TestGenerationContext;
|
||||
import org.springframework.beans.factory.aot.BeanRegistrationAotContribution;
|
||||
import org.springframework.beans.factory.aot.BeanRegistrationCode;
|
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
import org.springframework.beans.factory.support.RegisteredBean;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.core.DecoratingProxy;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link AuthenticationManagerBeanRegistrationAotProcessor}
|
||||
*
|
||||
* @author Marcus da Coregio
|
||||
*/
|
||||
class AuthenticationManagerBeanRegistrationAotProcessorTests {
|
||||
|
||||
private final AuthenticationManagerBeanRegistrationAotProcessor processor = new AuthenticationManagerBeanRegistrationAotProcessor();
|
||||
|
||||
private final GenerationContext generationContext = new TestGenerationContext();
|
||||
|
||||
@Test
|
||||
void shouldSkipWhenInterfaceNotImplemented() {
|
||||
process(NoAuthenticationManager.class);
|
||||
assertThat(this.generationContext.getRuntimeHints().proxies().jdkProxyHints()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldProcessWhenImplementsInterface() {
|
||||
process(MyAuthenticationManager.class);
|
||||
assertThat(RuntimeHintsPredicates.proxies().forInterfaces(AuthenticationManager.class, SpringProxy.class,
|
||||
Advised.class, DecoratingProxy.class)).accepts(this.generationContext.getRuntimeHints());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldProcessWhenSuperclassImplementsInterface() {
|
||||
process(ChildAuthenticationManager.class);
|
||||
assertThat(RuntimeHintsPredicates.proxies().forInterfaces(AuthenticationManager.class, SpringProxy.class,
|
||||
Advised.class, DecoratingProxy.class)).accepts(this.generationContext.getRuntimeHints());
|
||||
}
|
||||
|
||||
private void process(Class<?> beanClass) {
|
||||
BeanRegistrationAotContribution contribution = createContribution(beanClass);
|
||||
if (contribution != null) {
|
||||
contribution.applyTo(this.generationContext, mock(BeanRegistrationCode.class));
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private BeanRegistrationAotContribution createContribution(Class<?> beanClass) {
|
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
|
||||
beanFactory.registerBeanDefinition(beanClass.getName(), new RootBeanDefinition(beanClass));
|
||||
return this.processor.processAheadOfTime(RegisteredBean.of(beanFactory, beanClass.getName()));
|
||||
}
|
||||
|
||||
static class NoAuthenticationManager {
|
||||
|
||||
}
|
||||
|
||||
static class MyAuthenticationManager implements AuthenticationManager {
|
||||
|
||||
@Override
|
||||
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class ChildAuthenticationManager extends MyAuthenticationManager {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+3
-2
@@ -40,8 +40,9 @@ import org.springframework.util.Assert;
|
||||
public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
|
||||
|
||||
/**
|
||||
* The plaintext password used to perform PasswordEncoder#matches(CharSequence,
|
||||
* String)} on when the user is not found to avoid SEC-2056.
|
||||
* The plaintext password used to perform
|
||||
* {@link PasswordEncoder#matches(CharSequence, String)} on when the user is not found
|
||||
* to avoid SEC-2056.
|
||||
*/
|
||||
private static final String USER_NOT_FOUND_PASSWORD = "userNotFoundPassword";
|
||||
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ import org.springframework.expression.Expression;
|
||||
* Represents an {@link AuthorizationDecision} based on a {@link Expression}
|
||||
*
|
||||
* @author Marcus Da Coregio
|
||||
* @since 5.6
|
||||
* @since 5.8
|
||||
*/
|
||||
public class ExpressionAuthorizationDecision extends AuthorizationDecision {
|
||||
|
||||
|
||||
+9
-9
@@ -15,17 +15,17 @@ dependencies {
|
||||
api platform("org.springframework.data:spring-data-bom:2022.0.0")
|
||||
api platform("org.jetbrains.kotlin:kotlin-bom:$kotlinVersion")
|
||||
api platform("org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.6.4")
|
||||
api platform("com.fasterxml.jackson:jackson-bom:2.14.0")
|
||||
api platform("com.fasterxml.jackson:jackson-bom:2.14.1")
|
||||
constraints {
|
||||
api "ch.qos.logback:logback-classic:1.4.4"
|
||||
api "ch.qos.logback:logback-classic:1.4.5"
|
||||
api "com.google.inject:guice:3.0"
|
||||
api "com.nimbusds:nimbus-jose-jwt:9.24.4"
|
||||
api "com.nimbusds:oauth2-oidc-sdk:9.43.1"
|
||||
api "com.squareup.okhttp3:mockwebserver:3.14.9"
|
||||
api "com.squareup.okhttp3:okhttp:3.14.9"
|
||||
api "com.unboundid:unboundid-ldapsdk:6.0.6"
|
||||
api "com.unboundid:unboundid-ldapsdk:6.0.7"
|
||||
api "commons-collections:commons-collections:3.2.2"
|
||||
api "io.mockk:mockk:1.13.2"
|
||||
api "io.mockk:mockk:1.13.3"
|
||||
api "io.micrometer:micrometer-observation:$micrometerVersion"
|
||||
api "jakarta.annotation:jakarta.annotation-api:2.1.1"
|
||||
api "jakarta.inject:jakarta.inject-api:2.0.1"
|
||||
@@ -45,17 +45,17 @@ dependencies {
|
||||
api "org.apache.directory.server:apacheds-protocol-shared:1.5.5"
|
||||
api "org.apache.directory.server:apacheds-server-jndi:1.5.5"
|
||||
api "org.apache.directory.shared:shared-ldap:0.9.15"
|
||||
api "org.apache.httpcomponents:httpclient:4.5.13"
|
||||
api "org.apache.httpcomponents:httpclient:4.5.14"
|
||||
api "org.aspectj:aspectjrt:$aspectjVersion"
|
||||
api "org.aspectj:aspectjweaver:$aspectjVersion"
|
||||
api "org.assertj:assertj-core:3.23.1"
|
||||
api "org.bouncycastle:bcpkix-jdk15on:1.70"
|
||||
api "org.bouncycastle:bcprov-jdk15on:1.70"
|
||||
api "org.eclipse.jetty:jetty-server:11.0.12"
|
||||
api "org.eclipse.jetty:jetty-servlet:11.0.12"
|
||||
api "org.eclipse.jetty:jetty-server:11.0.13"
|
||||
api "org.eclipse.jetty:jetty-servlet:11.0.13"
|
||||
api "jakarta.persistence:jakarta.persistence-api:3.1.0"
|
||||
api "org.hamcrest:hamcrest:2.2"
|
||||
api "org.hibernate.orm:hibernate-core:6.1.5.Final"
|
||||
api "org.hibernate.orm:hibernate-core:6.1.6.Final"
|
||||
api "org.hsqldb:hsqldb:2.7.1"
|
||||
api "org.jasig.cas.client:cas-client-core:3.6.4"
|
||||
api "org.opensaml:opensaml-core:$openSamlVersion"
|
||||
@@ -67,7 +67,7 @@ dependencies {
|
||||
api "org.seleniumhq.selenium:selenium-support:3.141.59"
|
||||
api "org.skyscreamer:jsonassert:1.5.1"
|
||||
api "org.slf4j:log4j-over-slf4j:1.7.36"
|
||||
api "org.slf4j:slf4j-api:2.0.4"
|
||||
api "org.slf4j:slf4j-api:2.0.6"
|
||||
api "org.springframework.ldap:spring-ldap-core:3.0.0"
|
||||
api "org.synchronoss.cloud:nio-multipart-parser:1.1.0"
|
||||
api 'org.apache.maven.resolver:maven-resolver-connector-basic:1.8.2'
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
/* Copyright (c) 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
const { promises: fsp } = require('fs')
|
||||
const ospath = require('path')
|
||||
|
||||
/**
|
||||
* Rewrites local content sources to support the use of linked worktrees.
|
||||
*
|
||||
* @author Dan Allen <dan@opendevise.com>
|
||||
*/
|
||||
module.exports.register = function () {
|
||||
this.once('playbookBuilt', async ({ playbook }) => {
|
||||
const expandPath = this.require('@antora/expand-path-helper')
|
||||
for (const contentSource of playbook.content.sources) {
|
||||
const { url, branches } = contentSource
|
||||
if (url.charAt() !== '.') continue
|
||||
const absdir = expandPath(url, { dot: playbook.dir })
|
||||
const gitfile = ospath.join(absdir, '.git')
|
||||
if (await fsp.stat(gitfile).then((stat) => !stat.isDirectory(), () => false)) {
|
||||
const worktreeGitdir = await fsp.readFile(gitfile, 'utf8')
|
||||
.then((contents) => contents.trimRight().substr(8))
|
||||
const worktreeBranch = await fsp.readFile(ospath.join(worktreeGitdir, 'HEAD'), 'utf8')
|
||||
.then((contents) => contents.trimRight().replace(/^ref: (?:refs\/heads\/)?/, ''))
|
||||
const reldir = ospath.relative(
|
||||
playbook.dir,
|
||||
await fsp.readFile(ospath.join(worktreeGitdir, 'commondir'), 'utf8')
|
||||
.then((contents) => {
|
||||
const gitdir = ospath.join(worktreeGitdir, contents.trimRight())
|
||||
return ospath.basename(gitdir) === '.git' ? ospath.dirname(gitdir) : gitdir
|
||||
})
|
||||
)
|
||||
contentSource.url = reldir ? `.${ospath.sep}${reldir}` : '.'
|
||||
if (!branches) continue
|
||||
contentSource.branches = (branches.constructor === Array ? branches : [branches])
|
||||
.map((pattern) => pattern.replaceAll('HEAD', worktreeBranch))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
# The purpose of this Antora playbook is to generate a preview of the docs in the current branch.
|
||||
antora:
|
||||
extensions:
|
||||
- ./antora-linked-worktree-patch.js
|
||||
- '@antora/collector-extension'
|
||||
site:
|
||||
title: Spring Security
|
||||
|
||||
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 119 KiB After Width: | Height: | Size: 143 KiB |
@@ -192,7 +192,7 @@ Instead, you can alter the provided `ObservationRegistry` with an `ObservationPr
|
||||
----
|
||||
@Bean
|
||||
ObservationRegistryCustomizer<ObservationRegistry> noSpringSecurityObservations() {
|
||||
ObservationPredicate predicate = (name, context) -> name.startsWith("spring.security.")
|
||||
ObservationPredicate predicate = (name, context) -> !name.startsWith("spring.security.")
|
||||
return (registry) -> registry.observationConfig().observationPredicate(predicate)
|
||||
}
|
||||
----
|
||||
@@ -202,7 +202,7 @@ ObservationRegistryCustomizer<ObservationRegistry> noSpringSecurityObservations(
|
||||
----
|
||||
@Bean
|
||||
fun noSpringSecurityObservations(): ObservationRegistryCustomizer<ObservationRegistry> {
|
||||
ObservationPredicate predicate = (name: String, context: Observation.Context) -> name.startsWith("spring.security.")
|
||||
ObservationPredicate predicate = (name: String, context: Observation.Context) -> !name.startsWith("spring.security.")
|
||||
(registry: ObservationRegistry) -> registry.observationConfig().observationPredicate(predicate)
|
||||
}
|
||||
----
|
||||
|
||||
@@ -65,12 +65,12 @@ When used in the context of receiving a permission, an SID is generally called a
|
||||
The only columns are the ID and the Java class name.
|
||||
Thus, there is a single row for each unique Class for which we wish to store ACL permissions.
|
||||
|
||||
* Finally, `ACL_ENTRY` stores the individual permissions assigned to each recipient.
|
||||
Columns include a foreign key to the ACL_OBJECT_IDENTITY, the recipient (which is a foreign key to ACL_SID), whether we audit or not, and the integer bit mask that represents the actual permission being granted or denied.
|
||||
* `ACL_OBJECT_IDENTITY` stores information for each unique domain object instance in the system.
|
||||
Columns include the ID, a foreign key to the ACL_CLASS table, a unique identifier so we know the ACL_CLASS instance for which we provide information, the parent, a foreign key to the ACL_SID table to represent the owner of the domain object instance, and whether we allow ACL entries to inherit from any parent ACL.
|
||||
We have a single row for every domain object instance for which we store ACL permissions.
|
||||
|
||||
* Finally, ACL_ENTRY stores the individual permissions assigned to each recipient.
|
||||
Columns include a foreign key to the ACL_OBJECT_IDENTITY, the recipient (i.e. a foreign key to ACL_SID), whether we'll be auditing or not, and the integer bit mask that represents the actual permission being granted or denied.
|
||||
* Finally, `ACL_ENTRY` stores the individual permissions assigned to each recipient.
|
||||
Columns include a foreign key to the `ACL_OBJECT_IDENTITY`, the recipient (i.e. a foreign key to ACL_SID), whether we'll be auditing or not, and the integer bit mask that represents the actual permission being granted or denied.
|
||||
We have a single row for every recipient that receives a permission to work with a domain object.
|
||||
|
||||
|
||||
|
||||
@@ -48,12 +48,10 @@ image::{figures}/authorizationfilter.png[]
|
||||
|
||||
* image:{icondir}/number_1.png[] First, the `AuthorizationFilter` obtains an xref:servlet/authentication/architecture.adoc#servlet-authentication-authentication[Authentication] from the xref:servlet/authentication/architecture.adoc#servlet-authentication-securitycontextholder[SecurityContextHolder].
|
||||
It wraps this in an `Supplier` in order to delay lookup.
|
||||
* image:{icondir}/number_2.png[] Second, `AuthorizationFilter` creates a {security-api-url}org/springframework/security/web/FilterInvocation.html[`FilterInvocation`] from the `HttpServletRequest`, `HttpServletResponse`, and `FilterChain`.
|
||||
// FIXME: link to FilterInvocation
|
||||
* image:{icondir}/number_3.png[] Next, it passes the `Supplier<Authentication>` and `FilterInvocation` to the xref:servlet/architecture.adoc#authz-authorization-manager[`AuthorizationManager`].
|
||||
** image:{icondir}/number_4.png[] If authorization is denied, an `AccessDeniedException` is thrown.
|
||||
* image:{icondir}/number_2.png[] Second, it passes the `Supplier<Authentication>` and the `HttpServletRequest` to the xref:servlet/architecture.adoc#authz-authorization-manager[`AuthorizationManager`].
|
||||
** image:{icondir}/number_3.png[] If authorization is denied, an `AccessDeniedException` is thrown.
|
||||
In this case the xref:servlet/architecture.adoc#servlet-exceptiontranslationfilter[`ExceptionTranslationFilter`] handles the `AccessDeniedException`.
|
||||
** image:{icondir}/number_5.png[] If access is granted, `AuthorizationFilter` continues with the xref:servlet/architecture.adoc#servlet-filters-review[FilterChain] which allows the application to process normally.
|
||||
** image:{icondir}/number_4.png[] If access is granted, `AuthorizationFilter` continues with the xref:servlet/architecture.adoc#servlet-filters-review[FilterChain] which allows the application to process normally.
|
||||
|
||||
We can configure Spring Security to have different rules by adding more rules in order of precedence.
|
||||
|
||||
@@ -70,7 +68,8 @@ SecurityFilterChain web(HttpSecurity http) throws Exception {
|
||||
.requestMatchers("/resources/**", "/signup", "/about").permitAll() // <2>
|
||||
.requestMatchers("/admin/**").hasRole("ADMIN") // <3>
|
||||
.requestMatchers("/db/**").access(new WebExpressionAuthorizationManager("hasRole('ADMIN') and hasRole('DBA')")) // <4>
|
||||
.anyRequest().denyAll() // <5>
|
||||
// .requestMatchers("/db/**").access(AuthorizationManagers.allOf(AuthorityAuthorizationManager.hasRole("ADMIN"), AuthorityAuthorizationManager.hasRole("DBA"))) // <5>
|
||||
.anyRequest().denyAll() // <6>
|
||||
);
|
||||
|
||||
return http.build();
|
||||
@@ -85,7 +84,8 @@ Specifically, any user can access a request if the URL starts with "/resources/"
|
||||
You will notice that since we are invoking the `hasRole` method we do not need to specify the "ROLE_" prefix.
|
||||
<4> Any URL that starts with "/db/" requires the user to have both "ROLE_ADMIN" and "ROLE_DBA".
|
||||
You will notice that since we are using the `hasRole` expression we do not need to specify the "ROLE_" prefix.
|
||||
<5> Any URL that has not already been matched on is denied access.
|
||||
<5> The same rule from 4, could be written by combining multiple `AuthorizationManager`.
|
||||
<6> Any URL that has not already been matched on is denied access.
|
||||
This is a good strategy if you do not want to accidentally forget to update your authorization rules.
|
||||
|
||||
You can take a bean-based approach by constructing your own xref:servlet/authorization/architecture.adoc#authz-delegate-authorization-manager[`RequestMatcherDelegatingAuthorizationManager`] like so:
|
||||
@@ -118,7 +118,7 @@ AuthorizationManager<RequestAuthorizationContext> requestMatcherAuthorizationMan
|
||||
RequestMatcher admin = mvcMatcherBuilder.pattern("/admin/**");
|
||||
RequestMatcher db = mvcMatcherBuilder.pattern("/db/**");
|
||||
RequestMatcher any = AnyRequestMatcher.INSTANCE;
|
||||
AuthorizationManager<HttpRequestServlet> manager = RequestMatcherDelegatingAuthorizationManager.builder()
|
||||
AuthorizationManager<HttpServletRequest> manager = RequestMatcherDelegatingAuthorizationManager.builder()
|
||||
.add(permitAll, (context) -> new AuthorizationDecision(true))
|
||||
.add(admin, AuthorityAuthorizationManager.hasRole("ADMIN"))
|
||||
.add(db, AuthorityAuthorizationManager.hasRole("DBA"))
|
||||
|
||||
@@ -144,7 +144,7 @@ You could then refer to the method as follows:
|
||||
----
|
||||
http
|
||||
.authorizeHttpRequests(authorize -> authorize
|
||||
.requestMatchers("/user/**").access("@webSecurity.check(authentication,request)")
|
||||
.requestMatchers("/user/**").access(new WebExpressionAuthorizationManager("@webSecurity.check(authentication,request)"))
|
||||
...
|
||||
)
|
||||
----
|
||||
|
||||
@@ -197,7 +197,7 @@ Instead, you can alter the provided `ObservationRegistry` with an `ObservationPr
|
||||
----
|
||||
@Bean
|
||||
ObservationRegistryCustomizer<ObservationRegistry> noSpringSecurityObservations() {
|
||||
ObservationPredicate predicate = (name, context) -> name.startsWith("spring.security.")
|
||||
ObservationPredicate predicate = (name, context) -> !name.startsWith("spring.security.");
|
||||
return (registry) -> registry.observationConfig().observationPredicate(predicate)
|
||||
}
|
||||
----
|
||||
@@ -207,7 +207,7 @@ ObservationRegistryCustomizer<ObservationRegistry> noSpringSecurityObservations(
|
||||
----
|
||||
@Bean
|
||||
fun noSpringSecurityObservations(): ObservationRegistryCustomizer<ObservationRegistry> {
|
||||
ObservationPredicate predicate = (name: String, context: Observation.Context) -> name.startsWith("spring.security.")
|
||||
ObservationPredicate predicate = (name: String, context: Observation.Context) -> !name.startsWith("spring.security.")
|
||||
(registry: ObservationRegistry) -> registry.observationConfig().observationPredicate(predicate)
|
||||
}
|
||||
----
|
||||
|
||||
@@ -248,7 +248,7 @@ public class TenantJWSKeySelector
|
||||
}
|
||||
|
||||
private JWSKeySelector<SecurityContext> fromTenant(String tenant) {
|
||||
return Optional.ofNullable(this.tenantRepository.findById(tenant)) <3>
|
||||
return Optional.ofNullable(this.tenants.findById(tenant)) <3>
|
||||
.map(t -> t.getAttrbute("jwks_uri"))
|
||||
.map(this::fromUri)
|
||||
.orElseThrow(() -> new IllegalArgumentException("unknown tenant"));
|
||||
|
||||
@@ -6,7 +6,7 @@ apply plugin: 'io.spring.convention.docs'
|
||||
apply plugin: 'java'
|
||||
|
||||
antora {
|
||||
version = '~3.1'
|
||||
version = '3.2.0-alpha.2'
|
||||
playbook = file('local-antora-playbook.yml')
|
||||
options = ['--clean', '--stacktrace']
|
||||
environment = [
|
||||
@@ -15,7 +15,7 @@ antora {
|
||||
'ALGOLIA_INDEX_NAME': 'security-docs'
|
||||
]
|
||||
dependencies = [
|
||||
'@antora/collector-extension': '1.0.0-alpha.2'
|
||||
'@antora/collector-extension': '1.0.0-alpha.3'
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
+5
-5
@@ -1,12 +1,12 @@
|
||||
aspectjVersion=1.9.9.1
|
||||
reactorVersion=2022.0.0
|
||||
reactorVersion=2022.0.1
|
||||
springJavaformatVersion=0.0.35
|
||||
springBootVersion=2.4.2
|
||||
springFrameworkVersion=6.0.0
|
||||
micrometerVersion=1.10.1
|
||||
springFrameworkVersion=6.0.3
|
||||
micrometerVersion=1.10.2
|
||||
openSamlVersion=4.1.1
|
||||
version=6.0.0
|
||||
kotlinVersion=1.7.21
|
||||
version=6.0.1
|
||||
kotlinVersion=1.7.22
|
||||
samplesBranch=main
|
||||
org.gradle.jvmargs=-Xmx3g -XX:+HeapDumpOnOutOfMemoryError
|
||||
org.gradle.parallel=true
|
||||
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.ldap.userdetails;
|
||||
|
||||
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.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.ldap.core.ContextSource;
|
||||
import org.springframework.ldap.core.DirContextAdapter;
|
||||
import org.springframework.ldap.core.DistinguishedName;
|
||||
import org.springframework.security.core.authority.AuthorityUtils;
|
||||
import org.springframework.security.ldap.DefaultSpringSecurityContextSource;
|
||||
import org.springframework.security.ldap.server.ApacheDSContainer;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Dayan Kodippily
|
||||
*/
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration(
|
||||
classes = DefaultLdapAuthoritiesPopulatorGetGrantedAuthoritiesTests.ApacheDsContainerWithUndefinedGroupRoleAttributeConfig.class)
|
||||
public class DefaultLdapAuthoritiesPopulatorGetGrantedAuthoritiesTests {
|
||||
|
||||
@Autowired
|
||||
private DefaultSpringSecurityContextSource contextSource;
|
||||
|
||||
private DefaultLdapAuthoritiesPopulator populator;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
this.populator = new DefaultLdapAuthoritiesPopulator(this.contextSource, "ou=groups");
|
||||
this.populator.setIgnorePartialResultException(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void groupSearchDoesNotAllowNullRoles() {
|
||||
this.populator.setRolePrefix("ROLE_");
|
||||
this.populator.setGroupRoleAttribute("ou");
|
||||
this.populator.setSearchSubtree(true);
|
||||
this.populator.setSearchSubtree(false);
|
||||
this.populator.setConvertToUpperCase(true);
|
||||
this.populator.setGroupSearchFilter("(member={0})");
|
||||
|
||||
DirContextAdapter ctx = new DirContextAdapter(
|
||||
new DistinguishedName("uid=dayan,ou=people,dc=springframework,dc=org"));
|
||||
|
||||
Set<String> authorities = AuthorityUtils.authorityListToSet(this.populator.getGrantedAuthorities(ctx, "dayan"));
|
||||
|
||||
assertThat(authorities).as("Should have 1 role").hasSize(2);
|
||||
|
||||
assertThat(authorities.contains("ROLE_DEVELOPER")).isTrue();
|
||||
assertThat(authorities.contains("ROLE_")).isTrue();
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class ApacheDsContainerWithUndefinedGroupRoleAttributeConfig implements DisposableBean {
|
||||
|
||||
private ApacheDSContainer container;
|
||||
|
||||
@Bean
|
||||
ApacheDSContainer ldapContainer() throws Exception {
|
||||
this.container = new ApacheDSContainer("dc=springframework,dc=org",
|
||||
"classpath:test-server-with-undefined-group-role-attributes.ldif");
|
||||
this.container.setPort(0);
|
||||
return this.container;
|
||||
}
|
||||
|
||||
@Bean
|
||||
ContextSource contextSource(ApacheDSContainer ldapContainer) {
|
||||
return new DefaultSpringSecurityContextSource(
|
||||
"ldap://127.0.0.1:" + ldapContainer.getLocalPort() + "/dc=springframework,dc=org");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
this.container.stop();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
dn: ou=groups,dc=springframework,dc=org
|
||||
objectclass: top
|
||||
objectclass: organizationalUnit
|
||||
ou: groups
|
||||
|
||||
dn: ou=people,dc=springframework,dc=org
|
||||
objectclass: top
|
||||
objectclass: organizationalUnit
|
||||
ou: people
|
||||
|
||||
dn: uid=dayan,ou=people,dc=springframework,dc=org
|
||||
objectclass: top
|
||||
objectclass: person
|
||||
objectclass: organizationalPerson
|
||||
objectclass: inetOrgPerson
|
||||
cn: Dayan K
|
||||
sn: Dayan
|
||||
uid: dayan
|
||||
userPassword: dayanspassword
|
||||
|
||||
|
||||
|
||||
dn: cn=managers,ou=groups,dc=springframework,dc=org
|
||||
objectclass: top
|
||||
objectclass: groupOfNames
|
||||
cn: managers
|
||||
ou:
|
||||
member: uid=dayan,ou=people,dc=springframework,dc=org
|
||||
|
||||
dn: cn=researchers,ou=groups,dc=springframework,dc=org
|
||||
objectclass: top
|
||||
objectclass: groupOfNames
|
||||
cn: researchers
|
||||
member: uid=dayan,ou=people,dc=springframework,dc=org
|
||||
|
||||
dn: cn=developers,ou=groups,dc=springframework,dc=org
|
||||
objectclass: top
|
||||
objectclass: groupOfNames
|
||||
cn: developers
|
||||
ou: developer
|
||||
member: uid=dayan,ou=people,dc=springframework,dc=org
|
||||
+13
-2
@@ -37,6 +37,7 @@ import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.ldap.SpringSecurityLdapTemplate;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
/**
|
||||
* The default strategy for obtaining user role information from the directory.
|
||||
@@ -169,7 +170,14 @@ public class DefaultLdapAuthoritiesPopulator implements LdapAuthoritiesPopulator
|
||||
logger.info("Will perform group search from the context source base since groupSearchBase is empty.");
|
||||
}
|
||||
this.authorityMapper = (record) -> {
|
||||
String role = record.get(this.groupRoleAttribute).get(0);
|
||||
List<String> roles = record.get(this.groupRoleAttribute);
|
||||
if (CollectionUtils.isEmpty(roles)) {
|
||||
return null;
|
||||
}
|
||||
String role = roles.get(0);
|
||||
if (role == null) {
|
||||
return null;
|
||||
}
|
||||
if (this.convertToUpperCase) {
|
||||
role = role.toUpperCase();
|
||||
}
|
||||
@@ -225,7 +233,10 @@ public class DefaultLdapAuthoritiesPopulator implements LdapAuthoritiesPopulator
|
||||
new String[] { this.groupRoleAttribute });
|
||||
logger.debug(LogMessage.of(() -> "Found roles from search " + userRoles));
|
||||
for (Map<String, List<String>> role : userRoles) {
|
||||
authorities.add(this.authorityMapper.apply(role));
|
||||
GrantedAuthority authority = this.authorityMapper.apply(role);
|
||||
if (authority != null) {
|
||||
authorities.add(authority);
|
||||
}
|
||||
}
|
||||
return authorities;
|
||||
}
|
||||
|
||||
+1
-1
@@ -195,8 +195,8 @@ public class OAuth2AuthorizationRequestRedirectFilter extends OncePerRequestFilt
|
||||
if (authorizationRequest == null) {
|
||||
throw authzEx;
|
||||
}
|
||||
this.sendRedirectForAuthorization(request, response, authorizationRequest);
|
||||
this.requestCache.saveRequest(request, response);
|
||||
this.sendRedirectForAuthorization(request, response, authorizationRequest);
|
||||
}
|
||||
catch (Exception failed) {
|
||||
this.unsuccessfulRedirectForAuthorization(request, response, failed);
|
||||
|
||||
+7
@@ -25,6 +25,7 @@ import io.r2dbc.h2.H2ConnectionFactory;
|
||||
import io.r2dbc.spi.ConnectionFactory;
|
||||
import io.r2dbc.spi.Result;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
@@ -114,12 +115,14 @@ public class R2dbcReactiveOAuth2AuthorizedClientServiceTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled // until https://github.com/reactor/reactor-core/issues/3307 is resolved
|
||||
public void loadAuthorizedClientWhenDoesNotExistThenReturnNull() {
|
||||
this.authorizedClientService.loadAuthorizedClient("registration-not-found", "principalName")
|
||||
.as(StepVerifier::create).expectNextCount(0).verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled // until https://github.com/reactor/reactor-core/issues/3307 is resolved
|
||||
public void loadAuthorizedClientWhenExistsThenReturnAuthorizedClient() {
|
||||
Authentication principal = createPrincipal();
|
||||
OAuth2AuthorizedClient expected = createAuthorizedClient(principal, this.clientRegistration);
|
||||
@@ -150,6 +153,7 @@ public class R2dbcReactiveOAuth2AuthorizedClientServiceTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled // until https://github.com/reactor/reactor-core/issues/3307 is resolved
|
||||
public void loadAuthorizedClientWhenExistsButNotFoundInClientRegistrationRepositoryThenThrowDataRetrievalFailureException() {
|
||||
given(this.clientRegistrationRepository.findByRegistrationId(any())).willReturn(Mono.empty());
|
||||
Authentication principal = createPrincipal();
|
||||
@@ -186,6 +190,7 @@ public class R2dbcReactiveOAuth2AuthorizedClientServiceTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled // until https://github.com/reactor/reactor-core/issues/3307 is resolved
|
||||
public void saveAuthorizedClientWhenSaveThenLoadReturnsSaved() {
|
||||
Authentication principal = createPrincipal();
|
||||
final OAuth2AuthorizedClient expected = createAuthorizedClient(principal, this.clientRegistration);
|
||||
@@ -244,6 +249,7 @@ public class R2dbcReactiveOAuth2AuthorizedClientServiceTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled // until https://github.com/reactor/reactor-core/issues/3307 is resolved
|
||||
public void saveAuthorizedClientWhenSaveClientWithExistingPrimaryKeyThenUpdate() {
|
||||
// Given a saved authorized client
|
||||
Authentication principal = createPrincipal();
|
||||
@@ -297,6 +303,7 @@ public class R2dbcReactiveOAuth2AuthorizedClientServiceTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled // until https://github.com/reactor/reactor-core/issues/3307 is resolved
|
||||
public void removeAuthorizedClientWhenExistsThenRemoved() {
|
||||
Authentication principal = createPrincipal();
|
||||
OAuth2AuthorizedClient authorizedClient = createAuthorizedClient(principal, this.clientRegistration);
|
||||
|
||||
+19
@@ -50,6 +50,7 @@ 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.BDDMockito.willAnswer;
|
||||
import static org.mockito.BDDMockito.willThrow;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
@@ -325,4 +326,22 @@ public class OAuth2AuthorizationRequestRedirectFilterTests {
|
||||
+ "redirect_uri=http://localhost/login/oauth2/code/registration-id");
|
||||
}
|
||||
|
||||
// gh-11602
|
||||
|
||||
@Test
|
||||
public void doFilterWhenNotAuthorizationRequestAndClientAuthorizationRequiredExceptionThrownThenSaveRequestBeforeCommitted()
|
||||
throws Exception {
|
||||
String requestUri = "/path";
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
|
||||
request.setServletPath(requestUri);
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
FilterChain filterChain = mock(FilterChain.class);
|
||||
willAnswer((invocation) -> assertThat((invocation.<HttpServletResponse>getArgument(1)).isCommitted()).isFalse())
|
||||
.given(this.requestCache).saveRequest(any(HttpServletRequest.class), any(HttpServletResponse.class));
|
||||
willThrow(new ClientAuthorizationRequiredException(this.registration1.getRegistrationId())).given(filterChain)
|
||||
.doFilter(any(ServletRequest.class), any(ServletResponse.class));
|
||||
this.filter.doFilter(request, response, filterChain);
|
||||
assertThat(response.isCommitted()).isTrue();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+10
-8
@@ -177,17 +177,19 @@ public final class ObservationFilterChainDecorator implements FilterChainProxy.F
|
||||
private void wrapFilter(ServletRequest request, ServletResponse response, FilterChain chain)
|
||||
throws IOException, ServletException {
|
||||
AroundFilterObservation parent = observation((HttpServletRequest) request);
|
||||
FilterChainObservationContext parentBefore = (FilterChainObservationContext) parent.before().getContext();
|
||||
parentBefore.setChainSize(this.size);
|
||||
parentBefore.setFilterName(this.name);
|
||||
parentBefore.setChainPosition(this.position);
|
||||
if (parent.before().getContext() instanceof FilterChainObservationContext parentBefore) {
|
||||
parentBefore.setChainSize(this.size);
|
||||
parentBefore.setFilterName(this.name);
|
||||
parentBefore.setChainPosition(this.position);
|
||||
}
|
||||
parent.before().event(Observation.Event.of(this.name + " before"));
|
||||
this.filter.doFilter(request, response, chain);
|
||||
parent.start();
|
||||
FilterChainObservationContext parentAfter = (FilterChainObservationContext) parent.after().getContext();
|
||||
parentAfter.setChainSize(this.size);
|
||||
parentAfter.setFilterName(this.name);
|
||||
parentAfter.setChainPosition(this.size - this.position + 1);
|
||||
if (parent.after().getContext() instanceof FilterChainObservationContext parentAfter) {
|
||||
parentAfter.setChainSize(this.size);
|
||||
parentAfter.setFilterName(this.name);
|
||||
parentAfter.setChainPosition(this.size - this.position + 1);
|
||||
}
|
||||
parent.after().event(Observation.Event.of(this.name + " after"));
|
||||
}
|
||||
|
||||
|
||||
+20
-20
@@ -21,6 +21,8 @@ import java.util.function.Supplier;
|
||||
|
||||
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;
|
||||
|
||||
@@ -28,7 +30,7 @@ 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.web.filter.OncePerRequestFilter;
|
||||
import org.springframework.web.filter.GenericFilterBean;
|
||||
|
||||
/**
|
||||
* A {@link jakarta.servlet.Filter} that uses the {@link SecurityContextRepository} to
|
||||
@@ -40,17 +42,18 @@ import org.springframework.web.filter.OncePerRequestFilter;
|
||||
* mechanisms to choose individually if authentication should be persisted.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @author Marcus da Coregio
|
||||
* @since 5.7
|
||||
*/
|
||||
public class SecurityContextHolderFilter extends OncePerRequestFilter {
|
||||
public class SecurityContextHolderFilter extends GenericFilterBean {
|
||||
|
||||
private static final String FILTER_APPLIED = SecurityContextHolderFilter.class.getName() + ".APPLIED";
|
||||
|
||||
private final SecurityContextRepository securityContextRepository;
|
||||
|
||||
private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder
|
||||
.getContextHolderStrategy();
|
||||
|
||||
private boolean shouldNotFilterErrorDispatch;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
* @param securityContextRepository the repository to use. Cannot be null.
|
||||
@@ -61,23 +64,29 @@ public class SecurityContextHolderFilter extends OncePerRequestFilter {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
|
||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
|
||||
throws IOException, ServletException {
|
||||
doFilter((HttpServletRequest) request, (HttpServletResponse) response, chain);
|
||||
}
|
||||
|
||||
private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
|
||||
throws ServletException, IOException {
|
||||
if (request.getAttribute(FILTER_APPLIED) != null) {
|
||||
chain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
|
||||
Supplier<SecurityContext> deferredContext = this.securityContextRepository.loadDeferredContext(request);
|
||||
try {
|
||||
this.securityContextHolderStrategy.setDeferredContext(deferredContext);
|
||||
filterChain.doFilter(request, response);
|
||||
chain.doFilter(request, response);
|
||||
}
|
||||
finally {
|
||||
this.securityContextHolderStrategy.clearContext();
|
||||
request.removeAttribute(FILTER_APPLIED);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean shouldNotFilterErrorDispatch() {
|
||||
return this.shouldNotFilterErrorDispatch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link SecurityContextHolderStrategy} to use. The default action is to use
|
||||
* the {@link SecurityContextHolderStrategy} stored in {@link SecurityContextHolder}.
|
||||
@@ -89,13 +98,4 @@ public class SecurityContextHolderFilter extends OncePerRequestFilter {
|
||||
this.securityContextHolderStrategy = securityContextHolderStrategy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables {@link SecurityContextHolderFilter} for error dispatch.
|
||||
* @param shouldNotFilterErrorDispatch if the Filter should be disabled for error
|
||||
* dispatch. Default is false.
|
||||
*/
|
||||
public void setShouldNotFilterErrorDispatch(boolean shouldNotFilterErrorDispatch) {
|
||||
this.shouldNotFilterErrorDispatch = shouldNotFilterErrorDispatch;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+10
-10
@@ -196,18 +196,18 @@ public final class ObservationWebFilterChainDecorator implements WebFilterChainP
|
||||
|
||||
private Mono<Void> wrapFilter(ServerWebExchange exchange, WebFilterChain chain) {
|
||||
AroundWebFilterObservation parent = observation(exchange);
|
||||
WebFilterChainObservationContext parentBefore = (WebFilterChainObservationContext) parent.before()
|
||||
.getContext();
|
||||
parentBefore.setChainSize(this.size);
|
||||
parentBefore.setFilterName(this.name);
|
||||
parentBefore.setChainPosition(this.position);
|
||||
if (parent.before().getContext() instanceof WebFilterChainObservationContext parentBefore) {
|
||||
parentBefore.setChainSize(this.size);
|
||||
parentBefore.setFilterName(this.name);
|
||||
parentBefore.setChainPosition(this.position);
|
||||
}
|
||||
return this.filter.filter(exchange, chain).doOnSuccess((result) -> {
|
||||
parent.start();
|
||||
WebFilterChainObservationContext parentAfter = (WebFilterChainObservationContext) parent.after()
|
||||
.getContext();
|
||||
parentAfter.setChainSize(this.size);
|
||||
parentAfter.setFilterName(this.name);
|
||||
parentAfter.setChainPosition(this.size - this.position + 1);
|
||||
if (parent.after().getContext() instanceof WebFilterChainObservationContext parentAfter) {
|
||||
parentAfter.setChainSize(this.size);
|
||||
parentAfter.setFilterName(this.name);
|
||||
parentAfter.setChainPosition(this.size - this.position + 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF 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;
|
||||
|
||||
import io.micrometer.observation.ObservationHandler;
|
||||
import io.micrometer.observation.ObservationRegistry;
|
||||
import jakarta.servlet.FilterChain;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
|
||||
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 ObservationFilterChainDecorator}
|
||||
*/
|
||||
public class ObservationFilterChainDecoratorTests {
|
||||
|
||||
@Test
|
||||
void decorateWhenDefaultsThenObserves() throws Exception {
|
||||
ObservationHandler<?> handler = mock(ObservationHandler.class);
|
||||
given(handler.supportsContext(any())).willReturn(true);
|
||||
ObservationRegistry registry = ObservationRegistry.create();
|
||||
registry.observationConfig().observationHandler(handler);
|
||||
ObservationFilterChainDecorator decorator = new ObservationFilterChainDecorator(registry);
|
||||
FilterChain chain = mock(FilterChain.class);
|
||||
FilterChain decorated = decorator.decorate(chain);
|
||||
decorated.doFilter(new MockHttpServletRequest("GET", "/"), new MockHttpServletResponse());
|
||||
verify(handler).onStart(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void decorateWhenNoopThenDoesNotObserve() throws Exception {
|
||||
ObservationHandler<?> handler = mock(ObservationHandler.class);
|
||||
given(handler.supportsContext(any())).willReturn(true);
|
||||
ObservationRegistry registry = ObservationRegistry.NOOP;
|
||||
registry.observationConfig().observationHandler(handler);
|
||||
ObservationFilterChainDecorator decorator = new ObservationFilterChainDecorator(registry);
|
||||
FilterChain chain = mock(FilterChain.class);
|
||||
FilterChain decorated = decorator.decorate(chain);
|
||||
decorated.doFilter(new MockHttpServletRequest("GET", "/"), new MockHttpServletResponse());
|
||||
verifyNoInteractions(handler);
|
||||
}
|
||||
|
||||
}
|
||||
+40
-5
@@ -18,6 +18,7 @@ package org.springframework.security.web.context;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import jakarta.servlet.DispatcherType;
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
@@ -25,11 +26,15 @@ 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.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.EnumSource;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Captor;
|
||||
import org.mockito.InOrder;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import org.springframework.mock.web.MockFilterChain;
|
||||
import org.springframework.security.authentication.TestAuthentication;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
@@ -39,11 +44,17 @@ import org.springframework.security.core.context.SecurityContextImpl;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.inOrder;
|
||||
import static org.mockito.Mockito.lenient;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class SecurityContextHolderFilterTests {
|
||||
|
||||
private static final String FILTER_APPLIED = "org.springframework.security.web.context.SecurityContextHolderFilter.APPLIED";
|
||||
|
||||
@Mock
|
||||
private SecurityContextRepository repository;
|
||||
|
||||
@@ -104,14 +115,38 @@ class SecurityContextHolderFilterTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotFilterErrorDispatchWhenDefault() {
|
||||
assertThat(this.filter.shouldNotFilterErrorDispatch()).isFalse();
|
||||
void doFilterWhenFilterAppliedThenDoNothing() throws Exception {
|
||||
given(this.request.getAttribute(FILTER_APPLIED)).willReturn(true);
|
||||
this.filter.doFilter(this.request, this.response, new MockFilterChain());
|
||||
verify(this.request, times(1)).getAttribute(FILTER_APPLIED);
|
||||
verifyNoInteractions(this.repository, this.response);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotFilterErrorDispatchWhenOverridden() {
|
||||
this.filter.setShouldNotFilterErrorDispatch(true);
|
||||
assertThat(this.filter.shouldNotFilterErrorDispatch()).isTrue();
|
||||
void doFilterWhenNotAppliedThenSetsAndRemovesAttribute() throws Exception {
|
||||
given(this.repository.loadDeferredContext(this.requestArg.capture())).willReturn(
|
||||
new SupplierDeferredSecurityContext(SecurityContextHolder::createEmptyContext, this.strategy));
|
||||
|
||||
this.filter.doFilter(this.request, this.response, new MockFilterChain());
|
||||
|
||||
InOrder inOrder = inOrder(this.request, this.repository);
|
||||
inOrder.verify(this.request).setAttribute(FILTER_APPLIED, true);
|
||||
inOrder.verify(this.repository).loadDeferredContext(this.request);
|
||||
inOrder.verify(this.request).removeAttribute(FILTER_APPLIED);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@EnumSource(DispatcherType.class)
|
||||
void doFilterWhenAnyDispatcherTypeThenFilter(DispatcherType dispatcherType) throws Exception {
|
||||
lenient().when(this.request.getDispatcherType()).thenReturn(dispatcherType);
|
||||
Authentication authentication = TestAuthentication.authenticatedUser();
|
||||
SecurityContext expectedContext = new SecurityContextImpl(authentication);
|
||||
given(this.repository.loadDeferredContext(this.requestArg.capture()))
|
||||
.willReturn(new SupplierDeferredSecurityContext(() -> expectedContext, this.strategy));
|
||||
FilterChain filterChain = (request, response) -> assertThat(SecurityContextHolder.getContext())
|
||||
.isEqualTo(expectedContext);
|
||||
|
||||
this.filter.doFilter(this.request, this.response, filterChain);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF 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.server;
|
||||
|
||||
import io.micrometer.observation.ObservationHandler;
|
||||
import io.micrometer.observation.ObservationRegistry;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
|
||||
import org.springframework.mock.web.server.MockServerWebExchange;
|
||||
import org.springframework.web.server.WebFilterChain;
|
||||
|
||||
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 ObservationWebFilterChainDecorator}
|
||||
*/
|
||||
public class ObservationWebFilterChainDecoratorTests {
|
||||
|
||||
@Test
|
||||
void decorateWhenDefaultsThenObserves() {
|
||||
ObservationHandler<?> handler = mock(ObservationHandler.class);
|
||||
given(handler.supportsContext(any())).willReturn(true);
|
||||
ObservationRegistry registry = ObservationRegistry.create();
|
||||
registry.observationConfig().observationHandler(handler);
|
||||
ObservationWebFilterChainDecorator decorator = new ObservationWebFilterChainDecorator(registry);
|
||||
WebFilterChain chain = mock(WebFilterChain.class);
|
||||
given(chain.filter(any())).willReturn(Mono.empty());
|
||||
WebFilterChain decorated = decorator.decorate(chain);
|
||||
decorated.filter(MockServerWebExchange.from(MockServerHttpRequest.get("/").build())).block();
|
||||
verify(handler).onStart(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void decorateWhenNoopThenDoesNotObserve() {
|
||||
ObservationHandler<?> handler = mock(ObservationHandler.class);
|
||||
given(handler.supportsContext(any())).willReturn(true);
|
||||
ObservationRegistry registry = ObservationRegistry.NOOP;
|
||||
registry.observationConfig().observationHandler(handler);
|
||||
ObservationWebFilterChainDecorator decorator = new ObservationWebFilterChainDecorator(registry);
|
||||
WebFilterChain chain = mock(WebFilterChain.class);
|
||||
given(chain.filter(any())).willReturn(Mono.empty());
|
||||
WebFilterChain decorated = decorator.decorate(chain);
|
||||
decorated.filter(MockServerWebExchange.from(MockServerHttpRequest.get("/").build())).block();
|
||||
verifyNoInteractions(handler);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user