1
0
mirror of synced 2026-08-05 09:47:05 +00:00

Compare commits

...

25 Commits

Author SHA1 Message Date
dependabot[bot] f686a9e313 Bump com.webauthn4j:webauthn4j-core
Bumps [com.webauthn4j:webauthn4j-core](https://github.com/webauthn4j/webauthn4j) from 0.31.8.RELEASE to 0.31.9.RELEASE.
- [Release notes](https://github.com/webauthn4j/webauthn4j/releases)
- [Commits](https://github.com/webauthn4j/webauthn4j/compare/0.31.8.RELEASE...0.31.9.RELEASE)

---
updated-dependencies:
- dependency-name: com.webauthn4j:webauthn4j-core
  dependency-version: 0.31.9.RELEASE
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-05 03:17:09 +00:00
Josh Cummings 08d062bdc0 Merge branch '7.0.x'
# Conflicts:
#	docs/modules/ROOT/pages/features/exploits/http.adoc
2026-08-03 17:53:54 -06:00
Sumit Kumar Das 2c4db99229 Differentiate Forwarded and X-Forwarded headers in proxy docs
The proxy server section pointed at RFC 7239 and then told the reader to
configure the application server for the X-Forwarded headers, conflating
the standard Forwarded header with the non-standard X-Forwarded-* set.

Describe the two kinds of headers separately, note that most proxies send
X-Forwarded-* while Spring Framework and servers such as Reactor Netty and
Jetty understand both, and state that the edge proxy has to drop or
overwrite untrusted values for both kinds rather than only one.

Closes gh-19461

Signed-off-by: Sumit Kumar Das <skdas5405@gmail.com>
2026-08-03 17:52:32 -06:00
Josh Cummings c84875f923 Merge branch 'main' of github.com:spring-projects/spring-security 2026-08-03 16:10:02 -06:00
Josh Cummings 8d452e2c32 Merge branch '7.0.x' 2026-08-03 16:07:26 -06:00
Josh Cummings 0f6f453ea0 Increase Default NimbusJwtDecoder Timeouts to 30 Seconds
NimbusJwtDecoder's default RestOperations now respects the JDK's
sun.net.client.defaultConnectTimeout/defaultReadTimeout system properties,
falling back to 30 seconds instead of the previous 500 milliseconds,
matching JwtDecoderProviderConfigurationUtils's existing behavior.

Also documents this default and the RestOperations override in the
reference guide and migration guide (the reference guide's existing
"Configuring Timeouts" section already claimed 30 seconds -- it's been
inaccurate since the 500ms default shipped and is now correct again), and
documents providing a custom JwtDecoderFactory<ClientRegistration> for
OAuth2 Login's ID Token decoding.

Issue gh-19474

Signed-off-by: Josh Cummings <3627351+jzheaux@users.noreply.github.com>
2026-08-03 15:51:41 -06:00
dependabot[bot] 7ec316a030 Bump org.apache.httpcomponents.client5:httpclient5 from 5.6.2 to 5.6.3
Bumps [org.apache.httpcomponents.client5:httpclient5](https://github.com/apache/httpcomponents-client) from 5.6.2 to 5.6.3.
- [Changelog](https://github.com/apache/httpcomponents-client/blob/rel/v5.6.3/RELEASE_NOTES.txt)
- [Commits](https://github.com/apache/httpcomponents-client/compare/rel/v5.6.2...rel/v5.6.3)

---
updated-dependencies:
- dependency-name: org.apache.httpcomponents.client5:httpclient5
  dependency-version: 5.6.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-03 03:16:40 +00:00
Josh Cummings 61fae604d2 Merge branch '7.0.x' 2026-07-31 16:03:03 -06:00
Josh Cummings 1eef373ca0 Improve Error Message for Invalid JSR-250 Usage
This commit adds an IllegalStateException guard when spring-security-access
is missing and the application is using @EnableGlobalMethodSecurity with
jsr250Enabled.

Issue gh-19441

Signed-off-by: Josh Cummings <3627351+jzheaux@users.noreply.github.com>
2026-07-31 16:01:21 -06:00
jyx-07 7a03cd5a55 Fail fast when spring-security-access is missing
The "Move Core Access API" refactoring (gh-17847) relocated
MethodSecurityMetadataSourceAdvisor and MethodSecurityInterceptor
from spring-security-core, a mandatory dependency of
spring-security-config, into the new spring-security-access module,
which spring-security-config only depends on optionally.

GlobalMethodSecuritySelector (backing the deprecated
@EnableGlobalMethodSecurity) and ReactiveMethodSecuritySelector
(backing @EnableReactiveMethodSecurity(useAuthorizationManager =
false)) still unconditionally import configuration that constructs
those classes: MethodSecurityMetadataSourceAdvisorRegistrar in proxy
mode, GlobalMethodSecurityConfiguration in both proxy and aspectj
mode, and ReactiveMethodSecurityConfiguration for the legacy reactive
path. Applications that use any of these deprecated configuration
paths without explicitly adding spring-security-access now fail at
startup with a confusing NoClassDefFoundError deep inside Spring's
configuration-processing machinery, instead of an actionable message.

@EnableMethodSecurity and @EnableReactiveMethodSecurity's default
(AuthorizationManager-based) mode, the non-deprecated replacements,
never reference these classes and are unaffected either way.

Add a ClassUtils.isPresent check to both selectors so that, whenever
a legacy configuration path that needs it is chosen (proxy mode,
aspectj mode, or the legacy reactive interceptor), a missing
spring-security-access dependency now fails fast with a clear
IllegalStateException that names the missing dependency and points
to the supported alternative, rather than a NoClassDefFoundError.

This preserves gh-17847's footprint-reduction intent: the check only
runs for the deprecated legacy annotations, so the majority of
applications using @EnableMethodSecurity see no change in behavior or
dependencies. @EnableGlobalMethodSecurity remains deprecated; this
change adds no new investment in it beyond giving existing users of
it a clear diagnostic instead of a confusing crash.

Closes gh-19441

Signed-off-by: jyx-07 <s25069@gsm.hs.kr>
2026-07-31 16:01:21 -06:00
Josh Cummings bb59d5c0dd Merge branch '7.0.x'
# Conflicts:
#	oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2PushedAuthorizationRequestUri.java
2026-07-31 13:22:17 -06:00
Andrey Litvitski e4fafce066 Fix OAuth2PushedAuthorizationRequestUri parsing
OAuth2PushedAuthorizationRequestUri uses Base64URL encoding for the state, which can produce a value containing ___, the same sequence used as the delimiter. As a result, indexOf may locate a delimiter within the state instead of the delimiter preceding the expiration timestamp.

Use lastIndexOf to reliably locate the delimiter before the timestamp without changing the existing request URI format.

Closes gh-19444

Signed-off-by: Andrey Litvitski <andrey1010102008@gmail.com>
2026-07-31 13:19:21 -06:00
Josh Cummings 2104c49597 Add tests for OAuth2PushedAuthorizationRequestUri
Adds baseline test coverage for OAuth2PushedAuthorizationRequestUri,
which previously had none: a round-trip via create()/parse() and an
explicit parse() case on a hand-constructed request URI.

See gh-19444.

Signed-off-by: Josh Cummings <3627351+jzheaux@users.noreply.github.com>
2026-07-31 13:19:21 -06:00
Josh Cummings 5bee669ae3 Merge branch '7.0.x' 2026-07-31 12:31:45 -06:00
Josh Cummings c5b1f78118 Support Testing Against Snapshot Spring LDAP Versions
Adds an opt-in override to spring-security-dependencies so it can be
built against a specific Spring LDAP version instead of whatever's
declared in the version catalog, matching the existing pattern for
Spring Framework/Reactor/Spring Data/Micrometer. Passing
-PisOverrideVersionCatalog=true -PspringLdapVersion=X takes precedence
over the catalog. Default behavior (no properties passed) is unchanged.

Closes gh-19481

Signed-off-by: Josh Cummings <3627351+jzheaux@users.noreply.github.com>
2026-07-31 12:31:17 -06:00
dependabot[bot] f94cef17b8 Bump ch.qos.logback:logback-classic from 1.6.0 to 1.6.1
Bumps [ch.qos.logback:logback-classic](https://github.com/qos-ch/logback) from 1.6.0 to 1.6.1.
- [Release notes](https://github.com/qos-ch/logback/releases)
- [Commits](https://github.com/qos-ch/logback/compare/v_1.6.0...v_1.6.1)

---
updated-dependencies:
- dependency-name: ch.qos.logback:logback-classic
  dependency-version: 1.6.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-29 03:16:09 +00:00
dependabot[bot] 25f2e1c2a3 Bump ch.qos.logback:logback-classic from 1.5.38 to 1.6.0
Bumps [ch.qos.logback:logback-classic](https://github.com/qos-ch/logback) from 1.5.38 to 1.6.0.
- [Release notes](https://github.com/qos-ch/logback/releases)
- [Commits](https://github.com/qos-ch/logback/compare/v_1.5.38...v_1.6.0)

---
updated-dependencies:
- dependency-name: ch.qos.logback:logback-classic
  dependency-version: 1.6.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-24 03:13:25 +00:00
dependabot[bot] 67a3de25eb Bump actions/checkout from 7.0.0 to 7.0.1
Bumps [actions/checkout](https://github.com/actions/checkout) from 7.0.0 to 7.0.1.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0...3d3c42e5aac5ba805825da76410c181273ba90b1)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 7.0.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-24 00:44:38 +00:00
dependabot[bot] 5bd6b4a570 Bump actions/setup-java from 5.5.0 to 5.6.0
Bumps [actions/setup-java](https://github.com/actions/setup-java) from 5.5.0 to 5.6.0.
- [Release notes](https://github.com/actions/setup-java/releases)
- [Commits](https://github.com/actions/setup-java/compare/0f481fcb613427c0f801b606911222b5b6f3083a...03ad4de0992f5dab5e18fcb136590ce7c4a0ac95)

---
updated-dependencies:
- dependency-name: actions/setup-java
  dependency-version: 5.6.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-17 00:45:28 +00:00
dependabot[bot] 08dcb1c0e4 Bump org-jetbrains-kotlin from 2.4.0 to 2.4.10
Bumps `org-jetbrains-kotlin` from 2.4.0 to 2.4.10.

Updates `org.jetbrains.kotlin:kotlin-bom` from 2.4.0 to 2.4.10
- [Release notes](https://github.com/JetBrains/kotlin/releases)
- [Changelog](https://github.com/JetBrains/kotlin/blob/master/ChangeLog.md)
- [Commits](https://github.com/JetBrains/kotlin/compare/v2.4.0...v2.4.10)

Updates `org.jetbrains.kotlin:kotlin-gradle-plugin` from 2.4.0 to 2.4.10
- [Release notes](https://github.com/JetBrains/kotlin/releases)
- [Changelog](https://github.com/JetBrains/kotlin/blob/master/ChangeLog.md)
- [Commits](https://github.com/JetBrains/kotlin/compare/v2.4.0...v2.4.10)

---
updated-dependencies:
- dependency-name: org.jetbrains.kotlin:kotlin-bom
  dependency-version: 2.4.10
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.jetbrains.kotlin:kotlin-gradle-plugin
  dependency-version: 2.4.10
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-15 03:20:37 +00:00
dependabot[bot] e1ad5d2392 Bump org.hibernate.orm:hibernate-core from 7.4.4.Final to 7.4.5.Final
Bumps [org.hibernate.orm:hibernate-core](https://github.com/hibernate/hibernate-orm) from 7.4.4.Final to 7.4.5.Final.
- [Release notes](https://github.com/hibernate/hibernate-orm/releases)
- [Changelog](https://github.com/hibernate/hibernate-orm/blob/7.4.5/changelog.txt)
- [Commits](https://github.com/hibernate/hibernate-orm/compare/7.4.4...7.4.5)

---
updated-dependencies:
- dependency-name: org.hibernate.orm:hibernate-core
  dependency-version: 7.4.5.Final
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-14 03:16:11 +00:00
dependabot[bot] 7654aa2f6f Bump org.junit:junit-bom from 6.1.1 to 6.1.2
Bumps [org.junit:junit-bom](https://github.com/junit-team/junit-framework) from 6.1.1 to 6.1.2.
- [Release notes](https://github.com/junit-team/junit-framework/releases)
- [Commits](https://github.com/junit-team/junit-framework/compare/r6.1.1...r6.1.2)

---
updated-dependencies:
- dependency-name: org.junit:junit-bom
  dependency-version: 6.1.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-14 03:15:24 +00:00
dependabot[bot] a29d4ad5a4 Bump com.nimbusds:oauth2-oidc-sdk from 11.37.2 to 11.38.1
Bumps [com.nimbusds:oauth2-oidc-sdk](https://bitbucket.org/connect2id/oauth-2.0-sdk-with-openid-connect-extensions) from 11.37.2 to 11.38.1.
- [Changelog](https://bitbucket.org/connect2id/oauth-2.0-sdk-with-openid-connect-extensions/src/master/CHANGELOG.txt)
- [Commits](https://bitbucket.org/connect2id/oauth-2.0-sdk-with-openid-connect-extensions/branches/compare/11.38.1..11.37.2)

---
updated-dependencies:
- dependency-name: com.nimbusds:oauth2-oidc-sdk
  dependency-version: 11.38.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-13 03:17:43 +00:00
dependabot[bot] ab33b40f19 Bump org-bouncycastle from 1.84 to 1.85
Bumps `org-bouncycastle` from 1.84 to 1.85.

Updates `org.bouncycastle:bcpkix-jdk18on` from 1.84 to 1.85
- [Changelog](https://github.com/bcgit/bc-java/blob/main/docs/releasenotes.html)
- [Commits](https://github.com/bcgit/bc-java/commits)

Updates `org.bouncycastle:bcprov-jdk18on` from 1.84 to 1.85
- [Changelog](https://github.com/bcgit/bc-java/blob/main/docs/releasenotes.html)
- [Commits](https://github.com/bcgit/bc-java/commits)

---
updated-dependencies:
- dependency-name: org.bouncycastle:bcpkix-jdk18on
  dependency-version: '1.85'
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: org.bouncycastle:bcprov-jdk18on
  dependency-version: '1.85'
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-13 03:16:56 +00:00
dependabot[bot] d3d6497798 Bump tools.jackson:jackson-bom from 3.2.0 to 3.2.1
Bumps [tools.jackson:jackson-bom](https://github.com/FasterXML/jackson-bom) from 3.2.0 to 3.2.1.
- [Commits](https://github.com/FasterXML/jackson-bom/compare/jackson-bom-3.2.0...jackson-bom-3.2.1)

---
updated-dependencies:
- dependency-name: tools.jackson:jackson-bom
  dependency-version: 3.2.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-13 03:14:19 +00:00
20 changed files with 412 additions and 29 deletions
+1 -1
View File
@@ -15,7 +15,7 @@ jobs:
issues: write issues: write
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Compute Version - name: Compute Version
id: compute-version id: compute-version
uses: spring-io/spring-release-actions/compute-version@a1f321783a0769dd2aea4fad6c2ae2f95a52b885 # 0.0.5 uses: spring-io/spring-release-actions/compute-version@a1f321783a0769dd2aea4fad6c2ae2f95a52b885 # 0.0.5
+1 -1
View File
@@ -17,7 +17,7 @@ jobs:
if: github.repository_owner == 'spring-projects' if: github.repository_owner == 'spring-projects'
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with: with:
ref: docs-build ref: docs-build
fetch-depth: 1 fetch-depth: 1
@@ -20,9 +20,9 @@ jobs:
git config --global user.name 'github-actions[bot]' git config --global user.name 'github-actions[bot]'
git config --global user.email 'github-actions[bot]@users.noreply.github.com' git config --global user.email 'github-actions[bot]@users.noreply.github.com'
- name: Checkout - name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up JDK 25 - name: Set up JDK 25
uses: actions/setup-java@0f481fcb613427c0f801b606911222b5b6f3083a # v5.5.0 uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5.6.0
with: with:
java-version: '25' java-version: '25'
distribution: 'temurin' distribution: 'temurin'
+2 -2
View File
@@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: ${{ github.repository == 'spring-projects/spring-security' }} if: ${{ github.repository == 'spring-projects/spring-security' }}
steps: steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up gradle - name: Set up gradle
uses: spring-io/spring-gradle-build-action@c8668747d7c264864c8c7f7026d0d277d14a78dc # v2.0.6 uses: spring-io/spring-gradle-build-action@c8668747d7c264864c8c7f7026d0d277d14a78dc # v2.0.6
with: with:
@@ -24,7 +24,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: ${{ github.repository == 'spring-projects/spring-security' }} if: ${{ github.repository == 'spring-projects/spring-security' }}
steps: steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up gradle - name: Set up gradle
uses: spring-io/spring-gradle-build-action@c8668747d7c264864c8c7f7026d0d277d14a78dc # v2.0.6 uses: spring-io/spring-gradle-build-action@c8668747d7c264864c8c7f7026d0d277d14a78dc # v2.0.6
with: with:
+1 -1
View File
@@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with: with:
fetch-depth: 1 fetch-depth: 1
- name: Dispatch - name: Dispatch
@@ -39,6 +39,9 @@ import org.springframework.util.ClassUtils;
@Deprecated @Deprecated
final class GlobalMethodSecuritySelector implements ImportSelector { final class GlobalMethodSecuritySelector implements ImportSelector {
private static final boolean isAccessPresent = ClassUtils.isPresent(
"org.springframework.security.access.intercept.aopalliance.MethodSecurityMetadataSourceAdvisor", null);
@Override @Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) { public String[] selectImports(AnnotationMetadata importingClassMetadata) {
Class<EnableGlobalMethodSecurity> annoType = EnableGlobalMethodSecurity.class; Class<EnableGlobalMethodSecurity> annoType = EnableGlobalMethodSecurity.class;
@@ -59,16 +62,26 @@ final class GlobalMethodSecuritySelector implements ImportSelector {
boolean jsr250Enabled = attributes.getBoolean("jsr250Enabled"); boolean jsr250Enabled = attributes.getBoolean("jsr250Enabled");
List<String> classNames = new ArrayList<>(4); List<String> classNames = new ArrayList<>(4);
if (isProxy) { if (isProxy) {
assertAccessModulePresent();
classNames.add(MethodSecurityMetadataSourceAdvisorRegistrar.class.getName()); classNames.add(MethodSecurityMetadataSourceAdvisorRegistrar.class.getName());
} }
classNames.add(autoProxyClassName); classNames.add(autoProxyClassName);
if (!skipMethodSecurityConfiguration) { if (!skipMethodSecurityConfiguration) {
assertAccessModulePresent();
classNames.add(GlobalMethodSecurityConfiguration.class.getName()); classNames.add(GlobalMethodSecurityConfiguration.class.getName());
} }
if (jsr250Enabled) { if (jsr250Enabled) {
assertAccessModulePresent();
classNames.add(Jsr250MetadataSourceConfiguration.class.getName()); classNames.add(Jsr250MetadataSourceConfiguration.class.getName());
} }
return classNames.toArray(new String[0]); return classNames.toArray(new String[0]);
} }
private static void assertAccessModulePresent() {
Assert.state(isAccessPresent,
() -> "@EnableGlobalMethodSecurity requires the spring-security-access dependency on the "
+ "classpath. Please add spring-security-access, or migrate to @EnableMethodSecurity "
+ "which does not require it.");
}
} }
@@ -26,6 +26,7 @@ import org.springframework.context.annotation.AutoProxyRegistrar;
import org.springframework.context.annotation.ImportSelector; import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata; import org.springframework.core.type.AnnotationMetadata;
import org.springframework.lang.NonNull; import org.springframework.lang.NonNull;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
/** /**
@@ -35,6 +36,8 @@ import org.springframework.util.ClassUtils;
*/ */
class ReactiveMethodSecuritySelector implements ImportSelector { class ReactiveMethodSecuritySelector implements ImportSelector {
private static final String METHOD_SECURITY_METADATA_SOURCE_ADVISOR = "org.springframework.security.access.intercept.aopalliance.MethodSecurityMetadataSourceAdvisor";
private static final boolean isDataPresent = ClassUtils private static final boolean isDataPresent = ClassUtils
.isPresent("org.springframework.security.data.aot.hint.AuthorizeReturnObjectDataHintsRegistrar", null); .isPresent("org.springframework.security.data.aot.hint.AuthorizeReturnObjectDataHintsRegistrar", null);
@@ -56,6 +59,11 @@ class ReactiveMethodSecuritySelector implements ImportSelector {
imports.add(ReactiveAuthorizationManagerMethodSecurityConfiguration.class.getName()); imports.add(ReactiveAuthorizationManagerMethodSecurityConfiguration.class.getName());
} }
else { else {
Assert.state(
ClassUtils.isPresent(METHOD_SECURITY_METADATA_SOURCE_ADVISOR, ClassUtils.getDefaultClassLoader()),
() -> "@EnableReactiveMethodSecurity(useAuthorizationManager = false) requires the "
+ "spring-security-access dependency on the classpath. Please add spring-security-access, "
+ "or use the default useAuthorizationManager = true which does not require it.");
imports.add(ReactiveMethodSecurityConfiguration.class.getName()); imports.add(ReactiveMethodSecurityConfiguration.class.getName());
} }
if (isDataPresent) { if (isDataPresent) {
@@ -0,0 +1,138 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF 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.method.configuration;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.AdviceMode;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.test.support.ClassPathExclusions;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* Tests for gh-19441: {@code spring-security-access} moved
* {@link org.springframework.security.access.intercept.aopalliance.MethodSecurityMetadataSourceAdvisor}
* out of {@code spring-security-core} and into the optional
* {@code spring-security-access} module. {@link EnableMethodSecurity} and
* {@link EnableReactiveMethodSecurity}'s default (AuthorizationManager-based) mode never
* needed that class and continue to work without {@code spring-security-access} on the
* classpath, but the deprecated legacy method security annotations do need it and
* previously failed with a confusing {@link NoClassDefFoundError} instead of an
* actionable message.
*/
@ClassPathExclusions("spring-security-access-*.jar")
public class Gh19441Tests {
@Test
public void enableMethodSecurityWhenAccessModuleAbsentThenContextStartsCleanly() {
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext()) {
context.register(EnableMethodSecurityConfig.class);
context.refresh();
}
}
@Test
public void enableReactiveMethodSecurityWhenAccessModuleAbsentThenContextStartsCleanly() {
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext()) {
context.register(EnableReactiveMethodSecurityConfig.class);
context.refresh();
}
}
@Test
public void enableGlobalMethodSecurityWhenProxyModeAndAccessModuleAbsentThenClearException() {
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext()) {
context.register(EnableGlobalMethodSecurityProxyConfig.class);
assertThatExceptionOfType(Exception.class).isThrownBy(context::refresh)
.havingRootCause()
.isInstanceOf(IllegalStateException.class)
.withMessageContaining("spring-security-access");
}
}
@Test
public void enableGlobalMethodSecurityWhenAspectJModeAndAccessModuleAbsentThenClearException() {
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext()) {
context.register(EnableGlobalMethodSecurityAspectJConfig.class);
assertThatExceptionOfType(Exception.class).isThrownBy(context::refresh)
.havingRootCause()
.isInstanceOf(IllegalStateException.class)
.withMessageContaining("spring-security-access");
}
}
@Test
public void enableReactiveMethodSecurityWhenUseAuthorizationManagerFalseAndAccessModuleAbsentThenClearException() {
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext()) {
context.register(EnableReactiveMethodSecurityLegacyConfig.class);
assertThatExceptionOfType(Exception.class).isThrownBy(context::refresh)
.havingRootCause()
.isInstanceOf(IllegalStateException.class)
.withMessageContaining("spring-security-access");
}
}
@Test
public void enableGlobalMethodSecurityWhenAspectJModeAndJsr250EnabledAndConfigurationSubclassedAndAccessModuleAbsentThenClearException() {
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext()) {
context.register(EnableGlobalMethodSecurityAspectJJsr250SubclassedConfig.class);
assertThatExceptionOfType(Exception.class).isThrownBy(context::refresh)
.havingRootCause()
.isInstanceOf(IllegalStateException.class)
.withMessageContaining("spring-security-access");
}
}
@Configuration
@EnableMethodSecurity
static class EnableMethodSecurityConfig {
}
@Configuration
@EnableReactiveMethodSecurity
static class EnableReactiveMethodSecurityConfig {
}
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
static class EnableGlobalMethodSecurityProxyConfig {
}
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true, mode = AdviceMode.ASPECTJ)
static class EnableGlobalMethodSecurityAspectJConfig {
}
@Configuration
@EnableReactiveMethodSecurity(useAuthorizationManager = false)
static class EnableReactiveMethodSecurityLegacyConfig {
}
@Configuration
@EnableGlobalMethodSecurity(jsr250Enabled = true, mode = AdviceMode.ASPECTJ)
static class EnableGlobalMethodSecurityAspectJJsr250SubclassedConfig extends GlobalMethodSecurityConfiguration {
}
}
+5 -1
View File
@@ -76,7 +76,11 @@ dependencies {
api libs.org.skyscreamer.jsonassert api libs.org.skyscreamer.jsonassert
api libs.org.slf4j.log4j.over.slf4j api libs.org.slf4j.log4j.over.slf4j
api libs.org.slf4j.slf4j.api api libs.org.slf4j.slf4j.api
api libs.org.springframework.ldap.spring.ldap.core if (project.hasProperty("isOverrideVersionCatalog") && project.hasProperty("springLdapVersion")) {
api "org.springframework.ldap:spring-ldap-core:${springLdapVersion}"
} else {
api libs.org.springframework.ldap.spring.ldap.core
}
api libs.org.synchronoss.cloud.nio.multipart.parser api libs.org.synchronoss.cloud.nio.multipart.parser
api libs.org.apache.maven.resolver.maven.resolver.connector.basic api libs.org.apache.maven.resolver.maven.resolver.connector.basic
api libs.org.apache.maven.resolver.maven.resolver.impl api libs.org.apache.maven.resolver.maven.resolver.impl
@@ -23,10 +23,28 @@ When using a proxy server, it is important to ensure that you have configured yo
For example, many applications have a load balancer that responds to request for `\https://example.com/` by forwarding the request to an application server at `\https://192.168.0.107` For example, many applications have a load balancer that responds to request for `\https://example.com/` by forwarding the request to an application server at `\https://192.168.0.107`
Without proper configuration, the application server can not know that the load balancer exists and treats the request as though `\https://192.168.0.107:8080` was requested by the client. Without proper configuration, the application server can not know that the load balancer exists and treats the request as though `\https://192.168.0.107:8080` was requested by the client.
To fix this, you can use https://tools.ietf.org/html/rfc7239[RFC 7239] to specify that a load balancer is being used. To fix this, the proxy needs to pass on the details of the original request, and the application needs to be configured to use them.
To make the application aware of this, you need to configure your application server to be aware of the X-Forwarded headers. Two kinds of headers are used for this, and it is important to know which of them applies in your deployment:
* The standard `Forwarded` header, defined by https://tools.ietf.org/html/rfc7239[RFC 7239], which carries the original host, protocol, and client in a single header.
* The non-standard `X-Forwarded-*` headers, such as `X-Forwarded-Host`, `X-Forwarded-Proto`, and `X-Forwarded-For`, which predate RFC 7239.
Most proxies still send the `X-Forwarded-*` headers rather than the standard `Forwarded` header, while Spring Framework and servers such as Reactor Netty and Jetty understand both.
Do not assume that only one of them is in use.
[NOTE]
====
Both kinds of headers are supplied by the client unless a proxy overwrites them, so an application that trusts them without a trusted proxy in front of it can be made to believe a request arrived over a different host, protocol, or client address than it really did.
====
For this reason, the proxy at the edge of your network must be configured to remove or overwrite any forwarded headers that arrive from the outside, for _both_ kinds of headers.
Dropping only the `Forwarded` header while passing through `X-Forwarded-*` (or the reverse) leaves the application open to the same spoofing through the other set.
Only headers added by a proxy you control should reach the application.
Once untrusted values are handled at the edge, the application server can be configured to apply the headers.
For example, Tomcat uses https://tomcat.apache.org/tomcat-10.1-doc/api/org/apache/catalina/valves/RemoteIpValve.html[`RemoteIpValve`] and Jetty uses https://eclipse.dev/jetty/javadoc/jetty-11/org/eclipse/jetty/server/ForwardedRequestCustomizer.html[`ForwardedRequestCustomizer`]. For example, Tomcat uses https://tomcat.apache.org/tomcat-10.1-doc/api/org/apache/catalina/valves/RemoteIpValve.html[`RemoteIpValve`] and Jetty uses https://eclipse.dev/jetty/javadoc/jetty-11/org/eclipse/jetty/server/ForwardedRequestCustomizer.html[`ForwardedRequestCustomizer`].
Alternatively, Spring users can use {spring-framework-reference-url}web/webmvc/filters.html#filters-forwarded-headers[`ForwardedHeaderFilter`] with the Servlet stack or {spring-framework-reference-url}web/webflux/reactive-spring.html#webflux-forwarded-headers[`ForwardedHeaderTransformer`] with the Reactive stack. Alternatively, Spring users can use {spring-framework-reference-url}web/webmvc/filters.html#filters-forwarded-headers[`ForwardedHeaderFilter`] with the Servlet stack or {spring-framework-reference-url}web/webflux/reactive-spring.html#webflux-forwarded-headers[`ForwardedHeaderTransformer`] with the Reactive stack.
Both handle the `Forwarded` header and the `X-Forwarded-*` headers, and both can be configured to remove the headers instead of applying them, which is useful when the application is not behind a proxy.
Spring Boot users can use the `server.forward-headers-strategy` property to configure the application. Spring Boot users can use the `server.forward-headers-strategy` property to configure the application.
See the {spring-boot-reference-url}how-to/webserver.html#howto.webserver.use-behind-a-proxy-server[Spring Boot documentation] for further details. See the {spring-boot-reference-url}how-to/webserver.html#howto.webserver.use-behind-a-proxy-server[Spring Boot documentation] for further details.
@@ -132,4 +132,11 @@ authenticationConverter.setBearerTokenResolver(myBearerTokenResolver)
authenticationConverter.setAuthenticationDetailsSource(myAuthenticationDetailsSource) authenticationConverter.setAuthenticationDetailsSource(myAuthenticationDetailsSource)
val filter = BearerTokenAuthenticationFilter(authenticationManager, authenticationConverter) val filter = BearerTokenAuthenticationFilter(authenticationManager, authenticationConverter)
---- ----
== `NimbusJwtDecoder`'s Default Connect and Read Timeouts Are Now 30 Seconds
`NimbusJwtDecoder`'s default `RestOperations`, used to fetch a JWK Set when no `RestOperations` is otherwise configured, previously used a 500 millisecond connect and read timeout.
This value was too short for many deployments and is now 30 seconds, aligning with the connect and read timeouts already used elsewhere in the OAuth2 Client and Resource Server support, such as xref:servlet/oauth2/client/index.adoc[`ClientRegistrations`].
If your application relies on the previous, shorter timeout -- for example, expecting a fast failure when the authorization server is unreachable -- you can restore it either by setting the JDK's `sun.net.client.defaultConnectTimeout` and `sun.net.client.defaultReadTimeout` system properties (in milliseconds), or by providing your own `RestOperations`, as described in xref:servlet/oauth2/resource-server/jwt.adoc#oauth2resourceserver-jwt-timeouts[Configuring Timeouts].
====== ======
@@ -944,5 +944,52 @@ For MAC-based algorithms (such as `HS256`, `HS384`, or `HS512`), the `client-sec
If more than one `ClientRegistration` is configured for OpenID Connect 1.0 Authentication, the JWS algorithm resolver may evaluate the provided `ClientRegistration` to determine which algorithm to return. If more than one `ClientRegistration` is configured for OpenID Connect 1.0 Authentication, the JWS algorithm resolver may evaluate the provided `ClientRegistration` to determine which algorithm to return.
==== ====
[[oauth2login-advanced-idtoken-decoder-factory]]
== Providing a Custom JwtDecoderFactory
`OidcIdTokenDecoderFactory` is deliberately narrow: it exists to make it easy to support multiple `ClientRegistration` instances, each potentially needing a different `JwtDecoder`, and its configuration surface is limited to what most applications need for that purpose, such as the JWS algorithm resolver shown above.
If your application needs deeper control over how the ID Token's `JwtDecoder` is constructed -- for example, providing your own `RestOperations` -- you can instead provide your own `JwtDecoderFactory<ClientRegistration>` `@Bean`.
Because OAuth2 Login does not use a resource server `JwtDecoder` bean for this purpose, this is the supported way to reach the same level of control that `NimbusJwtDecoder`'s builder already offers, without introducing additional configuration properties on `OidcIdTokenDecoderFactory` itself.
For a single `ClientRegistration`, this can be as simple as:
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
JwtDecoderFactory<ClientRegistration> idTokenDecoderFactory(RestOperations rest) {
return (clientRegistration) -> {
String issuerUri = clientRegistration.getProviderDetails().getIssuerUri();
NimbusJwtDecoder decoder = NimbusJwtDecoder.withIssuerLocation(issuerUri).restOperations(rest).build();
decoder.setJwtValidator(JwtValidators.createDefaultWithValidators(new OidcIdTokenValidator(clientRegistration)));
decoder.setClaimTypeConverter(OidcIdTokenDecoderFactory.createDefaultClaimTypeConverter());
return decoder;
};
}
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Bean
fun idTokenDecoderFactory(rest: RestOperations): JwtDecoderFactory<ClientRegistration> {
return JwtDecoderFactory { clientRegistration ->
val issuerUri = clientRegistration.providerDetails.issuerUri
val decoder = NimbusJwtDecoder.withIssuerLocation(issuerUri).restOperations(rest).build()
decoder.setJwtValidator(JwtValidators.createDefaultWithValidators(OidcIdTokenValidator(clientRegistration)))
decoder.setClaimTypeConverter(OidcIdTokenDecoderFactory.createDefaultClaimTypeConverter())
decoder
}
}
----
======
If you have multiple `ClientRegistration` instances and want to avoid rebuilding a `JwtDecoder` on every request, cache the decoder per registration, for example by annotating the factory method with `@Cacheable` or by keying a `Map` on `ClientRegistration#getRegistrationId()`.
[[oauth2login-advanced-oidc-logout]] [[oauth2login-advanced-oidc-logout]]
Then, you can proceed to configure xref:servlet/oauth2/login/logout.adoc[logout] Then, you can proceed to configure xref:servlet/oauth2/login/logout.adoc[logout]
@@ -1622,6 +1622,7 @@ fun jwtDecoder(): JwtDecoder {
== Configuring Timeouts == Configuring Timeouts
By default, Resource Server uses connection and socket timeouts of 30 seconds each for coordinating with the authorization server. By default, Resource Server uses connection and socket timeouts of 30 seconds each for coordinating with the authorization server.
You can override these defaults without changing any code by setting the JDK's `sun.net.client.defaultConnectTimeout` and `sun.net.client.defaultReadTimeout` system properties (in milliseconds).
This may be too short in some scenarios. This may be too short in some scenarios.
Further, it doesn't take into account more sophisticated patterns like back-off and discovery. Further, it doesn't take into account more sophisticated patterns like back-off and discovery.
+9 -9
View File
@@ -7,10 +7,10 @@ io-spring-nohttp = "0.0.11"
jakarta-websocket = "2.2.0" jakarta-websocket = "2.2.0"
org-apache-maven-resolver = "1.9.27" org-apache-maven-resolver = "1.9.27"
org-aspectj = "1.9.25.1" org-aspectj = "1.9.25.1"
org-bouncycastle = "1.84" org-bouncycastle = "1.85"
org-eclipse-jetty = "11.0.26" org-eclipse-jetty = "11.0.26"
org-jetbrains-dokka = "2.2.0" org-jetbrains-dokka = "2.2.0"
org-jetbrains-kotlin = "2.4.0" org-jetbrains-kotlin = "2.4.10"
org-jetbrains-kotlinx = "1.11.0" org-jetbrains-kotlinx = "1.11.0"
org-mockito = "5.23.0" org-mockito = "5.23.0"
org-opensaml5 = "5.2.3" org-opensaml5 = "5.2.3"
@@ -18,12 +18,12 @@ org-springframework = "7.0.8"
com-password4j = "1.8.4" com-password4j = "1.8.4"
[libraries] [libraries]
ch-qos-logback-logback-classic = "ch.qos.logback:logback-classic:1.5.38" ch-qos-logback-logback-classic = "ch.qos.logback:logback-classic:1.6.1"
com-fasterxml-jackson-jackson-bom = "com.fasterxml.jackson:jackson-bom:2.22.1" com-fasterxml-jackson-jackson-bom = "com.fasterxml.jackson:jackson-bom:2.22.1"
com-google-inject-guice = "com.google.inject:guice:3.0" 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" com-netflix-nebula-nebula-project-plugin = "com.netflix.nebula:nebula-project-plugin:8.2.0"
com-nimbusds-nimbus-jose-jwt = "com.nimbusds:nimbus-jose-jwt:10.9" com-nimbusds-nimbus-jose-jwt = "com.nimbusds:nimbus-jose-jwt:10.9"
com-nimbusds-oauth2-oidc-sdk = "com.nimbusds:oauth2-oidc-sdk:11.37.2" com-nimbusds-oauth2-oidc-sdk = "com.nimbusds:oauth2-oidc-sdk:11.38.1"
com-squareup-okhttp3-mockwebserver = { module = "com.squareup.okhttp3:mockwebserver", version.ref = "com-squareup-okhttp3" } 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-squareup-okhttp3-okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "com-squareup-okhttp3" }
com-unboundid-unboundid-ldapsdk = "com.unboundid:unboundid-ldapsdk:7.0.5" com-unboundid-unboundid-ldapsdk = "com.unboundid:unboundid-ldapsdk:7.0.5"
@@ -52,7 +52,7 @@ jakarta-xml-bind-jakarta-xml-bind-api = "jakarta.xml.bind:jakarta.xml.bind-api:4
ldapsdk = "ldapsdk:ldapsdk:4.1" ldapsdk = "ldapsdk:ldapsdk:4.1"
net-sourceforge-htmlunit = "net.sourceforge.htmlunit:htmlunit:2.70.0" net-sourceforge-htmlunit = "net.sourceforge.htmlunit:htmlunit:2.70.0"
org-htmlunit-htmlunit = "org.htmlunit:htmlunit:4.21.0" org-htmlunit-htmlunit = "org.htmlunit:htmlunit:4.21.0"
org-apache-httpcomponents-httpclient = "org.apache.httpcomponents.client5:httpclient5:5.6.2" org-apache-httpcomponents-httpclient = "org.apache.httpcomponents.client5:httpclient5:5.6.3"
org-apache-kerby-simplekdc='org.apache.kerby:kerb-simplekdc:2.1.2' org-apache-kerby-simplekdc='org.apache.kerby:kerb-simplekdc:2.1.2'
org-apache-maven-maven-resolver-provider = "org.apache.maven:maven-resolver-provider:3.9.16" org-apache-maven-maven-resolver-provider = "org.apache.maven:maven-resolver-provider:3.9.16"
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-connector-basic = { module = "org.apache.maven.resolver:maven-resolver-connector-basic", version.ref = "org-apache-maven-resolver" }
@@ -68,13 +68,13 @@ org-bouncycastle-bcprov-jdk15on = { module = "org.bouncycastle:bcprov-jdk18on",
org-eclipse-jetty-jetty-server = { module = "org.eclipse.jetty:jetty-server", version.ref = "org-eclipse-jetty" } 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-eclipse-jetty-jetty-servlet = { module = "org.eclipse.jetty:jetty-servlet", version.ref = "org-eclipse-jetty" }
org-hamcrest = "org.hamcrest:hamcrest:2.2" org-hamcrest = "org.hamcrest:hamcrest:2.2"
org-hibernate-orm-hibernate-core = "org.hibernate.orm:hibernate-core:7.4.4.Final" org-hibernate-orm-hibernate-core = "org.hibernate.orm:hibernate-core:7.4.5.Final"
org-hsqldb = "org.hsqldb:hsqldb:2.7.4" org-hsqldb = "org.hsqldb:hsqldb:2.7.4"
org-jetbrains-dokka-dokka-gradle-plugin = { module = "org.jetbrains.dokka:dokka-gradle-plugin", version.ref = "org-jetbrains-dokka" } org-jetbrains-dokka-dokka-gradle-plugin = { module = "org.jetbrains.dokka:dokka-gradle-plugin", version.ref = "org-jetbrains-dokka" }
org-jetbrains-kotlin-kotlin-bom = { module = "org.jetbrains.kotlin:kotlin-bom", version.ref = "org-jetbrains-kotlin" } org-jetbrains-kotlin-kotlin-bom = { module = "org.jetbrains.kotlin:kotlin-bom", version.ref = "org-jetbrains-kotlin" }
org-jetbrains-kotlin-kotlin-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "org-jetbrains-kotlin" } org-jetbrains-kotlin-kotlin-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "org-jetbrains-kotlin" }
org-jetbrains-kotlinx-kotlinx-coroutines-bom = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-bom", version.ref = "org-jetbrains-kotlinx" } 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:6.1.1" org-junit-junit-bom = "org.junit:junit-bom:6.1.2"
org-mockito-mockito-bom = { module = "org.mockito:mockito-bom", version.ref = "org-mockito" } org-mockito-mockito-bom = { module = "org.mockito:mockito-bom", version.ref = "org-mockito" }
org-opensaml-opensaml5-saml-api = { module = "org.opensaml:opensaml-saml-api", version.ref = "org-opensaml5" } org-opensaml-opensaml5-saml-api = { module = "org.opensaml:opensaml-saml-api", version.ref = "org-opensaml5" }
org-opensaml-opensaml5-saml-impl = { module = "org.opensaml:opensaml-saml-impl", version.ref = "org-opensaml5" } org-opensaml-opensaml5-saml-impl = { module = "org.opensaml:opensaml-saml-impl", version.ref = "org-opensaml5" }
@@ -89,7 +89,7 @@ org-springframework-data-spring-data-bom = "org.springframework.data:spring-data
org-springframework-ldap-spring-ldap-core = "org.springframework.ldap:spring-ldap-core:4.1.0" org-springframework-ldap-spring-ldap-core = "org.springframework.ldap:spring-ldap-core:4.1.0"
org-springframework-spring-framework-bom = { module = "org.springframework:spring-framework-bom", version.ref = "org-springframework" } 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" org-synchronoss-cloud-nio-multipart-parser = "org.synchronoss.cloud:nio-multipart-parser:1.1.0"
tools-jackson-jackson-bom = "tools.jackson:jackson-bom:3.2.0" tools-jackson-jackson-bom = "tools.jackson:jackson-bom:3.2.1"
com-google-code-gson-gson = "com.google.code.gson:gson:2.14.0" com-google-code-gson-gson = "com.google.code.gson:gson:2.14.0"
com-thaiopensource-trag = "com.thaiopensource:trang:20091111" com-thaiopensource-trag = "com.thaiopensource:trang:20091111"
@@ -106,7 +106,7 @@ org-sonarsource-scanner-gradle-sonarqube-gradle-plugin = "org.sonarsource.scanne
org-instancio-instancio-junit = "org.instancio:instancio-junit:3.7.1" org-instancio-instancio-junit = "org.instancio:instancio-junit:3.7.1"
spring-nullability = 'io.spring.nullability:io.spring.nullability.gradle.plugin:0.0.14' spring-nullability = 'io.spring.nullability:io.spring.nullability.gradle.plugin:0.0.14'
webauthn4j-core = 'com.webauthn4j:webauthn4j-core:0.31.8.RELEASE' webauthn4j-core = 'com.webauthn4j:webauthn4j-core:0.31.9.RELEASE'
com-password4j-password4j = { module = "com.password4j:password4j", version.ref = "com-password4j" } com-password4j-password4j = { module = "com.password4j:password4j", version.ref = "com-password4j" }
[plugins] [plugins]
@@ -27,6 +27,7 @@ import org.springframework.security.crypto.keygen.StringKeyGenerator;
* Requests. * Requests.
* *
* @author Joe Grandja * @author Joe Grandja
* @author Andrey Litvitski
* @since 7.0 * @since 7.0
*/ */
final class OAuth2PushedAuthorizationRequestUri { final class OAuth2PushedAuthorizationRequestUri {
@@ -57,7 +58,7 @@ final class OAuth2PushedAuthorizationRequestUri {
static OAuth2PushedAuthorizationRequestUri parse(String requestUri) { static OAuth2PushedAuthorizationRequestUri parse(String requestUri) {
int stateStartIndex = REQUEST_URI_PREFIX.length(); int stateStartIndex = REQUEST_URI_PREFIX.length();
int expiresAtStartIndex = requestUri.indexOf(REQUEST_URI_DELIMITER) + REQUEST_URI_DELIMITER.length(); int expiresAtStartIndex = requestUri.lastIndexOf(REQUEST_URI_DELIMITER) + REQUEST_URI_DELIMITER.length();
String state = requestUri.substring(stateStartIndex); String state = requestUri.substring(stateStartIndex);
Instant expiresAt = Instant.ofEpochMilli(Long.parseLong(requestUri.substring(expiresAtStartIndex))); Instant expiresAt = Instant.ofEpochMilli(Long.parseLong(requestUri.substring(expiresAtStartIndex)));
return new OAuth2PushedAuthorizationRequestUri(requestUri, state, expiresAt); return new OAuth2PushedAuthorizationRequestUri(requestUri, state, expiresAt);
@@ -0,0 +1,71 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.server.authorization.authentication;
import java.time.Instant;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link OAuth2PushedAuthorizationRequestUri}.
*
* @author Josh Cummings
* @author Andrey Litvitski
*/
public class OAuth2PushedAuthorizationRequestUriTests {
@Test
public void parseWhenValidRequestUriThenReturnsExpectedValues() {
String state = "abcXYZ123-abcXYZ123";
long epochMilli = 1700000000000L;
String requestUri = "urn:ietf:params:oauth:request_uri:" + state + "___" + epochMilli;
OAuth2PushedAuthorizationRequestUri parsed = OAuth2PushedAuthorizationRequestUri.parse(requestUri);
assertThat(parsed.getRequestUri()).isEqualTo(requestUri);
assertThat(parsed.getState()).isEqualTo(state + "___" + epochMilli);
assertThat(parsed.getExpiresAt()).isEqualTo(Instant.ofEpochMilli(epochMilli));
}
@Test
public void createWhenParsedThenReturnsEquivalentValues() {
Instant expiresAt = Instant.ofEpochMilli(1700000000000L);
OAuth2PushedAuthorizationRequestUri created = OAuth2PushedAuthorizationRequestUri.create(expiresAt);
OAuth2PushedAuthorizationRequestUri parsed = OAuth2PushedAuthorizationRequestUri.parse(created.getRequestUri());
assertThat(parsed.getRequestUri()).isEqualTo(created.getRequestUri());
assertThat(parsed.getState()).isEqualTo(created.getState());
assertThat(parsed.getExpiresAt()).isEqualTo(created.getExpiresAt());
}
@Test
public void parseWhenStateContainsDelimiterThenParsesSuccessfully() {
String state = "xXMGJTZwzXIFL8i_DFu_EM8IeWC___frCWjpiF2q-xs=";
long epochMillis = 1781670640281L;
String requestUri = "urn:ietf:params:oauth:request_uri:" + state + "___" + epochMillis;
OAuth2PushedAuthorizationRequestUri parsedUri = OAuth2PushedAuthorizationRequestUri.parse(requestUri);
assertThat(parsedUri.getRequestUri()).isEqualTo(requestUri);
assertThat(parsedUri.getState()).isEqualTo(state + "___" + epochMillis);
assertThat(parsedUri.getExpiresAt()).isEqualTo(Instant.ofEpochMilli(epochMillis));
}
}
@@ -66,11 +66,9 @@ final class JwtDecoderProviderConfigurationUtils {
private static final RestTemplate rest = new RestTemplate(); private static final RestTemplate rest = new RestTemplate();
static { static {
int connectTimeout = Integer.parseInt(System.getProperty("sun.net.client.defaultConnectTimeout", "30000"));
int readTimeout = Integer.parseInt(System.getProperty("sun.net.client.defaultReadTimeout", "30000"));
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory(); SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
requestFactory.setConnectTimeout(connectTimeout); requestFactory.setConnectTimeout(getConnectTimeout());
requestFactory.setReadTimeout(readTimeout); requestFactory.setReadTimeout(getReadTimeout());
rest.setRequestFactory(requestFactory); rest.setRequestFactory(requestFactory);
} }
@@ -80,6 +78,27 @@ final class JwtDecoderProviderConfigurationUtils {
private JwtDecoderProviderConfigurationUtils() { private JwtDecoderProviderConfigurationUtils() {
} }
/**
* Returns the default HTTP connect timeout, in milliseconds, for fetching
* provider/JWK Set metadata. Honors the JDK's
* {@code sun.net.client.defaultConnectTimeout} system property when set, otherwise
* defaults to 30 seconds.
* @return the default HTTP connect timeout, in milliseconds
*/
static int getConnectTimeout() {
return Integer.parseInt(System.getProperty("sun.net.client.defaultConnectTimeout", "30000"));
}
/**
* Returns the default HTTP read timeout, in milliseconds, for fetching provider/JWK
* Set metadata. Honors the JDK's {@code sun.net.client.defaultReadTimeout} system
* property when set, otherwise defaults to 30 seconds.
* @return the default HTTP read timeout, in milliseconds
*/
static int getReadTimeout() {
return Integer.parseInt(System.getProperty("sun.net.client.defaultReadTimeout", "30000"));
}
static Map<String, Object> getConfigurationForOidcIssuerLocation(String oidcIssuerLocation) { static Map<String, Object> getConfigurationForOidcIssuerLocation(String oidcIssuerLocation) {
return getConfiguration(oidcIssuerLocation, rest, oidc(oidcIssuerLocation)); return getConfiguration(oidcIssuerLocation, rest, oidc(oidcIssuerLocation));
} }
@@ -299,7 +299,7 @@ public final class NimbusJwtDecoder implements JwtDecoder {
private final Set<SignatureAlgorithm> signatureAlgorithms = new HashSet<>(); private final Set<SignatureAlgorithm> signatureAlgorithms = new HashSet<>();
private RestOperations restOperations = new RestTemplateWithNimbusDefaultTimeouts(); private RestOperations restOperations = new RestTemplateWithDefaultTimeouts();
private Cache cache = new NoOpCache("default"); private Cache cache = new NoOpCache("default");
@@ -577,12 +577,12 @@ public final class NimbusJwtDecoder implements JwtDecoder {
* A RestTemplate with timeouts configured to avoid blocking indefinitely when * A RestTemplate with timeouts configured to avoid blocking indefinitely when
* fetching JWK Sets while holding the reentrantLock. * fetching JWK Sets while holding the reentrantLock.
*/ */
private static final class RestTemplateWithNimbusDefaultTimeouts extends RestTemplate { private static final class RestTemplateWithDefaultTimeouts extends RestTemplate {
private RestTemplateWithNimbusDefaultTimeouts() { private RestTemplateWithDefaultTimeouts() {
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory(); SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
requestFactory.setConnectTimeout(JWKSourceBuilder.DEFAULT_HTTP_CONNECT_TIMEOUT); requestFactory.setConnectTimeout(JwtDecoderProviderConfigurationUtils.getConnectTimeout());
requestFactory.setReadTimeout(JWKSourceBuilder.DEFAULT_HTTP_READ_TIMEOUT); requestFactory.setReadTimeout(JwtDecoderProviderConfigurationUtils.getReadTimeout());
setRequestFactory(requestFactory); setRequestFactory(requestFactory);
} }
@@ -30,6 +30,7 @@ import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.source.JWKSource; import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext; import com.nimbusds.jose.proc.SecurityContext;
import com.nimbusds.jose.util.Base64URL; import com.nimbusds.jose.util.Base64URL;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.security.oauth2.jose.TestKeys; import org.springframework.security.oauth2.jose.TestKeys;
@@ -46,6 +47,40 @@ import static org.mockito.BDDMockito.mock;
public class JwtDecoderProviderConfigurationUtilsTests { public class JwtDecoderProviderConfigurationUtilsTests {
@AfterEach
public void cleanup() {
System.clearProperty("sun.net.client.defaultConnectTimeout");
System.clearProperty("sun.net.client.defaultReadTimeout");
}
// gh-19474
@Test
public void getConnectTimeoutWhenPropertyNotSetThenDefaultsToThirtySeconds() {
System.clearProperty("sun.net.client.defaultConnectTimeout");
assertThat(JwtDecoderProviderConfigurationUtils.getConnectTimeout()).isEqualTo(30000);
}
// gh-19474
@Test
public void getConnectTimeoutWhenPropertySetThenUses() {
System.setProperty("sun.net.client.defaultConnectTimeout", "5000");
assertThat(JwtDecoderProviderConfigurationUtils.getConnectTimeout()).isEqualTo(5000);
}
// gh-19474
@Test
public void getReadTimeoutWhenPropertyNotSetThenDefaultsToThirtySeconds() {
System.clearProperty("sun.net.client.defaultReadTimeout");
assertThat(JwtDecoderProviderConfigurationUtils.getReadTimeout()).isEqualTo(30000);
}
// gh-19474
@Test
public void getReadTimeoutWhenPropertySetThenUses() {
System.setProperty("sun.net.client.defaultReadTimeout", "5000");
assertThat(JwtDecoderProviderConfigurationUtils.getReadTimeout()).isEqualTo(5000);
}
@Test @Test
public void getSignatureAlgorithmsWhenJwkSetSpecifiesAlgorithmThenUses() throws Exception { public void getSignatureAlgorithmsWhenJwkSetSpecifiesAlgorithmThenUses() throws Exception {
JWKSource<SecurityContext> jwkSource = mock(JWKSource.class); JWKSource<SecurityContext> jwkSource = mock(JWKSource.class);
@@ -67,14 +67,17 @@ import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.RequestEntity; import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.security.oauth2.core.OAuth2Error; import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.OAuth2TokenValidator; import org.springframework.security.oauth2.core.OAuth2TokenValidator;
import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult; import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult;
import org.springframework.security.oauth2.jose.TestKeys; import org.springframework.security.oauth2.jose.TestKeys;
import org.springframework.security.oauth2.jose.jws.MacAlgorithm; import org.springframework.security.oauth2.jose.jws.MacAlgorithm;
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm; import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.web.client.RestClientException; import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestOperations; import org.springframework.web.client.RestOperations;
import org.springframework.web.client.RestTemplate;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
@@ -391,6 +394,24 @@ public class NimbusJwtDecoderTests {
// @formatter:on // @formatter:on
} }
// gh-19474
@Test
public void withJwkSetUriWhenDefaultRestOperationsThenUsesConfiguredTimeouts() {
try {
System.setProperty("sun.net.client.defaultConnectTimeout", "12345");
System.setProperty("sun.net.client.defaultReadTimeout", "23456");
NimbusJwtDecoder.JwkSetUriJwtDecoderBuilder builder = NimbusJwtDecoder.withJwkSetUri(JWK_SET_URI);
RestOperations restOperations = (RestOperations) ReflectionTestUtils.getField(builder, "restOperations");
ClientHttpRequestFactory requestFactory = ((RestTemplate) restOperations).getRequestFactory();
assertThat(ReflectionTestUtils.getField(requestFactory, "connectTimeout")).isEqualTo(12345);
assertThat(ReflectionTestUtils.getField(requestFactory, "readTimeout")).isEqualTo(23456);
}
finally {
System.clearProperty("sun.net.client.defaultConnectTimeout");
System.clearProperty("sun.net.client.defaultReadTimeout");
}
}
@Test @Test
public void cacheWhenNullThenThrowsException() { public void cacheWhenNullThenThrowsException() {
NimbusJwtDecoder.JwkSetUriJwtDecoderBuilder builder = NimbusJwtDecoder.withJwkSetUri(JWK_SET_URI); NimbusJwtDecoder.JwkSetUriJwtDecoderBuilder builder = NimbusJwtDecoder.withJwkSetUri(JWK_SET_URI);