Compare commits
117 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| af2d210ccb | |||
| 69aeeeade7 | |||
| 4d3152894d | |||
| 4eae9f45e5 | |||
| c7047add5d | |||
| 142b268a21 | |||
| 6dd29520b0 | |||
| 70dfb3d391 | |||
| 0bd3b9e420 | |||
| a162875f41 | |||
| 1bb5fe409b | |||
| 6f0ce5c2b0 | |||
| 87e6986de5 | |||
| dce87beb07 | |||
| 6bede4cf7a | |||
| 3c514bd747 | |||
| 83585125ff | |||
| 42432cc3de | |||
| c9a7f2487c | |||
| 78aaaa7bce | |||
| b2c3bc6b3a | |||
| 652f98a88c | |||
| fd02d7077d | |||
| 40dd7a0d2d | |||
| 53b640e8e2 | |||
| c6326062da | |||
| 837189995d | |||
| 03e37e79ab | |||
| 10b3a9b234 | |||
| e4cc0a4755 | |||
| ff71ef46b7 | |||
| 5593655bbf | |||
| 47812e506f | |||
| b03680ca58 | |||
| d464c00315 | |||
| 17fbf396db | |||
| 27c4ee3048 | |||
| 426b61cd3c | |||
| 3f11908eac | |||
| 986414fd4f | |||
| 4308b284a7 | |||
| 89fd30f235 | |||
| c3c068376a | |||
| 4886951379 | |||
| a650fe9e87 | |||
| 6d68f403fc | |||
| d130a18d3c | |||
| f0772dc788 | |||
| 74d06f020d | |||
| 966c01fa90 | |||
| c4a99fc942 | |||
| e896b14046 | |||
| bdec2d3044 | |||
| 6e636e6abb | |||
| 9f90661b6f | |||
| be11812fe4 | |||
| 981bc1e751 | |||
| 1783a70fdb | |||
| 9c44b700f5 | |||
| 32cf79d1a9 | |||
| bc648e0491 | |||
| 247a9fdabf | |||
| 4f6a4d6471 | |||
| 956438362d | |||
| a969f67c14 | |||
| c453c82948 | |||
| 674ce38bd3 | |||
| 7aa6e0d787 | |||
| f6843b2bd9 | |||
| 15febfc65f | |||
| b5c43290e6 | |||
| d912914ea7 | |||
| aa732107dc | |||
| c540f885d3 | |||
| f4314edc78 | |||
| 4e37499e28 | |||
| 40e8b20257 | |||
| 3ab235cd56 | |||
| 641722823e | |||
| f536b2652f | |||
| c336ca49fb | |||
| c623303ca5 | |||
| a98baa7522 | |||
| b9b2df845f | |||
| 4d07b9d632 | |||
| 1723d88bdf | |||
| 34c4b8904e | |||
| 6f45be28a0 | |||
| 8cdd53f531 | |||
| 54b8f3874f | |||
| a73c5d754a | |||
| 67424adef3 | |||
| 34cbf8ed10 | |||
| e9e198b1e3 | |||
| 7c1e6f36c9 | |||
| 60d310e315 | |||
| 57f7eff568 | |||
| 6e1605314a | |||
| 1d769b5afc | |||
| 0beda02a14 | |||
| abe2029cb4 | |||
| c2d88eca5a | |||
| c0355884f3 | |||
| f25b3fba2f | |||
| a4c107bd3c | |||
| d09ce0baff | |||
| 008f0d413d | |||
| 515e8216b1 | |||
| d6ac0357fe | |||
| c6265c5a7c | |||
| 5ef31c17da | |||
| 533ad7107f | |||
| 8e93e4715f | |||
| d99e98515c | |||
| ba851266c5 | |||
| 3f6b6aa523 | |||
| 9749c23c74 |
@@ -49,26 +49,6 @@ updates:
|
||||
- dependency-name: "*"
|
||||
update-types: [ "version-update:semver-major", "version-update:semver-minor" ]
|
||||
|
||||
- package-ecosystem: "gradle"
|
||||
target-branch: "6.0.x"
|
||||
milestone: 143 # 6.0.x
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
time: "03:00"
|
||||
timezone: "Etc/UTC"
|
||||
labels: [ "type: dependency-upgrade" ]
|
||||
ignore:
|
||||
- dependency-name: "com.nimbusds:nimbus-jose-jwt" # nimbus-jose-jwt gets updated when oauth2-oidc-sdk is updated to ensure consistency
|
||||
- dependency-name: "org.python:jython" # jython updates break integration tests
|
||||
- dependency-name: "org.apache.directory.server:*" # ApacheDS version > 1.5.5 contains break changes
|
||||
- dependency-name: "org.junit:junit-bom"
|
||||
update-types: [ "version-update:semver-major" ]
|
||||
- dependency-name: "org.mockito:mockito-bom"
|
||||
update-types: [ "version-update:semver-major" ]
|
||||
- dependency-name: "*"
|
||||
update-types: [ "version-update:semver-major", "version-update:semver-minor" ]
|
||||
|
||||
- package-ecosystem: "gradle"
|
||||
target-branch: "5.8.x"
|
||||
milestone: 246 # 5.8.x
|
||||
|
||||
@@ -32,7 +32,7 @@ jobs:
|
||||
project_version: ${{ steps.continue.outputs.project_version }}
|
||||
samples_branch: ${{ steps.continue.outputs.samples_branch }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- id: continue
|
||||
name: Determine if should continue
|
||||
run: |
|
||||
@@ -52,9 +52,9 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
if: needs.prerequisites.outputs.runjobs
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v3
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'temurin'
|
||||
@@ -82,9 +82,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
if: needs.prerequisites.outputs.runjobs
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up gradle
|
||||
uses: spring-io/spring-gradle-build-action@v1
|
||||
uses: spring-io/spring-gradle-build-action@v2
|
||||
with:
|
||||
java-version: ${{ matrix.java-version }}
|
||||
distribution: 'temurin'
|
||||
@@ -100,9 +100,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
if: needs.prerequisites.outputs.runjobs
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up gradle
|
||||
uses: spring-io/spring-gradle-build-action@v1
|
||||
uses: spring-io/spring-gradle-build-action@v2
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'temurin'
|
||||
@@ -125,9 +125,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
if: needs.prerequisites.outputs.runjobs
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up gradle
|
||||
uses: spring-io/spring-gradle-build-action@v1
|
||||
uses: spring-io/spring-gradle-build-action@v2
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'temurin'
|
||||
@@ -142,9 +142,9 @@ jobs:
|
||||
needs: [build_jdk_17, snapshot_tests, check_samples, check_tangles]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up gradle
|
||||
uses: spring-io/spring-gradle-build-action@v1
|
||||
uses: spring-io/spring-gradle-build-action@v2
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'temurin'
|
||||
@@ -166,9 +166,9 @@ jobs:
|
||||
needs: [build_jdk_17, snapshot_tests, check_samples, check_tangles]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up gradle
|
||||
uses: spring-io/spring-gradle-build-action@v1
|
||||
uses: spring-io/spring-gradle-build-action@v2
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'temurin'
|
||||
@@ -187,9 +187,9 @@ jobs:
|
||||
needs: [build_jdk_17, snapshot_tests, check_samples, check_tangles]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up gradle
|
||||
uses: spring-io/spring-gradle-build-action@v1
|
||||
uses: spring-io/spring-gradle-build-action@v2
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'temurin'
|
||||
@@ -217,11 +217,11 @@ jobs:
|
||||
TOKEN: ${{ github.token }}
|
||||
VERSION: ${{ needs.prerequisites.outputs.project_version }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
token: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
|
||||
- name: Set up gradle
|
||||
uses: spring-io/spring-gradle-build-action@v1
|
||||
uses: spring-io/spring-gradle-build-action@v2
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'temurin'
|
||||
@@ -254,7 +254,7 @@ jobs:
|
||||
./gradlew createGitHubRelease -PnextVersion=$VERSION -Pbranch=$BRANCH -PcreateRelease=true -PgitHubAccessToken=$TOKEN
|
||||
- name: Announce Release on Slack
|
||||
id: spring-security-announcing
|
||||
uses: slackapi/slack-github-action@v1.19.0
|
||||
uses: slackapi/slack-github-action@v1.24.0
|
||||
with:
|
||||
payload: |
|
||||
{
|
||||
@@ -298,9 +298,9 @@ jobs:
|
||||
TOKEN: ${{ github.token }}
|
||||
VERSION: ${{ needs.prerequisites.outputs.project_version }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up gradle
|
||||
uses: spring-io/spring-gradle-build-action@v1
|
||||
uses: spring-io/spring-gradle-build-action@v2
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'temurin'
|
||||
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
if: github.repository_owner == 'spring-projects'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: docs-build
|
||||
fetch-depth: 1
|
||||
|
||||
@@ -20,7 +20,7 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v3
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'temurin'
|
||||
|
||||
@@ -32,7 +32,7 @@ jobs:
|
||||
actions: read
|
||||
steps:
|
||||
- name: Send Slack message
|
||||
uses: Gamesight/slack-workflow-status@v1.0.1
|
||||
uses: Gamesight/slack-workflow-status@v1.2.0
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
slack_webhook_url: ${{ secrets.SLACK_WEBHOOK_URL }}
|
||||
|
||||
@@ -2,6 +2,11 @@ name: PR Build
|
||||
|
||||
on: pull_request
|
||||
|
||||
env:
|
||||
GRADLE_ENTERPRISE_CACHE_USERNAME: ${{ secrets.GRADLE_ENTERPRISE_CACHE_USER }}
|
||||
GRADLE_ENTERPRISE_CACHE_PASSWORD: ${{ secrets.GRADLE_ENTERPRISE_CACHE_PASSWORD }}
|
||||
GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }}
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
@@ -11,9 +16,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.repository == 'spring-projects/spring-security' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up gradle
|
||||
uses: spring-io/spring-gradle-build-action@v1
|
||||
uses: spring-io/spring-gradle-build-action@v2
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'temurin'
|
||||
|
||||
@@ -11,11 +11,11 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
# List of active maintenance branches.
|
||||
branch: [ main, 6.1.x, 6.0.x, 5.8.x ]
|
||||
branch: [ main, 6.1.x, 5.8.x ]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
- name: Dispatch
|
||||
|
||||
@@ -23,11 +23,11 @@ jobs:
|
||||
steps:
|
||||
- id: checkout-source
|
||||
name: Checkout Source Code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
token: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
|
||||
- name: Set up gradle
|
||||
uses: spring-io/spring-gradle-build-action@v1
|
||||
uses: spring-io/spring-gradle-build-action@v2
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'temurin'
|
||||
@@ -72,7 +72,7 @@ jobs:
|
||||
- id: send-slack-notification
|
||||
name: Send Slack message
|
||||
if: failure()
|
||||
uses: Gamesight/slack-workflow-status@v1.0.1
|
||||
uses: Gamesight/slack-workflow-status@v1.2.0
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
slack_webhook_url: ${{ secrets.SLACK_WEBHOOK_URL }}
|
||||
|
||||
+1
-1
@@ -169,7 +169,7 @@ wrapperUpgrade {
|
||||
gradle {
|
||||
'spring-security' {
|
||||
repo = 'spring-projects/spring-security'
|
||||
baseBranch = '6.0.x' // runs only on 6.0.x and the update is merged forward to main
|
||||
baseBranch = '6.1.x' // runs only on 6.1.x and the update is merged forward to main
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,17 +21,38 @@ import org.gradle.api.publish.maven.plugins.MavenPublishPlugin
|
||||
|
||||
class ArtifactoryPlugin implements Plugin<Project> {
|
||||
|
||||
private static final String ARTIFACTORY_URL_NAME = "ARTIFACTORY_URL"
|
||||
|
||||
private static final String ARTIFACTORY_SNAPSHOT_REPOSITORY = "ARTIFACTORY_SNAPSHOT_REPOSITORY"
|
||||
|
||||
private static final String ARTIFACTORY_MILESTONE_REPOSITORY = "ARTIFACTORY_MILESTONE_REPOSITORY"
|
||||
|
||||
private static final String ARTIFACTORY_RELEASE_REPOSITORY = "ARTIFACTORY_RELEASE_REPOSITORY"
|
||||
|
||||
private static final String DEFAULT_ARTIFACTORY_URL = "https://repo.spring.io"
|
||||
|
||||
private static final String DEFAULT_ARTIFACTORY_SNAPSHOT_REPOSITORY = "libs-snapshot-local"
|
||||
|
||||
private static final String DEFAULT_ARTIFACTORY_MILESTONE_REPOSITORY = "libs-milestone-local"
|
||||
|
||||
private static final String DEFAULT_ARTIFACTORY_RELEASE_REPOSITORY = "libs-release-local"
|
||||
|
||||
@Override
|
||||
void apply(Project project) {
|
||||
project.plugins.apply('com.jfrog.artifactory')
|
||||
String name = Utils.getProjectName(project);
|
||||
boolean isSnapshot = Utils.isSnapshot(project);
|
||||
boolean isMilestone = Utils.isMilestone(project);
|
||||
Map<String, String> env = System.getenv()
|
||||
String artifactoryUrl = env.getOrDefault(ARTIFACTORY_URL_NAME, DEFAULT_ARTIFACTORY_URL)
|
||||
String snapshotRepository = env.getOrDefault(ARTIFACTORY_SNAPSHOT_REPOSITORY, DEFAULT_ARTIFACTORY_SNAPSHOT_REPOSITORY)
|
||||
String milestoneRepository = env.getOrDefault(ARTIFACTORY_MILESTONE_REPOSITORY, DEFAULT_ARTIFACTORY_MILESTONE_REPOSITORY)
|
||||
String releaseRepository = env.getOrDefault(ARTIFACTORY_RELEASE_REPOSITORY, DEFAULT_ARTIFACTORY_RELEASE_REPOSITORY)
|
||||
project.artifactory {
|
||||
contextUrl = 'https://repo.spring.io'
|
||||
contextUrl = artifactoryUrl
|
||||
publish {
|
||||
repository {
|
||||
repoKey = isSnapshot ? 'libs-snapshot-local' : isMilestone ? 'libs-milestone-local' : 'libs-release-local'
|
||||
repoKey = isSnapshot ? snapshotRepository : isMilestone ? milestoneRepository : releaseRepository
|
||||
if(project.hasProperty('artifactoryUsername')) {
|
||||
username = artifactoryUsername
|
||||
password = artifactoryPassword
|
||||
|
||||
+27
-12
@@ -23,12 +23,14 @@ import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.function.Function;
|
||||
|
||||
import jakarta.servlet.DispatcherType;
|
||||
import jakarta.servlet.ServletContext;
|
||||
import jakarta.servlet.ServletRegistration;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
@@ -44,7 +46,6 @@ import org.springframework.security.web.util.matcher.RegexRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.function.SingletonSupplier;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
|
||||
|
||||
@@ -76,6 +77,8 @@ public abstract class AbstractRequestMatcherRegistry<C> {
|
||||
AbstractRequestMatcherRegistry.class.getClassLoader());
|
||||
}
|
||||
|
||||
private final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
protected final void setApplicationContext(ApplicationContext context) {
|
||||
this.context = context;
|
||||
}
|
||||
@@ -209,7 +212,12 @@ public abstract class AbstractRequestMatcherRegistry<C> {
|
||||
matchers.add(resolve(ant, mvc, servletContext));
|
||||
}
|
||||
else {
|
||||
matchers.add(new DeferredRequestMatcher(() -> resolve(ant, mvc, servletContext), mvc, ant));
|
||||
this.logger
|
||||
.warn("The ServletRegistration API was not available at startup time. This may be due to a misconfiguration; "
|
||||
+ "if you are using AbstractSecurityWebApplicationInitializer, please double-check the recommendations outlined in "
|
||||
+ "https://docs.spring.io/spring-security/reference/servlet/configuration/java.html#abstractsecuritywebapplicationinitializer-with-spring-mvc");
|
||||
matchers.add(new DeferredRequestMatcher((request) -> resolve(ant, mvc, request.getServletContext()),
|
||||
mvc, ant));
|
||||
}
|
||||
}
|
||||
return requestMatchers(matchers.toArray(new RequestMatcher[0]));
|
||||
@@ -466,27 +474,34 @@ public abstract class AbstractRequestMatcherRegistry<C> {
|
||||
|
||||
static class DeferredRequestMatcher implements RequestMatcher {
|
||||
|
||||
final Supplier<RequestMatcher> requestMatcher;
|
||||
final Function<HttpServletRequest, RequestMatcher> requestMatcherFactory;
|
||||
|
||||
final AtomicReference<String> description = new AtomicReference<>();
|
||||
|
||||
DeferredRequestMatcher(Supplier<RequestMatcher> resolver, RequestMatcher... candidates) {
|
||||
this.requestMatcher = SingletonSupplier.of(() -> {
|
||||
RequestMatcher matcher = resolver.get();
|
||||
this.description.set(matcher.toString());
|
||||
return matcher;
|
||||
});
|
||||
volatile RequestMatcher requestMatcher;
|
||||
|
||||
DeferredRequestMatcher(Function<HttpServletRequest, RequestMatcher> resolver, RequestMatcher... candidates) {
|
||||
this.requestMatcherFactory = (request) -> {
|
||||
if (this.requestMatcher == null) {
|
||||
synchronized (this) {
|
||||
if (this.requestMatcher == null) {
|
||||
this.requestMatcher = resolver.apply(request);
|
||||
}
|
||||
}
|
||||
}
|
||||
return this.requestMatcher;
|
||||
};
|
||||
this.description.set("Deferred " + Arrays.toString(candidates));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(HttpServletRequest request) {
|
||||
return this.requestMatcher.get().matches(request);
|
||||
return this.requestMatcherFactory.apply(request).matches(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MatchResult matcher(HttpServletRequest request) {
|
||||
return this.requestMatcher.get().matcher(request);
|
||||
return this.requestMatcherFactory.apply(request).matcher(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+9
@@ -49,6 +49,7 @@ import org.springframework.security.web.FilterInvocation;
|
||||
import org.springframework.security.web.ObservationFilterChainDecorator;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.access.AuthorizationManagerWebInvocationPrivilegeEvaluator;
|
||||
import org.springframework.security.web.access.AuthorizationManagerWebInvocationPrivilegeEvaluator.HttpServletRequestTransformer;
|
||||
import org.springframework.security.web.access.DefaultWebInvocationPrivilegeEvaluator;
|
||||
import org.springframework.security.web.access.RequestMatcherDelegatingWebInvocationPrivilegeEvaluator;
|
||||
import org.springframework.security.web.access.WebInvocationPrivilegeEvaluator;
|
||||
@@ -108,6 +109,8 @@ public final class WebSecurity extends AbstractConfiguredSecurityBuilder<Filter,
|
||||
|
||||
private ObservationRegistry observationRegistry = ObservationRegistry.NOOP;
|
||||
|
||||
private HttpServletRequestTransformer privilegeEvaluatorRequestTransformer;
|
||||
|
||||
private DefaultWebSecurityExpressionHandler defaultWebSecurityExpressionHandler = new DefaultWebSecurityExpressionHandler();
|
||||
|
||||
private SecurityExpressionHandler<FilterInvocation> expressionHandler = this.defaultWebSecurityExpressionHandler;
|
||||
@@ -350,6 +353,9 @@ public final class WebSecurity extends AbstractConfiguredSecurityBuilder<Filter,
|
||||
AuthorizationManagerWebInvocationPrivilegeEvaluator evaluator = new AuthorizationManagerWebInvocationPrivilegeEvaluator(
|
||||
authorizationManager);
|
||||
evaluator.setServletContext(this.servletContext);
|
||||
if (this.privilegeEvaluatorRequestTransformer != null) {
|
||||
evaluator.setRequestTransformer(this.privilegeEvaluatorRequestTransformer);
|
||||
}
|
||||
privilegeEvaluators.add(evaluator);
|
||||
}
|
||||
}
|
||||
@@ -386,6 +392,9 @@ public final class WebSecurity extends AbstractConfiguredSecurityBuilder<Filter,
|
||||
}
|
||||
catch (NoSuchBeanDefinitionException ex) {
|
||||
}
|
||||
Class<HttpServletRequestTransformer> requestTransformerClass = HttpServletRequestTransformer.class;
|
||||
this.privilegeEvaluatorRequestTransformer = applicationContext.getBeanProvider(requestTransformerClass)
|
||||
.getIfUnique();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+220
@@ -16,9 +16,25 @@
|
||||
|
||||
package org.springframework.security.config.annotation.web.configuration;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import jakarta.servlet.Filter;
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.ServletRequest;
|
||||
import jakarta.servlet.ServletResponse;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.springframework.beans.BeanMetadataElement;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
|
||||
import org.springframework.beans.factory.support.ManagedList;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
@@ -26,13 +42,21 @@ import org.springframework.context.expression.BeanFactoryResolver;
|
||||
import org.springframework.expression.BeanResolver;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.core.context.SecurityContextHolderStrategy;
|
||||
import org.springframework.security.web.FilterChainProxy;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.access.HandlerMappingIntrospectorRequestTransformer;
|
||||
import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;
|
||||
import org.springframework.security.web.firewall.HttpFirewall;
|
||||
import org.springframework.security.web.firewall.RequestRejectedHandler;
|
||||
import org.springframework.security.web.method.annotation.AuthenticationPrincipalArgumentResolver;
|
||||
import org.springframework.security.web.method.annotation.CsrfTokenArgumentResolver;
|
||||
import org.springframework.security.web.method.annotation.CurrentSecurityContextArgumentResolver;
|
||||
import org.springframework.security.web.servlet.support.csrf.CsrfRequestDataValueProcessor;
|
||||
import org.springframework.web.filter.CompositeFilter;
|
||||
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
|
||||
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
|
||||
import org.springframework.web.servlet.support.RequestDataValueProcessor;
|
||||
|
||||
/**
|
||||
@@ -50,6 +74,8 @@ import org.springframework.web.servlet.support.RequestDataValueProcessor;
|
||||
*/
|
||||
class WebMvcSecurityConfiguration implements WebMvcConfigurer, ApplicationContextAware {
|
||||
|
||||
private static final String HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME = "mvcHandlerMappingIntrospector";
|
||||
|
||||
private BeanResolver beanResolver;
|
||||
|
||||
private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder
|
||||
@@ -84,4 +110,198 @@ class WebMvcSecurityConfiguration implements WebMvcConfigurer, ApplicationContex
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to ensure Spring MVC request matching is cached.
|
||||
*
|
||||
* Creates a {@link BeanDefinitionRegistryPostProcessor} that detects if a bean named
|
||||
* HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME is defined. If so, it moves the
|
||||
* AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME to another bean name
|
||||
* and then adds a {@link CompositeFilter} that contains
|
||||
* {@link HandlerMappingIntrospector#createCacheFilter()} and the original
|
||||
* FilterChainProxy under the original Bean name.
|
||||
* @return
|
||||
*/
|
||||
@Bean
|
||||
static BeanDefinitionRegistryPostProcessor springSecurityHandlerMappingIntrospectorBeanDefinitionRegistryPostProcessor() {
|
||||
return new BeanDefinitionRegistryPostProcessor() {
|
||||
@Override
|
||||
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
|
||||
if (!registry.containsBeanDefinition(HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME)) {
|
||||
return;
|
||||
}
|
||||
|
||||
BeanDefinition hmiRequestTransformer = BeanDefinitionBuilder
|
||||
.rootBeanDefinition(HandlerMappingIntrospectorRequestTransformer.class)
|
||||
.addConstructorArgReference(HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME)
|
||||
.getBeanDefinition();
|
||||
registry.registerBeanDefinition(HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME + "RequestTransformer",
|
||||
hmiRequestTransformer);
|
||||
|
||||
BeanDefinition filterChainProxy = registry
|
||||
.getBeanDefinition(AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME);
|
||||
|
||||
BeanDefinitionBuilder hmiCacheFilterBldr = BeanDefinitionBuilder
|
||||
.rootBeanDefinition(HandlerMappingIntrospectorCachFilterFactoryBean.class)
|
||||
.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
|
||||
|
||||
ManagedList<BeanMetadataElement> filters = new ManagedList<>();
|
||||
filters.add(hmiCacheFilterBldr.getBeanDefinition());
|
||||
filters.add(filterChainProxy);
|
||||
BeanDefinitionBuilder compositeSpringSecurityFilterChainBldr = BeanDefinitionBuilder
|
||||
.rootBeanDefinition(CompositeFilterChainProxy.class)
|
||||
.addConstructorArgValue(filters);
|
||||
|
||||
registry.removeBeanDefinition(AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME);
|
||||
registry.registerBeanDefinition(AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME,
|
||||
compositeSpringSecurityFilterChainBldr.getBeanDefinition());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link FactoryBean} to defer creation of
|
||||
* {@link HandlerMappingIntrospector#createCacheFilter()}
|
||||
*/
|
||||
static class HandlerMappingIntrospectorCachFilterFactoryBean
|
||||
implements ApplicationContextAware, FactoryBean<Filter> {
|
||||
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) {
|
||||
this.applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Filter getObject() throws Exception {
|
||||
HandlerMappingIntrospector handlerMappingIntrospector = this.applicationContext
|
||||
.getBean(HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME, HandlerMappingIntrospector.class);
|
||||
return handlerMappingIntrospector.createCacheFilter();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getObjectType() {
|
||||
return Filter.class;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Extends {@link FilterChainProxy} to provide as much passivity as possible but
|
||||
* delegates to {@link CompositeFilter} for
|
||||
* {@link #doFilter(ServletRequest, ServletResponse, FilterChain)}.
|
||||
*/
|
||||
static class CompositeFilterChainProxy extends FilterChainProxy {
|
||||
|
||||
/**
|
||||
* Used for {@link #doFilter(ServletRequest, ServletResponse, FilterChain)}
|
||||
*/
|
||||
private final Filter doFilterDelegate;
|
||||
|
||||
private final FilterChainProxy springSecurityFilterChain;
|
||||
|
||||
/**
|
||||
* Creates a new instance
|
||||
* @param filters the Filters to delegate to. One of which must be
|
||||
* FilterChainProxy.
|
||||
*/
|
||||
CompositeFilterChainProxy(List<? extends Filter> filters) {
|
||||
this.doFilterDelegate = createDoFilterDelegate(filters);
|
||||
this.springSecurityFilterChain = findFilterChainProxy(filters);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
this.springSecurityFilterChain.afterPropertiesSet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
|
||||
throws IOException, ServletException {
|
||||
this.doFilterDelegate.doFilter(request, response, chain);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Filter> getFilters(String url) {
|
||||
return this.springSecurityFilterChain.getFilters(url);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SecurityFilterChain> getFilterChains() {
|
||||
return this.springSecurityFilterChain.getFilterChains();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSecurityContextHolderStrategy(SecurityContextHolderStrategy securityContextHolderStrategy) {
|
||||
this.springSecurityFilterChain.setSecurityContextHolderStrategy(securityContextHolderStrategy);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFilterChainValidator(FilterChainValidator filterChainValidator) {
|
||||
this.springSecurityFilterChain.setFilterChainValidator(filterChainValidator);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFilterChainDecorator(FilterChainDecorator filterChainDecorator) {
|
||||
this.springSecurityFilterChain.setFilterChainDecorator(filterChainDecorator);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFirewall(HttpFirewall firewall) {
|
||||
this.springSecurityFilterChain.setFirewall(firewall);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRequestRejectedHandler(RequestRejectedHandler requestRejectedHandler) {
|
||||
this.springSecurityFilterChain.setRequestRejectedHandler(requestRejectedHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used through reflection by Spring Security's Test support to lookup the
|
||||
* FilterChainProxy Filters for a specific HttpServletRequest.
|
||||
* @param request
|
||||
* @return
|
||||
*/
|
||||
private List<? extends Filter> getFilters(HttpServletRequest request) {
|
||||
List<SecurityFilterChain> filterChains = this.springSecurityFilterChain.getFilterChains();
|
||||
for (SecurityFilterChain chain : filterChains) {
|
||||
if (chain.matches(request)) {
|
||||
return chain.getFilters();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the Filter to delegate to for doFilter
|
||||
* @param filters the Filters to delegate to.
|
||||
* @return the Filter for doFilter
|
||||
*/
|
||||
private static Filter createDoFilterDelegate(List<? extends Filter> filters) {
|
||||
CompositeFilter delegate = new CompositeFilter();
|
||||
delegate.setFilters(filters);
|
||||
return delegate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the FilterChainProxy in a List of Filter
|
||||
* @param filters
|
||||
* @return non-null FilterChainProxy
|
||||
* @throws IllegalStateException if the FilterChainProxy cannot be found
|
||||
*/
|
||||
private static FilterChainProxy findFilterChainProxy(List<? extends Filter> filters) {
|
||||
for (Filter filter : filters) {
|
||||
if (filter instanceof FilterChainProxy fcp) {
|
||||
return fcp;
|
||||
}
|
||||
}
|
||||
throw new IllegalStateException("Couldn't find FilterChainProxy in " + filters);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+44
-3
@@ -24,6 +24,7 @@ import jakarta.servlet.ServletRequest;
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
import org.springframework.beans.BeanMetadataElement;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.BeanReference;
|
||||
@@ -36,6 +37,8 @@ import org.springframework.beans.factory.support.ManagedList;
|
||||
import org.springframework.beans.factory.support.ManagedMap;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.beans.factory.xml.ParserContext;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.security.access.vote.AffirmativeBased;
|
||||
import org.springframework.security.access.vote.AuthenticatedVoter;
|
||||
import org.springframework.security.access.vote.RoleVoter;
|
||||
@@ -46,6 +49,7 @@ import org.springframework.security.core.context.SecurityContextHolderStrategy;
|
||||
import org.springframework.security.core.session.SessionRegistryImpl;
|
||||
import org.springframework.security.web.access.AuthorizationManagerWebInvocationPrivilegeEvaluator;
|
||||
import org.springframework.security.web.access.DefaultWebInvocationPrivilegeEvaluator;
|
||||
import org.springframework.security.web.access.HandlerMappingIntrospectorRequestTransformer;
|
||||
import org.springframework.security.web.access.channel.ChannelDecisionManagerImpl;
|
||||
import org.springframework.security.web.access.channel.ChannelProcessingFilter;
|
||||
import org.springframework.security.web.access.channel.InsecureChannelProcessor;
|
||||
@@ -82,6 +86,7 @@ import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.util.xml.DomUtils;
|
||||
import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
|
||||
|
||||
/**
|
||||
* Stateful class which helps HttpSecurityBDP to create the configuration for the
|
||||
@@ -93,6 +98,11 @@ import org.springframework.util.xml.DomUtils;
|
||||
*/
|
||||
class HttpConfigurationBuilder {
|
||||
|
||||
private static final String HANDLER_MAPPING_INTROSPECTOR = "org.springframework.web.servlet.handler.HandlerMappingIntrospector";
|
||||
|
||||
private static final boolean mvcPresent = ClassUtils.isPresent(HANDLER_MAPPING_INTROSPECTOR,
|
||||
HttpConfigurationBuilder.class.getClassLoader());
|
||||
|
||||
private static final String ATT_CREATE_SESSION = "create-session";
|
||||
|
||||
private static final String ATT_SESSION_FIXATION_PROTECTION = "session-fixation-protection";
|
||||
@@ -744,10 +754,14 @@ class HttpConfigurationBuilder {
|
||||
// Create and register a AuthorizationManagerWebInvocationPrivilegeEvaluator for
|
||||
// use with
|
||||
// taglibs etc.
|
||||
BeanDefinition wipe = BeanDefinitionBuilder
|
||||
BeanDefinitionBuilder wipeBldr = BeanDefinitionBuilder
|
||||
.rootBeanDefinition(AuthorizationManagerWebInvocationPrivilegeEvaluator.class)
|
||||
.addConstructorArgReference(authorizationFilterParser.getAuthorizationManagerRef())
|
||||
.getBeanDefinition();
|
||||
.addConstructorArgReference(authorizationFilterParser.getAuthorizationManagerRef());
|
||||
if (mvcPresent) {
|
||||
wipeBldr.addPropertyValue("requestTransformer",
|
||||
new RootBeanDefinition(HandlerMappingIntrospectorRequestTransformerFactoryBean.class));
|
||||
}
|
||||
BeanDefinition wipe = wipeBldr.getBeanDefinition();
|
||||
this.pc.registerBeanComponent(
|
||||
new BeanComponentDefinition(wipe, this.pc.getReaderContext().generateBeanName(wipe)));
|
||||
this.fsi = new RuntimeBeanReference(fsiId);
|
||||
@@ -913,6 +927,33 @@ class HttpConfigurationBuilder {
|
||||
return BeanDefinitionBuilder.rootBeanDefinition(ObservationRegistryFactory.class).getBeanDefinition();
|
||||
}
|
||||
|
||||
static class HandlerMappingIntrospectorRequestTransformerFactoryBean
|
||||
implements FactoryBean<AuthorizationManagerWebInvocationPrivilegeEvaluator.HttpServletRequestTransformer>,
|
||||
ApplicationContextAware {
|
||||
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
@Override
|
||||
public AuthorizationManagerWebInvocationPrivilegeEvaluator.HttpServletRequestTransformer getObject()
|
||||
throws Exception {
|
||||
HandlerMappingIntrospector hmi = this.applicationContext.getBeanProvider(HandlerMappingIntrospector.class)
|
||||
.getIfAvailable();
|
||||
return (hmi != null) ? new HandlerMappingIntrospectorRequestTransformer(hmi)
|
||||
: AuthorizationManagerWebInvocationPrivilegeEvaluator.HttpServletRequestTransformer.IDENTITY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getObjectType() {
|
||||
return AuthorizationManagerWebInvocationPrivilegeEvaluator.HttpServletRequestTransformer.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
this.applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class RoleVoterBeanFactory extends AbstractGrantedAuthorityDefaultsBeanFactory {
|
||||
|
||||
private RoleVoter voter = new RoleVoter();
|
||||
|
||||
+1
-1
@@ -307,7 +307,7 @@ public class AbstractRequestMatcherRegistryTests {
|
||||
List<RequestMatcher> requestMatchers = new ArrayList<>();
|
||||
for (RequestMatcher requestMatcher : wrappedMatchers) {
|
||||
if (requestMatcher instanceof AbstractRequestMatcherRegistry.DeferredRequestMatcher) {
|
||||
requestMatchers.add(((DeferredRequestMatcher) requestMatcher).requestMatcher.get());
|
||||
requestMatchers.add(((DeferredRequestMatcher) requestMatcher).requestMatcher);
|
||||
}
|
||||
else {
|
||||
requestMatchers.add(requestMatcher);
|
||||
|
||||
+2
-2
@@ -20,6 +20,7 @@ import java.io.IOException;
|
||||
|
||||
import io.micrometer.observation.ObservationRegistry;
|
||||
import io.micrometer.observation.ObservationTextPublisher;
|
||||
import jakarta.servlet.Filter;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
@@ -39,7 +40,6 @@ import org.springframework.security.config.annotation.web.configuration.WebSecur
|
||||
import org.springframework.security.core.userdetails.PasswordEncodedUser;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
|
||||
import org.springframework.security.web.FilterChainProxy;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.firewall.HttpStatusRequestRejectedHandler;
|
||||
import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher;
|
||||
@@ -67,7 +67,7 @@ public class WebSecurityTests {
|
||||
MockFilterChain chain;
|
||||
|
||||
@Autowired
|
||||
FilterChainProxy springSecurityFilterChain;
|
||||
Filter springSecurityFilterChain;
|
||||
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.config.annotation.web.configuration;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.authentication.TestAuthentication;
|
||||
import org.springframework.security.config.test.SpringTestContext;
|
||||
import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners;
|
||||
import org.springframework.security.web.access.AuthorizationManagerWebInvocationPrivilegeEvaluator.HttpServletRequestTransformer;
|
||||
import org.springframework.security.web.access.HandlerMappingIntrospectorRequestTransformer;
|
||||
import org.springframework.security.web.access.WebInvocationPrivilegeEvaluator;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
import org.springframework.test.context.web.WebAppConfiguration;
|
||||
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* Checks that HandlerMappingIntrospectorRequestTransformer is autowired into
|
||||
* {@link org.springframework.security.web.access.AuthorizationManagerWebInvocationPrivilegeEvaluator}.
|
||||
*
|
||||
* @author Rob Winch
|
||||
*/
|
||||
@ContextConfiguration
|
||||
@WebAppConfiguration
|
||||
@ExtendWith({ SpringExtension.class })
|
||||
@SecurityTestExecutionListeners
|
||||
public class AuthorizationManagerWebInvocationPrivilegeEvaluatorConfigTests {
|
||||
|
||||
public final SpringTestContext spring = new SpringTestContext(this);
|
||||
|
||||
@Autowired(required = false)
|
||||
HttpServletRequestTransformer requestTransformer;
|
||||
|
||||
@Autowired
|
||||
WebInvocationPrivilegeEvaluator wipe;
|
||||
|
||||
@Test
|
||||
void mvcEnabledConfigThenHandlerMappingIntrospectorRequestTransformerBeanExists() {
|
||||
this.spring.register(MvcEnabledConfig.class).autowire();
|
||||
assertThat(this.requestTransformer).isInstanceOf(HandlerMappingIntrospectorRequestTransformer.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void mvcNotEnabledThenNoRequestTransformerBeanExists() {
|
||||
this.spring.register(MvcNotEnabledConfig.class).autowire();
|
||||
assertThat(this.requestTransformer).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void mvcNotEnabledAndTransformerThenWIPEDelegatesToTransformer() {
|
||||
this.spring.register(MvcNotEnabledConfig.class, TransformerConfig.class).autowire();
|
||||
|
||||
this.wipe.isAllowed("/uri", TestAuthentication.authenticatedUser());
|
||||
|
||||
verify(this.requestTransformer).transform(any());
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class TransformerConfig {
|
||||
|
||||
@Bean
|
||||
HttpServletRequestTransformer httpServletRequestTransformer() {
|
||||
return mock(HttpServletRequestTransformer.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
@EnableWebSecurity
|
||||
static class MvcEnabledConfig {
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
static class MvcNotEnabledConfig {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.config.annotation.web.configuration;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import jakarta.servlet.Filter;
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.ServletRequest;
|
||||
import jakarta.servlet.ServletResponse;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.test.SpringTestContext;
|
||||
import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners;
|
||||
import org.springframework.security.test.context.support.WithMockUser;
|
||||
import org.springframework.stereotype.Component;
|
||||
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.context.WebApplicationContext;
|
||||
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
|
||||
import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
|
||||
import org.springframework.web.servlet.handler.HandlerMappingIntrospector.CachedResult;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
*/
|
||||
@ContextConfiguration
|
||||
@WebAppConfiguration
|
||||
@ExtendWith({ SpringExtension.class })
|
||||
@SecurityTestExecutionListeners
|
||||
class HandlerMappingIntrospectorCacheFilterConfigTests {
|
||||
|
||||
@Autowired
|
||||
WebApplicationContext context;
|
||||
|
||||
MockMvc mockMvc;
|
||||
|
||||
public final SpringTestContext spring = new SpringTestContext(this);
|
||||
|
||||
@Autowired(required = false)
|
||||
MvcEnabledConfig.CaptureHandlerMappingIntrospectorCache captureCacheFilter;
|
||||
|
||||
@Autowired(required = false)
|
||||
HandlerMappingIntrospector hmi;
|
||||
|
||||
@Test
|
||||
@WithMockUser
|
||||
void hmiIsCached() throws Exception {
|
||||
this.spring.register(MvcEnabledConfig.class).autowire();
|
||||
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
|
||||
.apply(springSecurity())
|
||||
.addFilter(this.captureCacheFilter)
|
||||
.build();
|
||||
this.mockMvc.perform(get("/"));
|
||||
assertThat(this.captureCacheFilter.cachedResult).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser
|
||||
void configurationLoadsIfNoHMI() {
|
||||
// no BeanCreationException due to missing HandlerMappingIntrospector
|
||||
this.spring.register(MvcNotEnabledConfig.class).autowire();
|
||||
// ensure assumption of HandlerMappingIntrospector is null is true
|
||||
assertThat(this.hmi).isNull();
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
@EnableWebSecurity
|
||||
static class MvcEnabledConfig {
|
||||
|
||||
@Component
|
||||
static class CaptureHandlerMappingIntrospectorCache implements Filter {
|
||||
|
||||
final HandlerMappingIntrospector hmi;
|
||||
|
||||
private CachedResult cachedResult;
|
||||
|
||||
CaptureHandlerMappingIntrospectorCache(HandlerMappingIntrospector hmi) {
|
||||
this.hmi = hmi;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
|
||||
throws IOException, ServletException {
|
||||
// capture the old cached value to check that caching has already occurred
|
||||
this.cachedResult = this.hmi.setCache((HttpServletRequest) request);
|
||||
chain.doFilter(request, response);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
static class MvcNotEnabledConfig {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+1
-1
@@ -67,7 +67,7 @@ public class AuthorizationEvent extends ApplicationEvent {
|
||||
|
||||
/**
|
||||
* Get the response to the principal's request
|
||||
* @return
|
||||
* @return the response to the principal's request
|
||||
*/
|
||||
public AuthorizationDecision getAuthorizationDecision() {
|
||||
return this.decision;
|
||||
|
||||
+17
-6
@@ -95,17 +95,28 @@ final class AuthorizationAnnotationUtils {
|
||||
|
||||
private static <A extends Annotation> boolean hasDuplicate(MergedAnnotations mergedAnnotations,
|
||||
Class<A> annotationType) {
|
||||
boolean alreadyFound = false;
|
||||
MergedAnnotation<Annotation> alreadyFound = null;
|
||||
for (MergedAnnotation<Annotation> mergedAnnotation : mergedAnnotations) {
|
||||
if (isSynthetic(mergedAnnotation.getSource())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (mergedAnnotation.getType() == annotationType) {
|
||||
if (alreadyFound) {
|
||||
return true;
|
||||
}
|
||||
alreadyFound = true;
|
||||
if (mergedAnnotation.getType() != annotationType) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (alreadyFound == null) {
|
||||
alreadyFound = mergedAnnotation;
|
||||
continue;
|
||||
}
|
||||
|
||||
// https://github.com/spring-projects/spring-framework/issues/31803
|
||||
if (!mergedAnnotation.getSource().equals(alreadyFound.getSource())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (mergedAnnotation.getRoot().getType() != alreadyFound.getRoot().getType()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
+27
@@ -41,6 +41,13 @@ class AuthorizationAnnotationUtilsTests {
|
||||
.isThrownBy(() -> AuthorizationAnnotationUtils.findUniqueAnnotation(method, PreAuthorize.class));
|
||||
}
|
||||
|
||||
@Test // gh-13625
|
||||
void annotationsFromSuperSuperInterfaceShouldNotTriggerAnnotationConfigurationException() throws Exception {
|
||||
Method method = HelloImpl.class.getMethod("sayHello");
|
||||
assertThatNoException()
|
||||
.isThrownBy(() -> AuthorizationAnnotationUtils.findUniqueAnnotation(method, PreAuthorize.class));
|
||||
}
|
||||
|
||||
private interface BaseRepository<T> {
|
||||
|
||||
Iterable<T> findAll();
|
||||
@@ -55,4 +62,24 @@ class AuthorizationAnnotationUtilsTests {
|
||||
|
||||
}
|
||||
|
||||
private interface Hello {
|
||||
|
||||
@PreAuthorize("hasRole('someRole')")
|
||||
String sayHello();
|
||||
|
||||
}
|
||||
|
||||
private interface SayHello extends Hello {
|
||||
|
||||
}
|
||||
|
||||
private static class HelloImpl implements SayHello {
|
||||
|
||||
@Override
|
||||
public String sayHello() {
|
||||
return "hello";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -434,7 +434,7 @@ spring:
|
||||
oauth2:
|
||||
resourceserver:
|
||||
jwt:
|
||||
jws-algorithm: RS512
|
||||
jws-algorithms: RS512
|
||||
jwk-set-uri: https://idp.example.org/.well-known/jwks.json
|
||||
----
|
||||
|
||||
|
||||
@@ -281,7 +281,7 @@ To do that, you can configure your application to <<servlet-logging,log the secu
|
||||
[[adding-custom-filter]]
|
||||
=== Adding a Custom Filter to the Filter Chain
|
||||
|
||||
Mostly of the times, the default security filters are enough to provide security to your application.
|
||||
Most of the time, the default security filters are enough to provide security to your application.
|
||||
However, there might be times that you want to add a custom `Filter` to the security filter chain.
|
||||
|
||||
For example, let's say that you want to add a `Filter` that gets a tenant id header and check if the current user has access to that tenant.
|
||||
@@ -336,7 +336,7 @@ Instead of implementing `Filter`, you can extend from {spring-framework-api-url}
|
||||
====
|
||||
|
||||
Now, we need to add the filter to the security filter chain.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
|
||||
@@ -161,7 +161,7 @@ Of course, Spring Security is expressly designed to handle this common requireme
|
||||
== AuthenticationManager
|
||||
|
||||
{security-api-url}org/springframework/security/authentication/AuthenticationManager.html[`AuthenticationManager`] is the API that defines how Spring Security's Filters perform xref:features/authentication/index.adoc#authentication[authentication].
|
||||
The <<servlet-authentication-authentication,`Authentication`>> that is returned is then set on the <<servlet-authentication-securitycontextholder>> by the controller (that is, by <<servlet-security-filters,Spring Security's `Filters` instances>>) that invoked the `AuthenticationManager`.
|
||||
The <<servlet-authentication-authentication,`Authentication`>> that is returned is then set on the <<servlet-authentication-securitycontextholder>> by the controller (that is, by xref:servlet/architecture.adoc#servlet-security-filters[Spring Security's `Filters` instances]) that invoked the `AuthenticationManager`.
|
||||
If you are not integrating with Spring Security's `Filters` instances, you can set the `SecurityContextHolder` directly and are not required to use an `AuthenticationManager`.
|
||||
|
||||
While the implementation of `AuthenticationManager` could be anything, the most common implementation is <<servlet-authentication-providermanager,`ProviderManager`>>.
|
||||
|
||||
@@ -145,7 +145,7 @@ Many sites allow certain limited access under remember-me authentication, but re
|
||||
|
||||
[[authz-authorization-managers]]
|
||||
==== AuthorizationManagers
|
||||
There are also helpful static factories in `AuthenticationManagers` for composing individual ``AuthenticationManager``s into more sophisticated expressions.
|
||||
There are also helpful static factories in {security-api-url}org/springframework/security/authorization/AuthorizationManagers.html[`AuthorizationManagers`] for composing individual ``AuthorizationManager``s into more sophisticated expressions.
|
||||
|
||||
[[authz-custom-authorization-manager]]
|
||||
==== Custom Authorization Managers
|
||||
|
||||
@@ -749,7 +749,7 @@ SecurityFilterChain web(HttpSecurity http) throws Exception {
|
||||
Each rule is considered in the order they were declared.
|
||||
<2> Dispatches `FORWARD` and `ERROR` are permitted to allow {spring-framework-reference-url}web.html#spring-web[Spring MVC] to render views and Spring Boot to render errors
|
||||
<3> We specified multiple URL patterns that any user can access.
|
||||
Specifically, any user can access a request if the URL starts with "/resources/", equals "/signup", or equals "/about".
|
||||
Specifically, any user can access a request if the URL starts with "/static/", equals "/signup", or equals "/about".
|
||||
<4> Any URL that starts with "/admin/" will be restricted to users who have the role "ROLE_ADMIN".
|
||||
You will notice that since we are invoking the `hasRole` method we do not need to specify the "ROLE_" prefix.
|
||||
<5> Any URL that starts with "/db/" requires the user to have both been granted the "db" permission as well as be a "ROLE_ADMIN".
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
= Authorization Events
|
||||
|
||||
For each authorization that is denied, an `AuthorizationDeniedEvent` is fired.
|
||||
Also, it's possible to fire and `AuthorizationGrantedEvent` for authorizations that are granted.
|
||||
Also, it's possible to fire an `AuthorizationGrantedEvent` for authorizations that are granted.
|
||||
|
||||
To listen for these events, you must first publish an `AuthorizationEventPublisher`.
|
||||
|
||||
|
||||
@@ -1214,9 +1214,9 @@ Kotlin::
|
||||
companion object {
|
||||
@Bean
|
||||
fun methodSecurityExpressionHandler(val roleHierarchy: RoleHierarchy) : MethodSecurityExpressionHandler {
|
||||
val handler = DefaultMethodSecurityExpressionHandler();
|
||||
handler.setRoleHierarchy(roleHierarchy);
|
||||
return handler;
|
||||
val handler = DefaultMethodSecurityExpressionHandler()
|
||||
handler.setRoleHierarchy(roleHierarchy)
|
||||
return handler
|
||||
}
|
||||
}
|
||||
----
|
||||
@@ -1260,14 +1260,14 @@ Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
import static org.springframework.security.authorization.AuthorityAuthorizationManager.hasRole;
|
||||
import static org.springframework.security.authorization.AuthorityAuthorizationManager.hasRole
|
||||
|
||||
@Bean
|
||||
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
static Advisor protectServicePointcut() {
|
||||
JdkRegexpMethodPointcut pattern = new JdkRegexpMethodPointcut();
|
||||
pattern.setPattern("execution(* com.mycompany.*Service.*(..))");
|
||||
return new AuthorizationManagerBeforeMethodInterceptor(pattern, hasRole("USER"));
|
||||
AspectJExpressionPointcut pattern = new AspectJExpressionPointcut()
|
||||
pattern.setExpression("execution(* com.mycompany.*Service.*(..))")
|
||||
return new AuthorizationManagerBeforeMethodInterceptor(pattern, hasRole("USER"))
|
||||
}
|
||||
----
|
||||
|
||||
@@ -1275,26 +1275,28 @@ Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
import static org.springframework.security.authorization.AuthorityAuthorizationManager.hasRole;
|
||||
import static org.springframework.security.authorization.AuthorityAuthorizationManager.hasRole
|
||||
|
||||
companion object {
|
||||
@Bean
|
||||
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
fun protectServicePointcut(): Advisor {
|
||||
var pattern = JdkRegexpMethodPointcut();
|
||||
pattern.setPattern("execution(* com.mycompany.*Service.*(..))");
|
||||
return new AuthorizationManagerBeforeMethodInterceptor(pattern, hasRole("USER"));
|
||||
val pattern = AspectJExpressionPointcut()
|
||||
pattern.setExpression("execution(* com.mycompany.*Service.*(..))")
|
||||
return new AuthorizationManagerBeforeMethodInterceptor(pattern, hasRole("USER"))
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
[source,xml]
|
||||
Xml::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<sec:method-security>
|
||||
<protect-pointcut expression="execution(* com.mycompany.*Service.*(..))" access="hasRole('USER')"/>
|
||||
</sec:method-security>
|
||||
----
|
||||
======
|
||||
|
||||
[[weave-aspectj]]
|
||||
=== Integrate with AspectJ Byte-weaving
|
||||
@@ -1445,7 +1447,7 @@ open class MyService {
|
||||
fun readResource(...): MyResource
|
||||
|
||||
@PreAuthorize("@authz.check(#root)")
|
||||
fun shareResource(...): MyResource;
|
||||
fun shareResource(...): MyResource
|
||||
}
|
||||
----
|
||||
|
||||
@@ -1769,8 +1771,8 @@ class MyExpressionHandler: DefaultMethodSecurityExpressionHandler {
|
||||
val context = super.createEvaluationContext(authentication, mi) as StandardEvaluationContext
|
||||
val delegate = context.getRootObject().getValue() as MethodSecurityExpressionOperations
|
||||
val root = MySecurityExpressionRoot(delegate)
|
||||
context.setRootObject(root);
|
||||
return context;
|
||||
context.setRootObject(root)
|
||||
return context
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
@@ -114,7 +114,7 @@ public class SecurityWebApplicationInitializer
|
||||
|
||||
This onlys register the `springSecurityFilterChain` for every URL in your application.
|
||||
After that, we need to ensure that `WebSecurityConfig` was loaded in our existing `ApplicationInitializer`.
|
||||
For example, if we use Spring MVC it is added in the `getRootConfigClasses()`:
|
||||
For example, if we use Spring MVC it is added in the `getServletConfigClasses()`:
|
||||
|
||||
[[message-web-application-inititializer-java]]
|
||||
[source,java]
|
||||
@@ -123,14 +123,42 @@ public class MvcWebApplicationInitializer extends
|
||||
AbstractAnnotationConfigDispatcherServletInitializer {
|
||||
|
||||
@Override
|
||||
protected Class<?>[] getRootConfigClasses() {
|
||||
return new Class[] { WebSecurityConfig.class };
|
||||
protected Class<?>[] getServletConfigClasses() {
|
||||
return new Class[] { WebSecurityConfig.class, WebMvcConfig.class };
|
||||
}
|
||||
|
||||
// ... other overrides ...
|
||||
}
|
||||
----
|
||||
|
||||
The reason for this is that Spring Security needs to be able to inspect some Spring MVC configuration in order to appropriately configure xref:servlet/authorization/authorize-http-requests.adoc#_request_matchers[underlying request matchers], so they need to be in the same application context.
|
||||
Placing Spring Security in `getRootConfigClasses` places it into a parent application context that may not be able to find Spring MVC's `HandlerMappingIntrospector`.
|
||||
|
||||
==== Configuring for Multiple Spring MVC Dispatchers
|
||||
|
||||
If desired, any Spring Security configuration that is unrelated to Spring MVC may be placed in a different configuration class like so:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
public class MvcWebApplicationInitializer extends
|
||||
AbstractAnnotationConfigDispatcherServletInitializer {
|
||||
|
||||
@Override
|
||||
protected Class<?>[] getRootConfigClasses() {
|
||||
return new Class[] { NonWebSecurityConfig.class };
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class<?>[] getServletConfigClasses() {
|
||||
return new Class[] { WebSecurityConfig.class, WebMvcConfig.class };
|
||||
}
|
||||
|
||||
// ... other overrides ...
|
||||
}
|
||||
----
|
||||
|
||||
This can be helpful if you have multiple instances of `AbstractAnnotationConfigDispatcherServletInitializer` and don't want to duplicate the general security configuration across both of them.
|
||||
|
||||
[[jc-httpsecurity]]
|
||||
== HttpSecurity
|
||||
|
||||
|
||||
@@ -574,7 +574,7 @@ spring:
|
||||
oauth2:
|
||||
resourceserver:
|
||||
jwt:
|
||||
jws-algorithm: RS512
|
||||
jws-algorithms: RS512
|
||||
jwk-set-uri: https://idp.example.org/.well-known/jwks.json
|
||||
----
|
||||
|
||||
|
||||
@@ -63,7 +63,42 @@ image:{icondir}/number_4.png[] If authentication is successful, then _Success_.
|
||||
== Minimal Dependencies
|
||||
|
||||
SAML 2.0 service provider support resides in `spring-security-saml2-service-provider`.
|
||||
It builds off of the OpenSAML library.
|
||||
It builds off of the OpenSAML library, and, for that reason, you must also include the Shibboleth Maven repository in your build configuration.
|
||||
Check https://shibboleth.atlassian.net/wiki/spaces/DEV/pages/1123844333/Use+of+Maven+Central#Publishing-to-Maven-Central[this link] for more details about why a separate repository is needed.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Maven::
|
||||
+
|
||||
[source,xml,role="primary"]
|
||||
----
|
||||
<repositories>
|
||||
<!-- ... -->
|
||||
<repository>
|
||||
<id>shibboleth-releases</id>
|
||||
<url>https://build.shibboleth.net/nexus/content/repositories/releases/</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-saml2-service-provider</artifactId>
|
||||
</dependency>
|
||||
----
|
||||
|
||||
Gradle::
|
||||
+
|
||||
[source,groovy,role="secondary"]
|
||||
----
|
||||
repositories {
|
||||
// ...
|
||||
maven { url "https://build.shibboleth.net/nexus/content/repositories/releases/" }
|
||||
}
|
||||
dependencies {
|
||||
// ...
|
||||
implementation 'org.springframework.security:spring-security-saml2-service-provider'
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
[[servlet-saml2login-minimalconfiguration]]
|
||||
== Minimal Configuration
|
||||
|
||||
@@ -9,7 +9,7 @@ Below are the highlights of the release.
|
||||
* https://github.com/spring-projects/spring-security/issues/5011[gh-5011] - xref:servlet/integrations/cors.adoc[(docs)] Automatically enable `.cors()` if `CorsConfigurationSource` bean is present
|
||||
* https://github.com/spring-projects/spring-security/issues/13204[gh-13204] - xref:migration-7/configuration.adoc#_use_with_instead_of_apply_for_custom_dsls[(docs)] Add `AbstractConfiguredSecurityBuilder.with(...)` method to apply configurers returning the builder
|
||||
* https://github.com/spring-projects/spring-security/pull/13587[gh-13587] - https://spring.io/blog/2023/08/22/tackling-the-oauth2-client-component-model-in-spring-security/[blog post] Simplify configuration of OAuth2 Client component model
|
||||
* https://github.com/spring-projects/spring-security/pull/13857[gh-13857] - xref:servlet/authorization/authorize-http-requests.adoc#match-by-mvc[docs] Add servlet pattern support to AuthorizeHttpRequests
|
||||
* https://github.com/spring-projects/spring-security/issues/13666[gh-13666], https://github.com/spring-projects/spring-security/pull/13667[gh-13667], https://github.com/spring-projects/spring-security/issues/13726[gh-13726], https://github.com/spring-projects/spring-security/issues/13850[gh-13850] - xref:servlet/authorization/authorize-http-requests.adoc#match-by-mvc[docs] Improved CVE-2023-34035 detection
|
||||
|
||||
== OAuth 2.0/OIDC
|
||||
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
springBootVersion=3.1.1
|
||||
version=6.2.0
|
||||
samplesBranch=main
|
||||
version=6.2.1
|
||||
samplesBranch=6.2.x
|
||||
org.gradle.jvmargs=-Xmx3g -XX:+HeapDumpOnOutOfMemoryError
|
||||
org.gradle.parallel=true
|
||||
org.gradle.caching=true
|
||||
|
||||
+13
-14
@@ -5,18 +5,18 @@ io-spring-javaformat = "0.0.40"
|
||||
io-spring-nohttp = "0.0.11"
|
||||
jakarta-websocket = "2.1.1"
|
||||
org-apache-directory-server = "1.5.5"
|
||||
org-apache-maven-resolver = "1.9.16"
|
||||
org-aspectj = "1.9.20.1"
|
||||
org-apache-maven-resolver = "1.9.18"
|
||||
org-aspectj = "1.9.21"
|
||||
org-bouncycastle = "1.70"
|
||||
org-eclipse-jetty = "11.0.18"
|
||||
org-jetbrains-kotlin = "1.9.20"
|
||||
org-jetbrains-kotlin = "1.9.21"
|
||||
org-jetbrains-kotlinx = "1.7.3"
|
||||
org-mockito = "5.5.0"
|
||||
org-opensaml = "4.3.0"
|
||||
org-springframework = "6.1.0"
|
||||
org-springframework = "6.1.2"
|
||||
|
||||
[libraries]
|
||||
ch-qos-logback-logback-classic = "ch.qos.logback:logback-classic:1.4.11"
|
||||
ch-qos-logback-logback-classic = "ch.qos.logback:logback-classic:1.4.14"
|
||||
com-fasterxml-jackson-jackson-bom = "com.fasterxml.jackson:jackson-bom:2.15.3"
|
||||
com-google-inject-guice = "com.google.inject:guice:3.0"
|
||||
com-netflix-nebula-nebula-project-plugin = "com.netflix.nebula:nebula-project-plugin:8.2.0"
|
||||
@@ -24,11 +24,11 @@ com-nimbusds-nimbus-jose-jwt = "com.nimbusds:nimbus-jose-jwt:9.24.4"
|
||||
com-nimbusds-oauth2-oidc-sdk = "com.nimbusds:oauth2-oidc-sdk:9.43.3"
|
||||
com-squareup-okhttp3-mockwebserver = { module = "com.squareup.okhttp3:mockwebserver", version.ref = "com-squareup-okhttp3" }
|
||||
com-squareup-okhttp3-okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "com-squareup-okhttp3" }
|
||||
com-unboundid-unboundid-ldapsdk = "com.unboundid:unboundid-ldapsdk:6.0.10"
|
||||
com-unboundid-unboundid-ldapsdk = "com.unboundid:unboundid-ldapsdk:6.0.11"
|
||||
commons-collections = "commons-collections:commons-collections:3.2.2"
|
||||
io-micrometer-micrometer-observation = "io.micrometer:micrometer-observation:1.12.0"
|
||||
io-micrometer-micrometer-observation = "io.micrometer:micrometer-observation:1.12.1"
|
||||
io-mockk = "io.mockk:mockk:1.13.8"
|
||||
io-projectreactor-reactor-bom = "io.projectreactor:reactor-bom:2023.0.0"
|
||||
io-projectreactor-reactor-bom = "io.projectreactor:reactor-bom:2023.0.1"
|
||||
io-rsocket-rsocket-bom = { module = "io.rsocket:rsocket-bom", version.ref = "io-rsocket" }
|
||||
io-spring-javaformat-spring-javaformat-checkstyle = { module = "io.spring.javaformat:spring-javaformat-checkstyle", version.ref = "io-spring-javaformat" }
|
||||
io-spring-javaformat-spring-javaformat-gradle-plugin = { module = "io.spring.javaformat:spring-javaformat-gradle-plugin", version.ref = "io-spring-javaformat" }
|
||||
@@ -52,7 +52,7 @@ org-apache-directory-server-apacheds-protocol-shared = { module = "org.apache.di
|
||||
org-apache-directory-server-apacheds-server-jndi = { module = "org.apache.directory.server:apacheds-server-jndi", version.ref = "org-apache-directory-server" }
|
||||
org-apache-directory-shared-shared-ldap = "org.apache.directory.shared:shared-ldap:0.9.15"
|
||||
org-apache-httpcomponents-httpclient = "org.apache.httpcomponents:httpclient:4.5.14"
|
||||
org-apache-maven-maven-resolver-provider = "org.apache.maven:maven-resolver-provider:3.9.5"
|
||||
org-apache-maven-maven-resolver-provider = "org.apache.maven:maven-resolver-provider:3.9.6"
|
||||
org-apache-maven-resolver-maven-resolver-connector-basic = { module = "org.apache.maven.resolver:maven-resolver-connector-basic", version.ref = "org-apache-maven-resolver" }
|
||||
org-apache-maven-resolver-maven-resolver-impl = { module = "org.apache.maven.resolver:maven-resolver-impl", version.ref = "org-apache-maven-resolver" }
|
||||
org-apache-maven-resolver-maven-resolver-transport-http = { module = "org.apache.maven.resolver:maven-resolver-transport-http", version.ref = "org-apache-maven-resolver" }
|
||||
@@ -66,10 +66,10 @@ org-bouncycastle-bcprov-jdk15on = { module = "org.bouncycastle:bcprov-jdk15on",
|
||||
org-eclipse-jetty-jetty-server = { module = "org.eclipse.jetty:jetty-server", version.ref = "org-eclipse-jetty" }
|
||||
org-eclipse-jetty-jetty-servlet = { module = "org.eclipse.jetty:jetty-servlet", version.ref = "org-eclipse-jetty" }
|
||||
org-hamcrest = "org.hamcrest:hamcrest:2.2"
|
||||
org-hibernate-orm-hibernate-core = "org.hibernate.orm:hibernate-core:6.3.1.Final"
|
||||
org-hibernate-orm-hibernate-core = "org.hibernate.orm:hibernate-core:6.3.2.Final"
|
||||
org-hsqldb = "org.hsqldb:hsqldb:2.7.2"
|
||||
org-jetbrains-kotlin-kotlin-bom = { module = "org.jetbrains.kotlin:kotlin-bom", version.ref = "org-jetbrains-kotlin" }
|
||||
org-jetbrains-kotlin-kotlin-gradle-plugin = "org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.20"
|
||||
org-jetbrains-kotlin-kotlin-gradle-plugin = "org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.21"
|
||||
org-jetbrains-kotlinx-kotlinx-coroutines-bom = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-bom", version.ref = "org-jetbrains-kotlinx" }
|
||||
org-junit-junit-bom = "org.junit:junit-bom:5.10.1"
|
||||
org-mockito-mockito-bom = { module = "org.mockito:mockito-bom", version.ref = "org-mockito" }
|
||||
@@ -83,8 +83,8 @@ org-seleniumhq-selenium-selenium-support = "org.seleniumhq.selenium:selenium-sup
|
||||
org-skyscreamer-jsonassert = "org.skyscreamer:jsonassert:1.5.1"
|
||||
org-slf4j-log4j-over-slf4j = "org.slf4j:log4j-over-slf4j:1.7.36"
|
||||
org-slf4j-slf4j-api = "org.slf4j:slf4j-api:2.0.9"
|
||||
org-springframework-data-spring-data-bom = "org.springframework.data:spring-data-bom:2023.1.0"
|
||||
org-springframework-ldap-spring-ldap-core = "org.springframework.ldap:spring-ldap-core:3.2.0"
|
||||
org-springframework-data-spring-data-bom = "org.springframework.data:spring-data-bom:2023.1.1"
|
||||
org-springframework-ldap-spring-ldap-core = "org.springframework.ldap:spring-ldap-core:3.2.1"
|
||||
org-springframework-spring-framework-bom = { module = "org.springframework:spring-framework-bom", version.ref = "org-springframework" }
|
||||
org-synchronoss-cloud-nio-multipart-parser = "org.synchronoss.cloud:nio-multipart-parser:1.1.0"
|
||||
|
||||
@@ -95,7 +95,6 @@ org-yaml-snakeyaml = "org.yaml:snakeyaml:1.30"
|
||||
org-apache-commons-commons-io = "org.apache.commons:commons-io:1.3.2"
|
||||
io-github-gradle-nexus-publish-plugin = "io.github.gradle-nexus:publish-plugin:1.1.0"
|
||||
org-gretty-gretty = "org.gretty:gretty:4.0.3"
|
||||
com-apollographql-apollo-apollo-runtime = "com.apollographql.apollo:apollo-runtime:2.4.5"
|
||||
com-github-ben-manes-gradle-versions-plugin = "com.github.ben-manes:gradle-versions-plugin:0.38.0"
|
||||
com-github-spullara-mustache-java-compiler = "com.github.spullara.mustache.java:compiler:0.9.11"
|
||||
org-hidetake-gradle-ssh-plugin = "org.hidetake:gradle-ssh-plugin:2.10.1"
|
||||
|
||||
Vendored
BIN
Binary file not shown.
+2
-2
@@ -1,7 +1,7 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionSha256Sum=3e1af3ae886920c3ac87f7a91f816c0c7c436f276a6eefdb3da152100fef72ae
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
|
||||
distributionSha256Sum=9d926787066a081739e8200858338b4a69e837c3a821a33aca9db09dd4a41026
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
+1
-1
@@ -134,7 +134,7 @@ public interface ClaimAccessor {
|
||||
* @param claim the name of the claim
|
||||
* @return the claim value or {@code null} if the claim does not exist
|
||||
* @throws IllegalArgumentException if the claim value cannot be converted to a
|
||||
* {@code List}
|
||||
* {@code Map}
|
||||
* @throws NullPointerException if the claim value is {@code null}
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
|
||||
+3
@@ -145,6 +145,9 @@ public final class NimbusJwtDecoder implements JwtDecoder {
|
||||
}
|
||||
catch (Exception ex) {
|
||||
this.logger.trace("Failed to parse token", ex);
|
||||
if (ex instanceof ParseException) {
|
||||
throw new BadJwtException(String.format(DECODING_ERROR_MESSAGE_TEMPLATE, "Malformed token"), ex);
|
||||
}
|
||||
throw new BadJwtException(String.format(DECODING_ERROR_MESSAGE_TEMPLATE, ex.getMessage()), ex);
|
||||
}
|
||||
}
|
||||
|
||||
+8
@@ -100,6 +100,8 @@ public class NimbusJwtDecoderTests {
|
||||
|
||||
private static final String JWK_SET = "{\"keys\":[{\"kty\":\"RSA\",\"e\":\"AQAB\",\"use\":\"sig\",\"kid\":\"one\",\"n\":\"oXJ8OyOv_eRnce4akdanR4KYRfnC2zLV4uYNQpcFn6oHL0dj7D6kxQmsXoYgJV8ZVDn71KGmuLvolxsDncc2UrhyMBY6DVQVgMSVYaPCTgW76iYEKGgzTEw5IBRQL9w3SRJWd3VJTZZQjkXef48Ocz06PGF3lhbz4t5UEZtdF4rIe7u-977QwHuh7yRPBQ3sII-cVoOUMgaXB9SHcGF2iZCtPzL_IffDUcfhLQteGebhW8A6eUHgpD5A1PQ-JCw_G7UOzZAjjDjtNM2eqm8j-Ms_gqnm4MiCZ4E-9pDN77CAAPVN7kuX6ejs9KBXpk01z48i9fORYk9u7rAkh1HuQw\"}]}";
|
||||
|
||||
private static final String MALFORMED_TOKEN = "eyJhbGciOiJSUzI1NiJ9.eyJuYmYiOnt9LCJleHAiOjQ2ODQyMjUwODd9";
|
||||
|
||||
private static final String NEW_KID_JWK_SET = "{\"keys\":[{\"kty\":\"RSA\",\"e\":\"AQAB\",\"kid\":\"two\",\"n\":\"ra9UJw4I0fCHuOqr1xWJsh-qcVeZWtKEU3uoqq1sAg5fG67dujNCm_Q16yuO0ZdDiU0vlJkbc_MXFAvm4ZxdJ_qR7PAneV-BOGNtLpSaiPclscCy3m7zjRWkaqwt9ZZEsdK5UqXyPlBpcYhNKsmnQGjnX4sYb7d8b2jSCM_qto48-6451rbyEhXXywtFy_JqtTpbsw_IIdQHMr1O-MdSjsQxX9kkvZwPU8LsC-CcqlcsZ7mnpOhmIXaf4tbRwAaluXwYft0yykFsp8e5C4t9mMs9Vu8AB5gT8o-D_ovXd2qh4k3ejzVpYLtzD4nbfvPJA_TXmjhn-9GOPAqkzfON2Q\"}]}";
|
||||
|
||||
private static final String MALFORMED_JWK_SET = "malformed";
|
||||
@@ -195,6 +197,12 @@ public class NimbusJwtDecoderTests {
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decodeWhenTokenMalformedThenReturnsMalformedTokenMessage() {
|
||||
assertThatExceptionOfType(BadJwtException.class).isThrownBy(() -> this.jwtDecoder.decode(MALFORMED_TOKEN))
|
||||
.withMessage("An error occurred while attempting to decode the Jwt: Malformed token");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decodeWhenJwtFailsValidationThenReturnsCorrespondingErrorMessage() {
|
||||
OAuth2Error failure = new OAuth2Error("mock-error", "mock-description", "mock-uri");
|
||||
|
||||
+34
-2
@@ -40,6 +40,8 @@ public final class AuthorizationManagerWebInvocationPrivilegeEvaluator
|
||||
|
||||
private ServletContext servletContext;
|
||||
|
||||
private HttpServletRequestTransformer requestTransformer = HttpServletRequestTransformer.IDENTITY;
|
||||
|
||||
public AuthorizationManagerWebInvocationPrivilegeEvaluator(
|
||||
AuthorizationManager<HttpServletRequest> authorizationManager) {
|
||||
Assert.notNull(authorizationManager, "authorizationManager cannot be null");
|
||||
@@ -54,8 +56,8 @@ public final class AuthorizationManagerWebInvocationPrivilegeEvaluator
|
||||
@Override
|
||||
public boolean isAllowed(String contextPath, String uri, String method, Authentication authentication) {
|
||||
FilterInvocation filterInvocation = new FilterInvocation(contextPath, uri, method, this.servletContext);
|
||||
AuthorizationDecision decision = this.authorizationManager.check(() -> authentication,
|
||||
filterInvocation.getHttpRequest());
|
||||
HttpServletRequest httpRequest = this.requestTransformer.transform(filterInvocation.getHttpRequest());
|
||||
AuthorizationDecision decision = this.authorizationManager.check(() -> authentication, httpRequest);
|
||||
return decision == null || decision.isGranted();
|
||||
}
|
||||
|
||||
@@ -64,4 +66,34 @@ public final class AuthorizationManagerWebInvocationPrivilegeEvaluator
|
||||
this.servletContext = servletContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a {@link HttpServletRequestTransformer} to be used prior to passing to the
|
||||
* {@link AuthorizationManager}.
|
||||
* @param requestTransformer the {@link HttpServletRequestTransformer} to use.
|
||||
*/
|
||||
public void setRequestTransformer(HttpServletRequestTransformer requestTransformer) {
|
||||
Assert.notNull(requestTransformer, "requestTransformer cannot be null");
|
||||
this.requestTransformer = requestTransformer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to transform the {@link HttpServletRequest} prior to passing it into the
|
||||
* {@link AuthorizationManager}.
|
||||
*/
|
||||
public interface HttpServletRequestTransformer {
|
||||
|
||||
HttpServletRequestTransformer IDENTITY = (request) -> request;
|
||||
|
||||
/**
|
||||
* Return the {@link HttpServletRequest} that is passed into the
|
||||
* {@link AuthorizationManager}
|
||||
* @param request the {@link HttpServletRequest} created by the
|
||||
* {@link WebInvocationPrivilegeEvaluator}
|
||||
* @return the {@link HttpServletRequest} that is passed into the
|
||||
* {@link AuthorizationManager}
|
||||
*/
|
||||
HttpServletRequest transform(HttpServletRequest request);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.web.access;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import jakarta.servlet.DispatcherType;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletRequestWrapper;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
|
||||
|
||||
/**
|
||||
* Transforms by passing it into
|
||||
* {@link HandlerMappingIntrospector#setCache(HttpServletRequest)}. Before, it wraps the
|
||||
* {@link HttpServletRequest} to ensure that the methods needed work since some methods by
|
||||
* default throw {@link UnsupportedOperationException}.
|
||||
*
|
||||
* @author Rob Winch
|
||||
*/
|
||||
public class HandlerMappingIntrospectorRequestTransformer
|
||||
implements AuthorizationManagerWebInvocationPrivilegeEvaluator.HttpServletRequestTransformer {
|
||||
|
||||
private final HandlerMappingIntrospector introspector;
|
||||
|
||||
public HandlerMappingIntrospectorRequestTransformer(HandlerMappingIntrospector introspector) {
|
||||
Assert.notNull(introspector, "introspector canot be null");
|
||||
this.introspector = introspector;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpServletRequest transform(HttpServletRequest request) {
|
||||
CacheableRequestWrapper cacheableRequest = new CacheableRequestWrapper(request);
|
||||
this.introspector.setCache(cacheableRequest);
|
||||
return cacheableRequest;
|
||||
}
|
||||
|
||||
static final class CacheableRequestWrapper extends HttpServletRequestWrapper {
|
||||
|
||||
private final Map<String, Object> attributes = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Constructs a request object wrapping the given request.
|
||||
* @param request the {@link HttpServletRequest} to be wrapped.
|
||||
* @throws IllegalArgumentException if the request is null
|
||||
*/
|
||||
CacheableRequestWrapper(HttpServletRequest request) {
|
||||
super(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DispatcherType getDispatcherType() {
|
||||
return DispatcherType.REQUEST;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Enumeration<String> getAttributeNames() {
|
||||
return Collections.enumeration(this.attributes.keySet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getAttribute(String name) {
|
||||
return this.attributes.get(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAttribute(String name, Object o) {
|
||||
this.attributes.put(name, o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAttribute(String name) {
|
||||
this.attributes.remove(name);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+22
@@ -25,14 +25,17 @@ import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockServletContext;
|
||||
import org.springframework.security.authentication.TestAuthentication;
|
||||
import org.springframework.security.authorization.AuthorizationDecision;
|
||||
import org.springframework.security.authorization.AuthorizationManager;
|
||||
import org.springframework.security.web.access.AuthorizationManagerWebInvocationPrivilegeEvaluator.HttpServletRequestTransformer;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
@@ -45,6 +48,9 @@ class AuthorizationManagerWebInvocationPrivilegeEvaluatorTests {
|
||||
@Mock
|
||||
private AuthorizationManager<HttpServletRequest> authorizationManager;
|
||||
|
||||
@Mock
|
||||
private HttpServletRequestTransformer requestTransformer;
|
||||
|
||||
@Test
|
||||
void constructorWhenAuthorizationManagerNullThenIllegalArgument() {
|
||||
assertThatIllegalArgumentException()
|
||||
@@ -84,4 +90,20 @@ class AuthorizationManagerWebInvocationPrivilegeEvaluatorTests {
|
||||
assertThat(captor.getValue().getServletContext()).isSameAs(servletContext);
|
||||
}
|
||||
|
||||
@Test
|
||||
void setRequestTransformerWhenNullThenIllegalArgumentException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> this.privilegeEvaluator.setRequestTransformer(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void isAllowedWhenRequestTransformerThenUsesRequestTransformerResult() {
|
||||
HttpServletRequest request = new MockHttpServletRequest();
|
||||
given(this.requestTransformer.transform(any())).willReturn(request);
|
||||
this.privilegeEvaluator.setRequestTransformer(this.requestTransformer);
|
||||
|
||||
this.privilegeEvaluator.isAllowed("/test", TestAuthentication.authenticatedUser());
|
||||
|
||||
verify(this.authorizationManager).check(any(), eq(request));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+206
@@ -0,0 +1,206 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.web.access;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import jakarta.servlet.DispatcherType;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.assertj.core.api.AssertionsForClassTypes;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class HandlerMappingIntrospectorRequestTransformerTests {
|
||||
|
||||
@Mock
|
||||
HandlerMappingIntrospector hmi;
|
||||
|
||||
HandlerMappingIntrospectorRequestTransformer transformer;
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
this.transformer = new HandlerMappingIntrospectorRequestTransformer(this.hmi);
|
||||
}
|
||||
|
||||
@Test
|
||||
void constructorWhenHmiIsNullThenIllegalArgumentException() {
|
||||
AssertionsForClassTypes.assertThatExceptionOfType(IllegalArgumentException.class)
|
||||
.isThrownBy(() -> new HandlerMappingIntrospectorRequestTransformer(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void transformThenNewRequestPassedToSetCache() {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
|
||||
HttpServletRequest transformedRequest = this.transformer.transform(request);
|
||||
|
||||
ArgumentCaptor<HttpServletRequest> requestArg = ArgumentCaptor.forClass(HttpServletRequest.class);
|
||||
verify(this.hmi).setCache(requestArg.capture());
|
||||
assertThat(transformedRequest).isNotEqualTo(request);
|
||||
}
|
||||
|
||||
@Test
|
||||
void transformThenResultPassedToSetCache() {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
|
||||
HttpServletRequest transformedRequest = this.transformer.transform(request);
|
||||
|
||||
ArgumentCaptor<HttpServletRequest> requestArg = ArgumentCaptor.forClass(HttpServletRequest.class);
|
||||
verify(this.hmi).setCache(requestArg.capture());
|
||||
assertThat(requestArg.getValue()).isEqualTo(transformedRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* The request passed into the transformer does not allow interactions on certain
|
||||
* methods, we need to ensure that the methods used by
|
||||
* {@link HandlerMappingIntrospector#setCache(HttpServletRequest)} are overridden.
|
||||
*/
|
||||
@Test
|
||||
void transformThenResultDoesNotDelegateToSetAttribute() {
|
||||
HttpServletRequest request = mock(HttpServletRequest.class);
|
||||
|
||||
this.transformer.transform(request);
|
||||
|
||||
ArgumentCaptor<HttpServletRequest> requestArg = ArgumentCaptor.forClass(HttpServletRequest.class);
|
||||
verify(this.hmi).setCache(requestArg.capture());
|
||||
HttpServletRequest transformedRequest = requestArg.getValue();
|
||||
String attrName = "any";
|
||||
String attrValue = "value";
|
||||
transformedRequest.setAttribute(attrName, attrValue);
|
||||
verifyNoInteractions(request);
|
||||
assertThat(transformedRequest.getAttribute(attrName)).isEqualTo(attrValue);
|
||||
}
|
||||
|
||||
@Test
|
||||
void transformThenSetAttributeWorks() {
|
||||
HttpServletRequest request = mock(HttpServletRequest.class);
|
||||
|
||||
this.transformer.transform(request);
|
||||
|
||||
ArgumentCaptor<HttpServletRequest> requestArg = ArgumentCaptor.forClass(HttpServletRequest.class);
|
||||
verify(this.hmi).setCache(requestArg.capture());
|
||||
HttpServletRequest transformedRequest = requestArg.getValue();
|
||||
String attrName = "any";
|
||||
String attrValue = "value";
|
||||
transformedRequest.setAttribute(attrName, attrValue);
|
||||
assertThat(transformedRequest.getAttribute(attrName)).isEqualTo(attrValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* The request passed into the transformer does not allow interactions on certain
|
||||
* methods, we need to ensure that the methods used by
|
||||
* {@link HandlerMappingIntrospector#setCache(HttpServletRequest)} are overridden.
|
||||
*/
|
||||
@Test
|
||||
void transformThenResultDoesNotDelegateToGetAttribute() {
|
||||
HttpServletRequest request = mock(HttpServletRequest.class);
|
||||
|
||||
this.transformer.transform(request);
|
||||
|
||||
ArgumentCaptor<HttpServletRequest> requestArg = ArgumentCaptor.forClass(HttpServletRequest.class);
|
||||
verify(this.hmi).setCache(requestArg.capture());
|
||||
HttpServletRequest transformedRequest = requestArg.getValue();
|
||||
transformedRequest.getAttribute("any");
|
||||
verifyNoInteractions(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* The request passed into the transformer does not allow interactions on certain
|
||||
* methods, we need to ensure that the methods used by
|
||||
* {@link HandlerMappingIntrospector#setCache(HttpServletRequest)} are overridden.
|
||||
*/
|
||||
@Test
|
||||
void transformThenResultDoesNotDelegateToGetAttributeNames() {
|
||||
HttpServletRequest request = mock(HttpServletRequest.class);
|
||||
|
||||
this.transformer.transform(request);
|
||||
|
||||
ArgumentCaptor<HttpServletRequest> requestArg = ArgumentCaptor.forClass(HttpServletRequest.class);
|
||||
verify(this.hmi).setCache(requestArg.capture());
|
||||
HttpServletRequest transformedRequest = requestArg.getValue();
|
||||
transformedRequest.getAttributeNames();
|
||||
verifyNoInteractions(request);
|
||||
}
|
||||
|
||||
@Test
|
||||
void transformThenGetAttributeNamesWorks() {
|
||||
HttpServletRequest request = mock(HttpServletRequest.class);
|
||||
|
||||
this.transformer.transform(request);
|
||||
|
||||
ArgumentCaptor<HttpServletRequest> requestArg = ArgumentCaptor.forClass(HttpServletRequest.class);
|
||||
verify(this.hmi).setCache(requestArg.capture());
|
||||
HttpServletRequest transformedRequest = requestArg.getValue();
|
||||
String attrName = "any";
|
||||
String attrValue = "value";
|
||||
transformedRequest.setAttribute(attrName, attrValue);
|
||||
assertThat(Collections.list(transformedRequest.getAttributeNames())).containsExactly(attrName);
|
||||
}
|
||||
|
||||
/**
|
||||
* The request passed into the transformer does not allow interactions on certain
|
||||
* methods, we need to ensure that the methods used by
|
||||
* {@link HandlerMappingIntrospector#setCache(HttpServletRequest)} are overridden.
|
||||
*/
|
||||
@Test
|
||||
void transformThenResultDoesNotDelegateToRemoveAttribute() {
|
||||
HttpServletRequest request = mock(HttpServletRequest.class);
|
||||
|
||||
this.transformer.transform(request);
|
||||
|
||||
ArgumentCaptor<HttpServletRequest> requestArg = ArgumentCaptor.forClass(HttpServletRequest.class);
|
||||
verify(this.hmi).setCache(requestArg.capture());
|
||||
HttpServletRequest transformedRequest = requestArg.getValue();
|
||||
transformedRequest.removeAttribute("any");
|
||||
verifyNoInteractions(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* The request passed into the transformer does not allow interactions on certain
|
||||
* methods, we need to ensure that the methods used by
|
||||
* {@link HandlerMappingIntrospector#setCache(HttpServletRequest)} are overridden.
|
||||
*/
|
||||
@Test
|
||||
void transformThenResultDoesNotDelegateToGetDispatcherType() {
|
||||
HttpServletRequest request = mock(HttpServletRequest.class);
|
||||
|
||||
this.transformer.transform(request);
|
||||
|
||||
ArgumentCaptor<HttpServletRequest> requestArg = ArgumentCaptor.forClass(HttpServletRequest.class);
|
||||
verify(this.hmi).setCache(requestArg.capture());
|
||||
HttpServletRequest transformedRequest = requestArg.getValue();
|
||||
assertThat(transformedRequest.getDispatcherType()).isEqualTo(DispatcherType.REQUEST);
|
||||
verifyNoInteractions(request);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user