feat: Add Bearer Token authentication support for MCP requests (#3059)

* feat: 为MCP请求添加Bearer Token认证支持
- MCP请求现在支持Bearer Token和Basic Auth两种认证方式
- 普通HTTP请求保持原有Basic Auth认证方式不变
This commit is contained in:
Zhang Yu
2025-09-12 13:05:20 +08:00
committed by GitHub
parent 2e0ae5ec9a
commit ea346ff55e
3 changed files with 143 additions and 6 deletions
@@ -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[***]";
}
}
@@ -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;
}
@@ -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;
}
}