Skip to content

Commit

Permalink
tech: add request logging for oauth2 server requests
Browse files Browse the repository at this point in the history
  • Loading branch information
its-felix committed Aug 29, 2024
1 parent c7acccc commit df8d937
Showing 1 changed file with 148 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,17 @@

import com.gw2auth.oauth2.server.adapt.CustomOAuth2ServerAuthenticationProviders;
import com.gw2auth.oauth2.server.service.application.AuthorizationCodeParamAccessor;
import com.gw2auth.oauth2.server.util.ComposedMDCCloseable;
import com.gw2auth.oauth2.server.util.JWKHelper;
import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
Expand All @@ -24,6 +28,7 @@
import org.springframework.security.config.annotation.web.configurers.SecurityContextConfigurer;
import org.springframework.security.config.annotation.web.configurers.oauth2.client.OAuth2LoginConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationProvider;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationConsentAuthenticationProvider;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
Expand All @@ -34,10 +39,14 @@
import org.springframework.security.web.util.matcher.OrRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;

import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.util.*;
import java.util.function.Function;

@Configuration
public class OAuth2ServerConfiguration {
Expand Down Expand Up @@ -110,7 +119,8 @@ public SecurityFilterChain oauth2ServerHttpSecurityFilterChain(HttpSecurity http
.securityContext(securityContextCustomizer)
.requestCache(requestCacheCustomizer)
.oauth2Login(oauth2LoginCustomizer)
.with(configurer, ignored -> {});
.with(configurer, ignored -> {})
.addFilterBefore(new OAuth2ServerLoggingFilter(), SecurityContextHolderFilter.class);

return http.build();
}
Expand Down Expand Up @@ -143,4 +153,141 @@ public AuthorizationServerSettings authorizationServerSettings(@Value("${com.gw2
public AuthorizationCodeParamAccessor authorizationCodeParamAccessor() {
return AuthorizationCodeParamAccessor.DEFAULT;
}

private static class OAuth2ServerLoggingFilter extends OncePerRequestFilter {

private static final Logger LOG = LoggerFactory.getLogger(OAuth2ServerLoggingFilter.class);

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
Map<String, String> requestAttributes;
try {
requestAttributes = buildRequestAttributes(request);
} catch (Exception e) {
// better be safe than sorry
LOG.warn("failed to build request attributes", e);
requestAttributes = Map.of();
}

Exception exc = null;
try {
filterChain.doFilter(request, response);
} catch (Exception e) {
exc = e;
}

Map<String, String> responseAttributes;
try {
responseAttributes = buildResponseAttributes(response);
} catch (Exception e) {
LOG.warn("failed to build response attributes", e);
responseAttributes = Map.of();
}

try (ComposedMDCCloseable _unused = ComposedMDCCloseable.create(requestAttributes, Object::toString)) {
try (ComposedMDCCloseable __unused = ComposedMDCCloseable.create(responseAttributes, Object::toString)) {
if (exc == null) {
LOG.info("oauth2 request handled successfully");
} else {
LOG.info("oauth2 request failed", exc);
}
}
}

if (exc != null) {
switch (exc) {
case ServletException e:
throw e;

case IOException e:
throw e;

case RuntimeException e:
throw e;

default:
throw new RuntimeException("Unexpected error occurred while logging request", exc);
}
}
}

private static Map<String, String> buildRequestAttributes(HttpServletRequest request) {
final UriComponents uriComponents = UriComponentsBuilder.fromUriString(request.getRequestURI())
.query(request.getQueryString())
.build();

final Map<String, String> attributes = new HashMap<>();
attributes.put("request.method", request.getMethod());
attributes.put("request.url", uriComponents.getPath());
uriComponents.getQueryParams().forEach((key, value) -> {
if (!key.equalsIgnoreCase(OAuth2ParameterNames.CLIENT_SECRET)
&& !key.equalsIgnoreCase(OAuth2ParameterNames.STATE)
&& !key.equalsIgnoreCase("code_challenge")
&& !key.equalsIgnoreCase("code_verifier")) {

addMultiValue(attributes, "request.query." + key, value);
}
});

addHeaders(
attributes,
"request",
() -> request.getHeaderNames().asIterator(),
(v) -> () -> request.getHeaders(v).asIterator(),
Set.of(
"cookie",
"authorization"
)
);

return attributes;
}

private static Map<String, String> buildResponseAttributes(HttpServletResponse response) {
final Map<String, String> attributes = new HashMap<>();
attributes.put("response.status_code", Integer.toString(response.getStatus()));
addHeaders(
attributes,
"response",
response.getHeaderNames(),
response::getHeaders,
Set.of(
"set-cookie",
"pragma",
"x-xss-protection",
"x-content-type-options",
"expires",
"cache-control",
"x-frame-options"
)
);

return attributes;
}

private static void addHeaders(Map<String, String> map, String prefix, Iterable<String> names, Function<String, Iterable<String>> getHeaders, Set<String> ignore) {
for (String header : names) {
if (!ignore.contains(header.toLowerCase())) {
final List<String> values = new ArrayList<>();
for (String value : getHeaders.apply(header)) {
values.add(value);
}

addMultiValue(map, prefix + ".header." + header, values);
}
}
}

private static void addMultiValue(Map<String, String> map, String key, List<String> values) {
if (values.isEmpty()) {
map.put(key, "");
} else if (values.size() == 1) {
map.put(key, values.getFirst());
} else {
for (int i = 0; i < values.size(); i++) {
map.put(key + "." + i, values.get(i));
}
}
}
}
}

0 comments on commit df8d937

Please sign in to comment.