NIFI-9291 Added NiFi HTTP request logging

- Added nifi.web.request.log.format property
- Added Filters to set and retrieve authenticated username for logging

Signed-off-by: Joe Gresock <jgresock@gmail.com>

This closes #5527.
This commit is contained in:
exceptionfactory 2021-11-16 11:14:03 -06:00 committed by Joe Gresock
parent 18fc492e4c
commit 605346ae0f
No known key found for this signature in database
GPG Key ID: 37F5B9B6E258C8B7
16 changed files with 546 additions and 7 deletions

View File

@ -236,6 +236,7 @@ public class NiFiProperties extends ApplicationProperties {
public static final String WEB_REQUEST_TIMEOUT = "nifi.web.request.timeout";
public static final String WEB_REQUEST_IP_WHITELIST = "nifi.web.request.ip.whitelist";
public static final String WEB_SHOULD_SEND_SERVER_VERSION = "nifi.web.should.send.server.version";
public static final String WEB_REQUEST_LOG_FORMAT = "nifi.web.request.log.format";
// ui properties
public static final String UI_BANNER_TEXT = "nifi.ui.banner.text";

View File

@ -3877,6 +3877,19 @@ blank meaning all requests containing a proxy context path are rejected. Configu
|`nifi.web.max.access.token.requests.per.second`|The maximum number of requests for login Access Tokens from a connection per second. Requests in excess of this are rejected with HTTP 429.
|`nifi.web.request.ip.whitelist`|A comma separated list of IP addresses. Used to specify the IP addresses of clients which can exceed the maximum requests per second (`nifi.web.max.requests.per.second`). Does not apply to web request timeout.
|`nifi.web.request.timeout`|The request timeout for web requests. Requests running longer than this time will be forced to end with a HTTP 503 Service Unavailable response. Default value is `60 secs`.
|`nifi.web.request.log.format`|The parameterized format for HTTP request log messages.
The format property supports the modifiers and codes described in the Jetty
link:https://www.eclipse.org/jetty/javadoc/jetty-9/org/eclipse/jetty/server/CustomRequestLog.html[CustomRequestLog^].
The default value uses the Combined Log Format, which follows the
link:https://en.wikipedia.org/wiki/Common_Log_Format[Common Log Format^] with the addition of `Referer` and `User-Agent`
request headers. The default value is:
`%{client}a - %u %t "%r" %s %O "%{Referer}i" "%{User-Agent}i"`
The CustomRequestLog writes formatted messages using the following SLF4J logger:
`org.apache.nifi.web.server.RequestLog`
|====
[[security_properties]]

View File

@ -136,6 +136,7 @@
<nifi.web.request.timeout>60 secs</nifi.web.request.timeout>
<nifi.web.request.ip.whitelist />
<nifi.web.should.send.server.version>true</nifi.web.should.send.server.version>
<nifi.web.request.log.format>%{client}a - %u %t "%r" %s %O "%{Referer}i" "%{User-Agent}i"</nifi.web.request.log.format>
<!-- nifi.properties: security properties -->
<nifi.security.autoreload.enabled>false</nifi.security.autoreload.enabled>
<nifi.security.autoreload.interval>10 secs</nifi.security.autoreload.interval>

View File

@ -57,6 +57,17 @@
</encoder>
</appender>
<appender name="REQUEST_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${org.apache.nifi.bootstrap.config.log.dir}/nifi-request.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${org.apache.nifi.bootstrap.config.log.dir}/nifi-request_%d.log</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>%msg%n</pattern>
</encoder>
</appender>
<appender name="BOOTSTRAP_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${org.apache.nifi.bootstrap.config.log.dir}/nifi-bootstrap.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
@ -170,6 +181,10 @@
<appender-ref ref="USER_FILE"/>
</logger>
<!-- Web Server Request Log -->
<logger name="org.apache.nifi.web.server.RequestLog" level="INFO" additivity="false">
<appender-ref ref="REQUEST_FILE"/>
</logger>
<!--
Logger for capturing Bootstrap logs and NiFi's standard error and standard out.

View File

@ -161,6 +161,7 @@ nifi.web.max.access.token.requests.per.second=${nifi.web.max.access.token.reques
nifi.web.request.timeout=${nifi.web.request.timeout}
nifi.web.request.ip.whitelist=${nifi.web.request.ip.whitelist}
nifi.web.should.send.server.version=${nifi.web.should.send.server.version}
nifi.web.request.log.format=${nifi.web.request.log.format}
# Include or Exclude TLS Cipher Suites for HTTPS
nifi.web.https.ciphersuites.include=

View File

@ -60,6 +60,9 @@ import org.apache.nifi.web.security.headers.XContentTypeOptionsFilter;
import org.apache.nifi.web.security.headers.XFrameOptionsFilter;
import org.apache.nifi.web.security.headers.XSSProtectionFilter;
import org.apache.nifi.web.security.requests.ContentLengthFilter;
import org.apache.nifi.web.server.log.RequestAuthenticationFilter;
import org.apache.nifi.web.server.log.RequestLogProvider;
import org.apache.nifi.web.server.log.StandardRequestLogProvider;
import org.apache.nifi.web.server.util.TrustStoreScanner;
import org.eclipse.jetty.annotations.AnnotationConfiguration;
import org.eclipse.jetty.deploy.App;
@ -68,6 +71,7 @@ import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.RequestLog;
import org.eclipse.jetty.server.SecureRequestCustomizer;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
@ -228,6 +232,11 @@ public class JettyServer implements NiFiServer, ExtensionUiLoader {
deploymentManager.setContextAttribute(CONTAINER_INCLUDE_PATTERN_KEY, CONTAINER_INCLUDE_PATTERN_VALUE);
deploymentManager.setContexts(contextHandlers);
server.addBean(deploymentManager);
final String requestLogFormat = props.getProperty(NiFiProperties.WEB_REQUEST_LOG_FORMAT);
final RequestLogProvider requestLogProvider = new StandardRequestLogProvider(requestLogFormat);
final RequestLog requestLog = requestLogProvider.getRequestLog();
server.setRequestLog(requestLog);
}
/**
@ -635,6 +644,7 @@ public class JettyServer implements NiFiServer, ExtensionUiLoader {
if (props.isHTTPSConfigured()) {
filters.add(StrictTransportSecurityFilter.class);
filters.add(RequestAuthenticationFilter.class);
}
filters.forEach((filter) -> addFilters(filter, webappContext));
addDenialOfServiceFilters(webappContext, props);

View File

@ -0,0 +1,75 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.nifi.web.server.log;
import org.apache.nifi.web.security.log.AuthenticationUserAttribute;
import org.eclipse.jetty.security.DefaultUserIdentity;
import org.eclipse.jetty.security.UserAuthentication;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.UserIdentity;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.security.auth.Subject;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.security.Principal;
/**
* Request Authentication Filter sets Jetty Request Authentication using Spring Security Authentication as Principal
*/
public class RequestAuthenticationFilter extends OncePerRequestFilter {
private static final Subject DEFAULT_SUBJECT = new Subject();
private static final String[] DEFAULT_ROLES = new String[]{};
private static final String METHOD = "FILTER";
private static final Logger logger = LoggerFactory.getLogger(RequestAuthenticationFilter.class);
/**
* Read Authentication username from request attribute and set Jetty Authentication when found
*
* @param httpServletRequest HTTP Servlet Request
* @param httpServletResponse HTTP Servlet Response
* @param filterChain Filter Chain
* @throws ServletException Thrown on FilterChain.doFilter()
* @throws IOException Thrown on FilterChain.doFilter()
*/
@Override
protected void doFilterInternal(final HttpServletRequest httpServletRequest, final HttpServletResponse httpServletResponse, final FilterChain filterChain) throws ServletException, IOException {
filterChain.doFilter(httpServletRequest, httpServletResponse);
if (httpServletRequest instanceof Request) {
final Request request = (Request) httpServletRequest;
final Object usernameAttribute = httpServletRequest.getAttribute(AuthenticationUserAttribute.USERNAME.getName());
if (usernameAttribute == null) {
logger.debug("Username not found Remote Address [{}]", httpServletRequest.getRemoteAddr());
} else {
final String username = usernameAttribute.toString();
final Principal principal = new UserPrincipal(username);
final UserIdentity userIdentity = new DefaultUserIdentity(DEFAULT_SUBJECT, principal, DEFAULT_ROLES);
final UserAuthentication authentication = new UserAuthentication(METHOD, userIdentity);
request.setAuthentication(authentication);
}
}
}
}

View File

@ -0,0 +1,31 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.nifi.web.server.log;
import org.eclipse.jetty.server.RequestLog;
/**
* Jetty Request Log Provider encapsulates HTTP Request Log configuration
*/
public interface RequestLogProvider {
/**
* Get configured Request Log
*
* @return Request Log
*/
RequestLog getRequestLog();
}

View File

@ -0,0 +1,47 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.nifi.web.server.log;
import org.apache.commons.lang.StringUtils;
import org.eclipse.jetty.server.CustomRequestLog;
import org.eclipse.jetty.server.RequestLog;
import org.eclipse.jetty.server.Slf4jRequestLogWriter;
/**
* Standard implementation of Jetty Request Log Provider with configurable format string
*/
public class StandardRequestLogProvider implements RequestLogProvider {
private static final String LOGGER_NAME = "org.apache.nifi.web.server.RequestLog";
private final String format;
public StandardRequestLogProvider(final String format) {
this.format = StringUtils.defaultIfBlank(format, CustomRequestLog.EXTENDED_NCSA_FORMAT);
}
/**
* Get Request Log configured using specified format and SLF4J writer
*
* @return Custom Request Log
*/
@Override
public RequestLog getRequestLog() {
final Slf4jRequestLogWriter writer = new Slf4jRequestLogWriter();
writer.setLoggerName(LOGGER_NAME);
return new CustomRequestLog(writer, format);
}
}

View File

@ -0,0 +1,41 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.nifi.web.server.log;
import java.security.Principal;
import java.util.Objects;
/**
* User Principal implementation with standard name field
*/
public class UserPrincipal implements Principal {
private final String name;
public UserPrincipal(final String name) {
this.name = Objects.requireNonNull(name, "Name required");
}
@Override
public String getName() {
return name;
}
@Override
public String toString() {
return name;
}
}

View File

@ -0,0 +1,90 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.nifi.web.server.log;
import org.apache.nifi.web.security.log.AuthenticationUserAttribute;
import org.eclipse.jetty.security.UserAuthentication;
import org.eclipse.jetty.server.Authentication;
import org.eclipse.jetty.server.HttpChannel;
import org.eclipse.jetty.server.HttpInput;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.UserIdentity;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.security.Principal;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
@ExtendWith(MockitoExtension.class)
public class RequestAuthenticationFilterTest {
private static final String USERNAME = "User";
@Mock
private HttpServletResponse response;
@Mock
private FilterChain filterChain;
@Mock
private HttpChannel channel;
@Mock
private HttpInput input;
@Test
public void testDoFilterAuthenticationUsernameFound() throws ServletException, IOException {
final RequestAuthenticationFilter filter = new RequestAuthenticationFilter();
final Request request = new Request(channel, input);
request.setAttribute(AuthenticationUserAttribute.USERNAME.getName(), USERNAME);
filter.doFilter(request, response, filterChain);
final UserAuthentication authentication = (UserAuthentication) request.getAuthentication();
assertNotNull(authentication);
final UserIdentity userIdentity = authentication.getUserIdentity();
assertNotNull(userIdentity);
final Principal principal = userIdentity.getUserPrincipal();
assertNotNull(principal);
assertEquals(USERNAME, principal.getName());
}
@Test
public void testDoFilterAuthenticationUsernameNotFound() throws ServletException, IOException {
final RequestAuthenticationFilter filter = new RequestAuthenticationFilter();
final Request request = new Request(channel, input);
filter.doFilter(request, response, filterChain);
final Authentication authentication = request.getAuthentication();
assertNull(authentication);
}
}

View File

@ -0,0 +1,39 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.nifi.web.server.log;
import org.eclipse.jetty.server.RequestLog;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
public class StandardRequestLogProviderTest {
@Test
public void testGetRequestLogNullFormat() {
final StandardRequestLogProvider provider = new StandardRequestLogProvider(null);
final RequestLog requestLog = provider.getRequestLog();
assertNotNull(requestLog);
}
@Test
public void testGetRequestLogInvalidFormat() {
final StandardRequestLogProvider provider = new StandardRequestLogProvider("%Z");
assertThrows(IllegalArgumentException.class, provider::getRequestLog);
}
}

View File

@ -24,6 +24,7 @@ import org.apache.nifi.web.security.csrf.StandardCookieCsrfTokenRepository;
import org.apache.nifi.web.security.jwt.resolver.StandardBearerTokenResolver;
import org.apache.nifi.web.security.knox.KnoxAuthenticationFilter;
import org.apache.nifi.web.security.knox.KnoxAuthenticationProvider;
import org.apache.nifi.web.security.log.AuthenticationUserFilter;
import org.apache.nifi.web.security.oidc.OIDCEndpoints;
import org.apache.nifi.web.security.saml.SAMLEndpoints;
import org.apache.nifi.web.security.x509.X509AuthenticationFilter;
@ -116,17 +117,11 @@ public class NiFiWebApiSecurityConfiguration extends WebSecurityConfigurerAdapte
new AndRequestMatcher(CsrfFilter.DEFAULT_CSRF_MATCHER, new CsrfCookieRequestMatcher()))
.csrfTokenRepository(new StandardCookieCsrfTokenRepository(properties.getAllowedContextPathsAsList()));
// x509
http.addFilterBefore(x509FilterBean(), AnonymousAuthenticationFilter.class);
// jwt
http.addFilterBefore(bearerTokenAuthenticationFilter(), AnonymousAuthenticationFilter.class);
// knox
http.addFilterBefore(knoxFilterBean(), AnonymousAuthenticationFilter.class);
// anonymous
http.addFilterAfter(anonymousFilterBean(), AnonymousAuthenticationFilter.class);
http.addFilterAfter(new AuthenticationUserFilter(), AnonymousAuthenticationFilter.class);
// disable default anonymous handling because it doesn't handle conditional authentication well
http.anonymous().disable();

View File

@ -0,0 +1,34 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.nifi.web.security.log;
/**
* Request Attribute enumeration for Authentication User information
*/
public enum AuthenticationUserAttribute {
USERNAME("org.apache.nifi.web.security.log.username");
private String name;
AuthenticationUserAttribute(final String name) {
this.name = name;
}
public String getName() {
return name;
}
}

View File

@ -0,0 +1,59 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.nifi.web.security.log;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* Authentication User Filter sets authentication username in request attribute for later retrieval and logging
*/
public class AuthenticationUserFilter extends OncePerRequestFilter {
private static final Logger logger = LoggerFactory.getLogger(AuthenticationUserFilter.class);
/**
* Read Authentication username from Spring Security Context and set username request attribute when found
*
* @param request HTTP Servlet Request
* @param response HTTP Servlet Response
* @param filterChain Filter Chain
* @throws ServletException Thrown on FilterChain.doFilter()
* @throws IOException Thrown on FilterChain.doFilter()
*/
@Override
protected void doFilterInternal(final HttpServletRequest request, final HttpServletResponse response, final FilterChain filterChain) throws ServletException, IOException {
final SecurityContext securityContext = SecurityContextHolder.getContext();
final Authentication authentication = securityContext.getAuthentication();
if (authentication == null) {
logger.debug("Authentication not found in Security Context for Remote Address [{}]", request.getRemoteAddr());
} else {
final String username = authentication.getName();
request.setAttribute(AuthenticationUserAttribute.USERNAME.getName(), username);
}
filterChain.doFilter(request, response);
}
}

View File

@ -0,0 +1,87 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.nifi.web.security.log;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextImpl;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
public class AuthenticationUserFilterTest {
private static final String USERNAME = "User";
@Mock
private HttpServletRequest request;
@Mock
private HttpServletResponse response;
@Mock
private FilterChain filterChain;
@Mock
private Authentication authentication;
@Captor
private ArgumentCaptor<String> usernameCaptor;
@Test
public void testDoFilterInternalAuthenticationFound() throws ServletException, IOException {
final AuthenticationUserFilter filter = new AuthenticationUserFilter();
when(authentication.getName()).thenReturn(USERNAME);
final SecurityContext securityContext = new SecurityContextImpl(authentication);
SecurityContextHolder.setContext(securityContext);
filter.doFilterInternal(request, response, filterChain);
verify(request).setAttribute(eq(AuthenticationUserAttribute.USERNAME.getName()), usernameCaptor.capture());
assertEquals(USERNAME, usernameCaptor.getValue());
}
@Test
public void testDoFilterInternalAuthenticationNotFound() throws ServletException, IOException {
final AuthenticationUserFilter filter = new AuthenticationUserFilter();
final SecurityContext securityContext = new SecurityContextImpl();
SecurityContextHolder.setContext(securityContext);
filter.doFilterInternal(request, response, filterChain);
verify(request).getRemoteAddr();
verifyNoMoreInteractions(request);
}
}