mirror of
https://github.com/alibaba/arthas.git
synced 2026-10-21 05:13:26 +00:00
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:
@@ -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;
|
||||
}
|
||||
|
||||
+81
-6
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user