Skip to content

Commit f305148

Browse files
Instrumenting Spring Security (#2011)
* First draft of instrumenting Spring Security * Create events based on Authentication not SecurityContext * Simplifying security config * Checkstyle reformat * Create DocumentedSpan and EventValue for Spring Security instrumentation * Adding spring-security-bom to ensure the right dependencies are used. * TracingSecurityContextChangedListenerTests * Adding integration tests * Deleting spring-cloud-sleuth-sample-security * Checkstyle, license, javadoc * Inlining SleuthSecuritySpan methods * Deleting javadoc links from DocumentedSpan * Updating auto-generated docs
1 parent 1f986bb commit f305148

File tree

14 files changed

+672
-7
lines changed

14 files changed

+672
-7
lines changed

docs/src/main/asciidoc/_spans.adoc

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -532,6 +532,22 @@ Fully qualified name of the enclosing class `org.springframework.cloud.sleuth.in
532532
|method|Method name that got annotated with @Scheduled.
533533
|===
534534

535+
=== Security Context Change
536+
537+
> Indicates that a SecurityContextChangedEvent happened during the current span.
538+
539+
**Span name** `Security Context Change`.
540+
541+
Fully qualified name of the enclosing class `org.springframework.cloud.sleuth.instrument.security.SleuthSecuritySpan`
542+
543+
.Event Values
544+
|===
545+
|Name | Description
546+
|Authentication cleared %s|Event created when an Authentication object is removed from the SecurityContext. (since the name contains `%s` the final value will be resolved at runtime)
547+
|Authentication replaced %s|Event created when an Authentication object is replaced with a new one in the SecurityContext. (since the name contains `%s` the final value will be resolved at runtime)
548+
|Authentication set %s|Event created when an Authentication object is added to the SecurityContext. (since the name contains `%s` the final value will be resolved at runtime)
549+
|===
550+
535551
=== Session Create Span
536552

537553
> Span created when a new session has to be created.

pom.xml

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -76,14 +76,15 @@
7676
<brave.version>5.13.2</brave.version>
7777
<opentracing.version>0.32.0</opentracing.version>
7878
<spring-security-boot-autoconfigure.version>2.3.4.RELEASE</spring-security-boot-autoconfigure.version>
79+
<spring-security-oauth2.version>2.2.0.RELEASE</spring-security-oauth2.version>
80+
<spring-security-version>5.6.0-M2</spring-security-version>
7981
<disable.nohttp.checks>false</disable.nohttp.checks>
8082
<okhttp.version>4.9.0</okhttp.version>
8183
<mockwebserver.version>4.8.0</mockwebserver.version>
8284
<guava.version>20.0</guava.version>
8385
<javax.resource-api.version>1.7.1</javax.resource-api.version>
8486
<cglib-nodep.version>3.3.0</cglib-nodep.version>
8587
<objenesis.version>3.0.1</objenesis.version>
86-
<spring-security-oauth2.version>2.2.0.RELEASE</spring-security-oauth2.version>
8788
<p6spy.version>3.9.1</p6spy.version>
8889
<datasource-proxy.version>1.7</datasource-proxy.version>
8990
<tomcat-jdbc.version>10.0.6</tomcat-jdbc.version>
@@ -267,11 +268,22 @@
267268
<scope>import</scope>
268269
<type>pom</type>
269270
</dependency>
271+
<dependency>
272+
<groupId>org.springframework.security</groupId>
273+
<artifactId>spring-security-bom</artifactId>
274+
<version>${spring-security-version}</version>
275+
<type>pom</type>
276+
<scope>import</scope>
277+
</dependency>
270278
<dependency>
271279
<groupId>org.springframework.security.oauth.boot</groupId>
272280
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
273281
<version>${spring-security-boot-autoconfigure.version}</version>
274-
<optional>true</optional>
282+
</dependency>
283+
<dependency>
284+
<groupId>org.springframework.security.oauth</groupId>
285+
<artifactId>spring-security-oauth2</artifactId>
286+
<version>${spring-security-oauth2.version}</version>
275287
</dependency>
276288
<dependency>
277289
<groupId>cglib</groupId>
@@ -289,11 +301,6 @@
289301
<artifactId>mockwebserver</artifactId>
290302
<version>${mockwebserver.version}</version>
291303
</dependency>
292-
<dependency>
293-
<groupId>org.springframework.security.oauth</groupId>
294-
<artifactId>spring-security-oauth2</artifactId>
295-
<version>${spring-security-oauth2.version}</version>
296-
</dependency>
297304
<dependency>
298305
<groupId>io.zipkin.aws</groupId>
299306
<artifactId>brave-propagation-aws</artifactId>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* Copyright 2021-2021 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.cloud.sleuth.autoconfig.instrument.security;
18+
19+
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
20+
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
21+
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
22+
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
23+
import org.springframework.cloud.sleuth.Tracer;
24+
import org.springframework.cloud.sleuth.autoconfig.brave.BraveAutoConfiguration;
25+
import org.springframework.cloud.sleuth.instrument.security.TracingSecurityContextChangedListener;
26+
import org.springframework.context.annotation.Bean;
27+
import org.springframework.context.annotation.Configuration;
28+
import org.springframework.security.core.context.SecurityContextChangedListener;
29+
30+
/**
31+
* {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration
32+
* Auto-configuration} that registers instrumentation for Spring Security.
33+
*
34+
* @author Jonatan Ivanov
35+
* @since 3.1.0
36+
*/
37+
@Configuration(proxyBeanMethods = false)
38+
@ConditionalOnClass(SecurityContextChangedListener.class)
39+
@ConditionalOnProperty(value = "spring.sleuth.security.enabled", matchIfMissing = true)
40+
@ConditionalOnBean(Tracer.class)
41+
@AutoConfigureAfter(BraveAutoConfiguration.class)
42+
public class TraceSecurityAutoConfiguration {
43+
44+
@Bean
45+
public TracingSecurityContextChangedListener tracingSecurityContextChangedListener(Tracer tracer) {
46+
return new TracingSecurityContextChangedListener(tracer);
47+
}
48+
49+
}

spring-cloud-sleuth-autoconfigure/src/main/resources/META-INF/spring.factories

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ org.springframework.cloud.sleuth.autoconfig.instrument.web.client.feign.TraceFei
2222
org.springframework.cloud.sleuth.autoconfig.instrument.web.client.TraceWebAsyncClientAutoConfiguration,\
2323
org.springframework.cloud.sleuth.autoconfig.instrument.scheduling.TraceSchedulingAutoConfiguration,\
2424
org.springframework.cloud.sleuth.autoconfig.instrument.session.TraceSessionAutoConfiguration,\
25+
org.springframework.cloud.sleuth.autoconfig.instrument.security.TraceSecurityAutoConfiguration,\
2526
org.springframework.cloud.sleuth.autoconfig.instrument.reactor.TraceReactorAutoConfiguration,\
2627
org.springframework.cloud.sleuth.autoconfig.instrument.messaging.TraceFunctionAutoConfiguration,\
2728
org.springframework.cloud.sleuth.autoconfig.instrument.messaging.TraceSpringIntegrationAutoConfiguration,\

spring-cloud-sleuth-instrumentation/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,11 @@
212212
<artifactId>spring-session-data-redis</artifactId>
213213
<optional>true</optional>
214214
</dependency>
215+
<dependency>
216+
<groupId>org.springframework.security</groupId>
217+
<artifactId>spring-security-core</artifactId>
218+
<optional>true</optional>
219+
</dependency>
215220
<dependency>
216221
<groupId>io.projectreactor.kotlin</groupId>
217222
<artifactId>reactor-kotlin-extensions</artifactId>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/*
2+
* Copyright 2021-2021 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.cloud.sleuth.instrument.security;
18+
19+
import org.springframework.cloud.sleuth.docs.DocumentedSpan;
20+
import org.springframework.cloud.sleuth.docs.EventValue;
21+
22+
/**
23+
* DocumentedSpan for Spring Security Instrumentation.
24+
*
25+
* @author Jonatan Ivanov
26+
* @since 3.1.0
27+
*/
28+
enum SleuthSecuritySpan implements DocumentedSpan {
29+
30+
/**
31+
* Indicates that a SecurityContextChangedEvent happened during the current span.
32+
*/
33+
SECURITY_CONTEXT_CHANGE {
34+
@Override
35+
public String getName() {
36+
return "Security Context Change";
37+
}
38+
39+
@Override
40+
public EventValue[] getEvents() {
41+
return SleuthSecurityEvent.values();
42+
}
43+
};
44+
45+
enum SleuthSecurityEvent implements EventValue {
46+
47+
/**
48+
* Event created when an Authentication object is added to the SecurityContext.
49+
*/
50+
AUTHENTICATION_SET {
51+
@Override
52+
public String getValue() {
53+
return "Authentication set %s";
54+
}
55+
},
56+
57+
/**
58+
* Event created when an Authentication object is replaced with a new one in the
59+
* SecurityContext.
60+
*/
61+
AUTHENTICATION_REPLACED {
62+
@Override
63+
public String getValue() {
64+
return "Authentication replaced %s";
65+
}
66+
},
67+
68+
/**
69+
* Event created when an Authentication object is removed from the
70+
* SecurityContext.
71+
*/
72+
AUTHENTICATION_CLEARED {
73+
@Override
74+
public String getValue() {
75+
return "Authentication cleared %s";
76+
}
77+
}
78+
79+
}
80+
81+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/*
2+
* Copyright 2021-2021 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.cloud.sleuth.instrument.security;
18+
19+
import org.slf4j.Logger;
20+
import org.slf4j.LoggerFactory;
21+
22+
import org.springframework.cloud.sleuth.Span;
23+
import org.springframework.cloud.sleuth.Tracer;
24+
import org.springframework.security.core.Authentication;
25+
import org.springframework.security.core.context.SecurityContext;
26+
import org.springframework.security.core.context.SecurityContextChangedEvent;
27+
import org.springframework.security.core.context.SecurityContextChangedListener;
28+
29+
import static java.lang.String.format;
30+
import static org.springframework.cloud.sleuth.instrument.security.SleuthSecuritySpan.SECURITY_CONTEXT_CHANGE;
31+
import static org.springframework.cloud.sleuth.instrument.security.SleuthSecuritySpan.SleuthSecurityEvent;
32+
import static org.springframework.cloud.sleuth.instrument.security.SleuthSecuritySpan.SleuthSecurityEvent.AUTHENTICATION_CLEARED;
33+
import static org.springframework.cloud.sleuth.instrument.security.SleuthSecuritySpan.SleuthSecurityEvent.AUTHENTICATION_REPLACED;
34+
import static org.springframework.cloud.sleuth.instrument.security.SleuthSecuritySpan.SleuthSecurityEvent.AUTHENTICATION_SET;
35+
36+
/**
37+
* {@link SecurityContextChangedListener} that adds tracing support for Spring Security.
38+
*
39+
* @author Jonatan Ivanov
40+
* @since 3.1.0
41+
*/
42+
public class TracingSecurityContextChangedListener implements SecurityContextChangedListener {
43+
44+
private static final Logger LOGGER = LoggerFactory.getLogger(TracingSecurityContextChangedListener.class);
45+
46+
private final Tracer tracer;
47+
48+
public TracingSecurityContextChangedListener(Tracer tracer) {
49+
this.tracer = tracer;
50+
}
51+
52+
@Override
53+
public void securityContextChanged(SecurityContextChangedEvent securityContextChangedEvent) {
54+
SecurityContext previousContext = securityContextChangedEvent.getPreviousContext();
55+
SecurityContext currentContext = securityContextChangedEvent.getCurrentContext();
56+
Authentication previousAuthentication = previousContext != null ? previousContext.getAuthentication() : null;
57+
Authentication currentAuthentication = currentContext != null ? currentContext.getAuthentication() : null;
58+
59+
if (previousAuthentication != null) {
60+
if (currentAuthentication != null) {
61+
attachEvent(AUTHENTICATION_REPLACED, toString(previousAuthentication, currentAuthentication));
62+
}
63+
else {
64+
attachEvent(AUTHENTICATION_CLEARED, toString(previousAuthentication));
65+
}
66+
}
67+
else if (currentAuthentication != null) {
68+
attachEvent(AUTHENTICATION_SET, toString(currentAuthentication));
69+
}
70+
// null-null is not handled since we won't create an event for that case
71+
}
72+
73+
private String toString(Authentication previousAuthentication, Authentication currentAuthentication) {
74+
return toString(previousAuthentication) + " -> " + toString(currentAuthentication);
75+
}
76+
77+
private String toString(Authentication authentication) {
78+
return authentication != null ? authentication.getClass().getSimpleName() + authentication.getAuthorities()
79+
: "null";
80+
}
81+
82+
private void attachEvent(SleuthSecurityEvent sleuthSecurityEvent, String... params) {
83+
Span span = this.tracer.currentSpan();
84+
if (span != null) {
85+
String event = format(sleuthSecurityEvent.getValue(), (Object[]) params);
86+
LOGGER.info(event);
87+
SECURITY_CONTEXT_CHANGE.wrap(span).event(event);
88+
}
89+
}
90+
91+
}

0 commit comments

Comments
 (0)