Skip to content

Commit 02345b9

Browse files
committed
Polish Observation Event Names
Issue gh-12811
1 parent 59ba7f5 commit 02345b9

File tree

2 files changed

+82
-169
lines changed

2 files changed

+82
-169
lines changed

web/src/main/java/org/springframework/security/web/ObservationFilterChainDecorator.java

Lines changed: 52 additions & 150 deletions
Original file line numberDiff line numberDiff line change
@@ -38,36 +38,6 @@
3838
import org.apache.commons.logging.LogFactory;
3939

4040
import org.springframework.core.log.LogMessage;
41-
import org.springframework.security.web.access.ExceptionTranslationFilter;
42-
import org.springframework.security.web.access.channel.ChannelProcessingFilter;
43-
import org.springframework.security.web.access.intercept.AuthorizationFilter;
44-
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
45-
import org.springframework.security.web.authentication.AnonymousAuthenticationFilter;
46-
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
47-
import org.springframework.security.web.authentication.logout.LogoutFilter;
48-
import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter;
49-
import org.springframework.security.web.authentication.preauth.x509.X509AuthenticationFilter;
50-
import org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter;
51-
import org.springframework.security.web.authentication.switchuser.SwitchUserFilter;
52-
import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter;
53-
import org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter;
54-
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
55-
import org.springframework.security.web.authentication.www.DigestAuthenticationFilter;
56-
import org.springframework.security.web.context.SecurityContextHolderFilter;
57-
import org.springframework.security.web.context.SecurityContextPersistenceFilter;
58-
import org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter;
59-
import org.springframework.security.web.csrf.CsrfFilter;
60-
import org.springframework.security.web.header.HeaderWriterFilter;
61-
import org.springframework.security.web.jaasapi.JaasApiIntegrationFilter;
62-
import org.springframework.security.web.savedrequest.RequestCacheAwareFilter;
63-
import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter;
64-
import org.springframework.security.web.session.ConcurrentSessionFilter;
65-
import org.springframework.security.web.session.DisableEncodeUrlFilter;
66-
import org.springframework.security.web.session.ForceEagerSessionCreationFilter;
67-
import org.springframework.security.web.session.SessionManagementFilter;
68-
import org.springframework.util.Assert;
69-
import org.springframework.util.StringUtils;
70-
import org.springframework.web.filter.CorsFilter;
7141

7242
/**
7343
* A {@link org.springframework.security.web.FilterChainProxy.FilterChainDecorator} that
@@ -80,12 +50,6 @@ public final class ObservationFilterChainDecorator implements FilterChainProxy.F
8050

8151
private static final Log logger = LogFactory.getLog(FilterChainProxy.class);
8252

83-
private static final int OPENTELEMETRY_MAX_NAME_LENGTH = 63;
84-
85-
private static final int MAX_OBSERVATION_NAME_LENGTH = OPENTELEMETRY_MAX_NAME_LENGTH - ".before".length();
86-
87-
private static final Map<String, String> OBSERVATION_NAMES = new HashMap<>();
88-
8953
private static final String ATTRIBUTE = ObservationFilterChainDecorator.class + ".observation";
9054

9155
static final String UNSECURED_OBSERVATION_NAME = "spring.security.http.unsecured.requests";
@@ -136,83 +100,10 @@ private List<ObservationFilter> wrap(List<Filter> filters) {
136100
return observableFilters;
137101
}
138102

139-
static {
140-
registerName(DisableEncodeUrlFilter.class, "session.encode-url.disable");
141-
registerName(ForceEagerSessionCreationFilter.class, "session.create");
142-
registerName(ChannelProcessingFilter.class, "web.request.delivery.ensure");
143-
registerName(WebAsyncManagerIntegrationFilter.class, "web-async-manager.join.security-context");
144-
registerName(SecurityContextHolderFilter.class, "security-context.hold");
145-
registerName(SecurityContextPersistenceFilter.class, "security-context.persist");
146-
registerName(HeaderWriterFilter.class, "web.response.header.set");
147-
registerName(CorsFilter.class, "cors.process");
148-
registerName(CsrfFilter.class, "csrf.protect");
149-
registerName(LogoutFilter.class, "principal.logout");
150-
registerName("org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter",
151-
"web.request.oauth2.redirect");
152-
registerName(
153-
"org.springframework.security.saml2.provider.service.web." + "Saml2WebSsoAuthenticationRequestFilter",
154-
"web.request.saml2.redirect");
155-
registerName(X509AuthenticationFilter.class, "web.request.x509.auth");
156-
registerName(AbstractPreAuthenticatedProcessingFilter.class, "web.request.pre-auth.base.process");
157-
registerName("org.springframework.security.cas.web.CasAuthenticationFilter", "web.request.sas.auth");
158-
registerName("org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter",
159-
"web.response.oauth2.process");
160-
registerName("org.springframework.security.saml2.provider.service.web.authentication"
161-
+ ".Saml2WebSsoAuthenticationFilter", "web.request.saml2.auth");
162-
registerName(UsernamePasswordAuthenticationFilter.class, "web.request.username-password.auth");
163-
registerName(DefaultLoginPageGeneratingFilter.class, "web.login-page.default.generate");
164-
registerName(DefaultLogoutPageGeneratingFilter.class, "web.logout-page.default.generate");
165-
registerName(ConcurrentSessionFilter.class, "session.refresh");
166-
registerName(DigestAuthenticationFilter.class, "web.request.digest.auth");
167-
registerName("org.springframework.security.oauth2.server.resource.web.authentication."
168-
+ "BearerTokenAuthenticationFilter", "web.request.bearer.auth");
169-
registerName(BasicAuthenticationFilter.class, "web.request.basic.auth");
170-
registerName(RequestCacheAwareFilter.class, "web.request.cache.extract");
171-
registerName(SecurityContextHolderAwareRequestFilter.class, "web.request.security.wrap");
172-
registerName(JaasApiIntegrationFilter.class, "web.request.jass.auth");
173-
registerName(RememberMeAuthenticationFilter.class, "web.request.remember-me.auth");
174-
registerName(AnonymousAuthenticationFilter.class, "web.request.anonymous.auth");
175-
registerName("org.springframework.security.oauth2.client.web.OAuth2AuthorizationCodeGrantFilter",
176-
"web.response.oauth2.code-grant.process");
177-
registerName(SessionManagementFilter.class, "session.manage");
178-
registerName(ExceptionTranslationFilter.class, "exception.translate");
179-
registerName(FilterSecurityInterceptor.class, "web.response.security.intercept");
180-
registerName(AuthorizationFilter.class, "web.access.auth.restrict");
181-
registerName(SwitchUserFilter.class, "session.switch");
182-
}
183-
184-
public static void registerName(Class clazz, String name) {
185-
String keyName = clazz.getName();
186-
checkAlreadyRegistered(keyName);
187-
OBSERVATION_NAMES.put(keyName, limitLength(name));
188-
}
189-
190-
public static void registerName(String className, String name) {
191-
checkAlreadyRegistered(className);
192-
OBSERVATION_NAMES.put(className, name);
193-
}
194-
195103
static AroundFilterObservation observation(HttpServletRequest request) {
196104
return (AroundFilterObservation) request.getAttribute(ATTRIBUTE);
197105
}
198106

199-
private static String getObservationName(String className) {
200-
if (OBSERVATION_NAMES.containsKey(className)) {
201-
return OBSERVATION_NAMES.get(className);
202-
}
203-
throw new IllegalArgumentException("Class not registered for observation: " + className);
204-
}
205-
206-
private static String limitLength(String s) {
207-
Assert.isTrue(s.length() <= MAX_OBSERVATION_NAME_LENGTH,
208-
"The name must be less than MAX_OBSERVATION_NAME_LENGTH=" + MAX_OBSERVATION_NAME_LENGTH);
209-
return s;
210-
}
211-
212-
private static void checkAlreadyRegistered(String keyName) {
213-
Assert.isTrue(!OBSERVATION_NAMES.containsKey(keyName), "Observation name is registered already: " + keyName);
214-
}
215-
216107
private static final class VirtualFilterChain implements FilterChain {
217108

218109
private final FilterChain originalChain;
@@ -248,6 +139,49 @@ public void doFilter(ServletRequest request, ServletResponse response) throws IO
248139

249140
static final class ObservationFilter implements Filter {
250141

142+
private static final Map<String, String> OBSERVATION_NAMES = new HashMap<>();
143+
144+
static {
145+
OBSERVATION_NAMES.put("DisableEncodeUrlFilter", "session.url-encoding");
146+
OBSERVATION_NAMES.put("ForceEagerSessionCreationFilter", "session.eager-create");
147+
OBSERVATION_NAMES.put("ChannelProcessingFilter", "access.channel");
148+
OBSERVATION_NAMES.put("WebAsyncManagerIntegrationFilter", "context.async");
149+
OBSERVATION_NAMES.put("SecurityContextHolderFilter", "context.holder");
150+
OBSERVATION_NAMES.put("SecurityContextPersistenceFilter", "context.management");
151+
OBSERVATION_NAMES.put("HeaderWriterFilter", "header");
152+
OBSERVATION_NAMES.put("CorsFilter", "cors");
153+
OBSERVATION_NAMES.put("CsrfFilter", "csrf");
154+
OBSERVATION_NAMES.put("LogoutFilter", "logout");
155+
OBSERVATION_NAMES.put("OAuth2AuthorizationRequestRedirectFilter", "oauth2.authnrequest");
156+
OBSERVATION_NAMES.put("Saml2WebSsoAuthenticationRequestFilter", "saml2.authnrequest");
157+
OBSERVATION_NAMES.put("X509AuthenticationFilter", "authentication.x509");
158+
OBSERVATION_NAMES.put("J2eePreAuthenticatedProcessingFilter", "preauthentication.j2ee");
159+
OBSERVATION_NAMES.put("RequestHeaderAuthenticationFilter", "preauthentication.header");
160+
OBSERVATION_NAMES.put("RequestAttributeAuthenticationFilter", "preauthentication.attribute");
161+
OBSERVATION_NAMES.put("WebSpherePreAuthenticatedProcessingFilter", "preauthentication.websphere");
162+
OBSERVATION_NAMES.put("CasAuthenticationFilter", "cas.authentication");
163+
OBSERVATION_NAMES.put("OAuth2LoginAuthenticationFilter", "oauth2.authentication");
164+
OBSERVATION_NAMES.put("Saml2WebSsoAuthenticationFilter", "saml2.authentication");
165+
OBSERVATION_NAMES.put("UsernamePasswordAuthenticationFilter", "authentication.form");
166+
OBSERVATION_NAMES.put("DefaultLoginPageGeneratingFilter", "page.login");
167+
OBSERVATION_NAMES.put("DefaultLogoutPageGeneratingFilter", "page.logout");
168+
OBSERVATION_NAMES.put("ConcurrentSessionFilter", "session.concurrent");
169+
OBSERVATION_NAMES.put("DigestAuthenticationFilter", "authentication.digest");
170+
OBSERVATION_NAMES.put("BearerTokenAuthenticationFilter", "authentication.bearer");
171+
OBSERVATION_NAMES.put("BasicAuthenticationFilter", "authentication.basic");
172+
OBSERVATION_NAMES.put("RequestCacheAwareFilter", "requestcache");
173+
OBSERVATION_NAMES.put("SecurityContextHolderAwareRequestFilter", "context.servlet");
174+
OBSERVATION_NAMES.put("JaasApiIntegrationFilter", "jaas");
175+
OBSERVATION_NAMES.put("RememberMeAuthenticationFilter", "authentication.rememberme");
176+
OBSERVATION_NAMES.put("AnonymousAuthenticationFilter", "authentication.anonymous");
177+
OBSERVATION_NAMES.put("OAuth2AuthorizationCodeGrantFilter", "oauth2.client.code");
178+
OBSERVATION_NAMES.put("SessionManagementFilter", "session.management");
179+
OBSERVATION_NAMES.put("ExceptionTranslationFilter", "access.exceptions");
180+
OBSERVATION_NAMES.put("FilterSecurityInterceptor", "access.request");
181+
OBSERVATION_NAMES.put("AuthorizationFilter", "authorization");
182+
OBSERVATION_NAMES.put("SwitchUserFilter", "authentication.switch");
183+
}
184+
251185
private final ObservationRegistry registry;
252186

253187
private final FilterChainObservationConvention convention = new FilterChainObservationConvention();
@@ -256,7 +190,7 @@ static final class ObservationFilter implements Filter {
256190

257191
private final String name;
258192

259-
private final String observationName;
193+
private final String eventName;
260194

261195
private final int position;
262196

@@ -268,28 +202,16 @@ static final class ObservationFilter implements Filter {
268202
this.name = filter.getClass().getSimpleName();
269203
this.position = position;
270204
this.size = size;
271-
String tempObservationName;
272-
try {
273-
tempObservationName = ObservationFilterChainDecorator.getObservationName(filter.getClass().getName());
274-
}
275-
catch (IllegalArgumentException ex) {
276-
tempObservationName = compressName(this.name);
277-
logger.warn(
278-
"Class " + filter.getClass().getName()
279-
+ " is not registered for observation and will have name " + tempObservationName
280-
+ ". Please consider of registering this class with "
281-
+ ObservationFilterChainDecorator.class.getSimpleName() + ".registerName(class, name).",
282-
ex);
283-
}
284-
this.observationName = tempObservationName;
205+
this.eventName = eventName(this.name);
285206
}
286207

287-
String getName() {
288-
return this.name;
208+
private String eventName(String className) {
209+
String eventName = OBSERVATION_NAMES.get(className);
210+
return (eventName != null) ? eventName : className;
289211
}
290212

291-
String getObservationName() {
292-
return this.observationName;
213+
String getName() {
214+
return this.name;
293215
}
294216

295217
@Override
@@ -312,17 +234,15 @@ private void wrapFilter(ServletRequest request, ServletResponse response, Filter
312234
parentBefore.setFilterName(this.name);
313235
parentBefore.setChainPosition(this.position);
314236
}
315-
parent.before().event(Observation.Event.of(this.observationName + ".before",
316-
"before " + this.name));
237+
parent.before().event(Observation.Event.of(this.eventName + ".before", "before " + this.name));
317238
this.filter.doFilter(request, response, chain);
318239
parent.start();
319240
if (parent.after().getContext() instanceof FilterChainObservationContext parentAfter) {
320241
parentAfter.setChainSize(this.size);
321242
parentAfter.setFilterName(this.name);
322243
parentAfter.setChainPosition(this.size - this.position + 1);
323244
}
324-
parent.after().event(Observation.Event.of(this.observationName + ".after",
325-
"after " + this.name));
245+
parent.after().event(Observation.Event.of(this.eventName + ".after", "after " + this.name));
326246
}
327247

328248
private AroundFilterObservation parent(HttpServletRequest request) {
@@ -335,24 +255,6 @@ private AroundFilterObservation parent(HttpServletRequest request) {
335255
return parent;
336256
}
337257

338-
private String compressName(String className) {
339-
if (className.length() >= MAX_OBSERVATION_NAME_LENGTH) {
340-
return maximalCompressClassName(className, MAX_OBSERVATION_NAME_LENGTH);
341-
}
342-
return className;
343-
}
344-
345-
private String maximalCompressClassName(String className, int maxLength) {
346-
String[] names = className.split("(?=\\p{Lu})");
347-
for (int j = 0; j < names.length; j++) {
348-
final int maxPortionLength = maxLength / names.length;
349-
if (names[j].length() > maxPortionLength) {
350-
names[j] = names[j].substring(0, maxPortionLength);
351-
}
352-
}
353-
return StringUtils.arrayToDelimitedString(names, "");
354-
}
355-
356258
}
357259

358260
interface AroundFilterObservation extends FilterObservation {

web/src/test/java/org/springframework/security/web/ObservationFilterChainDecoratorTests.java

Lines changed: 30 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,17 @@
1616

1717
package org.springframework.security.web;
1818

19-
import java.lang.reflect.Field;
20-
import java.lang.reflect.Method;
19+
import java.io.IOException;
2120
import java.util.List;
2221

2322
import io.micrometer.observation.Observation;
2423
import io.micrometer.observation.ObservationHandler;
2524
import io.micrometer.observation.ObservationRegistry;
2625
import jakarta.servlet.Filter;
2726
import jakarta.servlet.FilterChain;
27+
import jakarta.servlet.ServletException;
28+
import jakarta.servlet.ServletRequest;
29+
import jakarta.servlet.ServletResponse;
2830
import org.junit.jupiter.api.Test;
2931
import org.mockito.ArgumentCaptor;
3032

@@ -80,7 +82,6 @@ void decorateFiltersWhenDefaultsThenObserves() throws Exception {
8082
FilterChain chain = mock(FilterChain.class);
8183
Filter filter = mock(Filter.class);
8284
FilterChain decorated = decorator.decorate(chain, List.of(filter));
83-
assertCompressedName(decorated);
8485
decorated.doFilter(new MockHttpServletRequest("GET", "/"), new MockHttpServletResponse());
8586
verify(handler, times(2)).onStart(any());
8687
ArgumentCaptor<Observation.Event> event = ArgumentCaptor.forClass(Observation.Event.class);
@@ -90,22 +91,32 @@ void decorateFiltersWhenDefaultsThenObserves() throws Exception {
9091
assertThat(events.get(1).getName()).isEqualTo(filter.getClass().getSimpleName() + ".after");
9192
}
9293

93-
void assertCompressedName(FilterChain filterChain) throws Exception {
94-
assertThat(filterChain.getClass().getSimpleName()).isEqualTo("VirtualFilterChain");
95-
Field field = filterChain.getClass().getDeclaredField("additionalFilters");
96-
field.setAccessible(true);
97-
List<ObservationFilterChainDecorator.ObservationFilter> additionalFilters =
98-
(List<ObservationFilterChainDecorator.ObservationFilter>) field.get(filterChain);
99-
assertThat(additionalFilters.size()).isEqualTo(1);
100-
final ObservationFilterChainDecorator.ObservationFilter observationFilter = additionalFilters.get(0);
101-
assertThat(observationFilter.getObservationName()).isEqualTo(observationFilter.getName());
102-
Method method = observationFilter.getClass().getDeclaredMethod("compressName", String.class);
103-
method.setAccessible(true);
104-
String compressed = (String) method.invoke(observationFilter, "ObservationFilterChainDecoratorTests");
105-
assertThat(compressed).isEqualTo("ObservationFilterChainDecoratorTests");
106-
String fakeCompressed = (String) method.invoke(observationFilter,
107-
"ObservationFilterChainDecoratorTestsObservationFilterChainDecoratorTests");
108-
assertThat(fakeCompressed).isEqualTo("ObserFilteChainDecorTestsObserFilteChainDecorTests");
94+
@Test
95+
void decorateFiltersWhenDefaultsThenUsesEventName() throws Exception {
96+
ObservationHandler<?> handler = mock(ObservationHandler.class);
97+
given(handler.supportsContext(any())).willReturn(true);
98+
ObservationRegistry registry = ObservationRegistry.create();
99+
registry.observationConfig().observationHandler(handler);
100+
ObservationFilterChainDecorator decorator = new ObservationFilterChainDecorator(registry);
101+
FilterChain chain = mock(FilterChain.class);
102+
Filter filter = new BasicAuthenticationFilter();
103+
FilterChain decorated = decorator.decorate(chain, List.of(filter));
104+
decorated.doFilter(new MockHttpServletRequest("GET", "/"), new MockHttpServletResponse());
105+
ArgumentCaptor<Observation.Event> event = ArgumentCaptor.forClass(Observation.Event.class);
106+
verify(handler, times(2)).onEvent(event.capture(), any());
107+
List<Observation.Event> events = event.getAllValues();
108+
assertThat(events.get(0).getName()).isEqualTo("authentication.basic.before");
109+
assertThat(events.get(1).getName()).isEqualTo("authentication.basic.after");
110+
}
111+
112+
private static class BasicAuthenticationFilter implements Filter {
113+
114+
@Override
115+
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
116+
throws IOException, ServletException {
117+
chain.doFilter(request, response);
118+
}
119+
109120
}
110121

111122
}

0 commit comments

Comments
 (0)