HOWTO · Java
Java.Net.SocketException 수정: Java에서 권한 거부
이 자습서는 java.net.SocketException: Java의 권한 거부 오류를 보여줍니다.
이 페이지의 내용
java.net.SocketException: Permission denied는 운영 체제 또는 보안 계층이 소켓 작업을 거부했다는 뜻입니다. 이 메시지만으로는 어느 계층이 왜 거부했는지 알 수 없습니다. 먼저 스택 추적과 프로그램이 수행하려던 작업을 확인한 다음, 해당 작업에 적용되는 정책을 점검하세요. 서버의 바인딩 실패와 클라이언트의 연결 실패는 원인과 해결 방법이 다를 수 있습니다.
java.net.SocketException: Permission denied의 의미
Java 소켓 호출이 성공하려면 호스트 환경이 요청된 작업을 허용해야 합니다. 운영 체제 권한, 방화벽 또는 엔드포인트 보안 정책, 컨테이너나 클러스터 정책, 런타임 또는 라이브러리 제한 때문에 거부될 수 있습니다. 이는 조사해 볼 가능성이지, 예외 메시지가 확정하는 원인은 아닙니다.
이 메시지는 포트가 이미 사용 중일 때의 오류와도 다릅니다. 포트 충돌에는 보통 주소가 이미 사용 중이라는 별도의 메시지가 표시됩니다. 권한 거부는 작업이 거부됐다는 뜻일 뿐, 포트가 예약되어 있거나 네트워크가 연결을 거부했거나 Java 버전이 너무 오래됐다는 증거는 아닙니다. 여러 설정을 한꺼번에 바꾸면 실제 제한을 파악하기 어려워지므로 피하세요.
소켓 작업이 실패하는 위치 확인
먼저 프로그램이 연결을 기다리는 소켓을 열려는지, 다른 호스트에 연결하려는지 확인하세요. 서버는 로컬 주소와 포트를 사용하기 위해 bind를 호출합니다. 클라이언트는 대개 원격 엔드포인트에 연결하려고 connect를 호출합니다. 스택 추적에서 Net.bind, ServerSocketChannel.bind 같은 프레임이나 서버 커넥터의 시작 코드가 보이면 로컬 바인딩 시도일 가능성이 있습니다. 이는 어떤 작업이 실패했는지 보여 주는 단서이지, 특정 정책이나 원인을 입증하지는 않습니다.
예를 들어 Tomcat 시작 스택 추적에 Net.bind와 NioEndpoint.bind가 있다면 시작 과정에서 로컬 커넥터를 바인딩하려 했다는 뜻입니다. Tomcat에 설정된 주소와 포트를 확인하고, 실패 시각을 운영 체제 또는 컨테이너 로그와 대조하세요. 스택 추적이 클라이언트 연결을 가리킨다면 대상 호스트와 포트를 기록하고 아웃바운드 정책, 라우팅, 프록시 설정을 살펴보세요. TLS 설정 중 실패했다는 사실만으로 인증서가 소켓 거부의 원인이라고 볼 수는 없습니다. 인증서 설정을 바꾸기 전에 실패한 소켓 호출부터 확인하세요.
서버 바인딩 권한 오류 해결
서버가 바인딩하지 못하는 이유 중 하나는 번호가 낮은 포트에 제한이 걸려 있기 때문일 수 있습니다. 기준과 규칙은 운영 체제 및 설정에 따라 다르므로, 포트 번호가 1024 미만이라는 사실만으로 모든 호스트에서 이것이 원인이라고 단정할 수는 없습니다. 실제 커넥터 포트와 호스트의 권한 또는 기능(capability) 정책을 확인하세요. 개발용 또는 내부 서비스라면 권한이 필요 없는 포트를 선택하는 것이 가장 범위가 좁은 해결책인 경우가 많습니다. 이에 맞춰 클라이언트 URL이나 리버스 프록시 매핑도 변경하세요. 프로덕션 환경에서는 적절한 경우 플랫폼이 지원하는 서비스 설정이나 포트 포워딩을 사용하세요.
Tomcat이나 Java 프로세스 전체를 root 또는 Administrator 권한으로 상시 실행하지 마세요. 높은 권한을 부여하면 접근 범위가 넓어져 애플리케이션의 결함이 호스트 전체의 위험으로 이어질 수 있습니다. 권한이 필요한 리스너를 사용해야 한다면 운영 체제가 지원하는 서비스 관리자, capability 또는 프런트엔드 프록시를 필요한 권한만 부여하도록 제한해 설정하세요.
OpenShift나 다른 컨테이너 플랫폼에서는 개발자 컴퓨터에서 작동하는 포트가 워크로드의 보안 컨텍스트나 할당된 사용자 때문에 허용되지 않을 수 있습니다. 배포에 설정된 컨테이너 포트, 실제 런타임 사용자와 보안 컨텍스트를 확인하고 시작 시점의 플랫폼 이벤트와 파드 로그를 살펴보세요. 플랫폼 정책이 허용하는 포트와 ID를 사용하세요. 증거 없이 워크로드 정책을 완화하거나 클러스터 방화벽을 원인으로 단정하지 마세요.
방화벽 및 런타임 제한 확인
실패한 호출이 connect라면 Java 프로세스와 해당 대상에 적용되는 아웃바운드 방화벽 또는 엔드포인트 보안 규칙을 확인하세요. 바인딩 실패라면 로컬 바인딩 정책과 수신 소켓을 제한하는 애플리케이션 제어 규칙을 살펴보세요. 관련 거부 로그를 확인한 뒤 필요한 주소, 포트, 방향, 프로세스에 한정해 정책을 최소한으로 변경하도록 요청하거나 직접 변경하세요. 방화벽이나 바이러스 백신을 끄는 것은 안전한 진단 방법이 아닙니다. 해당 환경의 일반적인 관리 절차에 따라 로그를 확인하거나 범위가 좁은 임시 규칙을 사용하세요.
애플리케이션이 컨테이너, 샌드박스, 관리형 호스팅 서비스 또는 네트워킹을 제한하는 보안 정책 아래에서 실행되는지도 확인하세요. 실제 런타임 사용자, 기능, 환경, 정책을 정상 작동하는 배포 환경과 비교하세요. 타사 라이브러리에 자체 설정 요구 사항이 있을 수 있지만, 의존성을 바꾸기 전에 문서와 실패한 호출을 확인하세요. 지원 버전 사용이나 보안상의 이유로 Java를 업데이트하는 것은 적절할 수 있지만, 오류 메시지만으로 구버전 런타임이 거부의 원인이라고 판단할 수는 없습니다.
시스템 속성 -Djava.net.preferIPv4Stack=true는 IPv4/IPv6 스택 선택이 관련된 경우에 사용하는 Java 7 시대의 오래된 진단 옵션입니다. 바인딩이나 연결 권한을 부여하지 않으며 일반적인 해결책으로 취급해서는 안 됩니다. 런타임과 관찰된 주소 계열 동작을 고려했을 때 관련이 있는 경우에만 사용하세요. 이 옵션을 사용하려고 오래된 버전을 채택하기보다 지원되는 Java 런타임을 사용하세요.
수정 결과 확인
변경하기 전에 작업 종류, 로컬 또는 원격 주소, 포트, 런타임 ID, 관련 정책을 기록하세요. 원인에 맞는 변경을 하나 적용한 다음 같은 작업을 다시 수행하고 같은 로그를 확인하세요. 서버라면 의도한 주소와 포트에서 수신 중인지, 서비스가 정상적으로 시작되는지 확인하세요. 클라이언트라면 의도한 대상에 연결되고 원격 서비스가 연결을 수락하는지 확인하세요. 거부가 계속되면 처음 의심한 원인만으로는 충분하지 않은 것입니다. 더 광범위한 권한을 부여하는 대신 새로운 스택 추적과 거부 기록을 바탕으로 진단을 이어가세요.
다음 예제를 실행하세요.
이 섹션의 코드와 표시되는 레이블 또는 출력은 영어로 유지됩니다.
public class SocketBindCheck {
public static void main(String[] args) {
try (java.net.ServerSocket socket = new java.net.ServerSocket(0)) {
int assignedPort = socket.getLocalPort();
if (assignedPort > 0 && assignedPort <= 65535) {
System.out.println("Ephemeral port assigned");
} else {
System.out.println("Unexpected port assignment");
}
} catch (java.io.IOException problem) {
System.err.println("Socket could not open: " + problem);
System.exit(1);
}
}
}
Ephemeral port assigned
요청 포트 0은 운영 체제가 사용 가능한 임시 포트를 선택하도록 합니다. 이 검사는 할당된 포트가 유효한 TCP/UDP 포트 범위에 있는지 확인하며, try-with-resources 블록은 리스닝 소켓을 닫습니다. 성공적으로 실행되면 출력에 Ephemeral port assigned라는 영어 문구가 표시되고 상태 코드 0으로 종료됩니다. 소켓 설정에 실패하면 프로그램은 IOException을 출력하고 0이 아닌 상태 코드로 종료하므로 자동화 과정에서 성공과 설정 실패를 구분할 수 있습니다. 원인은 호스트 로그와 보안 정책을 확인해 파악하세요. 이 검사는 특정 포트 설정, 원격 연결, Tomcat 또는 OpenShift 정책을 테스트하지 않습니다.
범위를 벗어난 포트는 다른 경계 사례입니다. Java가 운영 체제에 바인딩을 요청하기 전에 이를 거부하므로 권한 정책을 진단하는 사례가 아닙니다. 다음 검사는 인수 단계에서 발생하는 실패를 잡아 정상 종료합니다.
public class SocketPortBoundaryCheck {
public static void main(String[] args) {
try {
new java.net.ServerSocket(65536).close();
System.out.println("Unexpectedly accepted port");
} catch (IllegalArgumentException problem) {
System.out.println("Port rejected before bind");
} catch (java.io.IOException problem) {
System.out.println("Socket setup failed");
}
}
}