Compare commits
35 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d2179e0e78 | |||
| e5694ac7b5 | |||
| 9de0aad31b | |||
| 7293fc023b | |||
| ed3ed28b3c | |||
| 704a324273 | |||
| 0fc884f592 | |||
| 6162e71e18 | |||
| 627de76f67 | |||
| 5d55c8d13a | |||
| 342b9c3539 | |||
| 537845491e | |||
| e68738dd94 | |||
| 9f32f62d34 | |||
| 9fb4db12a5 | |||
| f493388513 | |||
| a79a2b031a | |||
| cc30c901c7 | |||
| dd986b0932 | |||
| 8429c23108 | |||
| 97f3567702 | |||
| b4fc01194f | |||
| 7e3bf9662c | |||
| 8b2a453301 | |||
| 894105aab5 | |||
| 312c2049f3 | |||
| b9653346a1 | |||
| 9e6bcbd1d0 | |||
| 5453224377 | |||
| a14ad770ab | |||
| 20a6863e07 | |||
| e145c07d5b | |||
| a933089ec2 | |||
| a8da9ec7f7 | |||
| acaed1ad96 |
+6
-2
@@ -83,8 +83,12 @@ public class VerifyDependenciesVersionsPlugin implements Plugin<Project> {
|
||||
String transitiveNimbusJoseJwtVersion = TransitiveDependencyLookupUtils.lookupJwtVersion(oauth2OidcSdkVersion);
|
||||
String expectedNimbusJoseJwtVersion = this.getExpectedNimbusJoseJwtVersion().get();
|
||||
if (!transitiveNimbusJoseJwtVersion.equals(expectedNimbusJoseJwtVersion)) {
|
||||
String message = String.format("Found transitive nimbus-jose-jwt:%s in oauth2-oidc-sdk:%s, but the project contains a different version of nimbus-jose-jwt [%s]. Please align the versions.", transitiveNimbusJoseJwtVersion, oauth2OidcSdkVersion, expectedNimbusJoseJwtVersion);
|
||||
throw new VerificationException(message);
|
||||
String transitiveNimbusJoseJwtMajorMinorVersion = transitiveNimbusJoseJwtVersion.substring(0, transitiveNimbusJoseJwtVersion.lastIndexOf("."));
|
||||
String expectedNimbusJoseJwtMajorMinorVersion = expectedNimbusJoseJwtVersion.substring(0, expectedNimbusJoseJwtVersion.lastIndexOf("."));
|
||||
if (!transitiveNimbusJoseJwtMajorMinorVersion.equals(expectedNimbusJoseJwtMajorMinorVersion)) {
|
||||
String message = String.format("Found transitive nimbus-jose-jwt:%s in oauth2-oidc-sdk:%s, but the project contains a different version of nimbus-jose-jwt [%s]. Please align the major/minor versions.", transitiveNimbusJoseJwtVersion, oauth2OidcSdkVersion, expectedNimbusJoseJwtVersion);
|
||||
throw new VerificationException(message);
|
||||
}
|
||||
}
|
||||
String message = String.format("Found transitive nimbus-jose-jwt:%s in oauth2-oidc-sdk:%s, the project contains expected version of nimbus-jose-jwt [%s]. Verified all versions align.", transitiveNimbusJoseJwtVersion, oauth2OidcSdkVersion, expectedNimbusJoseJwtVersion);
|
||||
try {
|
||||
|
||||
@@ -182,6 +182,7 @@ import org.springframework.security.saml2.provider.service.registration.OpenSaml
|
||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
|
||||
import org.springframework.security.saml2.provider.service.registration.TestRelyingPartyRegistrations;
|
||||
import org.springframework.security.web.PortResolverImpl;
|
||||
import org.springframework.security.web.authentication.AuthenticationFilter;
|
||||
import org.springframework.security.web.authentication.WebAuthenticationDetails;
|
||||
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
|
||||
import org.springframework.security.web.authentication.preauth.PreAuthenticatedCredentialsNotFoundException;
|
||||
@@ -442,7 +443,7 @@ final class SerializationSamples {
|
||||
generatorByClassName.put(AuthenticationSuccessEvent.class,
|
||||
(r) -> new AuthenticationSuccessEvent(authentication));
|
||||
generatorByClassName.put(InteractiveAuthenticationSuccessEvent.class,
|
||||
(r) -> new InteractiveAuthenticationSuccessEvent(authentication, Authentication.class));
|
||||
(r) -> new InteractiveAuthenticationSuccessEvent(authentication, AuthenticationFilter.class));
|
||||
generatorByClassName.put(LogoutSuccessEvent.class, (r) -> new LogoutSuccessEvent(authentication));
|
||||
generatorByClassName.put(JaasAuthenticationFailedEvent.class,
|
||||
(r) -> new JaasAuthenticationFailedEvent(authentication, new RuntimeException("message")));
|
||||
|
||||
BIN
Binary file not shown.
+1
-1
@@ -252,7 +252,7 @@ final class UniqueSecurityAnnotationScanner<A extends Annotation> extends Abstra
|
||||
}
|
||||
for (int i = 0; i < rootParameterTypes.length; i++) {
|
||||
Class<?> resolvedParameterType = ResolvableType.forMethodParameter(candidateMethod, i, sourceDeclaringClass)
|
||||
.resolve();
|
||||
.toClass();
|
||||
if (rootParameterTypes[i] != resolvedParameterType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
+32
@@ -22,6 +22,7 @@ import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.core.annotation.AnnotationConfigurationException;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
@@ -275,6 +276,14 @@ public class UniqueSecurityAnnotationScannerTests {
|
||||
assertThat(pre).isNotNull();
|
||||
}
|
||||
|
||||
// gh-17898
|
||||
@Test
|
||||
void scanWhenAnnotationOnParameterizedUndeclaredMethodAndThenLocates() throws Exception {
|
||||
Method method = ClassUtils.getMethod(GenericInterfaceImpl.class, "processOneAndTwo", Long.class, Object.class);
|
||||
PreAuthorize pre = this.scanner.scan(method, method.getDeclaringClass());
|
||||
assertThat(pre).isNotNull();
|
||||
}
|
||||
|
||||
@PreAuthorize("one")
|
||||
private interface AnnotationOnInterface {
|
||||
|
||||
@@ -637,4 +646,27 @@ public class UniqueSecurityAnnotationScannerTests {
|
||||
|
||||
}
|
||||
|
||||
interface GenericInterface<A, B> {
|
||||
|
||||
@PreAuthorize("hasAuthority('thirtythree')")
|
||||
void processOneAndTwo(A value1, B value2);
|
||||
|
||||
}
|
||||
|
||||
abstract static class GenericAbstractSuperclass<C> implements GenericInterface<Long, C> {
|
||||
|
||||
@Override
|
||||
public void processOneAndTwo(Long value1, C value2) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class GenericInterfaceImpl extends GenericAbstractSuperclass<String> {
|
||||
|
||||
// The compiler does not require us to declare a concrete
|
||||
// processOneAndTwo(Long, String) method, and we intentionally
|
||||
// do not declare one here.
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -544,6 +544,14 @@ open class BankService {
|
||||
The result is that the above method will only return the `Account` if its `owner` attribute matches the logged-in user's `name`.
|
||||
If not, Spring Security will throw an `AccessDeniedException` and return a 403 status code.
|
||||
|
||||
[NOTE]
|
||||
=====
|
||||
Note that `@PostAuthorize` is not recommended for classes that perform database writes since that typically means that a database change was made before the security invariants were checked.
|
||||
A common example of doing this is if you have `@Transactional` and `@PostAuthorize` on the same method.
|
||||
Instead, read the value first, using `@PostAuthorize` on the read, and then perform the database write, should that read is authorized.
|
||||
If you must do something like this, you can <<changing-the-order, ensure that `@EnableTransactionManagement` comes before `@EnableMethodSecurity`>>.
|
||||
=====
|
||||
|
||||
[[use-prefilter]]
|
||||
=== Filtering Method Parameters with `@PreFilter`
|
||||
|
||||
@@ -1795,39 +1803,7 @@ As already noted, there is a Spring AOP method interceptor for each annotation,
|
||||
|
||||
Namely, the `@PreFilter` method interceptor's order is 100, ``@PreAuthorize``'s is 200, and so on.
|
||||
|
||||
The reason this is important to note is that there are other AOP-based annotations like `@EnableTransactionManagement` that have an order of `Integer.MAX_VALUE`.
|
||||
In other words, they are located at the end of the advisor chain by default.
|
||||
|
||||
At times, it can be valuable to have other advice execute before Spring Security.
|
||||
For example, if you have a method annotated with `@Transactional` and `@PostAuthorize`, you might want the transaction to still be open when `@PostAuthorize` runs so that an `AccessDeniedException` will cause a rollback.
|
||||
|
||||
To get `@EnableTransactionManagement` to open a transaction before method authorization advice runs, you can set ``@EnableTransactionManagement``'s order like so:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@EnableTransactionManagement(order = 0)
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@EnableTransactionManagement(order = 0)
|
||||
----
|
||||
|
||||
Xml::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<tx:annotation-driven ref="txManager" order="0"/>
|
||||
----
|
||||
======
|
||||
|
||||
Since the earliest method interceptor (`@PreFilter`) is set to an order of 100, a setting of zero means that the transaction advice will run before all Spring Security advice.
|
||||
You can use the `offset` parameter on `@EnableMethodSecurity` to move all interceptors en masse to provide their advice earlier or later in a method invocation.
|
||||
|
||||
[[authorization-expressions]]
|
||||
== Expressing Authorization with SpEL
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@
|
||||
# limitations under the License.
|
||||
#
|
||||
springBootVersion=3.3.3
|
||||
version=6.4.9
|
||||
version=6.4.10
|
||||
samplesBranch=6.4.x
|
||||
org.gradle.jvmargs=-Xmx3g -XX:+HeapDumpOnOutOfMemoryError
|
||||
org.gradle.parallel=true
|
||||
|
||||
@@ -21,7 +21,7 @@ ch-qos-logback-logback-classic = "ch.qos.logback:logback-classic:1.5.18"
|
||||
com-fasterxml-jackson-jackson-bom = "com.fasterxml.jackson:jackson-bom:2.18.4.1"
|
||||
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-nimbusds-nimbus-jose-jwt = "com.nimbusds:nimbus-jose-jwt:9.37.3"
|
||||
com-nimbusds-nimbus-jose-jwt = "com.nimbusds:nimbus-jose-jwt:9.37.4"
|
||||
com-nimbusds-oauth2-oidc-sdk = "com.nimbusds:oauth2-oidc-sdk:9.43.6"
|
||||
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" }
|
||||
|
||||
+37
-108
@@ -30,34 +30,33 @@ import com.nimbusds.jose.crypto.RSASSASigner;
|
||||
import com.nimbusds.jwt.JWTClaimsSet;
|
||||
import com.nimbusds.jwt.PlainJWT;
|
||||
import net.minidev.json.JSONObject;
|
||||
import okhttp3.mockwebserver.MockResponse;
|
||||
import okhttp3.mockwebserver.MockWebServer;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.MockedStatic;
|
||||
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.AuthenticationManagerResolver;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||
import org.springframework.security.oauth2.jose.TestKeys;
|
||||
import org.springframework.security.oauth2.jwt.Jwt;
|
||||
import org.springframework.security.oauth2.jwt.JwtClaimNames;
|
||||
import org.springframework.security.oauth2.server.resource.authentication.JwtIssuerAuthenticationManagerResolver.TrustedIssuerJwtAuthenticationManagerResolver;
|
||||
import org.springframework.security.oauth2.jwt.JwtDecoder;
|
||||
import org.springframework.security.oauth2.jwt.JwtDecoders;
|
||||
import org.springframework.security.oauth2.jwt.TestJwts;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.BDDMockito.mock;
|
||||
import static org.mockito.BDDMockito.verify;
|
||||
import static org.mockito.Mockito.mockStatic;
|
||||
|
||||
/**
|
||||
* Tests for {@link JwtIssuerAuthenticationManagerResolver}
|
||||
*/
|
||||
public class JwtIssuerAuthenticationManagerResolverTests {
|
||||
|
||||
private static final String DEFAULT_RESPONSE_TEMPLATE = "{\n" + " \"issuer\": \"%s\", \n"
|
||||
+ " \"jwks_uri\": \"%s/.well-known/jwks.json\" \n" + "}";
|
||||
|
||||
private static final String JWK_SET = "{\"keys\":[{\"kty\":\"RSA\",\"e\":\"AQAB\",\"use\":\"sig\",\"kid\":\"one\",\"n\":\"3FlqJr5TRskIQIgdE3Dd7D9lboWdcTUT8a-fJR7MAvQm7XXNoYkm3v7MQL1NYtDvL2l8CAnc0WdSTINU6IRvc5Kqo2Q4csNX9SHOmEfzoROjQqahEcve1jBXluoCXdYuYpx4_1tfRgG6ii4Uhxh6iI8qNMJQX-fLfqhbfYfxBQVRPywBkAbIP4x1EAsbC6FSNmkhCxiMNqEgxaIpY8C2kJdJ_ZIV-WW4noDdzpKqHcwmB8FsrumlVY_DNVvUSDIipiq9PbP4H99TXN1o746oRaNa07rq1hoCgMSSy-85SagCoxlmyE-D-of9SsMY8Ol9t0rdzpobBuhyJ_o5dfvjKw\"}]}";
|
||||
|
||||
private String jwt = jwt("iss", "trusted");
|
||||
|
||||
private String evil = jwt("iss", "\"");
|
||||
@@ -66,31 +65,22 @@ public class JwtIssuerAuthenticationManagerResolverTests {
|
||||
|
||||
@Test
|
||||
public void resolveWhenUsingFromTrustedIssuersThenReturnsAuthenticationManager() throws Exception {
|
||||
try (MockWebServer server = new MockWebServer()) {
|
||||
server.start();
|
||||
String issuer = server.url("").toString();
|
||||
// @formatter:off
|
||||
server.enqueue(new MockResponse().setResponseCode(200)
|
||||
.setHeader("Content-Type", "application/json")
|
||||
.setBody(String.format(DEFAULT_RESPONSE_TEMPLATE, issuer, issuer)
|
||||
));
|
||||
server.enqueue(new MockResponse().setResponseCode(200)
|
||||
.setHeader("Content-Type", "application/json")
|
||||
.setBody(JWK_SET)
|
||||
);
|
||||
server.enqueue(new MockResponse().setResponseCode(200)
|
||||
.setHeader("Content-Type", "application/json")
|
||||
.setBody(JWK_SET)
|
||||
);
|
||||
// @formatter:on
|
||||
JWSObject jws = new JWSObject(new JWSHeader(JWSAlgorithm.RS256),
|
||||
new Payload(new JSONObject(Collections.singletonMap(JwtClaimNames.ISS, issuer))));
|
||||
jws.sign(new RSASSASigner(TestKeys.DEFAULT_PRIVATE_KEY));
|
||||
JwtIssuerAuthenticationManagerResolver authenticationManagerResolver = JwtIssuerAuthenticationManagerResolver
|
||||
.fromTrustedIssuers(issuer);
|
||||
Authentication token = withBearerToken(jws.serialize());
|
||||
AuthenticationManager authenticationManager = authenticationManagerResolver.resolve(null);
|
||||
assertThat(authenticationManager).isNotNull();
|
||||
String issuer = "https://idp.example";
|
||||
|
||||
// @formatter:on
|
||||
JWSObject jws = new JWSObject(new JWSHeader(JWSAlgorithm.RS256),
|
||||
new Payload(new JSONObject(Collections.singletonMap(JwtClaimNames.ISS, issuer))));
|
||||
jws.sign(new RSASSASigner(TestKeys.DEFAULT_PRIVATE_KEY));
|
||||
JwtIssuerAuthenticationManagerResolver authenticationManagerResolver = JwtIssuerAuthenticationManagerResolver
|
||||
.fromTrustedIssuers(issuer);
|
||||
Authentication token = withBearerToken(jws.serialize());
|
||||
AuthenticationManager authenticationManager = authenticationManagerResolver.resolve(null);
|
||||
assertThat(authenticationManager).isNotNull();
|
||||
JwtDecoder decoder = mock(JwtDecoder.class);
|
||||
Jwt jwt = TestJwts.user();
|
||||
given(decoder.decode(token.getName())).willReturn(jwt);
|
||||
try (MockedStatic<JwtDecoders> jwtDecoders = mockStatic(JwtDecoders.class)) {
|
||||
given(JwtDecoders.fromIssuerLocation(issuer)).willReturn(decoder);
|
||||
Authentication authentication = authenticationManager.authenticate(token);
|
||||
assertThat(authentication.isAuthenticated()).isTrue();
|
||||
}
|
||||
@@ -98,29 +88,20 @@ public class JwtIssuerAuthenticationManagerResolverTests {
|
||||
|
||||
@Test
|
||||
public void resolveWhenUsingFromTrustedIssuersPredicateThenReturnsAuthenticationManager() throws Exception {
|
||||
try (MockWebServer server = new MockWebServer()) {
|
||||
server.start();
|
||||
String issuer = server.url("").toString();
|
||||
// @formatter:off
|
||||
server.enqueue(new MockResponse().setResponseCode(200)
|
||||
.setHeader("Content-Type", "application/json")
|
||||
.setBody(String.format(DEFAULT_RESPONSE_TEMPLATE, issuer, issuer)
|
||||
));
|
||||
server.enqueue(new MockResponse().setResponseCode(200)
|
||||
.setHeader("Content-Type", "application/json")
|
||||
.setBody(JWK_SET)
|
||||
);
|
||||
server.enqueue(new MockResponse().setResponseCode(200)
|
||||
.setHeader("Content-Type", "application/json")
|
||||
.setBody(JWK_SET)
|
||||
);
|
||||
// @formatter:on
|
||||
JWSObject jws = new JWSObject(new JWSHeader(JWSAlgorithm.RS256),
|
||||
new Payload(new JSONObject(Collections.singletonMap(JwtClaimNames.ISS, issuer))));
|
||||
jws.sign(new RSASSASigner(TestKeys.DEFAULT_PRIVATE_KEY));
|
||||
JwtIssuerAuthenticationManagerResolver authenticationManagerResolver = JwtIssuerAuthenticationManagerResolver
|
||||
.fromTrustedIssuers(issuer::equals);
|
||||
Authentication token = withBearerToken(jws.serialize());
|
||||
String issuer = "https://idp.example";
|
||||
|
||||
// @formatter:on
|
||||
JWSObject jws = new JWSObject(new JWSHeader(JWSAlgorithm.RS256),
|
||||
new Payload(new JSONObject(Collections.singletonMap(JwtClaimNames.ISS, issuer))));
|
||||
jws.sign(new RSASSASigner(TestKeys.DEFAULT_PRIVATE_KEY));
|
||||
JwtIssuerAuthenticationManagerResolver authenticationManagerResolver = JwtIssuerAuthenticationManagerResolver
|
||||
.fromTrustedIssuers(issuer::equals);
|
||||
Authentication token = withBearerToken(jws.serialize());
|
||||
JwtDecoder decoder = mock(JwtDecoder.class);
|
||||
Jwt jwt = TestJwts.user();
|
||||
given(decoder.decode(token.getName())).willReturn(jwt);
|
||||
try (MockedStatic<JwtDecoders> jwtDecoders = mockStatic(JwtDecoders.class)) {
|
||||
given(JwtDecoders.fromIssuerLocation(issuer)).willReturn(decoder);
|
||||
AuthenticationManager authenticationManager = authenticationManagerResolver.resolve(null);
|
||||
assertThat(authenticationManager).isNotNull();
|
||||
Authentication authentication = authenticationManager.authenticate(token);
|
||||
@@ -128,58 +109,6 @@ public class JwtIssuerAuthenticationManagerResolverTests {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveWhednUsingTrustedIssuerThenReturnsAuthenticationManager() throws Exception {
|
||||
try (MockWebServer server = new MockWebServer()) {
|
||||
server.start();
|
||||
String issuer = server.url("").toString();
|
||||
// @formatter:off
|
||||
server.enqueue(new MockResponse().setResponseCode(500)
|
||||
.setHeader("Content-Type", "application/json")
|
||||
.setBody(String.format(DEFAULT_RESPONSE_TEMPLATE, issuer, issuer))
|
||||
);
|
||||
server.enqueue(new MockResponse().setResponseCode(200)
|
||||
.setHeader("Content-Type", "application/json")
|
||||
.setBody(String.format(DEFAULT_RESPONSE_TEMPLATE, issuer, issuer))
|
||||
);
|
||||
server.enqueue(new MockResponse().setResponseCode(200)
|
||||
.setHeader("Content-Type", "application/json")
|
||||
.setBody(JWK_SET)
|
||||
);
|
||||
// @formatter:on
|
||||
JWSObject jws = new JWSObject(new JWSHeader(JWSAlgorithm.RS256),
|
||||
new Payload(new JSONObject(Collections.singletonMap(JwtClaimNames.ISS, issuer))));
|
||||
jws.sign(new RSASSASigner(TestKeys.DEFAULT_PRIVATE_KEY));
|
||||
JwtIssuerAuthenticationManagerResolver authenticationManagerResolver = JwtIssuerAuthenticationManagerResolver
|
||||
.fromTrustedIssuers(issuer);
|
||||
Authentication token = withBearerToken(jws.serialize());
|
||||
AuthenticationManager authenticationManager = authenticationManagerResolver.resolve(null);
|
||||
assertThat(authenticationManager).isNotNull();
|
||||
assertThatExceptionOfType(IllegalArgumentException.class)
|
||||
.isThrownBy(() -> authenticationManager.authenticate(token));
|
||||
Authentication authentication = authenticationManager.authenticate(token);
|
||||
assertThat(authentication.isAuthenticated()).isTrue();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveWhenUsingSameIssuerThenReturnsSameAuthenticationManager() throws Exception {
|
||||
try (MockWebServer server = new MockWebServer()) {
|
||||
String issuer = server.url("").toString();
|
||||
server.enqueue(new MockResponse().setResponseCode(200)
|
||||
.setHeader("Content-Type", "application/json")
|
||||
.setBody(String.format(DEFAULT_RESPONSE_TEMPLATE, issuer, issuer)));
|
||||
server.enqueue(new MockResponse().setResponseCode(200)
|
||||
.setHeader("Content-Type", "application/json")
|
||||
.setBody(JWK_SET));
|
||||
TrustedIssuerJwtAuthenticationManagerResolver resolver = new TrustedIssuerJwtAuthenticationManagerResolver(
|
||||
(iss) -> iss.equals(issuer));
|
||||
AuthenticationManager authenticationManager = resolver.resolve(issuer);
|
||||
AuthenticationManager cachedAuthenticationManager = resolver.resolve(issuer);
|
||||
assertThat(authenticationManager).isSameAs(cachedAuthenticationManager);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveWhenUsingUntrustedIssuerThenException() {
|
||||
JwtIssuerAuthenticationManagerResolver authenticationManagerResolver = JwtIssuerAuthenticationManagerResolver
|
||||
|
||||
+12
-1
@@ -46,13 +46,14 @@ import org.springframework.util.StringUtils;
|
||||
* wraps the chain in before and after observations
|
||||
*
|
||||
* @author Josh Cummings
|
||||
* @author Nikita Konev
|
||||
* @since 6.0
|
||||
*/
|
||||
public final class ObservationFilterChainDecorator implements FilterChainProxy.FilterChainDecorator {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(FilterChainProxy.class);
|
||||
|
||||
private static final String ATTRIBUTE = ObservationFilterChainDecorator.class + ".observation";
|
||||
static final String ATTRIBUTE = ObservationFilterChainDecorator.class + ".observation";
|
||||
|
||||
static final String UNSECURED_OBSERVATION_NAME = "spring.security.http.unsecured.requests";
|
||||
|
||||
@@ -250,6 +251,16 @@ public final class ObservationFilterChainDecorator implements FilterChainProxy.F
|
||||
private AroundFilterObservation parent(HttpServletRequest request) {
|
||||
FilterChainObservationContext beforeContext = FilterChainObservationContext.before();
|
||||
FilterChainObservationContext afterContext = FilterChainObservationContext.after();
|
||||
|
||||
AroundFilterObservation existingParentObservation = (AroundFilterObservation) request
|
||||
.getAttribute(ATTRIBUTE);
|
||||
if (existingParentObservation != null) {
|
||||
beforeContext
|
||||
.setParentObservation(existingParentObservation.before().getContext().getParentObservation());
|
||||
afterContext
|
||||
.setParentObservation(existingParentObservation.after().getContext().getParentObservation());
|
||||
}
|
||||
|
||||
Observation before = Observation.createNotStarted(this.convention, () -> beforeContext, this.registry);
|
||||
Observation after = Observation.createNotStarted(this.convention, () -> afterContext, this.registry);
|
||||
AroundFilterObservation parent = AroundFilterObservation.create(before, after);
|
||||
|
||||
@@ -310,6 +310,65 @@ public class FilterChainProxyTests {
|
||||
assertFilterChainObservation(contexts.next(), "after", 1);
|
||||
}
|
||||
|
||||
// gh-12610
|
||||
@Test
|
||||
void parentObservationIsTakenIntoAccountDuringDispatchError() throws Exception {
|
||||
ObservationHandler<Observation.Context> handler = mock(ObservationHandler.class);
|
||||
given(handler.supportsContext(any())).willReturn(true);
|
||||
ObservationRegistry registry = ObservationRegistry.create();
|
||||
registry.observationConfig().observationHandler(handler);
|
||||
|
||||
given(this.matcher.matches(any())).willReturn(true);
|
||||
SecurityFilterChain sec = new DefaultSecurityFilterChain(this.matcher, Arrays.asList(this.filter));
|
||||
FilterChainProxy fcp = new FilterChainProxy(sec);
|
||||
fcp.setFilterChainDecorator(new ObservationFilterChainDecorator(registry));
|
||||
Filter initialFilter = ObservationFilterChainDecorator.FilterObservation
|
||||
.create(Observation.createNotStarted("wrap", registry))
|
||||
.wrap(fcp);
|
||||
|
||||
ServletRequest initialRequest = new MockHttpServletRequest("GET", "/");
|
||||
initialFilter.doFilter(initialRequest, new MockHttpServletResponse(), this.chain);
|
||||
|
||||
// simulate request attribute copying in case dispatching to ERROR
|
||||
ObservationFilterChainDecorator.AroundFilterObservation parentObservation = (ObservationFilterChainDecorator.AroundFilterObservation) initialRequest
|
||||
.getAttribute(ObservationFilterChainDecorator.ATTRIBUTE);
|
||||
assertThat(parentObservation).isNotNull();
|
||||
|
||||
// simulate dispatching error-related request
|
||||
Filter errorRelatedFilter = ObservationFilterChainDecorator.FilterObservation
|
||||
.create(Observation.createNotStarted("wrap", registry))
|
||||
.wrap(fcp);
|
||||
ServletRequest errorRelatedRequest = new MockHttpServletRequest("GET", "/error");
|
||||
errorRelatedRequest.setAttribute(ObservationFilterChainDecorator.ATTRIBUTE, parentObservation);
|
||||
errorRelatedFilter.doFilter(errorRelatedRequest, new MockHttpServletResponse(), this.chain);
|
||||
|
||||
ArgumentCaptor<Observation.Context> captor = ArgumentCaptor.forClass(Observation.Context.class);
|
||||
verify(handler, times(8)).onStart(captor.capture());
|
||||
verify(handler, times(8)).onStop(any());
|
||||
List<Observation.Context> contexts = captor.getAllValues();
|
||||
|
||||
Observation.Context initialRequestObservationContextBefore = contexts.get(1);
|
||||
Observation.Context initialRequestObservationContextAfter = contexts.get(3);
|
||||
assertFilterChainObservation(initialRequestObservationContextBefore, "before", 1);
|
||||
assertFilterChainObservation(initialRequestObservationContextAfter, "after", 1);
|
||||
|
||||
assertThat(initialRequestObservationContextBefore.getParentObservation()).isNotNull();
|
||||
assertThat(initialRequestObservationContextBefore.getParentObservation())
|
||||
.isSameAs(initialRequestObservationContextAfter.getParentObservation());
|
||||
|
||||
Observation.Context errorRelatedRequestObservationContextBefore = contexts.get(5);
|
||||
Observation.Context errorRelatedRequestObservationContextAfter = contexts.get(7);
|
||||
assertFilterChainObservation(errorRelatedRequestObservationContextBefore, "before", 1);
|
||||
assertFilterChainObservation(errorRelatedRequestObservationContextAfter, "after", 1);
|
||||
|
||||
assertThat(errorRelatedRequestObservationContextBefore.getParentObservation()).isNotNull();
|
||||
assertThat(errorRelatedRequestObservationContextBefore.getParentObservation())
|
||||
.isSameAs(initialRequestObservationContextBefore.getParentObservation());
|
||||
assertThat(errorRelatedRequestObservationContextAfter.getParentObservation()).isNotNull();
|
||||
assertThat(errorRelatedRequestObservationContextAfter.getParentObservation())
|
||||
.isSameAs(initialRequestObservationContextBefore.getParentObservation());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doFilterWhenMultipleFiltersThenObservationRegistryObserves() throws Exception {
|
||||
ObservationHandler<Observation.Context> handler = mock(ObservationHandler.class);
|
||||
|
||||
Reference in New Issue
Block a user