Skip to content

Commit 1a16799

Browse files
committed
Add support for automatic context-propagation with Micrometer
Closes spring-projectsgh-16665
1 parent f2d78a0 commit 1a16799

File tree

9 files changed

+195
-0
lines changed

9 files changed

+195
-0
lines changed

core/spring-security-core.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ dependencies {
1616
api 'io.micrometer:micrometer-observation'
1717

1818
optional 'com.fasterxml.jackson.core:jackson-databind'
19+
optional 'io.micrometer:context-propagation'
1920
optional 'io.projectreactor:reactor-core'
2021
optional 'jakarta.annotation:jakarta.annotation-api'
2122
optional 'org.aspectj:aspectjrt'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
* Copyright 2002-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.core.context;
18+
19+
import io.micrometer.context.ThreadLocalAccessor;
20+
import reactor.core.publisher.Mono;
21+
22+
import org.springframework.util.Assert;
23+
24+
/**
25+
* A {@link ThreadLocalAccessor} for accessing a {@link SecurityContext} with the
26+
* {@link ReactiveSecurityContextHolder}.
27+
* <p>
28+
* This class adapts the {@link ReactiveSecurityContextHolder} to the
29+
* {@link ThreadLocalAccessor} contract to allow Micrometer Context Propagation to
30+
* automatically propagate a {@link SecurityContext} in Reactive applications. It is
31+
* automatically registered with the {@link io.micrometer.context.ContextRegistry} through
32+
* the {@link java.util.ServiceLoader} mechanism when context-propagation is on the
33+
* classpath.
34+
*
35+
* @author Steve Riesenberg
36+
* @since 6.5
37+
* @see io.micrometer.context.ContextRegistry
38+
*/
39+
public final class ReactiveSecurityContextHolderThreadLocalAccessor
40+
implements ThreadLocalAccessor<Mono<SecurityContext>> {
41+
42+
private static final ThreadLocal<Mono<SecurityContext>> threadLocal = new ThreadLocal<>();
43+
44+
@Override
45+
public Object key() {
46+
return SecurityContext.class;
47+
}
48+
49+
@Override
50+
public Mono<SecurityContext> getValue() {
51+
return threadLocal.get();
52+
}
53+
54+
@Override
55+
public void setValue(Mono<SecurityContext> securityContext) {
56+
Assert.notNull(securityContext, "securityContext cannot be null");
57+
threadLocal.set(securityContext);
58+
}
59+
60+
@Override
61+
public void setValue() {
62+
threadLocal.remove();
63+
}
64+
65+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
* Copyright 2002-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.core.context;
18+
19+
import io.micrometer.context.ThreadLocalAccessor;
20+
21+
/**
22+
* A {@link ThreadLocalAccessor} for accessing a {@link SecurityContext} with the
23+
* {@link SecurityContextHolder}.
24+
* <p>
25+
* This class adapts the {@link SecurityContextHolder} to the {@link ThreadLocalAccessor}
26+
* contract to allow Micrometer Context Propagation to automatically propagate a
27+
* {@link SecurityContext} in Servlet applications. It is automatically registered with
28+
* the {@link io.micrometer.context.ContextRegistry} through the
29+
* {@link java.util.ServiceLoader} mechanism when context-propagation is on the classpath.
30+
*
31+
* @author Steve Riesenberg
32+
* @since 6.5
33+
* @see io.micrometer.context.ContextRegistry
34+
*/
35+
public final class SecurityContextHolderThreadLocalAccessor implements ThreadLocalAccessor<SecurityContext> {
36+
37+
@Override
38+
public Object key() {
39+
return SecurityContext.class.getName();
40+
}
41+
42+
@Override
43+
public SecurityContext getValue() {
44+
SecurityContext securityContext = SecurityContextHolder.getContext();
45+
SecurityContext emptyContext = SecurityContextHolder.createEmptyContext();
46+
47+
return !securityContext.equals(emptyContext) ? securityContext : null;
48+
}
49+
50+
@Override
51+
public void setValue(SecurityContext value) {
52+
SecurityContextHolder.setContext(value);
53+
}
54+
55+
@Override
56+
public void setValue() {
57+
SecurityContextHolder.clearContext();
58+
}
59+
60+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
org.springframework.security.core.context.ReactiveSecurityContextHolderThreadLocalAccessor
2+
org.springframework.security.core.context.SecurityContextHolderThreadLocalAccessor

dependencies/spring-security-dependencies.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ dependencies {
3535
api libs.com.unboundid.unboundid.ldapsdk
3636
api libs.commons.collections
3737
api libs.io.mockk
38+
api libs.io.micrometer.context.propagation
3839
api libs.io.micrometer.micrometer.observation
3940
api libs.jakarta.annotation.jakarta.annotation.api
4041
api libs.jakarta.inject.jakarta.inject.api

gradle/libs.versions.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ com-squareup-okhttp3-okhttp = { module = "com.squareup.okhttp3:okhttp", version.
2828
com-unboundid-unboundid-ldapsdk = "com.unboundid:unboundid-ldapsdk:6.0.11"
2929
com-unboundid-unboundid-ldapsdk7 = "com.unboundid:unboundid-ldapsdk:7.0.1"
3030
commons-collections = "commons-collections:commons-collections:3.2.2"
31+
io-micrometer-context-propagation = "io.micrometer:context-propagation:1.1.2"
3132
io-micrometer-micrometer-observation = "io.micrometer:micrometer-observation:1.14.4"
3233
io-mockk = "io.mockk:mockk:1.13.16"
3334
io-projectreactor-reactor-bom = "io.projectreactor:reactor-bom:2023.0.15"

web/spring-security-web.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ dependencies {
3636
api 'org.springframework:spring-web'
3737

3838
optional 'com.fasterxml.jackson.core:jackson-databind'
39+
optional 'io.micrometer:context-propagation'
3940
optional 'io.projectreactor:reactor-core'
4041
optional 'org.springframework:spring-jdbc'
4142
optional 'org.springframework:spring-tx'
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
* Copyright 2002-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.web.server;
18+
19+
import io.micrometer.context.ThreadLocalAccessor;
20+
21+
import org.springframework.util.Assert;
22+
import org.springframework.web.server.ServerWebExchange;
23+
24+
/**
25+
* A {@link ThreadLocalAccessor} for accessing a {@link ServerWebExchange}.
26+
* <p>
27+
* This class adapts the existing Reactor Context attribute
28+
* {@code ServerWebExchange.class} to the {@link ThreadLocalAccessor} contract to allow
29+
* Micrometer Context Propagation to automatically propagate a {@link ServerWebExchange}
30+
* in Reactive applications. It is automatically registered with the
31+
* {@link io.micrometer.context.ContextRegistry} through the
32+
* {@link java.util.ServiceLoader} mechanism when context-propagation is on the classpath.
33+
*
34+
* @author Steve Riesenberg
35+
* @since 6.5
36+
* @see io.micrometer.context.ContextRegistry
37+
*/
38+
public final class ServerWebExchangeThreadLocalAccessor implements ThreadLocalAccessor<ServerWebExchange> {
39+
40+
private static final ThreadLocal<ServerWebExchange> threadLocal = new ThreadLocal<>();
41+
42+
@Override
43+
public Object key() {
44+
return ServerWebExchange.class;
45+
}
46+
47+
@Override
48+
public ServerWebExchange getValue() {
49+
return threadLocal.get();
50+
}
51+
52+
@Override
53+
public void setValue(ServerWebExchange exchange) {
54+
Assert.notNull(exchange, "exchange must not be null");
55+
threadLocal.set(exchange);
56+
}
57+
58+
@Override
59+
public void setValue() {
60+
threadLocal.remove();
61+
}
62+
63+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
org.springframework.security.web.server.ServerWebExchangeThreadLocalAccessor

0 commit comments

Comments
 (0)