mirror of https://github.com/apache/nifi.git
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:
parent
18fc492e4c
commit
605346ae0f
|
@ -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";
|
||||
|
|
|
@ -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]]
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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=
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue