From cd08102b93dec81705e3f0b19ed72b6755cc9bee Mon Sep 17 00:00:00 2001 From: Mathieu Ouellet <6408576+mouellet@users.noreply.github.com> Date: Fri, 1 May 2020 08:55:22 -0400 Subject: [PATCH] Add debug logging Goal is to provide insight to devs on: - Authentication & Authorization success/failures - WebSession & SecurityContext - Request matchers, cache & authn/authz flow Fixes gh-5758 --- .../server/DefaultServerRedirectStrategy.java | 12 +++++-- ...egatingServerAuthenticationEntryPoint.java | 23 +++++++++++-- .../AnonymousAuthenticationWebFilter.java | 26 +++++++++------ .../AuthenticationWebFilter.java | 11 ++++++- .../logout/LogoutWebFilter.java | 11 ++++++- .../authorization/AuthorizationWebFilter.java | 26 +++++++++++---- ...elegatingReactiveAuthorizationManager.java | 24 ++++++++++---- ...essionServerSecurityContextRepository.java | 27 +++++++++++++--- .../CookieServerRequestCache.java | 13 +++++++- .../WebSessionServerRequestCache.java | 27 +++++++++++++--- .../matcher/AndServerWebExchangeMatcher.java | 18 +++++++++-- .../NegatedServerWebExchangeMatcher.java | 13 ++++++-- .../matcher/OrServerWebExchangeMatcher.java | 20 ++++++++++-- ...PatternParserServerWebExchangeMatcher.java | 32 ++++++++++++++++--- 14 files changed, 232 insertions(+), 51 deletions(-) diff --git a/web/src/main/java/org/springframework/security/web/server/DefaultServerRedirectStrategy.java b/web/src/main/java/org/springframework/security/web/server/DefaultServerRedirectStrategy.java index e076d55cf9..e63cc3fe48 100644 --- a/web/src/main/java/org/springframework/security/web/server/DefaultServerRedirectStrategy.java +++ b/web/src/main/java/org/springframework/security/web/server/DefaultServerRedirectStrategy.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,8 @@ package org.springframework.security.web.server; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.http.HttpStatus; import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.util.Assert; @@ -28,9 +30,11 @@ import java.net.URI; * The default {@link ServerRedirectStrategy} to use. * * @author Rob Winch + * @author Mathieu Ouellet * @since 5.0 */ public class DefaultServerRedirectStrategy implements ServerRedirectStrategy { + private static final Log logger = LogFactory.getLog(DefaultServerRedirectStrategy.class); private HttpStatus httpStatus = HttpStatus.FOUND; private boolean contextRelative = true; @@ -41,7 +45,11 @@ public class DefaultServerRedirectStrategy implements ServerRedirectStrategy { return Mono.fromRunnable(() -> { ServerHttpResponse response = exchange.getResponse(); response.setStatusCode(this.httpStatus); - response.getHeaders().setLocation(createLocation(exchange, location)); + URI newLocation = createLocation(exchange, location); + if (logger.isDebugEnabled()) { + logger.debug("Redirecting to '" + newLocation + "'"); + } + response.getHeaders().setLocation(newLocation); }); } diff --git a/web/src/main/java/org/springframework/security/web/server/DelegatingServerAuthenticationEntryPoint.java b/web/src/main/java/org/springframework/security/web/server/DelegatingServerAuthenticationEntryPoint.java index 6968b90dd0..27f88a441a 100644 --- a/web/src/main/java/org/springframework/security/web/server/DelegatingServerAuthenticationEntryPoint.java +++ b/web/src/main/java/org/springframework/security/web/server/DelegatingServerAuthenticationEntryPoint.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,8 @@ package org.springframework.security.web.server; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.http.HttpStatus; import org.springframework.util.Assert; import reactor.core.publisher.Flux; @@ -33,10 +35,13 @@ import java.util.List; * on a {@link ServerWebExchangeMatcher} * * @author Rob Winch + * @author Mathieu Ouellet * @since 5.0 */ public class DelegatingServerAuthenticationEntryPoint implements ServerAuthenticationEntryPoint { + private static final Log logger = LogFactory.getLog(DelegatingServerAuthenticationEntryPoint.class); + private final List entryPoints; private ServerAuthenticationEntryPoint defaultEntryPoint = (exchange, e) -> { @@ -61,12 +66,26 @@ public class DelegatingServerAuthenticationEntryPoint .filterWhen( entry -> isMatch(exchange, entry)) .next() .map( entry -> entry.getEntryPoint()) - .defaultIfEmpty(this.defaultEntryPoint) + .doOnNext(it -> { + if (logger.isDebugEnabled()) { + logger.debug("Match found! Executing " + it); + } + }) + .switchIfEmpty(Mono.just(this.defaultEntryPoint) + .doOnNext(it -> { + if (logger.isDebugEnabled()) { + logger.debug("No match found. Using default entry point " + defaultEntryPoint); + } + }) + ) .flatMap( entryPoint -> entryPoint.commence(exchange, e)); } private Mono isMatch(ServerWebExchange exchange, DelegateEntry entry) { ServerWebExchangeMatcher matcher = entry.getMatcher(); + if (logger.isDebugEnabled()) { + logger.debug("Trying to match using " + matcher); + } return matcher.matches(exchange) .map( result -> result.isMatch()); } diff --git a/web/src/main/java/org/springframework/security/web/server/authentication/AnonymousAuthenticationWebFilter.java b/web/src/main/java/org/springframework/security/web/server/authentication/AnonymousAuthenticationWebFilter.java index e75d0ff2cc..df2a17aa84 100644 --- a/web/src/main/java/org/springframework/security/web/server/authentication/AnonymousAuthenticationWebFilter.java +++ b/web/src/main/java/org/springframework/security/web/server/authentication/AnonymousAuthenticationWebFilter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,8 @@ package org.springframework.security.web.server.authentication; import java.util.List; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import reactor.core.publisher.Mono; import org.springframework.security.authentication.AnonymousAuthenticationToken; @@ -37,12 +39,11 @@ import org.springframework.web.server.WebFilterChain; * {@code ReactiveSecurityContextHolder}, and populates it with one if needed. * * @author Ankur Pathak + * @author Mathieu Ouellet * @since 5.2.0 */ public class AnonymousAuthenticationWebFilter implements WebFilter { - // ~ Instance fields - // ================================================================================================ - + private static final Log logger = LogFactory.getLog(AnonymousAuthenticationWebFilter.class); private String key; private Object principal; private List authorities; @@ -72,18 +73,25 @@ public class AnonymousAuthenticationWebFilter implements WebFilter { this.authorities = authorities; } - @Override public Mono filter(ServerWebExchange exchange, WebFilterChain chain) { return ReactiveSecurityContextHolder.getContext() .switchIfEmpty(Mono.defer(() -> { - SecurityContext securityContext = new SecurityContextImpl(); - securityContext.setAuthentication(createAuthentication(exchange)); + Authentication authentication = createAuthentication(exchange); + SecurityContext securityContext = new SecurityContextImpl(authentication); + if (logger.isDebugEnabled()) { + logger.debug("Populated SecurityContext with anonymous token: '" + authentication + "'"); + } return chain.filter(exchange) .subscriberContext(ReactiveSecurityContextHolder.withSecurityContext(Mono.just(securityContext))) .then(Mono.empty()); - })).flatMap(securityContext -> chain.filter(exchange)); - + })) + .flatMap(securityContext -> { + if (logger.isDebugEnabled()) { + logger.debug("SecurityContext contains anonymous token: '" + securityContext.getAuthentication() + "'"); + } + return chain.filter(exchange); + }); } protected Authentication createAuthentication(ServerWebExchange exchange) { diff --git a/web/src/main/java/org/springframework/security/web/server/authentication/AuthenticationWebFilter.java b/web/src/main/java/org/springframework/security/web/server/authentication/AuthenticationWebFilter.java index 1693381294..1ba194bd16 100644 --- a/web/src/main/java/org/springframework/security/web/server/authentication/AuthenticationWebFilter.java +++ b/web/src/main/java/org/springframework/security/web/server/authentication/AuthenticationWebFilter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,8 @@ package org.springframework.security.web.server.authentication; import java.util.function.Function; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import reactor.core.publisher.Mono; import org.springframework.security.authentication.ReactiveAuthenticationManager; @@ -65,9 +67,11 @@ import org.springframework.web.server.WebFilterChain; * * @author Rob Winch * @author Rafiullah Hamedy + * @author Mathieu Ouellet * @since 5.0 */ public class AuthenticationWebFilter implements WebFilter { + private static final Log logger = LogFactory.getLog(AuthenticationWebFilter.class); private final ReactiveAuthenticationManagerResolver authenticationManagerResolver; private ServerAuthenticationSuccessHandler authenticationSuccessHandler = new WebFilterChainServerAuthenticationSuccessHandler(); @@ -116,6 +120,11 @@ public class AuthenticationWebFilter implements WebFilter { .flatMap(authenticationManager -> authenticationManager.authenticate(token)) .switchIfEmpty(Mono.defer(() -> Mono.error(new IllegalStateException("No provider found for " + token.getClass())))) .flatMap(authentication -> onAuthenticationSuccess(authentication, webFilterExchange)) + .doOnError(AuthenticationException.class, e -> { + if (logger.isDebugEnabled()) { + logger.debug("Authentication failed: " + e.getMessage()); + } + }) .onErrorResume(AuthenticationException.class, e -> this.authenticationFailureHandler .onAuthenticationFailure(webFilterExchange, e)); } diff --git a/web/src/main/java/org/springframework/security/web/server/authentication/logout/LogoutWebFilter.java b/web/src/main/java/org/springframework/security/web/server/authentication/logout/LogoutWebFilter.java index 483bad70ad..a2a7f6eff6 100644 --- a/web/src/main/java/org/springframework/security/web/server/authentication/logout/LogoutWebFilter.java +++ b/web/src/main/java/org/springframework/security/web/server/authentication/logout/LogoutWebFilter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,8 @@ package org.springframework.security.web.server.authentication.logout; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import reactor.core.publisher.Mono; import org.springframework.http.HttpMethod; @@ -36,9 +38,12 @@ import org.springframework.web.server.WebFilterChain; * {@link ServerLogoutHandler}. * * @author Rob Winch + * @author Mathieu Ouellet * @since 5.0 */ public class LogoutWebFilter implements WebFilter { + private static final Log logger = LogFactory.getLog(LogoutWebFilter.class); + private AnonymousAuthenticationToken anonymousAuthenticationToken = new AnonymousAuthenticationToken("key", "anonymous", AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS")); @@ -69,6 +74,10 @@ public class LogoutWebFilter implements WebFilter { } private Mono logout(WebFilterExchange webFilterExchange, Authentication authentication) { + if (logger.isDebugEnabled()) { + logger.debug("Logging out user '" + authentication + + "' and transferring to logout destination"); + } return this.logoutHandler.logout(webFilterExchange, authentication) .then(this.logoutSuccessHandler .onLogoutSuccess(webFilterExchange, authentication)) diff --git a/web/src/main/java/org/springframework/security/web/server/authorization/AuthorizationWebFilter.java b/web/src/main/java/org/springframework/security/web/server/authorization/AuthorizationWebFilter.java index 2e387865e0..305cceaa57 100644 --- a/web/src/main/java/org/springframework/security/web/server/authorization/AuthorizationWebFilter.java +++ b/web/src/main/java/org/springframework/security/web/server/authorization/AuthorizationWebFilter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,7 +15,9 @@ */ package org.springframework.security.web.server.authorization; - +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.security.access.AccessDeniedException; import org.springframework.security.authorization.ReactiveAuthorizationManager; import org.springframework.security.core.context.ReactiveSecurityContextHolder; import org.springframework.security.core.context.SecurityContext; @@ -28,13 +30,15 @@ import reactor.core.publisher.Mono; /** * * @author Rob Winch + * @author Mathieu Ouellet * @since 5.0 */ public class AuthorizationWebFilter implements WebFilter { - private ReactiveAuthorizationManager accessDecisionManager; + private static final Log logger = LogFactory.getLog(AuthorizationWebFilter.class); + private ReactiveAuthorizationManager authorizationManager; - public AuthorizationWebFilter(ReactiveAuthorizationManager accessDecisionManager) { - this.accessDecisionManager = accessDecisionManager; + public AuthorizationWebFilter(ReactiveAuthorizationManager authorizationManager) { + this.authorizationManager = authorizationManager; } @Override @@ -42,7 +46,17 @@ public class AuthorizationWebFilter implements WebFilter { return ReactiveSecurityContextHolder.getContext() .filter(c -> c.getAuthentication() != null) .map(SecurityContext::getAuthentication) - .as(authentication -> this.accessDecisionManager.verify(authentication, exchange)) + .as(authentication -> this.authorizationManager.verify(authentication, exchange)) + .doOnSuccess(it -> { + if (logger.isDebugEnabled()) { + logger.debug("Authorization successful"); + } + }) + .doOnError(AccessDeniedException.class, e -> { + if (logger.isDebugEnabled()) { + logger.debug("Authorization failed: " + e.getMessage()); + } + }) .switchIfEmpty(chain.filter(exchange)); } } diff --git a/web/src/main/java/org/springframework/security/web/server/authorization/DelegatingReactiveAuthorizationManager.java b/web/src/main/java/org/springframework/security/web/server/authorization/DelegatingReactiveAuthorizationManager.java index 5e4382140d..986e4d69f5 100644 --- a/web/src/main/java/org/springframework/security/web/server/authorization/DelegatingReactiveAuthorizationManager.java +++ b/web/src/main/java/org/springframework/security/web/server/authorization/DelegatingReactiveAuthorizationManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,11 +15,13 @@ */ package org.springframework.security.web.server.authorization; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.security.authorization.AuthorizationDecision; import org.springframework.security.authorization.ReactiveAuthorizationManager; import org.springframework.security.core.Authentication; +import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher.MatchResult; import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcherEntry; -import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Flux; @@ -30,9 +32,11 @@ import java.util.List; /** * @author Rob Winch + * @author Mathieu Ouellet * @since 5.0 */ public class DelegatingReactiveAuthorizationManager implements ReactiveAuthorizationManager { + private static final Log logger = LogFactory.getLog(DelegatingReactiveAuthorizationManager.class); private final List>> mappings; private DelegatingReactiveAuthorizationManager(List>> mappings) { @@ -43,11 +47,17 @@ public class DelegatingReactiveAuthorizationManager implements ReactiveAuthoriza public Mono check(Mono authentication, ServerWebExchange exchange) { return Flux.fromIterable(mappings) .concatMap(mapping -> mapping.getMatcher().matches(exchange) - .filter(ServerWebExchangeMatcher.MatchResult::isMatch) - .map(r -> r.getVariables()) - .flatMap(variables -> mapping.getEntry() - .check(authentication, new AuthorizationContext(exchange, variables)) - ) + .filter(MatchResult::isMatch) + .map(MatchResult::getVariables) + .flatMap(variables -> { + if (logger.isDebugEnabled()) { + logger.debug("Checking authorization on '" + + exchange.getRequest().getPath().pathWithinApplication() + + "' using " + mapping.getEntry()); + } + return mapping.getEntry() + .check(authentication, new AuthorizationContext(exchange, variables)); + }) ) .next() .defaultIfEmpty(new AuthorizationDecision(false)); diff --git a/web/src/main/java/org/springframework/security/web/server/context/WebSessionServerSecurityContextRepository.java b/web/src/main/java/org/springframework/security/web/server/context/WebSessionServerSecurityContextRepository.java index dcc85b6c6e..0894003295 100644 --- a/web/src/main/java/org/springframework/security/web/server/context/WebSessionServerSecurityContextRepository.java +++ b/web/src/main/java/org/springframework/security/web/server/context/WebSessionServerSecurityContextRepository.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,11 +15,12 @@ */ package org.springframework.security.web.server.context; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.security.core.context.SecurityContext; import org.springframework.util.Assert; import org.springframework.web.server.ServerWebExchange; -import org.springframework.web.server.WebSession; import reactor.core.publisher.Mono; /** @@ -27,11 +28,14 @@ import reactor.core.publisher.Mono; * {@link org.springframework.web.server.WebSession}. When a {@link SecurityContext} is * saved, the session id is changed to prevent session fixation attacks. * @author Rob Winch + * @author Mathieu Ouellet * @since 5.0 */ public class WebSessionServerSecurityContextRepository implements ServerSecurityContextRepository { + private static final Log logger = LogFactory.getLog(WebSessionServerSecurityContextRepository.class); + /** * The default session attribute name to save and load the {@link SecurityContext} */ @@ -54,8 +58,14 @@ public class WebSessionServerSecurityContextRepository .doOnNext(session -> { if (context == null) { session.getAttributes().remove(this.springSecurityContextAttrName); + if (logger.isDebugEnabled()) { + logger.debug("Removed SecurityContext stored in WebSession: '" + session + "'"); + } } else { session.getAttributes().put(this.springSecurityContextAttrName, context); + if (logger.isDebugEnabled()) { + logger.debug("Saved SecurityContext '" + context + "' in WebSession: '" + session + "'"); + } } }) .flatMap(session -> session.changeSessionId()); @@ -63,9 +73,16 @@ public class WebSessionServerSecurityContextRepository public Mono load(ServerWebExchange exchange) { return exchange.getSession() - .map(WebSession::getAttributes) - .flatMap( attrs -> { - SecurityContext context = (SecurityContext) attrs.get(this.springSecurityContextAttrName); + .flatMap(session -> { + SecurityContext context = (SecurityContext) session.getAttribute(this.springSecurityContextAttrName); + if (logger.isDebugEnabled()) { + if (context == null) { + logger.debug("No SecurityContext found in WebSession: '" + session + "'"); + } + else { + logger.debug("Found SecurityContext '" + context + "' in WebSession: '" + session + "'"); + } + } return Mono.justOrEmpty(context); }); } diff --git a/web/src/main/java/org/springframework/security/web/server/savedrequest/CookieServerRequestCache.java b/web/src/main/java/org/springframework/security/web/server/savedrequest/CookieServerRequestCache.java index 52cc9444d6..d1311bb121 100644 --- a/web/src/main/java/org/springframework/security/web/server/savedrequest/CookieServerRequestCache.java +++ b/web/src/main/java/org/springframework/security/web/server/savedrequest/CookieServerRequestCache.java @@ -16,6 +16,8 @@ package org.springframework.security.web.server.savedrequest; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.http.HttpCookie; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; @@ -42,6 +44,7 @@ import java.util.Collections; * requested URI in a cookie. * * @author Eleftheria Stein + * @author Mathieu Ouellet * @since 5.4 */ public class CookieServerRequestCache implements ServerRequestCache { @@ -49,6 +52,8 @@ public class CookieServerRequestCache implements ServerRequestCache { private static final Duration COOKIE_MAX_AGE = Duration.ofSeconds(-1); + private static final Log logger = LogFactory.getLog(CookieServerRequestCache.class); + private ServerWebExchangeMatcher saveRequestMatcher = createDefaultRequestMatcher(); /** @@ -69,7 +74,13 @@ public class CookieServerRequestCache implements ServerRequestCache { .filter(m -> m.isMatch()) .map(m -> exchange.getResponse()) .map(ServerHttpResponse::getCookies) - .doOnNext(cookies -> cookies.add(REDIRECT_URI_COOKIE_NAME, createRedirectUriCookie(exchange.getRequest()))) + .doOnNext(cookies -> { + ResponseCookie redirectUriCookie = createRedirectUriCookie(exchange.getRequest()); + cookies.add(REDIRECT_URI_COOKIE_NAME, redirectUriCookie); + if (logger.isDebugEnabled()) { + logger.debug("Request added to Cookie: " + redirectUriCookie); + } + }) .then(); } diff --git a/web/src/main/java/org/springframework/security/web/server/savedrequest/WebSessionServerRequestCache.java b/web/src/main/java/org/springframework/security/web/server/savedrequest/WebSessionServerRequestCache.java index 07909d1dcf..7b55fc22eb 100644 --- a/web/src/main/java/org/springframework/security/web/server/savedrequest/WebSessionServerRequestCache.java +++ b/web/src/main/java/org/springframework/security/web/server/savedrequest/WebSessionServerRequestCache.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,6 +25,7 @@ import org.springframework.security.web.server.util.matcher.AndServerWebExchange import org.springframework.security.web.server.util.matcher.MediaTypeServerWebExchangeMatcher; import org.springframework.security.web.server.util.matcher.NegatedServerWebExchangeMatcher; import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher; +import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher.MatchResult; import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers; import org.springframework.util.Assert; import org.springframework.web.server.ServerWebExchange; @@ -41,12 +42,13 @@ import java.util.Collections; * The current implementation only saves the URL that was requested. * * @author Rob Winch + * @author Mathieu Ouellet * @since 5.0 */ public class WebSessionServerRequestCache implements ServerRequestCache { private static final String DEFAULT_SAVED_REQUEST_ATTR = "SPRING_SECURITY_SAVED_REQUEST"; - protected final Log logger = LogFactory.getLog(this.getClass()); + private static final Log logger = LogFactory.getLog(WebSessionServerRequestCache.class); private String sessionAttrName = DEFAULT_SAVED_REQUEST_ATTR; @@ -66,10 +68,16 @@ public class WebSessionServerRequestCache implements ServerRequestCache { @Override public Mono saveRequest(ServerWebExchange exchange) { return this.saveRequestMatcher.matches(exchange) - .filter(m -> m.isMatch()) + .filter(MatchResult::isMatch) .flatMap(m -> exchange.getSession()) .map(WebSession::getAttributes) - .doOnNext(attrs -> attrs.put(this.sessionAttrName, pathInApplication(exchange.getRequest()))) + .doOnNext(attrs -> { + String requestPath = pathInApplication(exchange.getRequest()); + attrs.put(this.sessionAttrName, requestPath); + if (logger.isDebugEnabled()) { + logger.debug("Request added to WebSession: '" + requestPath + "'"); + } + }) .then(); } @@ -85,7 +93,16 @@ public class WebSessionServerRequestCache implements ServerRequestCache { ServerWebExchange exchange) { return exchange.getSession() .map(WebSession::getAttributes) - .filter(attributes -> attributes.remove(this.sessionAttrName, pathInApplication(exchange.getRequest()))) + .filter(attributes -> { + String requestPath = pathInApplication(exchange.getRequest()); + boolean removed = attributes.remove(this.sessionAttrName, requestPath); + if (removed) { + if (logger.isDebugEnabled()) { + logger.debug("Request removed from WebSession: '" + requestPath + "'"); + } + } + return removed; + }) .map(attributes -> exchange.getRequest()); } diff --git a/web/src/main/java/org/springframework/security/web/server/util/matcher/AndServerWebExchangeMatcher.java b/web/src/main/java/org/springframework/security/web/server/util/matcher/AndServerWebExchangeMatcher.java index 1efe94bc1b..afe0b5c12a 100644 --- a/web/src/main/java/org/springframework/security/web/server/util/matcher/AndServerWebExchangeMatcher.java +++ b/web/src/main/java/org/springframework/security/web/server/util/matcher/AndServerWebExchangeMatcher.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,6 +15,8 @@ */ package org.springframework.security.web.server.util.matcher; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.util.Assert; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Flux; @@ -28,10 +30,12 @@ import java.util.Map; /** * Matches if all the provided {@link ServerWebExchangeMatcher} match * @author Rob Winch + * @author Mathieu Ouellet * @since 5.0 * @see OrServerWebExchangeMatcher */ public class AndServerWebExchangeMatcher implements ServerWebExchangeMatcher { + private static final Log logger = LogFactory.getLog(AndServerWebExchangeMatcher.class); private final List matchers; public AndServerWebExchangeMatcher(List matchers) { @@ -51,10 +55,20 @@ public class AndServerWebExchangeMatcher implements ServerWebExchangeMatcher { return Mono.defer(() -> { Map variables = new HashMap<>(); return Flux.fromIterable(matchers) + .doOnNext(it -> { + if (logger.isDebugEnabled()) { + logger.debug("Trying to match using " + it); + } + }) .flatMap(matcher -> matcher.matches(exchange)) .doOnNext(matchResult -> variables.putAll(matchResult.getVariables())) .all(MatchResult::isMatch) - .flatMap(allMatch -> allMatch ? MatchResult.match(variables) : MatchResult.notMatch()); + .flatMap(allMatch -> allMatch ? MatchResult.match(variables) : MatchResult.notMatch()) + .doOnNext(it -> { + if (logger.isDebugEnabled()) { + logger.debug(it.isMatch() ? "All requestMatchers returned true" : "Did not match"); + } + }); }); } diff --git a/web/src/main/java/org/springframework/security/web/server/util/matcher/NegatedServerWebExchangeMatcher.java b/web/src/main/java/org/springframework/security/web/server/util/matcher/NegatedServerWebExchangeMatcher.java index 971e73fbff..8e3f6d70ba 100644 --- a/web/src/main/java/org/springframework/security/web/server/util/matcher/NegatedServerWebExchangeMatcher.java +++ b/web/src/main/java/org/springframework/security/web/server/util/matcher/NegatedServerWebExchangeMatcher.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,6 +15,8 @@ */ package org.springframework.security.web.server.util.matcher; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.util.Assert; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; @@ -23,9 +25,11 @@ import reactor.core.publisher.Mono; * Negates the provided matcher. If the provided matcher returns true, then the result will be false. If the provided * matcher returns false, then the result will be true. * @author Tao Qian + * @author Mathieu Ouellet * @since 5.1 */ public class NegatedServerWebExchangeMatcher implements ServerWebExchangeMatcher { + private static final Log logger = LogFactory.getLog(NegatedServerWebExchangeMatcher.class); private final ServerWebExchangeMatcher matcher; public NegatedServerWebExchangeMatcher(ServerWebExchangeMatcher matcher) { @@ -39,7 +43,12 @@ public class NegatedServerWebExchangeMatcher implements ServerWebExchangeMatcher @Override public Mono matches(ServerWebExchange exchange) { return matcher.matches(exchange) - .flatMap(m -> m.isMatch() ? MatchResult.notMatch() : MatchResult.match()); + .flatMap(m -> m.isMatch() ? MatchResult.notMatch() : MatchResult.match()) + .doOnNext(it -> { + if (logger.isDebugEnabled()) { + logger.debug("matches = " + it.isMatch()); + } + }); } @Override diff --git a/web/src/main/java/org/springframework/security/web/server/util/matcher/OrServerWebExchangeMatcher.java b/web/src/main/java/org/springframework/security/web/server/util/matcher/OrServerWebExchangeMatcher.java index 526bec71f5..0d5976f0a7 100644 --- a/web/src/main/java/org/springframework/security/web/server/util/matcher/OrServerWebExchangeMatcher.java +++ b/web/src/main/java/org/springframework/security/web/server/util/matcher/OrServerWebExchangeMatcher.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,8 @@ package org.springframework.security.web.server.util.matcher; import java.util.Arrays; import java.util.List; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.util.Assert; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Flux; @@ -26,10 +28,12 @@ import reactor.core.publisher.Mono; /** * Matches if any of the provided {@link ServerWebExchangeMatcher} match * @author Rob Winch + * @author Mathieu Ouellet * @since 5.0 * @see AndServerWebExchangeMatcher */ public class OrServerWebExchangeMatcher implements ServerWebExchangeMatcher { + private static final Log logger = LogFactory.getLog(OrServerWebExchangeMatcher.class); private final List matchers; public OrServerWebExchangeMatcher(List matchers) { @@ -48,10 +52,20 @@ public class OrServerWebExchangeMatcher implements ServerWebExchangeMatcher { @Override public Mono matches(ServerWebExchange exchange) { return Flux.fromIterable(matchers) + .doOnNext(it -> { + if (logger.isDebugEnabled()) { + logger.debug("Trying to match using " + it); + } + }) .flatMap(m -> m.matches(exchange)) - .filter(m -> m.isMatch()) + .filter(MatchResult::isMatch) .next() - .switchIfEmpty(MatchResult.notMatch()); + .switchIfEmpty(MatchResult.notMatch()) + .doOnNext(it -> { + if (logger.isDebugEnabled()) { + logger.debug(it.isMatch() ? "matched" : "No matches found"); + } + }); } @Override diff --git a/web/src/main/java/org/springframework/security/web/server/util/matcher/PathPatternParserServerWebExchangeMatcher.java b/web/src/main/java/org/springframework/security/web/server/util/matcher/PathPatternParserServerWebExchangeMatcher.java index f6d0ea1cf6..8c2c67687a 100644 --- a/web/src/main/java/org/springframework/security/web/server/util/matcher/PathPatternParserServerWebExchangeMatcher.java +++ b/web/src/main/java/org/springframework/security/web/server/util/matcher/PathPatternParserServerWebExchangeMatcher.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,6 +15,8 @@ */ package org.springframework.security.web.server.util.matcher; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.http.HttpMethod; import org.springframework.http.server.PathContainer; import org.springframework.http.server.reactive.ServerHttpRequest; @@ -30,9 +32,11 @@ import java.util.Map; /** * Matches if the {@link PathPattern} matches the path within the application. * @author Rob Winch + * @author Mathieu Ouellet * @since 5.0 */ public final class PathPatternParserServerWebExchangeMatcher implements ServerWebExchangeMatcher { + private static final Log logger = LogFactory.getLog(PathPatternParserServerWebExchangeMatcher.class); private static final PathPatternParser DEFAULT_PATTERN_PARSER = new PathPatternParser(); private final PathPattern pattern; @@ -61,16 +65,34 @@ public final class PathPatternParserServerWebExchangeMatcher implements ServerWe @Override public Mono matches(ServerWebExchange exchange) { ServerHttpRequest request = exchange.getRequest(); - if (this.method != null && !this.method.equals(request.getMethod())) { - return MatchResult.notMatch(); - } PathContainer path = request.getPath().pathWithinApplication(); + if (this.method != null && !this.method.equals(request.getMethod())) { + return MatchResult.notMatch() + .doOnNext(result -> { + if (logger.isDebugEnabled()) { + logger.debug("Request '" + request.getMethod() + " " + path + + "' doesn't match '" + this.method + " " + + this.pattern.getPatternString() + "'"); + } + }); + } boolean match = this.pattern.matches(path); if (!match) { - return MatchResult.notMatch(); + return MatchResult.notMatch() + .doOnNext(result -> { + if (logger.isDebugEnabled()) { + logger.debug("Request '" + request.getMethod() + " " + path + + "' doesn't match '" + this.method + " " + + this.pattern.getPatternString() + "'"); + } + }); } Map pathVariables = this.pattern.matchAndExtract(path).getUriVariables(); Map variables = new HashMap<>(pathVariables); + if (logger.isDebugEnabled()) { + logger.debug("Checking match of request : '" + path + "'; against '" + + this.pattern.getPatternString() + "'"); + } return MatchResult.match(variables); }