1
0
mirror of synced 2026-07-13 14:45:10 +00:00

Compare commits

...

67 Commits

Author SHA1 Message Date
github-actions[bot] acc96f10be Release 6.2.4 2024-04-15 15:20:29 +00:00
github-actions[bot] 2ed013858a Merge branch '6.1.x' into 6.2.x 2024-04-15 04:06:08 +00:00
dependabot[bot] c74f6bdac4 Bump org.slf4j:slf4j-api from 2.0.12 to 2.0.13
Bumps org.slf4j:slf4j-api from 2.0.12 to 2.0.13.

---
updated-dependencies:
- dependency-name: org.slf4j:slf4j-api
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-14 21:05:20 -07:00
dependabot[bot] 82e8817c95 Bump org.slf4j:slf4j-api from 2.0.12 to 2.0.13
Bumps org.slf4j:slf4j-api from 2.0.12 to 2.0.13.

---
updated-dependencies:
- dependency-name: org.slf4j:slf4j-api
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-14 20:48:36 -07:00
dependabot[bot] e80ef2df6c Bump org.springframework.data:spring-data-bom from 2023.1.4 to 2023.1.5
Bumps [org.springframework.data:spring-data-bom](https://github.com/spring-projects/spring-data-bom) from 2023.1.4 to 2023.1.5.
- [Release notes](https://github.com/spring-projects/spring-data-bom/releases)
- [Commits](https://github.com/spring-projects/spring-data-bom/compare/2023.1.4...2023.1.5)

---
updated-dependencies:
- dependency-name: org.springframework.data:spring-data-bom
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-14 20:40:15 -07:00
dependabot[bot] 8065e2b308 Bump org.springframework:spring-framework-bom from 6.1.5 to 6.1.6
Bumps [org.springframework:spring-framework-bom](https://github.com/spring-projects/spring-framework) from 6.1.5 to 6.1.6.
- [Release notes](https://github.com/spring-projects/spring-framework/releases)
- [Commits](https://github.com/spring-projects/spring-framework/compare/v6.1.5...v6.1.6)

---
updated-dependencies:
- dependency-name: org.springframework:spring-framework-bom
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-11 21:14:13 -07:00
dependabot[bot] 5dd86473a3 Bump org.springframework.ldap:spring-ldap-core from 3.2.2 to 3.2.3
Bumps [org.springframework.ldap:spring-ldap-core](https://github.com/spring-projects/spring-ldap) from 3.2.2 to 3.2.3.
- [Release notes](https://github.com/spring-projects/spring-ldap/releases)
- [Changelog](https://github.com/spring-projects/spring-ldap/blob/main/changelog.txt)
- [Commits](https://github.com/spring-projects/spring-ldap/compare/3.2.2...3.2.3)

---
updated-dependencies:
- dependency-name: org.springframework.ldap:spring-ldap-core
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-11 21:10:15 -07:00
github-actions[bot] b2f7603e6d Merge branch '6.1.x' into 6.2.x 2024-04-12 03:39:04 +00:00
dependabot[bot] a55c5c1d0d Bump org.springframework:spring-framework-bom from 6.0.18 to 6.0.19
Bumps [org.springframework:spring-framework-bom](https://github.com/spring-projects/spring-framework) from 6.0.18 to 6.0.19.
- [Release notes](https://github.com/spring-projects/spring-framework/releases)
- [Commits](https://github.com/spring-projects/spring-framework/compare/v6.0.18...v6.0.19)

---
updated-dependencies:
- dependency-name: org.springframework:spring-framework-bom
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-11 20:38:19 -07:00
github-actions[bot] c5fefd4dec Merge branch '6.1.x' into 6.2.x 2024-04-12 03:27:23 +00:00
github-actions[bot] 8a7ae7a7f9 Merge branch '5.8.x' into 6.1.x 2024-04-12 03:27:23 +00:00
dependabot[bot] a608352689 Bump org.springframework:spring-framework-bom from 5.3.33 to 5.3.34
Bumps [org.springframework:spring-framework-bom](https://github.com/spring-projects/spring-framework) from 5.3.33 to 5.3.34.
- [Release notes](https://github.com/spring-projects/spring-framework/releases)
- [Commits](https://github.com/spring-projects/spring-framework/compare/v5.3.33...v5.3.34)

---
updated-dependencies:
- dependency-name: org.springframework:spring-framework-bom
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-11 20:26:39 -07:00
github-actions[bot] ce2b9f5c90 Merge branch '6.1.x' into 6.2.x 2024-04-10 04:21:44 +00:00
github-actions[bot] 0c20b14542 Merge branch '5.8.x' into 6.1.x 2024-04-10 04:21:44 +00:00
dependabot[bot] c3dbbb2d01 Bump io.projectreactor:reactor-bom from 2020.0.42 to 2020.0.43
Bumps [io.projectreactor:reactor-bom](https://github.com/reactor/reactor) from 2020.0.42 to 2020.0.43.
- [Release notes](https://github.com/reactor/reactor/releases)
- [Commits](https://github.com/reactor/reactor/compare/2020.0.42...2020.0.43)

---
updated-dependencies:
- dependency-name: io.projectreactor:reactor-bom
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-09 21:20:55 -07:00
github-actions[bot] 5082da37ba Merge branch '6.1.x' into 6.2.x 2024-04-10 04:05:49 +00:00
github-actions[bot] d187ddb5c6 Merge branch '5.8.x' into 6.1.x 2024-04-10 04:05:49 +00:00
dependabot[bot] 9a5319d5a2 Bump io.projectreactor.netty:reactor-netty from 1.0.43 to 1.0.44
Bumps [io.projectreactor.netty:reactor-netty](https://github.com/reactor/reactor-netty) from 1.0.43 to 1.0.44.
- [Release notes](https://github.com/reactor/reactor-netty/releases)
- [Commits](https://github.com/reactor/reactor-netty/compare/v1.0.43...v1.0.44)

---
updated-dependencies:
- dependency-name: io.projectreactor.netty:reactor-netty
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-09 21:05:03 -07:00
github-actions[bot] 53d4ec44b4 Merge branch '6.1.x' into 6.2.x 2024-04-10 03:56:12 +00:00
dependabot[bot] 1fb00879f6 Bump io.projectreactor:reactor-bom from 2022.0.17 to 2022.0.18
Bumps [io.projectreactor:reactor-bom](https://github.com/reactor/reactor) from 2022.0.17 to 2022.0.18.
- [Release notes](https://github.com/reactor/reactor/releases)
- [Commits](https://github.com/reactor/reactor/compare/2022.0.17...2022.0.18)

---
updated-dependencies:
- dependency-name: io.projectreactor:reactor-bom
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-09 20:55:24 -07:00
dependabot[bot] b6908a801a Bump io.projectreactor:reactor-bom from 2023.0.4 to 2023.0.5
Bumps [io.projectreactor:reactor-bom](https://github.com/reactor/reactor) from 2023.0.4 to 2023.0.5.
- [Release notes](https://github.com/reactor/reactor/releases)
- [Commits](https://github.com/reactor/reactor/compare/2023.0.4...2023.0.5)

---
updated-dependencies:
- dependency-name: io.projectreactor:reactor-bom
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-09 20:40:59 -07:00
dependabot[bot] eeaa682a6a Bump io.micrometer:micrometer-observation from 1.12.4 to 1.12.5
Bumps [io.micrometer:micrometer-observation](https://github.com/micrometer-metrics/micrometer) from 1.12.4 to 1.12.5.
- [Release notes](https://github.com/micrometer-metrics/micrometer/releases)
- [Commits](https://github.com/micrometer-metrics/micrometer/compare/v1.12.4...v1.12.5)

---
updated-dependencies:
- dependency-name: io.micrometer:micrometer-observation
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-08 20:17:15 -07:00
Marcus Hert Da Coregio 697d0c9af4 Merge branch '6.1.x' into 6.2.x
Closes gh-14865
2024-04-08 11:16:15 -03:00
Marcus Hert Da Coregio 472c9f8275 Avoid initializing raw bean during runtime in native-images
Closes gh-14825
2024-04-08 11:11:23 -03:00
Josh Cummings 8a8caaed3b Update to OpenSaml 4.3.1
Closes gh-14850
2024-04-05 16:42:13 -06:00
Josh Cummings 01f299f7ab Merge branch '6.1.x' into 6.2.x
Closes gh-14848
2024-04-04 16:56:11 -06:00
Josh Cummings ef00312991 Merge branch '5.8.x' into 6.1.x
Closes gh-14847
2024-04-04 16:55:52 -06:00
Josh Cummings 0af0751cfd Treat Map Method Parameter as Immutable
Closes gh-14802
2024-04-04 16:44:14 -06:00
Steve Riesenberg 16e2bdc9bc Merge branch '6.1.x' into 6.2.x 2024-04-04 14:55:45 -05:00
Steve Riesenberg c2447ec257 Merge branch '5.8.x' into 6.1.x 2024-04-04 14:55:03 -05:00
Steve Riesenberg 39dbd24dcb Polish gh-14742 2024-04-04 14:51:19 -05:00
sheheryarumair 33ebd5405a Removed dataSource null validation
Fixed data source validation
2024-04-04 14:21:18 -05:00
Josh Cummings 40a16d451c Merge branch '6.1.x' into 6.2.x 2024-04-02 10:53:00 -06:00
github-actions[bot] 76a465a544 Bump Gradle Wrapper from 8.6 to 8.7 2024-04-02 10:23:00 -06:00
github-actions[bot] 2e327437dd Merge branch '6.1.x' into 6.2.x 2024-03-29 03:48:37 +00:00
dependabot[bot] 5f1b9862cc Bump io.spring.ge.conventions from 0.0.15 to 0.0.16
Bumps [io.spring.ge.conventions](https://github.com/spring-io/gradle-enterprise-conventions) from 0.0.15 to 0.0.16.
- [Release notes](https://github.com/spring-io/gradle-enterprise-conventions/releases)
- [Commits](https://github.com/spring-io/gradle-enterprise-conventions/compare/v0.0.15...v0.0.16)

---
updated-dependencies:
- dependency-name: io.spring.ge.conventions
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-28 20:47:51 -07:00
github-actions[bot] 52eadd7b06 Merge branch '6.1.x' into 6.2.x 2024-03-29 03:44:43 +00:00
github-actions[bot] 6a66a5b74f Merge branch '5.8.x' into 6.1.x 2024-03-29 03:44:43 +00:00
dependabot[bot] 2d2c2d31fa Bump io.spring.ge.conventions from 0.0.15 to 0.0.16
Bumps [io.spring.ge.conventions](https://github.com/spring-io/gradle-enterprise-conventions) from 0.0.15 to 0.0.16.
- [Release notes](https://github.com/spring-io/gradle-enterprise-conventions/releases)
- [Commits](https://github.com/spring-io/gradle-enterprise-conventions/compare/v0.0.15...v0.0.16)

---
updated-dependencies:
- dependency-name: io.spring.ge.conventions
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-28 20:43:52 -07:00
dependabot[bot] 67d7cd4aea Bump io.spring.ge.conventions from 0.0.15 to 0.0.16
Bumps [io.spring.ge.conventions](https://github.com/spring-io/gradle-enterprise-conventions) from 0.0.15 to 0.0.16.
- [Release notes](https://github.com/spring-io/gradle-enterprise-conventions/releases)
- [Commits](https://github.com/spring-io/gradle-enterprise-conventions/compare/v0.0.15...v0.0.16)

---
updated-dependencies:
- dependency-name: io.spring.ge.conventions
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-28 20:39:53 -07:00
dependabot[bot] fd9ec72bb9 Bump org-aspectj from 1.9.21.2 to 1.9.22
Bumps `org-aspectj` from 1.9.21.2 to 1.9.22.

Updates `org.aspectj:aspectjrt` from 1.9.21.2 to 1.9.22
- [Release notes](https://github.com/eclipse/org.aspectj/releases)
- [Commits](https://github.com/eclipse/org.aspectj/commits)

Updates `org.aspectj:aspectjweaver` from 1.9.21.2 to 1.9.22
- [Release notes](https://github.com/eclipse/org.aspectj/releases)
- [Commits](https://github.com/eclipse/org.aspectj/commits)

---
updated-dependencies:
- dependency-name: org.aspectj:aspectjrt
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.aspectj:aspectjweaver
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-26 12:57:55 -07:00
Steve Riesenberg 6f8cc920cd Merge branch '6.1.x' into 6.2.x
Closes gh-14805
2024-03-26 12:18:42 -05:00
Steve Riesenberg 80845d0c9a Fix NPE in Kotlin docs example
Closes gh-14634
2024-03-26 12:18:06 -05:00
Steve Riesenberg ba575e8564 Add tests for invalid/missing token
Issue gh-14634
2024-03-26 12:18:06 -05:00
Steve Riesenberg 614123e6f9 Update tests that fail on Windows
Issue gh-14609
2024-03-26 10:49:47 -05:00
Steve Riesenberg 8bd6991976 Update nohttp allow list
Issue gh-14609
2024-03-25 17:02:08 -05:00
github-actions[bot] c73becfa86 Merge branch '6.1.x' into 6.2.x 2024-03-25 03:46:23 +00:00
dependabot[bot] 79801134b6 Bump org-aspectj from 1.9.21.2 to 1.9.22
Bumps `org-aspectj` from 1.9.21.2 to 1.9.22.

Updates `org.aspectj:aspectjrt` from 1.9.21.2 to 1.9.22
- [Release notes](https://github.com/eclipse/org.aspectj/releases)
- [Commits](https://github.com/eclipse/org.aspectj/commits)

Updates `org.aspectj:aspectjweaver` from 1.9.21.2 to 1.9.22
- [Release notes](https://github.com/eclipse/org.aspectj/releases)
- [Commits](https://github.com/eclipse/org.aspectj/commits)

---
updated-dependencies:
- dependency-name: org.aspectj:aspectjrt
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.aspectj:aspectjweaver
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-24 20:45:35 -07:00
Josh Cummings 44033cd8b9 Make Internal Logout URI Configurable
Closes gh-14609
2024-03-22 16:31:44 -06:00
Josh Cummings e18ec48134 Fix Test
Issue gh-14553
2024-03-22 16:31:42 -06:00
Josh Cummings afcce0c277 Merge branch '6.1.x' into 6.2.x
Closes gh-14795
2024-03-22 14:33:44 -06:00
Josh Cummings 7162046144 Remove Reference to MethodInvocationResult
Closes gh-14794
2024-03-22 14:33:23 -06:00
Josh Cummings c439cfef0f Merge branch '6.1.x' into 6.2.x 2024-03-21 17:24:34 -06:00
Thomas Hagelberg ce9f1821b1 Improve logging in AuthenticationWebFilter
Closes #14091
2024-03-21 17:24:10 -06:00
dependabot[bot] c036213892 Bump io.spring.gradle:spring-security-release-plugin from 1.0.1 to 1.0.2
Bumps [io.spring.gradle:spring-security-release-plugin](https://github.com/spring-io/spring-security-release-tools) from 1.0.1 to 1.0.2.
- [Release notes](https://github.com/spring-io/spring-security-release-tools/releases)
- [Commits](https://github.com/spring-io/spring-security-release-tools/compare/v1.0.1...v1.0.2)

---
updated-dependencies:
- dependency-name: io.spring.gradle:spring-security-release-plugin
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-20 21:00:45 -07:00
github-actions[bot] 69d2c6dff6 Merge branch '6.1.x' into 6.2.x 2024-03-21 03:37:27 +00:00
dependabot[bot] 153ec5fbde Bump io.spring.gradle:spring-security-release-plugin from 1.0.1 to 1.0.2
Bumps [io.spring.gradle:spring-security-release-plugin](https://github.com/spring-io/spring-security-release-tools) from 1.0.1 to 1.0.2.
- [Release notes](https://github.com/spring-io/spring-security-release-tools/releases)
- [Commits](https://github.com/spring-io/spring-security-release-tools/compare/v1.0.1...v1.0.2)

---
updated-dependencies:
- dependency-name: io.spring.gradle:spring-security-release-plugin
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-20 20:36:41 -07:00
Marcus Hert Da Coregio 3161b28701 Merge branch '6.1.x' into 6.2.x 2024-03-18 09:27:54 -03:00
Marcus Hert Da Coregio b58018dc0f Merge branch '5.8.x' into 6.1.x 2024-03-18 09:27:48 -03:00
Marcus Hert Da Coregio 6c9efc2f09 Merge branch '5.7.x' into 5.8.x 2024-03-18 09:27:41 -03:00
github-actions[bot] b1505016c6 Next development version 2024-03-18 12:24:38 +00:00
github-actions[bot] 55be9eabd4 Next development version 2024-03-18 12:24:28 +00:00
github-actions[bot] 1a163dca37 Next development version 2024-03-18 11:47:59 +00:00
github-actions[bot] 1b0c4d68da Next development version 2024-03-18 11:38:53 +00:00
github-actions[bot] b38b495630 Release 5.7.12 2024-03-18 11:10:08 +00:00
github-actions[bot] c8a0601e6a Release 5.8.11 2024-03-18 11:10:03 +00:00
github-actions[bot] f226490be3 Release 6.1.8 2024-03-18 11:09:37 +00:00
23 changed files with 501 additions and 82 deletions
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 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.
@@ -22,11 +22,14 @@ import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.Aware;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.core.NativeDetector;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.util.Assert;
@@ -55,14 +58,13 @@ final class AutowireBeanFactoryObjectPostProcessor
}
@Override
@SuppressWarnings("unchecked")
public <T> T postProcess(T object) {
if (object == null) {
return null;
}
T result = null;
try {
result = (T) this.autowireBeanFactory.initializeBean(object, object.toString());
result = initializeBeanIfNeeded(object);
}
catch (RuntimeException ex) {
Class<?> type = object.getClass();
@@ -78,6 +80,36 @@ final class AutowireBeanFactoryObjectPostProcessor
return result;
}
/**
* Invokes {@link AutowireCapableBeanFactory#initializeBean(Object, String)} only if
* needed, i.e when the application is not a native image or the object is not a CGLIB
* proxy.
* @param object the object to initialize
* @param <T> the type of the object
* @return the initialized bean or an existing bean if the object is a CGLIB proxy and
* the application is a native image
* @see <a href=
* "https://github.com/spring-projects/spring-security/issues/14825">Issue
* gh-14825</a>
*/
@SuppressWarnings("unchecked")
private <T> T initializeBeanIfNeeded(T object) {
if (!NativeDetector.inNativeImage() || !AopUtils.isCglibProxy(object)) {
return (T) this.autowireBeanFactory.initializeBean(object, object.toString());
}
ObjectProvider<?> provider = this.autowireBeanFactory.getBeanProvider(object.getClass());
Object bean = provider.getIfUnique();
if (bean == null) {
String msg = """
Failed to resolve an unique bean (single or primary) of type [%s] from the BeanFactory.
Because the object is a CGLIB Proxy, a raw bean cannot be initialized during runtime in a native image.
"""
.formatted(object.getClass());
throw new IllegalStateException(msg);
}
return (T) bean;
}
@Override
public void afterSingletonsInstantiated() {
for (SmartInitializingSingleton singleton : this.smartSingletons) {
@@ -19,6 +19,7 @@ package org.springframework.security.config.annotation.web.configurers.oauth2.cl
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import jakarta.servlet.http.HttpServletRequest;
@@ -37,10 +38,12 @@ import org.springframework.security.oauth2.client.oidc.session.OidcSessionRegist
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.http.converter.OAuth2ErrorHttpMessageConverter;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.security.web.util.UrlUtils;
import org.springframework.util.Assert;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestOperations;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
/**
@@ -61,7 +64,7 @@ final class OidcBackChannelLogoutHandler implements LogoutHandler {
private RestOperations restOperations = new RestTemplate();
private String logoutEndpointName = "/logout";
private String logoutUri = "{baseScheme}://localhost{basePort}/logout";
private String sessionCookieName = "JSESSIONID";
@@ -112,12 +115,32 @@ final class OidcBackChannelLogoutHandler implements LogoutHandler {
}
String computeLogoutEndpoint(HttpServletRequest request) {
String url = request.getRequestURL().toString();
return UriComponentsBuilder.fromHttpUrl(url)
.host("localhost")
.replacePath(this.logoutEndpointName)
.build()
.toUriString();
// @formatter:off
UriComponents uriComponents = UriComponentsBuilder
.fromHttpUrl(UrlUtils.buildFullRequestUrl(request))
.replacePath(request.getContextPath())
.replaceQuery(null)
.fragment(null)
.build();
Map<String, String> uriVariables = new HashMap<>();
String scheme = uriComponents.getScheme();
uriVariables.put("baseScheme", (scheme != null) ? scheme : "");
uriVariables.put("baseUrl", uriComponents.toUriString());
String host = uriComponents.getHost();
uriVariables.put("baseHost", (host != null) ? host : "");
String path = uriComponents.getPath();
uriVariables.put("basePath", (path != null) ? path : "");
int port = uriComponents.getPort();
uriVariables.put("basePort", (port == -1) ? "" : ":" + port);
return UriComponentsBuilder.fromUriString(this.logoutUri)
.buildAndExpand(uriVariables)
.toUriString();
// @formatter:on
}
private OAuth2Error oauth2Error(Collection<String> errors) {
@@ -164,7 +187,7 @@ final class OidcBackChannelLogoutHandler implements LogoutHandler {
*/
void setLogoutUri(String logoutUri) {
Assert.hasText(logoutUri, "logoutUri cannot be empty");
this.logoutEndpointName = logoutUri;
this.logoutUri = logoutUri;
}
/**
@@ -17,6 +17,7 @@
package org.springframework.security.config.annotation.web.configurers.oauth2.client;
import java.util.function.Consumer;
import java.util.function.Function;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
@@ -123,7 +124,7 @@ public final class OidcLogoutConfigurer<B extends HttpSecurityBuilder<B>>
private final AuthenticationManager authenticationManager = new ProviderManager(
new OidcBackChannelLogoutAuthenticationProvider());
private LogoutHandler logoutHandler;
private Function<B, LogoutHandler> logoutHandler = this::logoutHandler;
private AuthenticationConverter authenticationConverter(B http) {
if (this.authenticationConverter == null) {
@@ -139,18 +140,54 @@ public final class OidcLogoutConfigurer<B extends HttpSecurityBuilder<B>>
}
private LogoutHandler logoutHandler(B http) {
if (this.logoutHandler == null) {
OidcBackChannelLogoutHandler logoutHandler = new OidcBackChannelLogoutHandler();
logoutHandler.setSessionRegistry(OAuth2ClientConfigurerUtils.getOidcSessionRegistry(http));
return logoutHandler;
}
/**
* Use this endpoint when invoking a back-channel logout.
*
* <p>
* The resulting {@link LogoutHandler} will {@code POST} the session cookie and
* CSRF token to this endpoint to invalidate the corresponding end-user session.
*
* <p>
* Supports URI templates like {@code {baseUrl}}, {@code {baseScheme}}, and
* {@code {basePort}}.
*
* <p>
* By default, the URI is set to
* {@code {baseScheme}://localhost{basePort}/logout}, meaning that the scheme and
* port of the original back-channel request is preserved, while the host and
* endpoint are changed.
*
* <p>
* If you are using Spring Security for the logout endpoint, the path part of this
* URI should match the value configured there.
*
* <p>
* Otherwise, this is handy in the event that your server configuration means that
* the scheme, server name, or port in the {@code Host} header are different from
* how you would address the same server internally.
* @param logoutUri the URI to request logout on the back-channel
* @return the {@link BackChannelLogoutConfigurer} for further customizations
* @since 6.2.4
*/
public BackChannelLogoutConfigurer logoutUri(String logoutUri) {
this.logoutHandler = (http) -> {
OidcBackChannelLogoutHandler logoutHandler = new OidcBackChannelLogoutHandler();
logoutHandler.setSessionRegistry(OAuth2ClientConfigurerUtils.getOidcSessionRegistry(http));
this.logoutHandler = logoutHandler;
}
return this.logoutHandler;
logoutHandler.setLogoutUri(logoutUri);
return logoutHandler;
};
return this;
}
void configure(B http) {
OidcBackChannelLogoutFilter filter = new OidcBackChannelLogoutFilter(authenticationConverter(http),
authenticationManager());
filter.setLogoutHandler(logoutHandler(http));
filter.setLogoutHandler(this.logoutHandler.apply(http));
http.addFilterBefore(filter, CsrfFilter.class);
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 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.
@@ -42,7 +42,7 @@ public class JdbcUserServiceBeanDefinitionParser extends AbstractUserDetailsServ
@Override
protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
String dataSource = element.getAttribute(ATT_DATA_SOURCE);
if (dataSource != null) {
if (StringUtils.hasText(dataSource)) {
builder.addPropertyReference("dataSource", dataSource);
}
else {
@@ -18,6 +18,7 @@ package org.springframework.security.config.web.server;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
@@ -30,6 +31,7 @@ import reactor.core.publisher.Mono;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.client.oidc.authentication.logout.OidcLogoutToken;
@@ -42,6 +44,7 @@ import org.springframework.security.web.server.WebFilterExchange;
import org.springframework.security.web.server.authentication.logout.ServerLogoutHandler;
import org.springframework.util.Assert;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
/**
@@ -62,7 +65,7 @@ final class OidcBackChannelServerLogoutHandler implements ServerLogoutHandler {
private WebClient web = WebClient.create();
private String logoutEndpointName = "/logout";
private String logoutUri = "{baseScheme}://localhost{basePort}/logout";
private String sessionCookieName = "SESSION";
@@ -108,17 +111,36 @@ final class OidcBackChannelServerLogoutHandler implements ServerLogoutHandler {
for (Map.Entry<String, String> credential : session.getAuthorities().entrySet()) {
headers.add(credential.getKey(), credential.getValue());
}
String logout = computeLogoutEndpoint(exchange);
String logout = computeLogoutEndpoint(exchange.getExchange().getRequest());
return this.web.post().uri(logout).headers((h) -> h.putAll(headers)).retrieve().toBodilessEntity();
}
String computeLogoutEndpoint(WebFilterExchange exchange) {
String url = exchange.getExchange().getRequest().getURI().toString();
return UriComponentsBuilder.fromHttpUrl(url)
.host("localhost")
.replacePath(this.logoutEndpointName)
.build()
.toUriString();
String computeLogoutEndpoint(ServerHttpRequest request) {
// @formatter:off
UriComponents uriComponents = UriComponentsBuilder.fromUri(request.getURI())
.replacePath(request.getPath().contextPath().value())
.replaceQuery(null)
.fragment(null)
.build();
Map<String, String> uriVariables = new HashMap<>();
String scheme = uriComponents.getScheme();
uriVariables.put("baseScheme", (scheme != null) ? scheme : "");
uriVariables.put("baseUrl", uriComponents.toUriString());
String host = uriComponents.getHost();
uriVariables.put("baseHost", (host != null) ? host : "");
String path = uriComponents.getPath();
uriVariables.put("basePath", (path != null) ? path : "");
int port = uriComponents.getPort();
uriVariables.put("basePort", (port == -1) ? "" : ":" + port);
return UriComponentsBuilder.fromUriString(this.logoutUri)
.buildAndExpand(uriVariables)
.toUriString();
// @formatter:on
}
private OAuth2Error oauth2Error(Collection<?> errors) {
@@ -168,7 +190,7 @@ final class OidcBackChannelServerLogoutHandler implements ServerLogoutHandler {
*/
void setLogoutUri(String logoutUri) {
Assert.hasText(logoutUri, "logoutUri cannot be empty");
this.logoutEndpointName = logoutUri;
this.logoutUri = logoutUri;
}
/**
@@ -58,6 +58,7 @@ import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.ObservationReactiveAuthorizationManager;
import org.springframework.security.authorization.ReactiveAuthorizationManager;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.configurers.oauth2.client.OidcLogoutConfigurer;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
@@ -110,6 +111,7 @@ import org.springframework.security.oauth2.server.resource.web.access.server.Bea
import org.springframework.security.oauth2.server.resource.web.server.BearerTokenServerAuthenticationEntryPoint;
import org.springframework.security.oauth2.server.resource.web.server.authentication.ServerBearerTokenAuthenticationConverter;
import org.springframework.security.web.PortMapper;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.security.web.authentication.preauth.x509.SubjectDnX509PrincipalExtractor;
import org.springframework.security.web.authentication.preauth.x509.X509PrincipalExtractor;
import org.springframework.security.web.server.DefaultServerRedirectStrategy;
@@ -5074,7 +5076,7 @@ public class ServerHttpSecurity {
private final ReactiveAuthenticationManager authenticationManager = new OidcBackChannelLogoutReactiveAuthenticationManager();
private ServerLogoutHandler logoutHandler;
private Supplier<ServerLogoutHandler> logoutHandler = this::logoutHandler;
private ServerAuthenticationConverter authenticationConverter() {
if (this.authenticationConverter == null) {
@@ -5089,18 +5091,56 @@ public class ServerHttpSecurity {
}
private ServerLogoutHandler logoutHandler() {
if (this.logoutHandler == null) {
OidcBackChannelServerLogoutHandler logoutHandler = new OidcBackChannelServerLogoutHandler();
logoutHandler.setSessionRegistry(OidcLogoutSpec.this.getSessionRegistry());
return logoutHandler;
}
/**
* Use this endpoint when invoking a back-channel logout.
*
* <p>
* The resulting {@link LogoutHandler} will {@code POST} the session cookie
* and CSRF token to this endpoint to invalidate the corresponding end-user
* session.
*
* <p>
* Supports URI templates like {@code {baseUrl}}, {@code {baseScheme}}, and
* {@code {basePort}}.
*
* <p>
* By default, the URI is set to
* {@code {baseScheme}://localhost{basePort}/logout}, meaning that the scheme
* and port of the original back-channel request is preserved, while the host
* and endpoint are changed.
*
* <p>
* If you are using Spring Security for the logout endpoint, the path part of
* this URI should match the value configured there.
*
* <p>
* Otherwise, this is handy in the event that your server configuration means
* that the scheme, server name, or port in the {@code Host} header are
* different from how you would address the same server internally.
* @param logoutUri the URI to request logout on the back-channel
* @return the {@link OidcLogoutConfigurer.BackChannelLogoutConfigurer} for
* further customizations
* @since 6.2.4
*/
public BackChannelLogoutConfigurer logoutUri(String logoutUri) {
this.logoutHandler = () -> {
OidcBackChannelServerLogoutHandler logoutHandler = new OidcBackChannelServerLogoutHandler();
logoutHandler.setSessionRegistry(OidcLogoutSpec.this.getSessionRegistry());
this.logoutHandler = logoutHandler;
}
return this.logoutHandler;
logoutHandler.setLogoutUri(logoutUri);
return logoutHandler;
};
return this;
}
void configure(ServerHttpSecurity http) {
OidcBackChannelLogoutWebFilter filter = new OidcBackChannelLogoutWebFilter(authenticationConverter(),
authenticationManager());
filter.setLogoutHandler(logoutHandler());
filter.setLogoutHandler(this.logoutHandler.get());
http.addFilterBefore(filter, SecurityWebFiltersOrder.CSRF);
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 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.
@@ -16,9 +16,13 @@
package org.springframework.security.config.annotation.configuration;
import java.lang.reflect.Modifier;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mockito;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.DisposableBean;
@@ -31,13 +35,16 @@ import org.springframework.context.EnvironmentAware;
import org.springframework.context.MessageSourceAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.NativeDetector;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.web.context.ServletContextAware;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatException;
import static org.mockito.ArgumentMatchers.isNotNull;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
@@ -132,6 +139,59 @@ public class AutowireBeanFactoryObjectPostProcessorTests {
assertThat(bean.doStuff()).isEqualTo("null");
}
@Test
void postProcessWhenObjectIsCgLibProxyAndInNativeImageThenUseExistingBean() {
try (var detector = Mockito.mockStatic(NativeDetector.class)) {
given(NativeDetector.inNativeImage()).willReturn(true);
ProxyFactory proxyFactory = new ProxyFactory(new MyClass());
proxyFactory.setProxyTargetClass(!Modifier.isFinal(MyClass.class.getModifiers()));
MyClass myClass = (MyClass) proxyFactory.getProxy();
this.spring.register(Config.class, myClass.getClass()).autowire();
this.spring.getContext().getBean(myClass.getClass()).setIdentifier("0000");
MyClass postProcessed = this.objectObjectPostProcessor.postProcess(myClass);
assertThat(postProcessed.getIdentifier()).isEqualTo("0000");
}
}
@Test
void postProcessWhenObjectIsCgLibProxyAndInNativeImageAndBeanDoesNotExistsThenIllegalStateException() {
try (var detector = Mockito.mockStatic(NativeDetector.class)) {
given(NativeDetector.inNativeImage()).willReturn(true);
ProxyFactory proxyFactory = new ProxyFactory(new MyClass());
proxyFactory.setProxyTargetClass(!Modifier.isFinal(MyClass.class.getModifiers()));
MyClass myClass = (MyClass) proxyFactory.getProxy();
this.spring.register(Config.class).autowire();
assertThatException().isThrownBy(() -> this.objectObjectPostProcessor.postProcess(myClass))
.havingRootCause()
.isInstanceOf(IllegalStateException.class)
.withMessage(
"""
Failed to resolve an unique bean (single or primary) of type [class org.springframework.security.config.annotation.configuration.AutowireBeanFactoryObjectPostProcessorTests$MyClass$$SpringCGLIB$$0] from the BeanFactory.
Because the object is a CGLIB Proxy, a raw bean cannot be initialized during runtime in a native image.
""");
}
}
static class MyClass {
private String identifier = "1234";
String getIdentifier() {
return this.identifier;
}
void setIdentifier(String identifier) {
this.identifier = identifier;
}
}
@Configuration
static class Config {
@@ -29,10 +29,34 @@ public class OidcBackChannelLogoutHandlerTests {
public void computeLogoutEndpointWhenDifferentHostnameThenLocalhost() {
OidcBackChannelLogoutHandler logoutHandler = new OidcBackChannelLogoutHandler();
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/back-channel/logout");
request.setRemoteHost("host.docker.internal");
request.setServerName("host.docker.internal");
request.setServerPort(8090);
String endpoint = logoutHandler.computeLogoutEndpoint(request);
assertThat(endpoint).isEqualTo("http://localhost:8090/logout");
}
@Test
public void computeLogoutEndpointWhenUsingBaseUrlTemplateThenServerName() {
OidcBackChannelLogoutHandler logoutHandler = new OidcBackChannelLogoutHandler();
logoutHandler.setLogoutUri("{baseUrl}/logout");
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/back-channel/logout");
request.setServerName("host.docker.internal");
request.setServerPort(8090);
String endpoint = logoutHandler.computeLogoutEndpoint(request);
assertThat(endpoint).isEqualTo("http://host.docker.internal:8090/logout");
}
// gh-14609
@Test
public void computeLogoutEndpointWhenLogoutUriThenUses() {
OidcBackChannelLogoutHandler logoutHandler = new OidcBackChannelLogoutHandler();
logoutHandler.setLogoutUri("http://localhost:8090/logout");
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/back-channel/logout");
request.setScheme("https");
request.setServerName("server-one.com");
request.setServerPort(80);
String endpoint = logoutHandler.computeLogoutEndpoint(request);
assertThat(endpoint).isEqualTo("http://localhost:8090/logout");
}
}
@@ -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.
@@ -197,6 +197,25 @@ public class OidcLogoutConfigurerTests {
this.mvc.perform(get("/token/logout").session(three)).andExpect(status().isUnauthorized());
}
@Test
void logoutWhenRemoteLogoutUriThenUses() throws Exception {
this.spring.register(WebServerConfig.class, OidcProviderConfig.class, LogoutUriConfig.class).autowire();
String registrationId = this.clientRegistration.getRegistrationId();
MockHttpSession one = login();
String logoutToken = this.mvc.perform(get("/token/logout/all").session(one))
.andExpect(status().isOk())
.andReturn()
.getResponse()
.getContentAsString();
this.mvc
.perform(post(this.web.url("/logout/connect/back-channel/" + registrationId).toString())
.param("logout_token", logoutToken))
.andExpect(status().isBadRequest())
.andExpect(content().string(containsString("partial_logout")))
.andExpect(content().string(containsString("not all sessions were terminated")));
this.mvc.perform(get("/token/logout").session(one)).andExpect(status().isOk());
}
@Test
void logoutWhenRemoteLogoutFailsThenReportsPartialLogout() throws Exception {
this.spring.register(WebServerConfig.class, OidcProviderConfig.class, WithBrokenLogoutConfig.class).autowire();
@@ -312,6 +331,28 @@ public class OidcLogoutConfigurerTests {
}
@Configuration
@EnableWebSecurity
@Import(RegistrationConfig.class)
static class LogoutUriConfig {
@Bean
@Order(1)
SecurityFilterChain filters(HttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated())
.oauth2Login(Customizer.withDefaults())
.oidcLogout((oidc) -> oidc
.backChannel((backchannel) -> backchannel.logoutUri("http://localhost/wrong"))
);
// @formatter:on
return http.build();
}
}
@Configuration
@EnableWebSecurity
@Import(RegistrationConfig.class)
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 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.
@@ -19,7 +19,10 @@ package org.springframework.security.config.authentication;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.w3c.dom.Element;
import org.xml.sax.SAXParseException;
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
import org.springframework.beans.factory.xml.XmlBeanDefinitionStoreException;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.CachingUserDetailsService;
import org.springframework.security.authentication.ProviderManager;
@@ -33,6 +36,7 @@ import org.springframework.security.provisioning.JdbcUserDetailsManager;
import org.springframework.security.util.FieldUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.mockito.Mockito.mock;
/**
@@ -160,6 +164,38 @@ public class JdbcUserServiceBeanDefinitionParserTests {
assertThat(AuthorityUtils.authorityListToSet(rod.getAuthorities())).contains("PREFIX_ROLE_SUPERVISOR");
}
@Test
public void testEmptyDataSourceRef() {
// @formatter:off
String xml = "<authentication-manager>"
+ " <authentication-provider>"
+ " <jdbc-user-service data-source-ref=''/>"
+ " </authentication-provider>"
+ "</authentication-manager>";
assertThatExceptionOfType(BeanDefinitionParsingException.class)
.isThrownBy(() -> setContext(xml))
.withFailMessage("Expected exception due to empty data-source-ref")
.withMessageContaining("data-source-ref is required for jdbc-user-service");
// @formatter:on
}
@Test
public void testMissingDataSourceRef() {
// @formatter:off
String xml = "<authentication-manager>"
+ " <authentication-provider>"
+ " <jdbc-user-service/>"
+ " </authentication-provider>"
+ "</authentication-manager>";
assertThatExceptionOfType(XmlBeanDefinitionStoreException.class)
.isThrownBy(() -> setContext(xml))
.withFailMessage("Expected exception due to missing data-source-ref")
.havingRootCause()
.isInstanceOf(SAXParseException.class)
.withMessageContaining("Attribute 'data-source-ref' must appear on element 'jdbc-user-service'");
// @formatter:on
}
private void setContext(String context) {
this.appContext = new InMemoryXmlApplicationContext(context);
}
@@ -17,12 +17,8 @@
package org.springframework.security.config.web.server;
import org.junit.jupiter.api.Test;
import reactor.core.publisher.Mono;
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
import org.springframework.mock.web.server.MockServerWebExchange;
import org.springframework.security.web.server.WebFilterExchange;
import org.springframework.web.server.ServerWebExchange;
import static org.assertj.core.api.Assertions.assertThat;
@@ -38,9 +34,29 @@ public class OidcBackChannelServerLogoutHandlerTests {
MockServerHttpRequest request = MockServerHttpRequest
.get("https://host.docker.internal:8090/back-channel/logout")
.build();
ServerWebExchange exchange = new MockServerWebExchange.Builder(request).build();
String endpoint = logoutHandler.computeLogoutEndpoint(new WebFilterExchange(exchange, (ex) -> Mono.empty()));
String endpoint = logoutHandler.computeLogoutEndpoint(request);
assertThat(endpoint).isEqualTo("https://localhost:8090/logout");
}
@Test
public void computeLogoutEndpointWhenUsingBaseUrlTemplateThenServerName() {
OidcBackChannelServerLogoutHandler logoutHandler = new OidcBackChannelServerLogoutHandler();
logoutHandler.setLogoutUri("{baseUrl}/logout");
MockServerHttpRequest request = MockServerHttpRequest
.get("http://host.docker.internal:8090/back-channel/logout")
.build();
String endpoint = logoutHandler.computeLogoutEndpoint(request);
assertThat(endpoint).isEqualTo("http://host.docker.internal:8090/logout");
}
// gh-14609
@Test
public void computeLogoutEndpointWhenLogoutUriThenUses() {
OidcBackChannelServerLogoutHandler logoutHandler = new OidcBackChannelServerLogoutHandler();
logoutHandler.setLogoutUri("http://localhost:8090/logout");
MockServerHttpRequest request = MockServerHttpRequest.get("https://server-one.com/back-channel/logout").build();
String endpoint = logoutHandler.computeLogoutEndpoint(request);
assertThat(endpoint).isEqualTo("http://localhost:8090/logout");
}
}
@@ -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.
@@ -242,6 +242,32 @@ public class OidcLogoutSpecTests {
this.test.get().uri("/token/logout").cookie("SESSION", three).exchange().expectStatus().isUnauthorized();
}
@Test
void logoutWhenRemoteLogoutUriThenUses() {
this.spring.register(WebServerConfig.class, OidcProviderConfig.class, LogoutUriConfig.class).autowire();
String registrationId = this.clientRegistration.getRegistrationId();
String one = login();
String logoutToken = this.test.get()
.uri("/token/logout/all")
.cookie("SESSION", one)
.exchange()
.expectStatus()
.isOk()
.returnResult(String.class)
.getResponseBody()
.blockFirst();
this.test.post()
.uri(this.web.url("/logout/connect/back-channel/" + registrationId).toString())
.body(BodyInserters.fromFormData("logout_token", logoutToken))
.exchange()
.expectStatus()
.isBadRequest()
.expectBody(String.class)
.value(containsString("partial_logout"))
.value(containsString("not all sessions were terminated"));
this.test.get().uri("/token/logout").cookie("SESSION", one).exchange().expectStatus().isOk();
}
@Test
void logoutWhenRemoteLogoutFailsThenReportsPartialLogout() {
this.spring.register(WebServerConfig.class, OidcProviderConfig.class, WithBrokenLogoutConfig.class).autowire();
@@ -396,6 +422,28 @@ public class OidcLogoutSpecTests {
}
@Configuration
@EnableWebFluxSecurity
@Import(RegistrationConfig.class)
static class LogoutUriConfig {
@Bean
@Order(1)
SecurityWebFilterChain filters(ServerHttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeExchange((authorize) -> authorize.anyExchange().authenticated())
.oauth2Login(Customizer.withDefaults())
.oidcLogout((oidc) -> oidc
.backChannel((backchannel) -> backchannel.logoutUri("http://localhost/wrong"))
);
// @formatter:on
return http.build();
}
}
@Configuration
@EnableWebFluxSecurity
@Import(RegistrationConfig.class)
@@ -21,8 +21,6 @@ import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationConvention;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.security.authorization.method.MethodInvocationResult;
/**
* An {@link ObservationConvention} for translating authorizations into {@link KeyValues}.
*
@@ -85,10 +83,10 @@ public final class AuthorizationObservationConvention
if (context.getObject() instanceof MethodInvocation) {
return "method";
}
if (context.getObject() instanceof MethodInvocationResult) {
String className = context.getObject().getClass().getSimpleName();
if (className.contains("Method")) {
return "method";
}
String className = context.getObject().getClass().getSimpleName();
if (className.contains("Request")) {
return "request";
}
@@ -876,7 +876,7 @@ class SpaCsrfTokenRequestHandler : CsrfTokenRequestAttributeHandler() {
delegate.handle(request, response, csrfToken)
}
override fun resolveCsrfTokenValue(request: HttpServletRequest, csrfToken: CsrfToken): String {
override fun resolveCsrfTokenValue(request: HttpServletRequest, csrfToken: CsrfToken): String? {
/*
* If the request contains a request header, use CsrfTokenRequestAttributeHandler
* to resolve the CsrfToken. This applies when a single-page application includes
@@ -1221,6 +1221,24 @@ public class CsrfTests {
.andExpect(header().string(HttpHeaders.LOCATION, "/"));
}
@Test
public void loginWhenInvalidCsrfTokenThenForbidden() throws Exception {
this.mockMvc.perform(post("/login").with(csrf().useInvalidToken())
.accept(MediaType.TEXT_HTML)
.param("username", "user")
.param("password", "password"))
.andExpect(status().isForbidden());
}
@Test
public void loginWhenMissingCsrfTokenThenForbidden() throws Exception {
this.mockMvc.perform(post("/login")
.accept(MediaType.TEXT_HTML)
.param("username", "user")
.param("password", "password"))
.andExpect(status().isForbidden());
}
@Test
@WithMockUser
public void logoutWhenValidCsrfTokenThenSuccess() throws Exception {
@@ -1264,6 +1282,24 @@ class CsrfTests {
.andExpect(header().string(HttpHeaders.LOCATION, "/"))
}
@Test
fun loginWhenInvalidCsrfTokenThenForbidden() {
mockMvc.perform(post("/login").with(csrf().useInvalidToken())
.accept(MediaType.TEXT_HTML)
.param("username", "user")
.param("password", "password"))
.andExpect(status().isForbidden)
}
@Test
fun loginWhenMissingCsrfTokenThenForbidden() {
mockMvc.perform(post("/login")
.accept(MediaType.TEXT_HTML)
.param("username", "user")
.param("password", "password"))
.andExpect(status().isForbidden)
}
@Test
@WithMockUser
@Throws(Exception::class)
+2
View File
@@ -12,3 +12,5 @@
^http://openoffice.org/.*
^http://www.w3.org/2003/g/data-view
^http://schemas.openid.net/event/backchannel-logout
^http://host.docker.internal:8090/back-channel/logout
^http://host.docker.internal:8090/logout
+1 -1
View File
@@ -1,5 +1,5 @@
springBootVersion=3.1.1
version=6.2.3
version=6.2.4
samplesBranch=6.2.x
org.gradle.jvmargs=-Xmx3g -XX:+HeapDumpOnOutOfMemoryError
org.gradle.parallel=true
+9 -9
View File
@@ -6,14 +6,14 @@ io-spring-nohttp = "0.0.11"
jakarta-websocket = "2.1.1"
org-apache-directory-server = "1.5.5"
org-apache-maven-resolver = "1.9.18"
org-aspectj = "1.9.21.2"
org-aspectj = "1.9.22"
org-bouncycastle = "1.70"
org-eclipse-jetty = "11.0.20"
org-jetbrains-kotlin = "1.9.23"
org-jetbrains-kotlinx = "1.7.3"
org-mockito = "5.5.0"
org-opensaml = "4.3.0"
org-springframework = "6.1.5"
org-opensaml = "4.3.1"
org-springframework = "6.1.6"
[libraries]
ch-qos-logback-logback-classic = "ch.qos.logback:logback-classic:1.4.14"
@@ -26,15 +26,15 @@ com-squareup-okhttp3-mockwebserver = { module = "com.squareup.okhttp3:mockwebser
com-squareup-okhttp3-okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "com-squareup-okhttp3" }
com-unboundid-unboundid-ldapsdk = "com.unboundid:unboundid-ldapsdk:6.0.11"
commons-collections = "commons-collections:commons-collections:3.2.2"
io-micrometer-micrometer-observation = "io.micrometer:micrometer-observation:1.12.4"
io-micrometer-micrometer-observation = "io.micrometer:micrometer-observation:1.12.5"
io-mockk = "io.mockk:mockk:1.13.10"
io-projectreactor-reactor-bom = "io.projectreactor:reactor-bom:2023.0.4"
io-projectreactor-reactor-bom = "io.projectreactor:reactor-bom:2023.0.5"
io-rsocket-rsocket-bom = { module = "io.rsocket:rsocket-bom", version.ref = "io-rsocket" }
io-spring-javaformat-spring-javaformat-checkstyle = { module = "io.spring.javaformat:spring-javaformat-checkstyle", version.ref = "io-spring-javaformat" }
io-spring-javaformat-spring-javaformat-gradle-plugin = { module = "io.spring.javaformat:spring-javaformat-gradle-plugin", version.ref = "io-spring-javaformat" }
io-spring-nohttp-nohttp-checkstyle = { module = "io.spring.nohttp:nohttp-checkstyle", version.ref = "io-spring-nohttp" }
io-spring-nohttp-nohttp-gradle = { module = "io.spring.nohttp:nohttp-gradle", version.ref = "io-spring-nohttp" }
io-spring-security-release-plugin = "io.spring.gradle:spring-security-release-plugin:1.0.1"
io-spring-security-release-plugin = "io.spring.gradle:spring-security-release-plugin:1.0.2"
jakarta-annotation-jakarta-annotation-api = "jakarta.annotation:jakarta.annotation-api:2.1.1"
jakarta-inject-jakarta-inject-api = "jakarta.inject:jakarta.inject-api:2.0.1"
jakarta-persistence-jakarta-persistence-api = "jakarta.persistence:jakarta.persistence-api:3.1.0"
@@ -83,9 +83,9 @@ org-seleniumhq-selenium-selenium-java = "org.seleniumhq.selenium:selenium-java:3
org-seleniumhq-selenium-selenium-support = "org.seleniumhq.selenium:selenium-support:3.141.59"
org-skyscreamer-jsonassert = "org.skyscreamer:jsonassert:1.5.1"
org-slf4j-log4j-over-slf4j = "org.slf4j:log4j-over-slf4j:1.7.36"
org-slf4j-slf4j-api = "org.slf4j:slf4j-api:2.0.12"
org-springframework-data-spring-data-bom = "org.springframework.data:spring-data-bom:2023.1.4"
org-springframework-ldap-spring-ldap-core = "org.springframework.ldap:spring-ldap-core:3.2.2"
org-slf4j-slf4j-api = "org.slf4j:slf4j-api:2.0.13"
org-springframework-data-spring-data-bom = "org.springframework.data:spring-data-bom:2023.1.5"
org-springframework-ldap-spring-ldap-core = "org.springframework.ldap:spring-ldap-core:3.2.3"
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"
Binary file not shown.
+2 -2
View File
@@ -1,7 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionSha256Sum=9631d53cf3e74bfa726893aee1f8994fee4e060c401335946dba2156f440f24c
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
distributionSha256Sum=544c35d6bd849ae8a5ed0bcea39ba677dc40f49df7d1835561582da2009b961d
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
@@ -22,6 +22,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import org.apache.commons.logging.Log;
@@ -179,16 +180,17 @@ public class SpringOpaqueTokenIntrospector implements OpaqueTokenIntrospector {
}
private OAuth2AuthenticatedPrincipal convertClaimsSet(Map<String, Object> claims) {
claims.computeIfPresent(OAuth2TokenIntrospectionClaimNames.AUD, (k, v) -> {
Map<String, Object> converted = new LinkedHashMap<>(claims);
converted.computeIfPresent(OAuth2TokenIntrospectionClaimNames.AUD, (k, v) -> {
if (v instanceof String) {
return Collections.singletonList(v);
}
return v;
});
claims.computeIfPresent(OAuth2TokenIntrospectionClaimNames.CLIENT_ID, (k, v) -> v.toString());
claims.computeIfPresent(OAuth2TokenIntrospectionClaimNames.EXP,
converted.computeIfPresent(OAuth2TokenIntrospectionClaimNames.CLIENT_ID, (k, v) -> v.toString());
converted.computeIfPresent(OAuth2TokenIntrospectionClaimNames.EXP,
(k, v) -> Instant.ofEpochSecond(((Number) v).longValue()));
claims.computeIfPresent(OAuth2TokenIntrospectionClaimNames.IAT,
converted.computeIfPresent(OAuth2TokenIntrospectionClaimNames.IAT,
(k, v) -> Instant.ofEpochSecond(((Number) v).longValue()));
// RFC-7662 page 7 directs users to RFC-7519 for defining the values of these
// issuer fields.
@@ -208,11 +210,11 @@ public class SpringOpaqueTokenIntrospector implements OpaqueTokenIntrospector {
// may be awkward to debug, we do not want to manipulate this value. Previous
// versions of Spring Security
// would *only* allow valid URLs, which is not what we wish to achieve here.
claims.computeIfPresent(OAuth2TokenIntrospectionClaimNames.ISS, (k, v) -> v.toString());
claims.computeIfPresent(OAuth2TokenIntrospectionClaimNames.NBF,
converted.computeIfPresent(OAuth2TokenIntrospectionClaimNames.ISS, (k, v) -> v.toString());
converted.computeIfPresent(OAuth2TokenIntrospectionClaimNames.NBF,
(k, v) -> Instant.ofEpochSecond(((Number) v).longValue()));
Collection<GrantedAuthority> authorities = new ArrayList<>();
claims.computeIfPresent(OAuth2TokenIntrospectionClaimNames.SCOPE, (k, v) -> {
converted.computeIfPresent(OAuth2TokenIntrospectionClaimNames.SCOPE, (k, v) -> {
if (v instanceof String) {
Collection<String> scopes = Arrays.asList(((String) v).split(" "));
for (String scope : scopes) {
@@ -222,7 +224,7 @@ public class SpringOpaqueTokenIntrospector implements OpaqueTokenIntrospector {
}
return v;
});
return new OAuth2IntrospectionAuthenticatedPrincipal(claims, authorities);
return new OAuth2IntrospectionAuthenticatedPrincipal(converted, authorities);
}
}
@@ -22,6 +22,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import reactor.core.publisher.Mono;
@@ -136,16 +137,17 @@ public class SpringReactiveOpaqueTokenIntrospector implements ReactiveOpaqueToke
}
private OAuth2AuthenticatedPrincipal convertClaimsSet(Map<String, Object> claims) {
claims.computeIfPresent(OAuth2TokenIntrospectionClaimNames.AUD, (k, v) -> {
Map<String, Object> converted = new LinkedHashMap<>(claims);
converted.computeIfPresent(OAuth2TokenIntrospectionClaimNames.AUD, (k, v) -> {
if (v instanceof String) {
return Collections.singletonList(v);
}
return v;
});
claims.computeIfPresent(OAuth2TokenIntrospectionClaimNames.CLIENT_ID, (k, v) -> v.toString());
claims.computeIfPresent(OAuth2TokenIntrospectionClaimNames.EXP,
converted.computeIfPresent(OAuth2TokenIntrospectionClaimNames.CLIENT_ID, (k, v) -> v.toString());
converted.computeIfPresent(OAuth2TokenIntrospectionClaimNames.EXP,
(k, v) -> Instant.ofEpochSecond(((Number) v).longValue()));
claims.computeIfPresent(OAuth2TokenIntrospectionClaimNames.IAT,
converted.computeIfPresent(OAuth2TokenIntrospectionClaimNames.IAT,
(k, v) -> Instant.ofEpochSecond(((Number) v).longValue()));
// RFC-7662 page 7 directs users to RFC-7519 for defining the values of these
// issuer fields.
@@ -165,11 +167,11 @@ public class SpringReactiveOpaqueTokenIntrospector implements ReactiveOpaqueToke
// may be awkward to debug, we do not want to manipulate this value. Previous
// versions of Spring Security
// would *only* allow valid URLs, which is not what we wish to achieve here.
claims.computeIfPresent(OAuth2TokenIntrospectionClaimNames.ISS, (k, v) -> v.toString());
claims.computeIfPresent(OAuth2TokenIntrospectionClaimNames.NBF,
converted.computeIfPresent(OAuth2TokenIntrospectionClaimNames.ISS, (k, v) -> v.toString());
converted.computeIfPresent(OAuth2TokenIntrospectionClaimNames.NBF,
(k, v) -> Instant.ofEpochSecond(((Number) v).longValue()));
Collection<GrantedAuthority> authorities = new ArrayList<>();
claims.computeIfPresent(OAuth2TokenIntrospectionClaimNames.SCOPE, (k, v) -> {
converted.computeIfPresent(OAuth2TokenIntrospectionClaimNames.SCOPE, (k, v) -> {
if (v instanceof String) {
Collection<String> scopes = Arrays.asList(((String) v).split(" "));
for (String scope : scopes) {
@@ -179,7 +181,7 @@ public class SpringReactiveOpaqueTokenIntrospector implements ReactiveOpaqueToke
}
return v;
});
return new OAuth2IntrospectionAuthenticatedPrincipal(claims, authorities);
return new OAuth2IntrospectionAuthenticatedPrincipal(converted, authorities);
}
private OAuth2IntrospectionException onError(Throwable ex) {
+1 -1
View File
@@ -6,7 +6,7 @@ pluginManagement {
plugins {
id "com.gradle.enterprise" version "3.12.6"
id "io.spring.ge.conventions" version "0.0.15"
id "io.spring.ge.conventions" version "0.0.16"
}
dependencyResolutionManagement {
@@ -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.
@@ -125,7 +125,7 @@ public class AuthenticationWebFilter implements WebFilter {
.flatMap(
(authentication) -> onAuthenticationSuccess(authentication, new WebFilterExchange(exchange, chain)))
.doOnError(AuthenticationException.class,
(ex) -> logger.debug(LogMessage.format("Authentication failed: %s", ex.getMessage())));
(ex) -> logger.debug(LogMessage.format("Authentication failed: %s", ex.getMessage()), ex));
}
protected Mono<Void> onAuthenticationSuccess(Authentication authentication, WebFilterExchange webFilterExchange) {