diff --git a/core/src/main/java/com/taobao/arthas/core/security/BearerPrincipal.java b/core/src/main/java/com/taobao/arthas/core/security/BearerPrincipal.java new file mode 100644 index 000000000..525e25c49 --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/security/BearerPrincipal.java @@ -0,0 +1,55 @@ +package com.taobao.arthas.core.security; + +import java.security.Principal; + +/** + * Bearer Token {@link Principal}. + */ +public final class BearerPrincipal implements Principal { + + private final String token; + + public BearerPrincipal(String token) { + this.token = token; + } + + @Override + public String getName() { + return "bearer"; + } + + public String getToken() { + return token; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((token == null) ? 0 : token.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + BearerPrincipal other = (BearerPrincipal) obj; + if (token == null) { + if (other.token != null) + return false; + } else if (!token.equals(other.token)) + return false; + return true; + } + + @Override + public String toString() { + // do not display the token for security reasons + return "BearerPrincipal[***]"; + } +} diff --git a/core/src/main/java/com/taobao/arthas/core/security/SecurityAuthenticatorImpl.java b/core/src/main/java/com/taobao/arthas/core/security/SecurityAuthenticatorImpl.java index 88b6f4425..b684a11c3 100644 --- a/core/src/main/java/com/taobao/arthas/core/security/SecurityAuthenticatorImpl.java +++ b/core/src/main/java/com/taobao/arthas/core/security/SecurityAuthenticatorImpl.java @@ -66,6 +66,13 @@ public class SecurityAuthenticatorImpl implements SecurityAuthenticator { return subject; } } + if (principal instanceof BearerPrincipal) { + BearerPrincipal bearerPrincipal = (BearerPrincipal) principal; + // Bearer Token认证:将token作为password进行验证 + if (bearerPrincipal.getToken().equals(this.password)) { + return subject; + } + } if (principal instanceof LocalConnectionPrincipal) { return subject; } diff --git a/core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/BasicHttpAuthenticatorHandler.java b/core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/BasicHttpAuthenticatorHandler.java index 0e288c551..238f297c2 100644 --- a/core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/BasicHttpAuthenticatorHandler.java +++ b/core/src/main/java/com/taobao/arthas/core/shell/term/impl/http/BasicHttpAuthenticatorHandler.java @@ -12,6 +12,7 @@ import com.alibaba.arthas.deps.org.slf4j.LoggerFactory; import com.taobao.arthas.common.ArthasConstants; import com.taobao.arthas.core.security.AuthUtils; import com.taobao.arthas.core.security.BasicPrincipal; +import com.taobao.arthas.core.security.BearerPrincipal; import com.taobao.arthas.core.security.SecurityAuthenticator; import com.taobao.arthas.core.server.ArthasBootstrap; import com.taobao.arthas.core.shell.term.impl.http.session.HttpSession; @@ -68,11 +69,15 @@ public final class BasicHttpAuthenticatorHandler extends ChannelDuplexHandler { Principal principal = null; if (!authed) { - // 判断请求header里是否带有 username/password - principal = extractBasicAuthSubject(httpRequest); - if (principal == null) { - // 判断 url里是否有 username/password - principal = extractBasicAuthSubjectFromUrl(httpRequest); + boolean isMcpRequest = isMcpRequest(httpRequest); + + if (isMcpRequest) { + principal = extractMcpAuthSubject(httpRequest); + } else { + principal = extractBasicAuthSubject(httpRequest); + if (principal == null) { + principal = extractBasicAuthSubjectFromUrl(httpRequest); + } } } if (!authed && principal == null) { @@ -90,7 +95,15 @@ public final class BasicHttpAuthenticatorHandler extends ChannelDuplexHandler { if (!authed) { // restricted resource, so send back 401 to require valid username/password HttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.UNAUTHORIZED); - response.headers().set(HttpHeaderNames.WWW_AUTHENTICATE, "Basic realm=\"arthas webconsole\""); + + boolean isMcpRequest = isMcpRequest(httpRequest); + if (isMcpRequest) { + response.headers().add(HttpHeaderNames.WWW_AUTHENTICATE, "Bearer realm=\"arthas mcp\"") + .add(HttpHeaderNames.WWW_AUTHENTICATE, "Basic realm=\"arthas mcp\""); + } else { + response.headers().set(HttpHeaderNames.WWW_AUTHENTICATE, "Basic realm=\"arthas webconsole\""); + } + response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain"); response.headers().set(HttpHeaderNames.CONTENT_LENGTH, 0); @@ -181,4 +194,66 @@ public final class BasicHttpAuthenticatorHandler extends ChannelDuplexHandler { return null; } + /** + * 判断是否为MCP请求 + * + * @param request + */ + protected static boolean isMcpRequest(HttpRequest request) { + try { + String path = new java.net.URI(request.uri()).getPath(); + return path != null && path.endsWith("/mcp"); + } catch (Exception e) { + logger.debug("Failed to parse request URI: {}", request.uri(), e); + return false; + } + } + + /** + * 为MCP请求提取认证主体,支持Bearer Token和Basic Auth两种方式 + * + * @param request + */ + protected static Principal extractMcpAuthSubject(HttpRequest request) { + // 首先尝试Bearer Token认证 + BearerPrincipal tokenPrincipal = extractBearerTokenSubject(request); + if (tokenPrincipal != null) { + return tokenPrincipal; + } + + // 然后尝试Basic Auth认证 + BasicPrincipal basicPrincipal = extractBasicAuthSubject(request); + if (basicPrincipal != null) { + return basicPrincipal; + } + + // 最后尝试从URL参数提取 + return extractBasicAuthSubjectFromUrl(request); + } + + /** + * 从Authorization header中提取Bearer Token + * + * @param request + */ + protected static BearerPrincipal extractBearerTokenSubject(HttpRequest request) { + String auth = request.headers().get(HttpHeaderNames.AUTHORIZATION); + if (auth != null) { + String constraint = StringUtils.before(auth, " "); + if (constraint != null) { + if ("Bearer".equalsIgnoreCase(constraint.trim())) { + String token = StringUtils.after(auth, " "); + if (token == null || token.trim().isEmpty()) { + logger.error("Extracted Bearer Token failed, bad auth String: {}", auth); + return null; + } + BearerPrincipal principal = new BearerPrincipal(token.trim()); + logger.debug("Extracted Bearer Token principal: {}", principal); + return principal; + } + } + } + return null; + } + }