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;
        }
    }
}

引用的其他类