AuthenticationHandler.java
net.minecraft.server.jsonrpc.security.AuthenticationHandler
信息
- 全限定名:net.minecraft.server.jsonrpc.security.AuthenticationHandler
- 类型:public class
- 包:net.minecraft.server.jsonrpc.security
- 源码路径:src/main/java/net/minecraft/server/jsonrpc/security/AuthenticationHandler.java
- 起始行号:L25
- 继承:ChannelDuplexHandler
- 职责:
TODO
字段/常量
-
LOGGER- 类型:
Logger - 修饰符:
private final - 源码定位:
L26 - 说明:
TODO
- 类型:
-
AUTHENTICATED_KEY- 类型:
AttributeKey<Boolean> - 修饰符:
private static final - 源码定位:
L27 - 说明:
TODO
- 类型:
-
ATTR_WEBSOCKET_ALLOWED- 类型:
AttributeKey<Boolean> - 修饰符:
private static final - 源码定位:
L28 - 说明:
TODO
- 类型:
-
SUBPROTOCOL_VALUE- 类型:
String - 修饰符:
private static final - 源码定位:
L29 - 说明:
TODO
- 类型:
-
SUBPROTOCOL_HEADER_PREFIX- 类型:
String - 修饰符:
private static final - 源码定位:
L30 - 说明:
TODO
- 类型:
-
BEARER_PREFIX- 类型:
String - 修饰符:
public static final - 源码定位:
L31 - 说明:
TODO
- 类型:
-
securityConfig- 类型:
SecurityConfig - 修饰符:
private final - 源码定位:
L32 - 说明:
TODO
- 类型:
-
allowedOrigins- 类型:
Set<String> - 修饰符:
private final - 源码定位:
L33 - 说明:
TODO
- 类型:
内部类/嵌套类型
net.minecraft.server.jsonrpc.security.AuthenticationHandler.SecurityCheckResult- 类型:
class - 修饰符:
private static - 源码定位:
L141 - 说明:
TODO
- 类型:
构造器
public AuthenticationHandler(SecurityConfig securityConfig, String allowedOrigins) @ L35
- 构造器名:AuthenticationHandler
- 源码定位:L35
- 修饰符:public
参数:
- securityConfig: SecurityConfig
- allowedOrigins: String
说明:
TODO
方法
下面的方法块按源码顺序生成。
public void channelRead(ChannelHandlerContext context, Object msg) @ L40
- 方法名:channelRead
- 源码定位:L40
- 返回类型:void
- 修饰符:public
参数:
- context: ChannelHandlerContext
- msg: Object
说明:
TODO
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) @ L67
- 方法名:write
- 源码定位:L67
- 返回类型:void
- 修饰符:public
参数:
- ctx: ChannelHandlerContext
- msg: Object
- promise: ChannelPromise
说明:
TODO
private AuthenticationHandler.SecurityCheckResult performSecurityChecks(HttpRequest request) @ L79
- 方法名:performSecurityChecks
- 源码定位:L79
- 返回类型:AuthenticationHandler.SecurityCheckResult
- 修饰符:private
参数:
- request: HttpRequest
说明:
TODO
private boolean isAllowedOriginHeader(HttpRequest request) @ L101
- 方法名:isAllowedOriginHeader
- 源码定位:L101
- 返回类型:boolean
- 修饰符:private
参数:
- request: HttpRequest
说明:
TODO
private String parseTokenInAuthorizationHeader(HttpRequest request) @ L106
- 方法名:parseTokenInAuthorizationHeader
- 源码定位:L106
- 返回类型:String
- 修饰符:private
参数:
- request: HttpRequest
说明:
TODO
private String parseTokenInSecWebsocketProtocolHeader(HttpRequest request) @ L111
- 方法名:parseTokenInSecWebsocketProtocolHeader
- 源码定位:L111
- 返回类型:String
- 修饰符:private
参数:
- request: HttpRequest
说明:
TODO
public boolean isValidApiKey(String suppliedKey) @ L116
- 方法名:isValidApiKey
- 源码定位:L116
- 返回类型:boolean
- 修饰符:public
参数:
- suppliedKey: String
说明:
TODO
private String getClientIp(ChannelHandlerContext context) @ L126
- 方法名:getClientIp
- 源码定位:L126
- 返回类型:String
- 修饰符:private
参数:
- context: ChannelHandlerContext
说明:
TODO
private void sendUnauthorizedResponse(ChannelHandlerContext context, String reason) @ L131
- 方法名:sendUnauthorizedResponse
- 源码定位:L131
- 返回类型:void
- 修饰符:private
参数:
- context: ChannelHandlerContext
- reason: String
说明:
TODO
代码
@Sharable
public class AuthenticationHandler extends ChannelDuplexHandler {
private final Logger LOGGER = LogUtils.getLogger();
private static final AttributeKey<Boolean> AUTHENTICATED_KEY = AttributeKey.valueOf("authenticated");
private static final AttributeKey<Boolean> ATTR_WEBSOCKET_ALLOWED = AttributeKey.valueOf("websocket_auth_allowed");
private static final String SUBPROTOCOL_VALUE = "minecraft-v1";
private static final String SUBPROTOCOL_HEADER_PREFIX = "minecraft-v1,";
public static final String BEARER_PREFIX = "Bearer ";
private final SecurityConfig securityConfig;
private final Set<String> allowedOrigins;
public AuthenticationHandler(SecurityConfig securityConfig, String allowedOrigins) {
this.securityConfig = securityConfig;
this.allowedOrigins = Sets.newHashSet(allowedOrigins.split(","));
}
@Override
public void channelRead(ChannelHandlerContext context, Object msg) throws Exception {
String clientIp = this.getClientIp(context);
if (msg instanceof HttpRequest request) {
AuthenticationHandler.SecurityCheckResult result = this.performSecurityChecks(request);
if (!result.isAllowed()) {
this.LOGGER.debug("Authentication rejected for connection with ip {}: {}", clientIp, result.getReason());
context.channel().attr(AUTHENTICATED_KEY).set(false);
this.sendUnauthorizedResponse(context, result.getReason());
return;
}
context.channel().attr(AUTHENTICATED_KEY).set(true);
if (result.isTokenSentInSecWebsocketProtocol()) {
context.channel().attr(ATTR_WEBSOCKET_ALLOWED).set(Boolean.TRUE);
}
}
Boolean isAuthenticated = context.channel().attr(AUTHENTICATED_KEY).get();
if (Boolean.TRUE.equals(isAuthenticated)) {
super.channelRead(context, msg);
} else {
this.LOGGER.debug("Dropping unauthenticated connection with ip {}", clientIp);
context.close();
}
}
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
if (msg instanceof HttpResponse response
&& response.status().code() == HttpResponseStatus.SWITCHING_PROTOCOLS.code()
&& ctx.channel().attr(ATTR_WEBSOCKET_ALLOWED).get() != null
&& ctx.channel().attr(ATTR_WEBSOCKET_ALLOWED).get().equals(Boolean.TRUE)) {
response.headers().set(HttpHeaderNames.SEC_WEBSOCKET_PROTOCOL, "minecraft-v1");
}
super.write(ctx, msg, promise);
}
private AuthenticationHandler.SecurityCheckResult performSecurityChecks(HttpRequest request) {
String tokenInAuthorizationHeader = this.parseTokenInAuthorizationHeader(request);
if (tokenInAuthorizationHeader != null) {
return this.isValidApiKey(tokenInAuthorizationHeader)
? AuthenticationHandler.SecurityCheckResult.allowed()
: AuthenticationHandler.SecurityCheckResult.denied("Invalid API key");
} else {
String tokenInSecWebsocketProtocolHeader = this.parseTokenInSecWebsocketProtocolHeader(request);
if (tokenInSecWebsocketProtocolHeader != null) {
if (!this.isAllowedOriginHeader(request)) {
return AuthenticationHandler.SecurityCheckResult.denied("Origin Not Allowed");
} else {
return this.isValidApiKey(tokenInSecWebsocketProtocolHeader)
? AuthenticationHandler.SecurityCheckResult.allowed(true)
: AuthenticationHandler.SecurityCheckResult.denied("Invalid API key");
}
} else {
return AuthenticationHandler.SecurityCheckResult.denied("Missing API key");
}
}
}
private boolean isAllowedOriginHeader(HttpRequest request) {
String originHeader = request.headers().get(HttpHeaderNames.ORIGIN);
return originHeader != null && !originHeader.isEmpty() ? this.allowedOrigins.contains(originHeader) : false;
}
private @Nullable String parseTokenInAuthorizationHeader(HttpRequest request) {
String authHeader = request.headers().get(HttpHeaderNames.AUTHORIZATION);
return authHeader != null && authHeader.startsWith("Bearer ") ? authHeader.substring("Bearer ".length()).trim() : null;
}
private @Nullable String parseTokenInSecWebsocketProtocolHeader(HttpRequest request) {
String authHeader = request.headers().get(HttpHeaderNames.SEC_WEBSOCKET_PROTOCOL);
return authHeader != null && authHeader.startsWith("minecraft-v1,") ? authHeader.substring("minecraft-v1,".length()).trim() : null;
}
public boolean isValidApiKey(String suppliedKey) {
if (suppliedKey.isEmpty()) {
return false;
} else {
byte[] suppliedKeyBytes = suppliedKey.getBytes(StandardCharsets.UTF_8);
byte[] configuredKeyBytes = this.securityConfig.secretKey().getBytes(StandardCharsets.UTF_8);
return MessageDigest.isEqual(suppliedKeyBytes, configuredKeyBytes);
}
}
private String getClientIp(ChannelHandlerContext context) {
InetSocketAddress remoteAddress = (InetSocketAddress)context.channel().remoteAddress();
return remoteAddress.getAddress().getHostAddress();
}
private void sendUnauthorizedResponse(ChannelHandlerContext context, String reason) {
String responseBody = "{\"error\":\"Unauthorized\",\"message\":\"" + reason + "\"}";
byte[] content = responseBody.getBytes(StandardCharsets.UTF_8);
DefaultFullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.UNAUTHORIZED, Unpooled.wrappedBuffer(content));
response.headers().set(HttpHeaderNames.CONTENT_TYPE, "application/json");
response.headers().set(HttpHeaderNames.CONTENT_LENGTH, content.length);
response.headers().set(HttpHeaderNames.CONNECTION, "close");
context.writeAndFlush(response).addListener(future -> context.close());
}
private static class SecurityCheckResult {
private final boolean allowed;
private final String reason;
private final boolean tokenSentInSecWebsocketProtocol;
private SecurityCheckResult(boolean allowed, String reason, boolean tokenSentInSecWebsocketProtocol) {
this.allowed = allowed;
this.reason = reason;
this.tokenSentInSecWebsocketProtocol = tokenSentInSecWebsocketProtocol;
}
public static AuthenticationHandler.SecurityCheckResult allowed() {
return new AuthenticationHandler.SecurityCheckResult(true, null, false);
}
public static AuthenticationHandler.SecurityCheckResult allowed(boolean tokenSentInSecWebsocketProtocol) {
return new AuthenticationHandler.SecurityCheckResult(true, null, tokenSentInSecWebsocketProtocol);
}
public static AuthenticationHandler.SecurityCheckResult denied(String reason) {
return new AuthenticationHandler.SecurityCheckResult(false, reason, false);
}
public boolean isAllowed() {
return this.allowed;
}
public String getReason() {
return this.reason;
}
public boolean isTokenSentInSecWebsocketProtocol() {
return this.tokenSentInSecWebsocketProtocol;
}
}
}引用的其他类
- SecurityConfig
- 引用位置:
参数/字段
- 引用位置: