Compare commits
55 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8b9beb0e1f | |||
| 6230806d56 | |||
| 2159f3a93d | |||
| dc366cf8da | |||
| d32202bce3 | |||
| 349ac07c10 | |||
| 59be5553a1 | |||
| 2bfdb0c6cd | |||
| 2ed7b857c3 | |||
| a0b1d6317c | |||
| 731cd6131b | |||
| c2a589734c | |||
| e5e67f91e9 | |||
| ccb2f06d0d | |||
| fc658d10d3 | |||
| e0fc8f37b0 | |||
| 43ced5291b | |||
| 56c5fc281e | |||
| 7c3a6a567e | |||
| 07e0b1dc37 | |||
| 05d3c4b695 | |||
| 5f80cfc705 | |||
| 7011930305 | |||
| 8a75382b2d | |||
| ca10187fd1 | |||
| 56f486588f | |||
| 3f5f79d835 | |||
| 9985580534 | |||
| 24d4abe5fd | |||
| 16dc6be3c8 | |||
| c88aaedb48 | |||
| 9203567a20 | |||
| e058b559b8 | |||
| 33800c0124 | |||
| b71962e87d | |||
| b97c310aba | |||
| 59461d94b0 | |||
| fc007aa373 | |||
| eaaa813ede | |||
| 10b3a9b234 | |||
| e4cc0a4755 | |||
| ff71ef46b7 | |||
| 5593655bbf | |||
| 47812e506f | |||
| 89fd30f235 | |||
| c3c068376a | |||
| 6d68f403fc | |||
| 74d06f020d | |||
| e896b14046 | |||
| be11812fe4 | |||
| 9c44b700f5 | |||
| 40e8b20257 | |||
| c336ca49fb | |||
| c623303ca5 | |||
| a98baa7522 |
@@ -31,7 +31,7 @@ jobs:
|
||||
runjobs: ${{ steps.continue.outputs.runjobs }}
|
||||
project_version: ${{ steps.continue.outputs.project_version }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- id: continue
|
||||
name: Determine if should continue
|
||||
run: |
|
||||
@@ -49,15 +49,15 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
if: needs.prerequisites.outputs.runjobs
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up JDK 11
|
||||
uses: actions/setup-java@v3
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: '11'
|
||||
distribution: 'adopt'
|
||||
cache: 'gradle'
|
||||
- name: Set up Gradle
|
||||
uses: gradle/gradle-build-action@v2
|
||||
uses: gradle/gradle-build-action@v3
|
||||
- name: Set up gradle user name
|
||||
run: echo 'systemProp.user.name=spring-builds+github' >> gradle.properties
|
||||
- name: Build with Gradle
|
||||
@@ -72,9 +72,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: '11'
|
||||
distribution: 'adopt'
|
||||
@@ -90,9 +90,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: '11'
|
||||
distribution: 'adopt'
|
||||
@@ -114,9 +114,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: '11'
|
||||
distribution: 'adopt'
|
||||
@@ -131,9 +131,9 @@ jobs:
|
||||
needs: [build_jdk_11, 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: '11'
|
||||
distribution: 'adopt'
|
||||
@@ -155,9 +155,9 @@ jobs:
|
||||
needs: [build_jdk_11, 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: '11'
|
||||
distribution: 'adopt'
|
||||
@@ -176,9 +176,9 @@ jobs:
|
||||
needs: [build_jdk_11, 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: '11'
|
||||
distribution: 'adopt'
|
||||
@@ -206,11 +206,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: '11'
|
||||
distribution: 'adopt'
|
||||
@@ -243,7 +243,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.25.0
|
||||
with:
|
||||
payload: |
|
||||
{
|
||||
@@ -287,9 +287,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: '11'
|
||||
distribution: 'adopt'
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
name: Merge Dependabot PR
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
run-name: Merge Dependabot PR ${{ github.ref_name }}
|
||||
|
||||
jobs:
|
||||
merge-dependabot-pr:
|
||||
permissions: write-all
|
||||
uses: spring-io/spring-github-workflows/.github/workflows/spring-merge-dependabot-pr.yml@1e8b0587a1f4f01697f9753fa3339c3e0d30f396
|
||||
with:
|
||||
mergeArguments: '--auto --rebase'
|
||||
@@ -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.3.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: '11'
|
||||
distribution: 'adopt'
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
name: Trigger Dependabot Auto Merge Forward
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '*.x'
|
||||
|
||||
permissions: read-all
|
||||
|
||||
jobs:
|
||||
trigger-worflow:
|
||||
name: Trigger Workflow
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event.commits[0].author.username == 'dependabot[bot]' && github.repository == 'spring-projects/spring-security' }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
id: checkout
|
||||
uses: actions/checkout@v4
|
||||
- id: trigger
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
|
||||
run: gh workflow run dependabot-auto-merge-forward.yml -r main
|
||||
@@ -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: '11'
|
||||
distribution: 'adopt'
|
||||
@@ -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.3.0
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
slack_webhook_url: ${{ secrets.SLACK_WEBHOOK_URL }}
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
* 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.
|
||||
@@ -149,7 +149,7 @@ public class AuthenticationManagerBuilder
|
||||
* {@link #getDefaultUserDetailsService()} method. Note that additional
|
||||
* {@link UserDetailsService}'s may override this {@link UserDetailsService} as the
|
||||
* default. See the <a href=
|
||||
* "https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#user-schema"
|
||||
* "https://docs.spring.io/spring-security/reference/servlet/appendix/database-schema.html"
|
||||
* >User Schema</a> section of the reference for the default schema.
|
||||
* </p>
|
||||
* @return a {@link JdbcUserDetailsManagerConfigurer} to allow customization of the
|
||||
|
||||
+10
-7
@@ -43,6 +43,7 @@ import org.springframework.security.config.core.GrantedAuthorityDefaults;
|
||||
* @since 5.0
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
class ReactiveMethodSecurityConfiguration implements ImportAware {
|
||||
|
||||
private int advisorOrder;
|
||||
@@ -51,16 +52,17 @@ class ReactiveMethodSecurityConfiguration implements ImportAware {
|
||||
|
||||
@Bean
|
||||
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
MethodSecurityMetadataSourceAdvisor methodSecurityInterceptor(AbstractMethodSecurityMetadataSource source) {
|
||||
static MethodSecurityMetadataSourceAdvisor methodSecurityInterceptor(AbstractMethodSecurityMetadataSource source,
|
||||
ReactiveMethodSecurityConfiguration configuration) {
|
||||
MethodSecurityMetadataSourceAdvisor advisor = new MethodSecurityMetadataSourceAdvisor(
|
||||
"securityMethodInterceptor", source, "methodMetadataSource");
|
||||
advisor.setOrder(this.advisorOrder);
|
||||
advisor.setOrder(configuration.advisorOrder);
|
||||
return advisor;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
DelegatingMethodSecurityMetadataSource methodMetadataSource(
|
||||
static DelegatingMethodSecurityMetadataSource methodMetadataSource(
|
||||
MethodSecurityExpressionHandler methodSecurityExpressionHandler) {
|
||||
ExpressionBasedAnnotationAttributeFactory attributeFactory = new ExpressionBasedAnnotationAttributeFactory(
|
||||
methodSecurityExpressionHandler);
|
||||
@@ -70,7 +72,7 @@ class ReactiveMethodSecurityConfiguration implements ImportAware {
|
||||
}
|
||||
|
||||
@Bean
|
||||
PrePostAdviceReactiveMethodInterceptor securityMethodInterceptor(AbstractMethodSecurityMetadataSource source,
|
||||
static PrePostAdviceReactiveMethodInterceptor securityMethodInterceptor(AbstractMethodSecurityMetadataSource source,
|
||||
MethodSecurityExpressionHandler handler) {
|
||||
ExpressionBasedPostInvocationAdvice postAdvice = new ExpressionBasedPostInvocationAdvice(handler);
|
||||
ExpressionBasedPreInvocationAdvice preAdvice = new ExpressionBasedPreInvocationAdvice();
|
||||
@@ -80,10 +82,11 @@ class ReactiveMethodSecurityConfiguration implements ImportAware {
|
||||
|
||||
@Bean
|
||||
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
DefaultMethodSecurityExpressionHandler methodSecurityExpressionHandler() {
|
||||
static DefaultMethodSecurityExpressionHandler methodSecurityExpressionHandler(
|
||||
ReactiveMethodSecurityConfiguration configuration) {
|
||||
DefaultMethodSecurityExpressionHandler handler = new DefaultMethodSecurityExpressionHandler();
|
||||
if (this.grantedAuthorityDefaults != null) {
|
||||
handler.setDefaultRolePrefix(this.grantedAuthorityDefaults.getRolePrefix());
|
||||
if (configuration.grantedAuthorityDefaults != null) {
|
||||
handler.setDefaultRolePrefix(configuration.grantedAuthorityDefaults.getRolePrefix());
|
||||
}
|
||||
return handler;
|
||||
}
|
||||
|
||||
+28
-12
@@ -23,13 +23,16 @@ 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 javax.servlet.DispatcherType;
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.ServletRegistration;
|
||||
import javax.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;
|
||||
import org.springframework.http.HttpMethod;
|
||||
@@ -44,7 +47,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 +78,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;
|
||||
}
|
||||
@@ -327,7 +331,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]));
|
||||
@@ -584,27 +593,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
|
||||
|
||||
+19
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -28,6 +28,7 @@ import org.springframework.security.openid.OpenIDAuthenticationFilter;
|
||||
import org.springframework.security.web.DefaultSecurityFilterChain;
|
||||
import org.springframework.security.web.access.ExceptionTranslationFilter;
|
||||
import org.springframework.security.web.access.channel.ChannelProcessingFilter;
|
||||
import org.springframework.security.web.access.intercept.AuthorizationFilter;
|
||||
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
|
||||
import org.springframework.security.web.authentication.AnonymousAuthenticationFilter;
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||
@@ -38,7 +39,11 @@ import org.springframework.security.web.authentication.rememberme.RememberMeAuth
|
||||
import org.springframework.security.web.authentication.switchuser.SwitchUserFilter;
|
||||
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
|
||||
import org.springframework.security.web.authentication.www.DigestAuthenticationFilter;
|
||||
import org.springframework.security.web.context.SecurityContextHolderFilter;
|
||||
import org.springframework.security.web.context.SecurityContextPersistenceFilter;
|
||||
import org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter;
|
||||
import org.springframework.security.web.csrf.CsrfFilter;
|
||||
import org.springframework.security.web.header.HeaderWriterFilter;
|
||||
import org.springframework.security.web.jaasapi.JaasApiIntegrationFilter;
|
||||
import org.springframework.security.web.savedrequest.RequestCacheAwareFilter;
|
||||
import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter;
|
||||
@@ -46,6 +51,7 @@ import org.springframework.security.web.session.ConcurrentSessionFilter;
|
||||
import org.springframework.security.web.session.DisableEncodeUrlFilter;
|
||||
import org.springframework.security.web.session.ForceEagerSessionCreationFilter;
|
||||
import org.springframework.security.web.session.SessionManagementFilter;
|
||||
import org.springframework.web.filter.CorsFilter;
|
||||
|
||||
/**
|
||||
* @param <H>
|
||||
@@ -127,15 +133,24 @@ public interface HttpSecurityBuilder<H extends HttpSecurityBuilder<H>>
|
||||
* The ordering of the Filters is:
|
||||
*
|
||||
* <ul>
|
||||
* <li>{@link ForceEagerSessionCreationFilter}</li>
|
||||
* <li>{@link DisableEncodeUrlFilter}</li>
|
||||
* <li>{@link ForceEagerSessionCreationFilter}</li>
|
||||
* <li>{@link ChannelProcessingFilter}</li>
|
||||
* <li>{@link WebAsyncManagerIntegrationFilter}</li>
|
||||
* <li>{@link SecurityContextHolderFilter}</li>
|
||||
* <li>{@link SecurityContextPersistenceFilter}</li>
|
||||
* <li>{@link HeaderWriterFilter}</li>
|
||||
* <li>{@link CorsFilter}</li>
|
||||
* <li>{@link CsrfFilter}</li>
|
||||
* <li>{@link LogoutFilter}</li>
|
||||
* <li>{@link org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter}</li>
|
||||
* <li>{@link org.springframework.security.saml2.provider.service.web.Saml2WebSsoAuthenticationRequestFilter}</li>
|
||||
* <li>{@link X509AuthenticationFilter}</li>
|
||||
* <li>{@link AbstractPreAuthenticatedProcessingFilter}</li>
|
||||
* <li><a href="
|
||||
* {@docRoot}/org/springframework/security/cas/web/CasAuthenticationFilter.html">CasAuthenticationFilter</a></li>
|
||||
* <li>{@link org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter}</li>
|
||||
* <li>{@link org.springframework.security.saml2.provider.service.web.authentication.Saml2WebSsoAuthenticationFilter}</li>
|
||||
* <li>{@link UsernamePasswordAuthenticationFilter}</li>
|
||||
* <li>{@link OpenIDAuthenticationFilter}</li>
|
||||
* <li>{@link org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter}</li>
|
||||
@@ -149,9 +164,11 @@ public interface HttpSecurityBuilder<H extends HttpSecurityBuilder<H>>
|
||||
* <li>{@link JaasApiIntegrationFilter}</li>
|
||||
* <li>{@link RememberMeAuthenticationFilter}</li>
|
||||
* <li>{@link AnonymousAuthenticationFilter}</li>
|
||||
* <li>{@link org.springframework.security.oauth2.client.web.OAuth2AuthorizationCodeGrantFilter}</li>
|
||||
* <li>{@link SessionManagementFilter}</li>
|
||||
* <li>{@link ExceptionTranslationFilter}</li>
|
||||
* <li>{@link FilterSecurityInterceptor}</li>
|
||||
* <li>{@link AuthorizationFilter}</li>
|
||||
* <li>{@link SwitchUserFilter}</li>
|
||||
* </ul>
|
||||
* @param filter the {@link Filter} to add
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
+1
-1
@@ -801,7 +801,7 @@ public class HeadersConfigurer<H extends HttpSecurityBuilder<H>>
|
||||
* replaced with "#". For example:
|
||||
*
|
||||
* <pre>
|
||||
* X-XSS-Protection: 1 ; mode=block
|
||||
* X-XSS-Protection: 1; mode=block
|
||||
* </pre>
|
||||
* @param headerValue the new header value
|
||||
* @since 5.8
|
||||
|
||||
+12
-2
@@ -268,12 +268,14 @@ public final class Saml2LogoutConfigurer<H extends HttpSecurityBuilder<H>>
|
||||
return postProcess(logoutResponseFilter);
|
||||
}
|
||||
|
||||
private LogoutFilter createRelyingPartyLogoutFilter(RelyingPartyRegistrationResolver registrations) {
|
||||
private Saml2RelyingPartyInitiatedLogoutFilter createRelyingPartyLogoutFilter(
|
||||
RelyingPartyRegistrationResolver registrations) {
|
||||
LogoutHandler[] logoutHandlers = this.logoutHandlers.toArray(new LogoutHandler[0]);
|
||||
Saml2RelyingPartyInitiatedLogoutSuccessHandler logoutRequestSuccessHandler = createSaml2LogoutRequestSuccessHandler(
|
||||
registrations);
|
||||
logoutRequestSuccessHandler.setLogoutRequestRepository(this.logoutRequestConfigurer.logoutRequestRepository);
|
||||
LogoutFilter logoutFilter = new LogoutFilter(logoutRequestSuccessHandler, logoutHandlers);
|
||||
Saml2RelyingPartyInitiatedLogoutFilter logoutFilter = new Saml2RelyingPartyInitiatedLogoutFilter(
|
||||
logoutRequestSuccessHandler, logoutHandlers);
|
||||
logoutFilter.setLogoutRequestMatcher(createLogoutMatcher());
|
||||
return postProcess(logoutFilter);
|
||||
}
|
||||
@@ -568,4 +570,12 @@ public final class Saml2LogoutConfigurer<H extends HttpSecurityBuilder<H>>
|
||||
|
||||
}
|
||||
|
||||
private static class Saml2RelyingPartyInitiatedLogoutFilter extends LogoutFilter {
|
||||
|
||||
Saml2RelyingPartyInitiatedLogoutFilter(LogoutSuccessHandler logoutSuccessHandler, LogoutHandler... handlers) {
|
||||
super(logoutSuccessHandler, handlers);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+33
-4
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* 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.
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.springframework.security.config.http;
|
||||
|
||||
import org.opensaml.core.Version;
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
import org.springframework.beans.BeanMetadataElement;
|
||||
@@ -27,6 +28,7 @@ import org.springframework.security.saml2.provider.service.registration.RelyingP
|
||||
import org.springframework.security.saml2.provider.service.web.DefaultRelyingPartyRegistrationResolver;
|
||||
import org.springframework.security.saml2.provider.service.web.HttpSessionSaml2AuthenticationRequestRepository;
|
||||
import org.springframework.security.saml2.provider.service.web.Saml2AuthenticationTokenConverter;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
@@ -35,6 +37,8 @@ import org.springframework.util.StringUtils;
|
||||
*/
|
||||
final class Saml2LoginBeanDefinitionParserUtils {
|
||||
|
||||
private static final String OPEN_SAML_4_VERSION = "4";
|
||||
|
||||
private static final String ATT_RELYING_PARTY_REGISTRATION_REPOSITORY_REF = "relying-party-registration-repository-ref";
|
||||
|
||||
private static final String ATT_AUTHENTICATION_REQUEST_REPOSITORY_REF = "authentication-request-repository-ref";
|
||||
@@ -78,15 +82,27 @@ final class Saml2LoginBeanDefinitionParserUtils {
|
||||
.rootBeanDefinition(DefaultRelyingPartyRegistrationResolver.class)
|
||||
.addConstructorArgValue(relyingPartyRegistrationRepository)
|
||||
.getBeanDefinition();
|
||||
if (version().startsWith("4")) {
|
||||
return BeanDefinitionBuilder.rootBeanDefinition(
|
||||
"org.springframework.security.saml2.provider.service.web.authentication.OpenSaml4AuthenticationRequestResolver")
|
||||
.addConstructorArgValue(defaultRelyingPartyRegistrationResolver)
|
||||
.getBeanDefinition();
|
||||
}
|
||||
return BeanDefinitionBuilder.rootBeanDefinition(
|
||||
"org.springframework.security.saml2.provider.service.web.authentication.OpenSaml4AuthenticationRequestResolver")
|
||||
"org.springframework.security.saml2.provider.service.web.authentication.OpenSamlAuthenticationRequestResolver")
|
||||
.addConstructorArgValue(defaultRelyingPartyRegistrationResolver)
|
||||
.getBeanDefinition();
|
||||
}
|
||||
|
||||
static BeanDefinition createAuthenticationProvider() {
|
||||
return BeanDefinitionBuilder.rootBeanDefinition(
|
||||
"org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationProvider")
|
||||
if (version().startsWith("4")) {
|
||||
return BeanDefinitionBuilder.rootBeanDefinition(
|
||||
"org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationProvider")
|
||||
.getBeanDefinition();
|
||||
}
|
||||
return BeanDefinitionBuilder
|
||||
.rootBeanDefinition(
|
||||
"org.springframework.security.saml2.provider.service.authentication.OpenSamlAuthenticationProvider")
|
||||
.getBeanDefinition();
|
||||
}
|
||||
|
||||
@@ -108,4 +124,17 @@ final class Saml2LoginBeanDefinitionParserUtils {
|
||||
.getBeanDefinition();
|
||||
}
|
||||
|
||||
static String version() {
|
||||
String version = Version.getVersion();
|
||||
if (StringUtils.hasText(version)) {
|
||||
return version;
|
||||
}
|
||||
boolean openSaml4ClassPresent = ClassUtils
|
||||
.isPresent("org.opensaml.core.xml.persist.impl.PassthroughSourceStrategy", null);
|
||||
if (openSaml4ClassPresent) {
|
||||
return OPEN_SAML_4_VERSION;
|
||||
}
|
||||
throw new IllegalStateException("cannot determine OpenSAML version");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+32
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* 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.
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.springframework.security.config.http;
|
||||
|
||||
import org.opensaml.core.Version;
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
import org.springframework.beans.BeanMetadataElement;
|
||||
@@ -25,6 +26,7 @@ import org.springframework.security.saml2.provider.service.authentication.logout
|
||||
import org.springframework.security.saml2.provider.service.authentication.logout.OpenSamlLogoutResponseValidator;
|
||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
|
||||
import org.springframework.security.saml2.provider.service.web.authentication.logout.HttpSessionLogoutRequestRepository;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
@@ -33,6 +35,8 @@ import org.springframework.util.StringUtils;
|
||||
*/
|
||||
final class Saml2LogoutBeanDefinitionParserUtils {
|
||||
|
||||
private static final String OPEN_SAML_4_VERSION = "4";
|
||||
|
||||
private static final String ATT_RELYING_PARTY_REGISTRATION_REPOSITORY_REF = "relying-party-registration-repository-ref";
|
||||
|
||||
private static final String ATT_LOGOUT_REQUEST_VALIDATOR_REF = "logout-request-validator-ref";
|
||||
@@ -62,8 +66,14 @@ final class Saml2LogoutBeanDefinitionParserUtils {
|
||||
if (StringUtils.hasText(logoutResponseResolver)) {
|
||||
return new RuntimeBeanReference(logoutResponseResolver);
|
||||
}
|
||||
if (version().startsWith("4")) {
|
||||
return BeanDefinitionBuilder.rootBeanDefinition(
|
||||
"org.springframework.security.saml2.provider.service.web.authentication.logout.OpenSaml4LogoutResponseResolver")
|
||||
.addConstructorArgValue(registrations)
|
||||
.getBeanDefinition();
|
||||
}
|
||||
return BeanDefinitionBuilder.rootBeanDefinition(
|
||||
"org.springframework.security.saml2.provider.service.web.authentication.logout.OpenSaml4LogoutResponseResolver")
|
||||
"org.springframework.security.saml2.provider.service.web.authentication.logout.OpenSamlLogoutResponseResolver")
|
||||
.addConstructorArgValue(registrations)
|
||||
.getBeanDefinition();
|
||||
}
|
||||
@@ -97,10 +107,29 @@ final class Saml2LogoutBeanDefinitionParserUtils {
|
||||
if (StringUtils.hasText(logoutRequestResolver)) {
|
||||
return new RuntimeBeanReference(logoutRequestResolver);
|
||||
}
|
||||
if (version().startsWith("4")) {
|
||||
return BeanDefinitionBuilder.rootBeanDefinition(
|
||||
"org.springframework.security.saml2.provider.service.web.authentication.logout.OpenSaml4LogoutRequestResolver")
|
||||
.addConstructorArgValue(registrations)
|
||||
.getBeanDefinition();
|
||||
}
|
||||
return BeanDefinitionBuilder.rootBeanDefinition(
|
||||
"org.springframework.security.saml2.provider.service.web.authentication.logout.OpenSaml4LogoutRequestResolver")
|
||||
"org.springframework.security.saml2.provider.service.web.authentication.logout.OpenSamlLogoutRequestResolver")
|
||||
.addConstructorArgValue(registrations)
|
||||
.getBeanDefinition();
|
||||
}
|
||||
|
||||
static String version() {
|
||||
String version = Version.getVersion();
|
||||
if (StringUtils.hasText(version)) {
|
||||
return version;
|
||||
}
|
||||
boolean openSaml4ClassPresent = ClassUtils
|
||||
.isPresent("org.opensaml.core.xml.persist.impl.PassthroughSourceStrategy", null);
|
||||
if (openSaml4ClassPresent) {
|
||||
return OPEN_SAML_4_VERSION;
|
||||
}
|
||||
throw new IllegalStateException("cannot determine OpenSAML version");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+1
-1
@@ -385,7 +385,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
@@ -75,7 +75,7 @@ public class HeaderSpecTests {
|
||||
this.expectedHeaders.add(HttpHeaders.EXPIRES, "0");
|
||||
this.expectedHeaders.add(ContentTypeOptionsServerHttpHeadersWriter.X_CONTENT_OPTIONS, "nosniff");
|
||||
this.expectedHeaders.add(XFrameOptionsServerHttpHeadersWriter.X_FRAME_OPTIONS, "DENY");
|
||||
this.expectedHeaders.add(XXssProtectionServerHttpHeadersWriter.X_XSS_PROTECTION, "1 ; mode=block");
|
||||
this.expectedHeaders.add(XXssProtectionServerHttpHeadersWriter.X_XSS_PROTECTION, "1; mode=block");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -320,7 +320,7 @@ public class HeaderSpecTests {
|
||||
|
||||
@Test
|
||||
public void headersWhenXssProtectionValueEnabledModeBlockThenXssProtectionWritten() {
|
||||
this.expectedHeaders.set(XXssProtectionServerHttpHeadersWriter.X_XSS_PROTECTION, "1 ; mode=block");
|
||||
this.expectedHeaders.set(XXssProtectionServerHttpHeadersWriter.X_XSS_PROTECTION, "1; mode=block");
|
||||
// @formatter:off
|
||||
this.http.headers()
|
||||
.xssProtection()
|
||||
|
||||
+1
-1
@@ -70,7 +70,7 @@ class ServerHeadersDslTests {
|
||||
.expectHeader().valueEquals(HttpHeaders.CACHE_CONTROL, "no-cache, no-store, max-age=0, must-revalidate")
|
||||
.expectHeader().valueEquals(HttpHeaders.EXPIRES, "0")
|
||||
.expectHeader().valueEquals(HttpHeaders.PRAGMA, "no-cache")
|
||||
.expectHeader().valueEquals(XXssProtectionServerHttpHeadersWriter.X_XSS_PROTECTION, "1 ; mode=block")
|
||||
.expectHeader().valueEquals(XXssProtectionServerHttpHeadersWriter.X_XSS_PROTECTION, "1; mode=block")
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
|
||||
+1
-1
@@ -123,7 +123,7 @@ class ServerHttpSecurityDslTests {
|
||||
.expectHeader().valueEquals(HttpHeaders.CACHE_CONTROL, "no-cache, no-store, max-age=0, must-revalidate")
|
||||
.expectHeader().valueEquals(HttpHeaders.EXPIRES, "0")
|
||||
.expectHeader().valueEquals(HttpHeaders.PRAGMA, "no-cache")
|
||||
.expectHeader().valueEquals(XXssProtectionServerHttpHeadersWriter.X_XSS_PROTECTION, "1 ; mode=block")
|
||||
.expectHeader().valueEquals(XXssProtectionServerHttpHeadersWriter.X_XSS_PROTECTION, "1; mode=block")
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
|
||||
+1
-1
@@ -56,7 +56,7 @@ class ServerXssProtectionDslTests {
|
||||
this.client.get()
|
||||
.uri("/")
|
||||
.exchange()
|
||||
.expectHeader().valueEquals(XXssProtectionServerHttpHeadersWriter.X_XSS_PROTECTION, "1 ; mode=block")
|
||||
.expectHeader().valueEquals(XXssProtectionServerHttpHeadersWriter.X_XSS_PROTECTION, "1; mode=block")
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
|
||||
+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";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -62,7 +62,10 @@ This configuration enables <<rsocket-authentication-simple,simple authentication
|
||||
|
||||
For Spring Security to work we need to apply `SecuritySocketAcceptorInterceptor` to the `ServerRSocketFactory`.
|
||||
This is what connects our `PayloadSocketAcceptorInterceptor` we created with the RSocket infrastructure.
|
||||
In a Spring Boot application this is done automatically using `RSocketSecurityAutoConfiguration` with the following code.
|
||||
|
||||
Spring Boot registers it automatically in `RSocketSecurityAutoConfiguration` when you include {gh-samples-url}/reactive/rsocket/hello-security/build.gradle[the correct dependencies].
|
||||
|
||||
Or, if you are not using Boot's auto-configuration, you can register it manually in the following way:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -91,6 +94,8 @@ fun springSecurityRSocketSecurity(interceptor: SecuritySocketAcceptorInterceptor
|
||||
----
|
||||
======
|
||||
|
||||
To customize the interceptor itself, use `RSocketSecurity` to add <<rsocket-authentication,authentication>> and <<rsocket-authorization,authorization>>.
|
||||
|
||||
[[rsocket-authentication]]
|
||||
== RSocket Authentication
|
||||
|
||||
|
||||
@@ -370,7 +370,7 @@ spring:
|
||||
oauth2:
|
||||
resourceserver:
|
||||
jwt:
|
||||
jws-algorithm: RS512
|
||||
jws-algorithms: RS512
|
||||
jwk-set-uri: https://idp.example.org/.well-known/jwks.json
|
||||
----
|
||||
|
||||
|
||||
@@ -112,7 +112,7 @@ public class SecurityWebApplicationInitializer
|
||||
|
||||
This would simply only register the springSecurityFilterChain Filter for every URL in your application.
|
||||
After that we would ensure that `WebSecurityConfig` was loaded in our existing ApplicationInitializer.
|
||||
For example, if we were using Spring MVC it would be added in the `getRootConfigClasses()`
|
||||
For example, if we were using Spring MVC it would be added in the `getServletConfigClasses()`
|
||||
|
||||
[[message-web-application-inititializer-java]]
|
||||
[source,java]
|
||||
@@ -121,14 +121,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
|
||||
|
||||
|
||||
@@ -513,7 +513,7 @@ spring:
|
||||
oauth2:
|
||||
resourceserver:
|
||||
jwt:
|
||||
jws-algorithm: RS512
|
||||
jws-algorithms: RS512
|
||||
jwk-set-uri: https://idp.example.org/.well-known/jwks.json
|
||||
----
|
||||
|
||||
|
||||
@@ -374,29 +374,22 @@ Java::
|
||||
----
|
||||
@Component
|
||||
public class TenantJwtIssuerValidator implements OAuth2TokenValidator<Jwt> {
|
||||
private final TenantRepository tenants;
|
||||
private final Map<String, JwtIssuerValidator> validators = new ConcurrentHashMap<>();
|
||||
private final TenantRepository tenants;
|
||||
|
||||
public TenantJwtIssuerValidator(TenantRepository tenants) {
|
||||
this.tenants = tenants;
|
||||
}
|
||||
private final OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.INVALID_TOKEN, "The iss claim is not valid",
|
||||
"https://tools.ietf.org/html/rfc6750#section-3.1");
|
||||
|
||||
@Override
|
||||
public OAuth2TokenValidatorResult validate(Jwt token) {
|
||||
return this.validators.computeIfAbsent(toTenant(token), this::fromTenant)
|
||||
.validate(token);
|
||||
}
|
||||
public TenantJwtIssuerValidator(TenantRepository tenants) {
|
||||
this.tenants = tenants;
|
||||
}
|
||||
|
||||
private String toTenant(Jwt jwt) {
|
||||
return jwt.getIssuer();
|
||||
}
|
||||
|
||||
private JwtIssuerValidator fromTenant(String tenant) {
|
||||
return Optional.ofNullable(this.tenants.findById(tenant))
|
||||
.map(t -> t.getAttribute("issuer"))
|
||||
.map(JwtIssuerValidator::new)
|
||||
.orElseThrow(() -> new IllegalArgumentException("unknown tenant"));
|
||||
}
|
||||
@Override
|
||||
public OAuth2TokenValidatorResult validate(Jwt token) {
|
||||
if(this.tenants.findById(token.getIssuer()) != null) {
|
||||
return OAuth2TokenValidatorResult.success();
|
||||
}
|
||||
return OAuth2TokenValidatorResult.failure(this.error);
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
@@ -405,32 +398,17 @@ Kotlin::
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Component
|
||||
class TenantJwtIssuerValidator(tenants: TenantRepository) : OAuth2TokenValidator<Jwt> {
|
||||
private val tenants: TenantRepository
|
||||
private val validators: MutableMap<String, JwtIssuerValidator> = ConcurrentHashMap()
|
||||
class TenantJwtIssuerValidator(private val tenants: TenantRepository) : OAuth2TokenValidator<Jwt> {
|
||||
private val error: OAuth2Error = OAuth2Error(OAuth2ErrorCodes.INVALID_TOKEN, "The iss claim is not valid",
|
||||
"https://tools.ietf.org/html/rfc6750#section-3.1")
|
||||
|
||||
override fun validate(token: Jwt): OAuth2TokenValidatorResult {
|
||||
return validators.computeIfAbsent(toTenant(token)) { tenant: String -> fromTenant(tenant) }
|
||||
.validate(token)
|
||||
}
|
||||
|
||||
private fun toTenant(jwt: Jwt): String {
|
||||
return jwt.issuer.toString()
|
||||
}
|
||||
|
||||
private fun fromTenant(tenant: String): JwtIssuerValidator {
|
||||
return Optional.ofNullable(tenants.findById(tenant))
|
||||
.map({ t -> t.getAttribute("issuer") })
|
||||
.map({ JwtIssuerValidator() })
|
||||
.orElseThrow({ IllegalArgumentException("unknown tenant") })
|
||||
}
|
||||
|
||||
init {
|
||||
this.tenants = tenants
|
||||
return if (tenants.findById(token.issuer) != null)
|
||||
OAuth2TokenValidatorResult.success() else OAuth2TokenValidatorResult.failure(error)
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
Now that we have a tenant-aware processor and a tenant-aware validator, we can proceed with creating our xref:servlet/oauth2/resource-server/jwt.adoc#oauth2resourceserver-jwt-architecture-jwtdecoder[`JwtDecoder`]:
|
||||
|
||||
[tabs]
|
||||
|
||||
@@ -55,7 +55,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
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
springBootVersion=2.7.12
|
||||
version=5.8.9-SNAPSHOT
|
||||
version=5.8.10
|
||||
samplesBranch=5.8.x
|
||||
org.gradle.jvmargs=-Xmx3g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError
|
||||
org.gradle.parallel=true
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
com-squareup-okhttp3 = "3.14.9"
|
||||
io-r2dbc = "0.9.1.RELEASE"
|
||||
io-rsocket = "1.1.4"
|
||||
io-spring-javaformat = "0.0.40"
|
||||
io-spring-javaformat = "0.0.41"
|
||||
io-spring-nohttp = "0.0.11"
|
||||
org-apache-directory-server = "1.5.5"
|
||||
org-aspectj = "1.9.20.1"
|
||||
org-bouncycastle = "1.70"
|
||||
org-eclipse-jetty = "9.4.53.v20231009"
|
||||
org-eclipse-jetty = "9.4.54.v20240208"
|
||||
org-jetbrains-kotlin = "1.7.22"
|
||||
org-jetbrains-kotlinx = "1.6.4"
|
||||
org-junit-jupiter = "5.9.3"
|
||||
@@ -15,10 +15,10 @@ org-mockito = "4.8.1"
|
||||
org-opensaml4 = "4.1.0"
|
||||
org-opensaml3 = "3.4.6"
|
||||
org-slf4j = "1.7.36"
|
||||
org-springframework = "5.3.31"
|
||||
org-springframework = "5.3.32"
|
||||
|
||||
[libraries]
|
||||
ch-qos-logback-logback-classic = "ch.qos.logback:logback-classic:1.2.12"
|
||||
ch-qos-logback-logback-classic = "ch.qos.logback:logback-classic:1.2.13"
|
||||
com-fasterxml-jackson-jackson-bom = "com.fasterxml.jackson:jackson-bom:2.13.5"
|
||||
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"
|
||||
@@ -30,8 +30,8 @@ com-unboundid-unboundid-ldapsdk = "com.unboundid:unboundid-ldapsdk:4.0.14"
|
||||
commons-collections = "commons-collections:commons-collections:3.2.2"
|
||||
io-freefair-gradle-aspectj-plugin = "io.freefair.gradle:aspectj-plugin:6.5.1"
|
||||
io-mockk = "io.mockk:mockk:1.13.3"
|
||||
io-projectreactor-netty-reactor-netty = "io.projectreactor.netty:reactor-netty:1.0.39"
|
||||
io-projectreactor-reactor-bom = "io.projectreactor:reactor-bom:2020.0.38"
|
||||
io-projectreactor-netty-reactor-netty = "io.projectreactor.netty:reactor-netty:1.0.41"
|
||||
io-projectreactor-reactor-bom = "io.projectreactor:reactor-bom:2020.0.41"
|
||||
io-projectreactor-tools-blockhound = "io.projectreactor.tools:blockhound:1.0.8.RELEASE"
|
||||
io-r2dbc-r2dbc-h2 = { module = "io.r2dbc:r2dbc-h2", version.ref = "io-r2dbc" }
|
||||
io-r2dbc-r2dbc-spi-test = { module = "io.r2dbc:r2dbc-spi-test", version.ref = "io-r2dbc" }
|
||||
|
||||
+3
@@ -144,6 +144,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);
|
||||
}
|
||||
}
|
||||
|
||||
+9
-12
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -145,20 +145,17 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Jwt> decode(String token) throws JwtException {
|
||||
JWT jwt = parse(token);
|
||||
if (jwt instanceof PlainJWT) {
|
||||
throw new BadJwtException("Unsupported algorithm of " + jwt.getHeader().getAlgorithm());
|
||||
}
|
||||
return this.decode(jwt);
|
||||
}
|
||||
|
||||
private JWT parse(String token) {
|
||||
public Mono<Jwt> decode(String token) {
|
||||
try {
|
||||
return JWTParser.parse(token);
|
||||
JWT jwt = JWTParser.parse(token);
|
||||
if (jwt instanceof PlainJWT) {
|
||||
return Mono.error(new BadJwtException("Unsupported algorithm of " + jwt.getHeader().getAlgorithm()));
|
||||
}
|
||||
return this.decode(jwt);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new BadJwtException("An error occurred while attempting to decode the Jwt: " + ex.getMessage(), ex);
|
||||
return Mono.error(new BadJwtException(
|
||||
"An error occurred while attempting to decode the Jwt: " + ex.getMessage(), ex));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+8
@@ -97,6 +97,8 @@ import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
*/
|
||||
public class NimbusJwtDecoderTests {
|
||||
|
||||
private static final String MALFORMED_TOKEN = "eyJhbGciOiJSUzI1NiJ9.eyJuYmYiOnt9LCJleHAiOjQ2ODQyMjUwODd9";
|
||||
|
||||
private static final String JWK_SET = "{\"keys\":[{\"p\":\"49neceJFs8R6n7WamRGy45F5Tv0YM-R2ODK3eSBUSLOSH2tAqjEVKOkLE5fiNA3ygqq15NcKRadB2pTVf-Yb5ZIBuKzko8bzYIkIqYhSh_FAdEEr0vHF5fq_yWSvc6swsOJGqvBEtuqtJY027u-G2gAQasCQdhyejer68zsTn8M\",\"kty\":\"RSA\",\"q\":\"tWR-ysspjZ73B6p2vVRVyHwP3KQWL5KEQcdgcmMOE_P_cPs98vZJfLhxobXVmvzuEWBpRSiqiuyKlQnpstKt94Cy77iO8m8ISfF3C9VyLWXi9HUGAJb99irWABFl3sNDff5K2ODQ8CmuXLYM25OwN3ikbrhEJozlXg_NJFSGD4E\",\"d\":\"FkZHYZlw5KSoqQ1i2RA2kCUygSUOf1OqMt3uomtXuUmqKBm_bY7PCOhmwbvbn4xZYEeHuTR8Xix-0KpHe3NKyWrtRjkq1T_un49_1LLVUhJ0dL-9_x0xRquVjhl_XrsRXaGMEHs8G9pLTvXQ1uST585gxIfmCe0sxPZLvwoic-bXf64UZ9BGRV3lFexWJQqCZp2S21HfoU7wiz6kfLRNi-K4xiVNB1gswm_8o5lRuY7zB9bRARQ3TS2G4eW7p5sxT3CgsGiQD3_wPugU8iDplqAjgJ5ofNJXZezoj0t6JMB_qOpbrmAM1EnomIPebSLW7Ky9SugEd6KMdL5lW6AuAQ\",\"e\":\"AQAB\",\"use\":\"sig\",\"kid\":\"one\",\"qi\":\"wdkFu_tV2V1l_PWUUimG516Zvhqk2SWDw1F7uNDD-Lvrv_WNRIJVzuffZ8WYiPy8VvYQPJUrT2EXL8P0ocqwlaSTuXctrORcbjwgxDQDLsiZE0C23HYzgi0cofbScsJdhcBg7d07LAf7cdJWG0YVl1FkMCsxUlZ2wTwHfKWf-v4\",\"dp\":\"uwnPxqC-IxG4r33-SIT02kZC1IqC4aY7PWq0nePiDEQMQWpjjNH50rlq9EyLzbtdRdIouo-jyQXB01K15-XXJJ60dwrGLYNVqfsTd0eGqD1scYJGHUWG9IDgCsxyEnuG3s0AwbW2UolWVSsU2xMZGb9PurIUZECeD1XDZwMp2s0\",\"dq\":\"hra786AunB8TF35h8PpROzPoE9VJJMuLrc6Esm8eZXMwopf0yhxfN2FEAvUoTpLJu93-UH6DKenCgi16gnQ0_zt1qNNIVoRfg4rw_rjmsxCYHTVL3-RDeC8X_7TsEySxW0EgFTHh-nr6I6CQrAJjPM88T35KHtdFATZ7BCBB8AE\",\"n\":\"oXJ8OyOv_eRnce4akdanR4KYRfnC2zLV4uYNQpcFn6oHL0dj7D6kxQmsXoYgJV8ZVDn71KGmuLvolxsDncc2UrhyMBY6DVQVgMSVYaPCTgW76iYEKGgzTEw5IBRQL9w3SRJWd3VJTZZQjkXef48Ocz06PGF3lhbz4t5UEZtdF4rIe7u-977QwHuh7yRPBQ3sII-cVoOUMgaXB9SHcGF2iZCtPzL_IffDUcfhLQteGebhW8A6eUHgpD5A1PQ-JCw_G7UOzZAjjDjtNM2eqm8j-Ms_gqnm4MiCZ4E-9pDN77CAAPVN7kuX6ejs9KBXpk01z48i9fORYk9u7rAkh1HuQw\"}]}";
|
||||
|
||||
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\"}]}";
|
||||
@@ -194,6 +196,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");
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@ pluginManagement {
|
||||
|
||||
plugins {
|
||||
id "com.gradle.enterprise" version "3.11.4"
|
||||
id "io.spring.ge.conventions" version "0.0.14"
|
||||
id "io.spring.ge.conventions" version "0.0.15"
|
||||
}
|
||||
|
||||
dependencyResolutionManagement {
|
||||
|
||||
+2
-2
@@ -122,7 +122,7 @@ public final class XXssProtectionHeaderWriter implements HeaderWriter {
|
||||
* specify mode as blocked. The content will be replaced with "#". For example:
|
||||
*
|
||||
* <pre>
|
||||
* X-XSS-Protection: 1 ; mode=block
|
||||
* X-XSS-Protection: 1; mode=block
|
||||
* </pre>
|
||||
* @param headerValue the new header value
|
||||
* @throws IllegalArgumentException when headerValue is null
|
||||
@@ -134,7 +134,7 @@ public final class XXssProtectionHeaderWriter implements HeaderWriter {
|
||||
}
|
||||
|
||||
/**
|
||||
* The value of the x-xss-protection header. One of: "0", "1", "1 ; mode=block"
|
||||
* The value of the x-xss-protection header. One of: "0", "1", "1; mode=block"
|
||||
*
|
||||
* @author Daniel Garnier-Moiroux
|
||||
* @since 5.8
|
||||
|
||||
+3
-3
@@ -122,7 +122,7 @@ public class XXssProtectionServerHttpHeadersWriter implements ServerHttpHeadersW
|
||||
* specify mode as blocked. The content will be replaced with "#". For example:
|
||||
*
|
||||
* <pre>
|
||||
* X-XSS-Protection: 1 ; mode=block
|
||||
* X-XSS-Protection: 1; mode=block
|
||||
* </pre>
|
||||
* @param headerValue the new headerValue
|
||||
* @throws IllegalArgumentException if headerValue is null
|
||||
@@ -135,14 +135,14 @@ public class XXssProtectionServerHttpHeadersWriter implements ServerHttpHeadersW
|
||||
}
|
||||
|
||||
/**
|
||||
* The value of the x-xss-protection header. One of: "0", "1", "1 ; mode=block"
|
||||
* The value of the x-xss-protection header. One of: "0", "1", "1; mode=block"
|
||||
*
|
||||
* @author Daniel Garnier-Moiroux
|
||||
* @since 5.8
|
||||
*/
|
||||
public enum HeaderValue {
|
||||
|
||||
DISABLED("0"), ENABLED("1"), ENABLED_MODE_BLOCK("1 ; mode=block");
|
||||
DISABLED("0"), ENABLED("1"), ENABLED_MODE_BLOCK("1; mode=block");
|
||||
|
||||
private final String value;
|
||||
|
||||
|
||||
+2
-2
@@ -49,7 +49,7 @@ public class XXssProtectionServerHttpHeadersWriterTests {
|
||||
this.writer.writeHttpHeaders(this.exchange);
|
||||
assertThat(this.headers).hasSize(1);
|
||||
assertThat(this.headers.get(XXssProtectionServerHttpHeadersWriter.X_XSS_PROTECTION))
|
||||
.containsOnly("1 ; mode=block");
|
||||
.containsOnly("1; mode=block");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -99,7 +99,7 @@ public class XXssProtectionServerHttpHeadersWriterTests {
|
||||
this.writer.writeHttpHeaders(this.exchange);
|
||||
assertThat(this.headers).hasSize(1);
|
||||
assertThat(this.headers.get(XXssProtectionServerHttpHeadersWriter.X_XSS_PROTECTION))
|
||||
.containsOnly("1 ; mode=block");
|
||||
.containsOnly("1; mode=block");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user