This is an automated email from the ASF dual-hosted git repository.

xxyu pushed a commit to branch kylin5
in repository https://gitbox.apache.org/repos/asf/kylin.git

commit 794a63c8542843b398e178dec7a2e1356726486b
Author: fengguangyuan <qq272101...@gmail.com>
AuthorDate: Fri May 26 12:20:34 2023 +0800

    KYLIN-5697 Log http request details for the authentication failure
    
    ---------
    
    Co-authored-by: Guangyuan Feng <guangyuan.f...@kyligence.io>
---
 .../apache/kylin/rest/response/ErrorResponse.java  |   4 +-
 .../rest/security/NUnauthorisedEntryPoint.java     |  27 ++---
 .../java/org/apache/kylin/rest/util/HttpUtil.java  |  87 +++++++++++++++
 .../rest/security/NUnauthorisedEntryPointTest.java |   1 -
 .../org/apache/kylin/rest/util/HttpUtilTest.java   | 119 +++++++++++++++++++++
 5 files changed, 220 insertions(+), 18 deletions(-)

diff --git 
a/src/common-service/src/main/java/org/apache/kylin/rest/response/ErrorResponse.java
 
b/src/common-service/src/main/java/org/apache/kylin/rest/response/ErrorResponse.java
index e376e5733e..fdcf7dc727 100644
--- 
a/src/common-service/src/main/java/org/apache/kylin/rest/response/ErrorResponse.java
+++ 
b/src/common-service/src/main/java/org/apache/kylin/rest/response/ErrorResponse.java
@@ -21,11 +21,12 @@ package org.apache.kylin.rest.response;
 import static 
org.apache.kylin.common.exception.CommonErrorCode.FAILED_PARSE_JSON;
 import static 
org.apache.kylin.common.exception.CommonErrorCode.UNKNOWN_ERROR_CODE;
 
+import lombok.NoArgsConstructor;
 import org.apache.kylin.common.exception.KylinException;
+import org.apache.kylin.guava30.shaded.common.base.Throwables;
 
 import com.fasterxml.jackson.annotation.JsonProperty;
 import com.fasterxml.jackson.core.JsonParseException;
-import org.apache.kylin.guava30.shaded.common.base.Throwables;
 
 import lombok.Data;
 
@@ -33,6 +34,7 @@ import lombok.Data;
  * response to client when the return HTTP code is not 200
  */
 @Data
+@NoArgsConstructor
 public class ErrorResponse extends EnvelopeResponse<Object> {
 
     //stacktrace of the exception
diff --git 
a/src/common-service/src/main/java/org/apache/kylin/rest/security/NUnauthorisedEntryPoint.java
 
b/src/common-service/src/main/java/org/apache/kylin/rest/security/NUnauthorisedEntryPoint.java
index 3aceb2ad5c..fc238f3510 100644
--- 
a/src/common-service/src/main/java/org/apache/kylin/rest/security/NUnauthorisedEntryPoint.java
+++ 
b/src/common-service/src/main/java/org/apache/kylin/rest/security/NUnauthorisedEntryPoint.java
@@ -23,9 +23,10 @@ import static 
org.apache.kylin.common.exception.ServerErrorCode.USER_DATA_SOURCE
 import static org.apache.kylin.common.exception.ServerErrorCode.USER_LOCKED;
 import static 
org.apache.kylin.common.exception.code.ErrorCodeServer.USER_LOGIN_FAILED;
 import static 
org.apache.kylin.common.exception.code.ErrorCodeServer.USER_UNAUTHORIZED;
+import static org.apache.kylin.rest.util.HttpUtil.formatRequest;
+import static org.apache.kylin.rest.util.HttpUtil.setErrorResponse;
 
 import java.io.IOException;
-import java.io.PrintWriter;
 import java.net.ConnectException;
 import java.util.Optional;
 
@@ -35,9 +36,6 @@ import javax.servlet.http.HttpServletResponse;
 
 import org.apache.kylin.common.exception.KylinException;
 import org.apache.kylin.common.msg.MsgPicker;
-import org.apache.kylin.common.util.JsonUtil;
-import org.apache.kylin.rest.response.ErrorResponse;
-import org.springframework.http.MediaType;
 import org.springframework.ldap.CommunicationException;
 import org.springframework.security.authentication.DisabledException;
 import 
org.springframework.security.authentication.InsufficientAuthenticationException;
@@ -46,6 +44,9 @@ import 
org.springframework.security.core.AuthenticationException;
 import org.springframework.security.web.AuthenticationEntryPoint;
 import org.springframework.stereotype.Component;
 
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
 @Component(value = "nUnauthorisedEntryPoint")
 public class NUnauthorisedEntryPoint implements AuthenticationEntryPoint {
 
@@ -58,10 +59,12 @@ public class NUnauthorisedEntryPoint implements 
AuthenticationEntryPoint {
         } else if (exception instanceof InsufficientAuthenticationException) {
             setErrorResponse(request, response, 
HttpServletResponse.SC_UNAUTHORIZED,
                     new KylinException(USER_UNAUTHORIZED));
+            logRequest(request);
             return;
         } else if (exception instanceof DisabledException) {
             setErrorResponse(request, response, 
HttpServletResponse.SC_UNAUTHORIZED,
                     new KylinException(LOGIN_FAILED, 
MsgPicker.getMsg().getDisabledUser()));
+            logRequest(request);
             return;
         }
         boolean present = 
Optional.ofNullable(exception).map(Throwable::getCause)
@@ -89,17 +92,9 @@ public class NUnauthorisedEntryPoint implements 
AuthenticationEntryPoint {
         setErrorResponse(request, response, 
HttpServletResponse.SC_UNAUTHORIZED, new KylinException(USER_LOGIN_FAILED));
     }
 
-    public void setErrorResponse(HttpServletRequest request, 
HttpServletResponse response, int statusCode, Exception ex)
-            throws IOException {
-        response.setStatus(statusCode);
-        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
-        ErrorResponse errorResponse = new 
ErrorResponse(request.getRequestURL().toString(), ex);
-        String errorStr = JsonUtil.writeValueAsIndentString(errorResponse);
-        response.setCharacterEncoding("UTF-8");
-        PrintWriter writer = response.getWriter();
-        writer.print(errorStr);
-        writer.flush();
-        writer.close();
+    private void logRequest(HttpServletRequest request) {
+        if (log.isDebugEnabled()) {
+            log.debug("Detail http request for authentication:\n" + 
formatRequest(request));
+        }
     }
-
 }
diff --git 
a/src/common-service/src/main/java/org/apache/kylin/rest/util/HttpUtil.java 
b/src/common-service/src/main/java/org/apache/kylin/rest/util/HttpUtil.java
new file mode 100644
index 0000000000..29001b6b36
--- /dev/null
+++ b/src/common-service/src/main/java/org/apache/kylin/rest/util/HttpUtil.java
@@ -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.kylin.rest.util;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.kylin.common.util.JsonUtil;
+import org.apache.kylin.rest.response.ErrorResponse;
+import org.springframework.http.MediaType;
+import org.springframework.http.server.ServletServerHttpRequest;
+
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+
+public class HttpUtil {
+    private static final String DEFAULT_CONTENT_TYPE = 
MediaType.APPLICATION_JSON_VALUE;
+    private static final Charset DEFAULT_CONTENT_CHARSET = 
StandardCharsets.UTF_8;
+
+    private HttpUtil() {}
+
+    public static String getFullRequestUrl(HttpServletRequest request) {
+        String url = request.getRequestURL().toString();
+        if (StringUtils.isNotBlank(request.getQueryString())) {
+            if (url.lastIndexOf("?") > -1) {
+                return url + "&" + request.getQueryString();
+            } else {
+                return url + "?" + request.getQueryString();
+            }
+        }
+        return request.getRequestURL().toString();
+    }
+
+    public static void setErrorResponse(HttpServletRequest request, 
HttpServletResponse response, int statusCode, Exception ex)
+            throws IOException {
+        response.setStatus(statusCode);
+        response.setContentType(DEFAULT_CONTENT_TYPE);
+        ErrorResponse errorResponse = new 
ErrorResponse(getFullRequestUrl(request), ex);
+
+        String errorStr = JsonUtil.writeValueAsIndentString(errorResponse);
+        response.setCharacterEncoding(DEFAULT_CONTENT_CHARSET.name());
+        byte[] responseData = errorStr.getBytes(DEFAULT_CONTENT_CHARSET);
+        ServletOutputStream writer = response.getOutputStream();
+        response.setContentLength(responseData.length);
+        writer.write(responseData, 0, responseData.length);
+        writer.flush();
+        writer.close();
+    }
+
+    public static String formatRequest(HttpServletRequest request) {
+        StringBuilder sb = new StringBuilder();
+        sb.append("Url: ").append(getFullRequestUrl(request)).append("\n");
+        sb.append("Headers: ").append(new 
ServletServerHttpRequest(request).getHeaders()).append("\n");
+        sb.append("RemoteAddr: ").append(request.getRemoteAddr()).append("\n");
+        sb.append("RemoteUser: ").append(request.getRemoteUser()).append("\n");
+        sb.append("Session: 
").append(formatSession(request.getSession(false))).append("\n");
+        Object attr = request.getAttribute("traceId");
+        if (attr != null) {
+            sb.append("TraceId: ").append(attr).append("\n");
+        }
+        return sb.toString();
+    }
+
+    public static String formatSession(HttpSession session) {
+        return String.format("Id=%s; createTime=%s; lastAccessedTime=%s",
+                session.getId(), session.getCreationTime(), 
session.getLastAccessedTime());
+    }
+}
diff --git 
a/src/common-service/src/test/java/org/apache/kylin/rest/security/NUnauthorisedEntryPointTest.java
 
b/src/common-service/src/test/java/org/apache/kylin/rest/security/NUnauthorisedEntryPointTest.java
index c89ebaada9..14b0e74711 100644
--- 
a/src/common-service/src/test/java/org/apache/kylin/rest/security/NUnauthorisedEntryPointTest.java
+++ 
b/src/common-service/src/test/java/org/apache/kylin/rest/security/NUnauthorisedEntryPointTest.java
@@ -78,6 +78,5 @@ public class NUnauthorisedEntryPointTest {
                 new org.springframework.ldap.AuthenticationException(new 
javax.naming.AuthenticationException())) {
         });
         Assertions.assertEquals(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, 
response.getStatus());
-
     }
 }
diff --git 
a/src/common-service/src/test/java/org/apache/kylin/rest/util/HttpUtilTest.java 
b/src/common-service/src/test/java/org/apache/kylin/rest/util/HttpUtilTest.java
new file mode 100644
index 0000000000..f5045a1b63
--- /dev/null
+++ 
b/src/common-service/src/test/java/org/apache/kylin/rest/util/HttpUtilTest.java
@@ -0,0 +1,119 @@
+/*
+ * 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.kylin.rest.util;
+
+import static org.apache.kylin.rest.util.HttpUtil.formatRequest;
+import static org.apache.kylin.rest.util.HttpUtil.formatSession;
+import static org.apache.kylin.rest.util.HttpUtil.getFullRequestUrl;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+import org.apache.kylin.common.exception.KylinException;
+import org.apache.kylin.rest.response.ErrorResponse;
+import org.junit.Before;
+import org.junit.Test;
+import org.springframework.http.MediaType;
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.mock.web.MockHttpServletResponse;
+import org.springframework.mock.web.MockHttpSession;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+public class HttpUtilTest {
+    private static final MockHttpServletRequest REQUEST_SAMPLE = new 
MockHttpServletRequest();
+
+    private static final MockHttpSession DEFAULT_SESSION = new 
MockHttpSession();
+
+    @Before
+    public void init() {
+        REQUEST_SAMPLE.setSession(DEFAULT_SESSION);
+        REQUEST_SAMPLE.setMethod("Get");
+        REQUEST_SAMPLE.setRequestURI("/api/projects");
+        REQUEST_SAMPLE.setServerPort(8081);
+        REQUEST_SAMPLE.setRemoteAddr("127.0.0.1");
+        REQUEST_SAMPLE.setParameter("project", "test");
+        REQUEST_SAMPLE.setParameter("valid", "true");
+        REQUEST_SAMPLE.setContentType(MediaType.APPLICATION_JSON_VALUE);
+        REQUEST_SAMPLE.setContent("{\"sample\": 
true}".getBytes(StandardCharsets.UTF_8));
+        REQUEST_SAMPLE.setAttribute("traceId", "1");
+    }
+
+    @Test
+    public void testFormatUrl() {
+        MockHttpServletRequest request = new MockHttpServletRequest();
+        request.setRequestURI("/api/projects");
+        request.setQueryString("project=test");
+        assertEquals("http://localhost/api/projects?project=test";, 
getFullRequestUrl(request));
+        request.setRequestURI("/api/projects?valid=true");
+        assertEquals("http://localhost/api/projects?valid=true&project=test";, 
getFullRequestUrl(request));
+    }
+
+    @Test
+    public void testErrorResponse() throws IOException {
+        MockHttpServletResponse response = new MockHttpServletResponse();
+        HttpUtil.setErrorResponse(REQUEST_SAMPLE, response, 200, new 
RuntimeException("Empty"));
+
+        assertEquals(200, response.getStatus());
+        assertEquals(StandardCharsets.UTF_8.name(), 
response.getCharacterEncoding());
+        assertEquals(MediaType.APPLICATION_JSON_VALUE + ";charset=UTF-8", 
response.getContentType());
+        assertTrue(response.getContentLength() > 0);
+
+        ObjectMapper mapper = new ObjectMapper();
+        ErrorResponse errorResponse =
+                
mapper.reader().readValue(mapper.createParser(response.getContentAsString()), 
ErrorResponse.class);
+
+        assertEquals(errorResponse.getUrl(), 
getFullRequestUrl(REQUEST_SAMPLE));
+        assertEquals(KylinException.CODE_UNDEFINED, errorResponse.getCode());
+        assertTrue(errorResponse.getStacktrace().length() > 0);
+        assertTrue(errorResponse.getMsg().length() > 0);
+        assertNull(errorResponse.getSuggestion());
+        assertNull(errorResponse.getErrorCode());
+    }
+
+    @Test
+    public void testFormatEmptyRequest() {
+        MockHttpServletRequest request = new MockHttpServletRequest();
+        request.setSession(DEFAULT_SESSION);
+        assertNotNull(request.getSession());
+
+        String expected = "Url: " + getFullRequestUrl(request) + "\n"
+                + "Headers: []\n"
+                + "RemoteAddr: 127.0.0.1\n"
+                + "RemoteUser: null\n"
+                + "Session: " + formatSession(request.getSession()) + "\n";
+        assertEquals(expected, formatRequest(request));
+    }
+
+    @Test
+    public void testFormatRequest() {
+        assertNotNull(REQUEST_SAMPLE.getSession());
+        String expected = "Url: " + getFullRequestUrl(REQUEST_SAMPLE) + "\n"
+                + "Headers: [Content-Type:\"application/json\", 
Content-Length:\"16\"]\n"
+                + "RemoteAddr: 127.0.0.1\n"
+                + "RemoteUser: null\n"
+                + "Session: " + formatSession(REQUEST_SAMPLE.getSession()) + 
"\n"
+                + "TraceId: 1\n";
+        assertEquals(expected, formatRequest(REQUEST_SAMPLE));
+    }
+}

Reply via email to