Skip to content

Commit

Permalink
Develop refactor request context (alibaba#12331)
Browse files Browse the repository at this point in the history
* Add RequestContext and RequestContextHolder.

* build RequestContext when request start.

* Refactor ClientAttributesFilter with RequestContext.

* InstanceController get client ip from request context.

* SubscribeServiceRequestHandler get app from requestcontext.

* config http api support use request context get user, app and source ip.

* Rename nacos request context filter.

* Unified naming request get source ip by request context.

* For checkstyle.
  • Loading branch information
KomachiSion authored Jul 10, 2024
1 parent 2aa9fc5 commit 2233e65
Show file tree
Hide file tree
Showing 41 changed files with 1,952 additions and 137 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@

import com.alibaba.nacos.api.common.Constants;
import com.alibaba.nacos.common.utils.StringUtils;
import com.alibaba.nacos.core.context.RequestContextHolder;
import com.alibaba.nacos.core.utils.WebUtils;
import com.alibaba.nacos.plugin.auth.api.IdentityContext;

import javax.servlet.http.HttpServletRequest;

Expand All @@ -28,30 +31,24 @@
*/
public class RequestUtil {

private static final String X_REAL_IP = "X-Real-IP";

private static final String X_FORWARDED_FOR = "X-Forwarded-For";

private static final String X_FORWARDED_FOR_SPLIT_SYMBOL = ",";

public static final String CLIENT_APPNAME_HEADER = "Client-AppName";

/**
* get real client ip
*
* <p>first use X-Forwarded-For header https://zh.wikipedia.org/wiki/X-Forwarded-For next nginx X-Real-IP last
* {@link HttpServletRequest#getRemoteAddr()}
* Get real client ip from context first, if no value, use
* {@link com.alibaba.nacos.core.utils.WebUtils#getRemoteIp(HttpServletRequest)}.
*
* @param request {@link HttpServletRequest}
* @return remote ip address.
*/
public static String getRemoteIp(HttpServletRequest request) {
String xForwardedFor = request.getHeader(X_FORWARDED_FOR);
if (!StringUtils.isBlank(xForwardedFor)) {
return xForwardedFor.split(X_FORWARDED_FOR_SPLIT_SYMBOL)[0].trim();
String remoteIp = RequestContextHolder.getContext().getBasicContext().getAddressContext().getSourceIp();
if (StringUtils.isBlank(remoteIp)) {
remoteIp = RequestContextHolder.getContext().getBasicContext().getAddressContext().getRemoteIp();
}
String nginxHeader = request.getHeader(X_REAL_IP);
return StringUtils.isBlank(nginxHeader) ? request.getRemoteAddr() : nginxHeader;
if (StringUtils.isBlank(remoteIp)) {
remoteIp = WebUtils.getRemoteIp(request);
}
return remoteIp;
}

/**
Expand All @@ -61,7 +58,12 @@ public static String getRemoteIp(HttpServletRequest request) {
* @return may be return null
*/
public static String getAppName(HttpServletRequest request) {
return request.getHeader(CLIENT_APPNAME_HEADER);
String result = RequestContextHolder.getContext().getBasicContext().getApp();
return isUnknownApp(result) ? request.getHeader(CLIENT_APPNAME_HEADER) : result;
}

private static boolean isUnknownApp(String appName) {
return StringUtils.isBlank(appName) || StringUtils.equalsIgnoreCase("unknown", appName);
}

/**
Expand All @@ -71,8 +73,12 @@ public static String getAppName(HttpServletRequest request) {
* @return may be return null
*/
public static String getSrcUserName(HttpServletRequest request) {
String result = (String) request.getSession()
.getAttribute(com.alibaba.nacos.plugin.auth.constant.Constants.Identity.IDENTITY_ID);
IdentityContext identityContext = RequestContextHolder.getContext().getAuthContext().getIdentityContext();
String result = StringUtils.EMPTY;
if (null != identityContext) {
result = (String) identityContext.getParameter(
com.alibaba.nacos.plugin.auth.constant.Constants.Identity.IDENTITY_ID);
}
// If auth is disabled, get username from parameters by agreed key
return StringUtils.isBlank(result) ? request.getParameter(Constants.USERNAME) : result;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@
package com.alibaba.nacos.config.server.utils;

import com.alibaba.nacos.api.common.Constants;
import com.alibaba.nacos.core.context.RequestContextHolder;
import com.alibaba.nacos.plugin.auth.api.IdentityContext;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.eq;
Expand All @@ -32,8 +34,13 @@ class RequestUtilTest {

private static final String X_FORWARDED_FOR = "X-Forwarded-For";

@AfterEach
void tearDown() {
RequestContextHolder.removeContext();
}

@Test
void testGetRemoteIp() {
void testGetRemoteIpFromRequest() {
HttpServletRequest request = Mockito.mock(HttpServletRequest.class);

Mockito.when(request.getRemoteAddr()).thenReturn("127.0.0.1");
Expand All @@ -56,29 +63,33 @@ void testGetRemoteIp() {
}

@Test
void testGetAppName() {
void testGetAppNameFromContext() {
RequestContextHolder.getContext().getBasicContext().setApp("contextApp");
HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
Mockito.when(request.getHeader(eq(RequestUtil.CLIENT_APPNAME_HEADER))).thenReturn("test");
assertEquals("contextApp", RequestUtil.getAppName(request));
}

@Test
void testGetAppNameFromRequest() {
HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
Mockito.when(request.getHeader(eq(RequestUtil.CLIENT_APPNAME_HEADER))).thenReturn("test");
assertEquals("test", RequestUtil.getAppName(request));
}

@Test
void testGetSrcUserNameV1() {
void testGetSrcUserNameFromContext() {
IdentityContext identityContext = new IdentityContext();
identityContext.setParameter(com.alibaba.nacos.plugin.auth.constant.Constants.Identity.IDENTITY_ID, "test");
RequestContextHolder.getContext().getAuthContext().setIdentityContext(identityContext);
HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
HttpSession session = Mockito.mock(HttpSession.class);
Mockito.when(request.getSession()).thenReturn(session);
Mockito.when(session.getAttribute(eq(com.alibaba.nacos.plugin.auth.constant.Constants.Identity.IDENTITY_ID))).thenReturn("test");
assertEquals("test", RequestUtil.getSrcUserName(request));
}

@Test
void testGetSrcUserNameV2() {
void testGetSrcUserNameFromRequest() {
HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
HttpSession session = Mockito.mock(HttpSession.class);
Mockito.when(request.getSession()).thenReturn(session);
Mockito.when(session.getAttribute(eq(com.alibaba.nacos.plugin.auth.constant.Constants.Identity.IDENTITY_ID))).thenReturn(null);
Mockito.when(request.getParameter(eq(Constants.USERNAME))).thenReturn("parameterName");
assertEquals("parameterName", RequestUtil.getSrcUserName(request));
}

}
26 changes: 8 additions & 18 deletions core/src/main/java/com/alibaba/nacos/core/auth/AuthFilter.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
import com.alibaba.nacos.common.utils.ExceptionUtil;
import com.alibaba.nacos.common.utils.StringUtils;
import com.alibaba.nacos.core.code.ControllerMethodsCache;
import com.alibaba.nacos.core.context.RequestContext;
import com.alibaba.nacos.core.context.RequestContextHolder;
import com.alibaba.nacos.core.utils.Loggers;
import com.alibaba.nacos.core.utils.WebUtils;
import com.alibaba.nacos.plugin.auth.api.IdentityContext;
Expand Down Expand Up @@ -120,11 +122,16 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha
Resource resource = protocolAuthService.parseResource(req, secured);
IdentityContext identityContext = protocolAuthService.parseIdentity(req);
boolean result = protocolAuthService.validateIdentity(identityContext, resource);
RequestContext requestContext = RequestContextHolder.getContext();
requestContext.getAuthContext().setIdentityContext(identityContext);
requestContext.getAuthContext().setResource(resource);
if (null == requestContext.getAuthContext().getAuthResult()) {
requestContext.getAuthContext().setAuthResult(result);
}
if (!result) {
// TODO Get reason of failure
throw new AccessException("Validate Identity failed.");
}
injectIdentityId(req, identityContext);
String action = secured.action().toString();
result = protocolAuthService.validateAuthority(identityContext, new Permission(resource, action));
if (!result) {
Expand All @@ -146,21 +153,4 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Server failed, " + e.getMessage());
}
}

/**
* Set identity id to request session, make sure some actual logic can get identity information.
*
* <p>May be replaced with whole identityContext.
*
* @param request http request
* @param identityContext identity context
*/
private void injectIdentityId(HttpServletRequest request, IdentityContext identityContext) {
String identityId = identityContext.getParameter(
com.alibaba.nacos.plugin.auth.constant.Constants.Identity.IDENTITY_ID, StringUtils.EMPTY);
request.getSession()
.setAttribute(com.alibaba.nacos.plugin.auth.constant.Constants.Identity.IDENTITY_ID, identityId);
request.getSession().setAttribute(com.alibaba.nacos.plugin.auth.constant.Constants.Identity.IDENTITY_CONTEXT,
identityContext);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
import com.alibaba.nacos.auth.annotation.Secured;
import com.alibaba.nacos.auth.config.AuthConfigs;
import com.alibaba.nacos.common.utils.ExceptionUtil;
import com.alibaba.nacos.core.context.RequestContext;
import com.alibaba.nacos.core.context.RequestContextHolder;
import com.alibaba.nacos.core.remote.AbstractRequestFilter;
import com.alibaba.nacos.core.utils.Loggers;
import com.alibaba.nacos.plugin.auth.api.IdentityContext;
Expand Down Expand Up @@ -75,6 +77,12 @@ public Response filter(Request request, RequestMeta meta, Class handlerClazz) th
Resource resource = protocolAuthService.parseResource(request, secured);
IdentityContext identityContext = protocolAuthService.parseIdentity(request);
boolean result = protocolAuthService.validateIdentity(identityContext, resource);
RequestContext requestContext = RequestContextHolder.getContext();
requestContext.getAuthContext().setIdentityContext(identityContext);
requestContext.getAuthContext().setResource(resource);
if (null == requestContext.getAuthContext().getAuthResult()) {
requestContext.getAuthContext().setAuthResult(result);
}
if (!result) {
// TODO Get reason of failure
throw new AccessException("Validate Identity failed.");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*
* Copyright 1999-2023 Alibaba Group Holding Ltd.
*
* Licensed 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 com.alibaba.nacos.core.context;

import com.alibaba.nacos.core.context.addition.AuthContext;
import com.alibaba.nacos.core.context.addition.BasicContext;
import com.alibaba.nacos.core.context.addition.EngineContext;

import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

/**
* Nacos request context.
*
* @author xiweng.yy
*/
public class RequestContext {

/**
* Optional, the request id.
* <ul>
* <li>For HTTP request, the id not usage, will generate automatically.</li>
* <li>For GRPC, the id is same with real request id.</li>
* </ul>
*/
private String requestId;

/**
* Request start timestamp.
*/
private final long requestTimestamp;

private final BasicContext basicContext;

private final EngineContext engineContext;

private final AuthContext authContext;

private final Map<String, Object> extensionContexts;

RequestContext(long requestTimestamp) {
this.requestId = UUID.randomUUID().toString();
this.requestTimestamp = requestTimestamp;
this.basicContext = new BasicContext();
this.engineContext = new EngineContext();
this.authContext = new AuthContext();
this.extensionContexts = new HashMap<>(1);
}

public void setRequestId(String requestId) {
this.requestId = requestId;
}

public String getRequestId() {
return requestId;
}

public long getRequestTimestamp() {
return requestTimestamp;
}

public BasicContext getBasicContext() {
return basicContext;
}

public EngineContext getEngineContext() {
return engineContext;
}

public AuthContext getAuthContext() {
return authContext;
}

public Object getExtensionContext(String key) {
return extensionContexts.get(key);
}

public void addExtensionContext(String key, Object value) {
extensionContexts.put(key, value);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright 1999-2023 Alibaba Group Holding Ltd.
*
* Licensed 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 com.alibaba.nacos.core.context;

import java.util.function.Supplier;

/**
* Holder for request context for each worker thread.
*
* @author xiweng.yy
*/
public class RequestContextHolder {

private static final Supplier<RequestContext> REQUEST_CONTEXT_FACTORY = () -> {
long requestTimestamp = System.currentTimeMillis();
return new RequestContext(requestTimestamp);
};

private static final ThreadLocal<RequestContext> CONTEXT_HOLDER = ThreadLocal.withInitial(REQUEST_CONTEXT_FACTORY);

public static RequestContext getContext() {
return CONTEXT_HOLDER.get();
}

public static void removeContext() {
CONTEXT_HOLDER.remove();
}
}
Loading

0 comments on commit 2233e65

Please sign in to comment.