JavaRush /Java Blog /Random-KO /HTTP에서 HTTPS로
Viacheslav
레벨 3

HTTP에서 HTTPS로

Random-KO 그룹에 게시되었습니다
HTTP에서 HTTPS로 - 1
콘텐츠:

소개

현대 사회에서는 웹 애플리케이션 없이는 살 수 없습니다. 그리고 작은 실험부터 시작하겠습니다. 어렸을 때 나는 모든 노점에서 "논쟁과 사실"과 같은 신문을 어떻게 팔았는지 기억합니다. 어린 시절부터 개인적인 인식에 따르면이 신문은 항상 이상해 보였기 때문에 기억했습니다. 그리고 나는 그들의 웹사이트를 방문해야 할지 결정했습니다.
HTTP에서 HTTPS로 - 2
Google Chrome 도움말로 이동하면 이 사이트는 보안 연결을 사용하지 않으며 귀하가 사이트와 교환하는 정보는 제3자가 액세스할 수 있다는 내용을 읽을 수 있습니다. 전자 매체인 Fontanka의 상트페테르부르크 뉴스와 같은 다른 뉴스도 확인해 보겠습니다.
HTTP에서 HTTPS로 - 3
보시다시피, 이러한 데이터에 따르면 Fontanka 웹사이트에는 보안 문제가 없습니다. 웹 리소스는 안전할 수도 있고 안전하지 않을 수도 있다는 것이 밝혀졌습니다. 또한 보호되지 않은 리소스에 대한 액세스가 HTTP 프로토콜을 통해 발생한다는 것을 알 수 있습니다. 그리고 리소스가 보호되면 데이터 교환은 HTTPS 프로토콜을 사용하여 수행됩니다. 여기서 끝에 있는 S는 "보안"을 의미합니다. HTTPS 프로토콜은 rfc2818 사양: " HTTP Over TLS "에 설명되어 있습니다. 우리만의 웹 애플리케이션을 만들고 그것이 어떻게 작동하는지 직접 확인해 봅시다. 그리고 그 과정에서 우리는 용어를 이해하게 될 것입니다.
HTTP에서 HTTPS로 - 4

Java의 웹 애플리케이션

따라서 우리는 Java로 매우 간단한 웹 애플리케이션을 만들어야 합니다. 먼저 Java 애플리케이션 자체가 필요합니다. 이를 위해 Gradle 프로젝트의 자동 빌드 시스템을 사용하겠습니다. 이를 통해 필요한 디렉토리 구조를 수동으로 생성할 필요가 없습니다. + Gradle은 프로젝트에 필요한 모든 라이브러리를 관리하고 코드 실행 시 해당 라이브러리가 사용 가능한지 확인합니다. 간단한 리뷰인 " A Brief Introduction to Gradle "에서 Gradle에 대해 자세히 알아볼 수 있습니다. Gradle Init Plugin을 사용 하고 다음 명령을 실행해 보겠습니다.
gradle init --type java-application
그런 다음 build.gradleGradle이 우리에게 제공할 프로젝트가 어떤 라이브러리로 구성되어 있는지 설명하는 빌드 스크립트를 열어 보겠습니다. 실험할 웹 서버에 대한 종속성을 추가해 보겠습니다.
dependencies {
    // Web server
    implementation 'io.undertow:undertow-core:2.0.20.Final'
     // Use JUnit test framework
     testImplementation 'junit:junit:4.12'
}
웹 애플리케이션이 작동하려면 애플리케이션이 호스팅될 웹 서버가 반드시 필요합니다. 매우 다양한 웹 서버가 있지만 주요 서버는 Tomcat, Jetty, Undertow입니다. 이번에는 Undertow를 선택하겠습니다. 이 웹 서버로 작업하는 방법을 이해하려면 공식 Undertow 웹사이트로 이동하여 문서 섹션 으로 이동하세요 . 귀하와 나는 Undertow Core에 대한 종속성을 연결했으므로 바로 이 Core , 즉 웹 서버의 기초인 핵심 에 관한 섹션에 관심이 있습니다 . 가장 쉬운 방법은 Undertow용 Builder API를 사용하는 것입니다.
public static void main(String[] args) {
	Undertow server = Undertow.builder()
            .addHttpListener(8080, "localhost")
            .setHandler(new HttpHandler() {
                @Override
                public void handleRequest(final HttpServerExchange exchange) throws Exception {
                    exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/plain");
                    exchange.getResponseSender().send("Hello World");
                }
            }).build();
    server.start();
}
코드를 실행하면 다음 웹 리소스로 이동할 수 있습니다.
HTTP에서 HTTPS로 - 5
그것은 간단하게 작동합니다. Undertow Builder API 덕분에 우리는 로컬 호스트와 포트 8080에 HTTP 리스너를 추가했습니다. 이 리스너는 웹 브라우저로부터 요청을 수신하고 응답으로 "Hello World" 문자열을 반환합니다. 훌륭한 웹 애플리케이션. 그러나 보시다시피 우리는 HTTP 프로토콜을 사용합니다. 이러한 유형의 데이터 교환은 안전하지 않습니다. HTTPS 프로토콜을 사용하여 교환이 어떻게 수행되는지 알아 보겠습니다.
HTTP에서 HTTPS로 - 6

HTTPS 요구 사항

HTTPS를 활성화하는 방법을 이해하기 위해 HTTPS 사양인 " RFC-2818: HTTP Over TLS " 로 돌아가 보겠습니다 . 사양에 따르면 HTTPS 프로토콜의 데이터는 SSL 또는 TLS 암호화 프로토콜을 통해 전송됩니다. 사람들은 종종 SSL과 TLS의 개념에 오해를 받습니다. 실제로 SSL은 버전이 발전하고 변경되었습니다. 나중에 TLS는 SSL 프로토콜 개발의 다음 단계가 되었습니다. 즉, TLS는 단순히 SSL의 새로운 버전입니다. 사양에는 "SSL 및 그 후속 TLS"라고 나와 있습니다. 그래서 SSL/TLS 암호화 프로토콜이 있다는 것을 알게 되었습니다. SSL 은 Secure Sockets Layer의 약어이며 "보안 소켓 계층"으로 번역됩니다. 영어로 번역된 소켓은 커넥터입니다. 네트워크를 통한 데이터 전송 참가자는 소켓을 프로그래밍 인터페이스(즉, API)로 사용하여 네트워크를 통해 서로 통신합니다. 브라우저는 클라이언트 역할을 하며 클라이언트 소켓을 사용하고, 요청을 받고 응답을 보내는 서버는 서버 소켓을 사용합니다. 그리고 이 소켓들 사이에서 데이터 교환이 발생합니다. 이것이 바로 프로토콜이 원래 SSL이라고 불리는 이유입니다. 그러나 시간이 흐르고 프로토콜이 발전했습니다. 그리고 어느 시점에서 SSL 프로토콜은 TLS 프로토콜이 되었습니다. TLS는 Transport Layer Security의 약자입니다. TLS 프로토콜은 SSL 프로토콜 사양 버전 3.0을 기반으로 합니다. TLS 프로토콜은 별도의 기사와 리뷰의 주제이므로 흥미롭다고 생각되는 자료만 표시하겠습니다. 즉, HTTPS의 기본은 TLS 핸드셰이크와 디지털 인증서를 사용한 "서버 ID" 확인(즉, 서버 식별)입니다. 그건 중요해. 이것을 기억하자 왜냐하면... 이 사실은 나중에 다시 다루겠습니다. 그래서 앞서 우리는 HttpListener를 사용하여 서버에 HTTP 프로토콜을 통해 작동하는 방법을 알려주었습니다. 위의 예에서 HTTP를 통해 작업하기 위해 HttpListener를 추가한 경우 HTTPS를 통해 작업하려면 HttpsListener를 추가해야 합니다.
HTTP에서 HTTPS로 - 7
그러나 이를 추가하려면 SSLContext가 필요합니다. 흥미롭게도 SSLContext는 Undertow의 클래스가 아니지만 javax.net.ssl.SSLContext. SSLContext 클래스는 인터넷 연결 보안을 보장하기 위한 Java 확장인 "JSSE( Java Secure Socket Extension )"의 일부입니다. 이 확장은 " JSSE(Java Secure Socket Extension) 참조 안내서 "에 설명되어 있습니다. 문서의 소개 부분에서 볼 수 있듯이 JSSE는 SSL 및 TLS 프로토콜의 프레임워크와 Java 구현을 제공합니다. SSLContext를 어떻게 얻나요? JavaDoc SSLContext를 열고 getInstance 메소드를 찾으십시오 . 보시다시피 SSLContext를 얻으려면 "Secure Socket Protocol"이라는 이름을 지정해야 합니다. 매개변수에 대한 설명은 이러한 이름이 " Java 암호화 아키텍처 표준 알고리즘 이름 문서 "에서 찾을 수 있음을 나타냅니다. 따라서 지침을 따르고 문서로 이동하겠습니다. 그리고 SSL과 TLS 중에서 선택할 수 있음을 알 수 있습니다.
HTTP에서 HTTPS로 - 8
이제 우리는 다음과 같이 SSLContext를 생성해야 한다는 것을 이해합니다.
public SSLContext getSSLContext() {
	// 1. Получаем контекст, в рамках которого будем работать по TLS протоколу
	SSLContext context = null;
	try {
		context = SSLContext.getInstance("TLS");
	} catch (NoSuchAlgorithmException e) {
		throw new IllegalStateException(e);
	}
	return context;
}
새 컨텍스트를 생성한 후 SSLContext가 " JSSE(Java Secure Socket Extension) 참조 가이드 "에 설명되어 있음을 기억합니다. “새로 생성된 SSLContext는 init 메소드를 호출하여 초기화되어야 합니다”를 읽고 확인합니다. 즉, 컨텍스트를 만드는 것만으로는 충분하지 않습니다. 초기화가 필요합니다. 그리고 이것은 논리적입니다. 왜냐하면 보안에 관해서는 TLS 프로토콜을 사용하고 싶다고만 말씀드렸습니다. SSLContext를 초기화하려면 KeyManager, TrustManager, SecureRandom의 세 가지를 제공해야 합니다.
HTTP에서 HTTPS로 - 9

키매니저

KeyManager 는 키 관리자입니다. 그는 우리에게 연락하는 사람에게 제공할 "인증 자격 증명"에 대한 책임이 있습니다. 자격 증명은 ID로 번역될 수 있습니다. 클라이언트가 서버가 자신이 주장하는 사람이고 신뢰할 수 있는지 확인하려면 ID가 필요합니다. 신분증은 무엇으로 사용되나요? 우리가 기억하는 것처럼 서버 ID는 서버의 디지털 인증서로 확인됩니다. 이 프로세스는 다음과 같이 표현될 수 있습니다.
HTTP에서 HTTPS로 - 10
또한 " JSSE 참조 가이드: SSL 작동 방식 "에서는 SSL이 "비대칭 암호화"를 사용한다고 나와 있는데, 이는 공개 키와 개인 키라는 키 쌍이 필요하다는 의미입니다. 암호화에 대해 이야기하고 있으므로 JCA(Java Cryptography Architecture)가 작동합니다. Oracle은 이 아키텍처에 대한 훌륭한 문서인 " JCA(Java Cryptography Architecture) 참조 가이드 "를 제공합니다. 또한 JavaRush에서 JCA에 대한 간략한 개요를 읽을 수 있습니다. " Java Cryptography Architecture: 첫 만남 ." 따라서 KeyManager를 초기화하려면 서버 인증서를 저장할 KeyStore가 필요합니다. 키 및 인증서 저장소를 생성하는 가장 일반적인 방법은 JDK에 포함된 keytool 유틸리티입니다. JSSE 문서 " JSSE와 함께 사용할 키 저장소 만들기 "에서 예제를 볼 수 있습니다. 따라서 KeyTool 유틸리티를 사용하여 키 저장소를 생성하고 거기에 인증서를 작성해야 합니다. 흥미롭게도 이전에는 -genkey를 사용하여 키 생성을 지정했지만 이제는 -genkeypair를 사용하는 것이 권장됩니다. 우리는 다음 사항을 정의해야 합니다:
  • alias : 별칭 또는 단순히 키 저장소에 항목이 저장될 이름
  • keyalg : 키 암호화 알고리즘입니다. 본질적으로 우리 목적에 맞는 표준 솔루션인 RSA 알고리즘을 선택해 보겠습니다.
  • keysize : 키 크기(비트 단위)입니다. 최소 권장 크기는 2048입니다. 왜냐하면... 더 작은 크기는 이미 깨졌습니다. 여기에서 자세한 내용을 읽을 수 있습니다: " 2048 비트의 SSL 인증서 ".
  • dname : 고유 이름, 고유 이름입니다.
요청된 리소스(예: https://localhost)가 해당 리소스와 비교된다는 점을 이해하는 것이 중요합니다. 이것을 "주체 CN 매칭"이라고 합니다.
  • 유효성 : 생성된 인증서가 유효한 기간(예: 유효한.
  • ext : " Named Extensions "에 지정된 인증서 확장입니다.
자체 서명된 인증서(즉, 독립적으로 생성된 인증서)의 경우 다음 확장자를 지정해야 합니다.
  • -ext san:tical=dns:localhost,ip:127.0.0.1 > SubjectAlternativeName을 기준으로 제목 일치를 수행합니다.
  • -ext bc=ca:false > 이 인증서가 다른 인증서에 서명하는 데 사용되지 않음을 나타냅니다.
다음 명령을 실행해 보겠습니다(Windows OS의 예).
keytool -genkeypair -alias ssl -keyalg RSA -keysize 2048 -dname "CN=localhost,OU=IT,O=Javarush,L=SaintPetersburg,C=RU,email=contact@email.com" -validity 90 -keystore C:/keystore.jks -storepass passw0rd -keypass passw0rd -ext san:critical=dns:localhost,ip:127.0.0.1 -ext bc=ca:false
왜냐하면 파일이 생성되면 파일을 생성할 수 있는 모든 권한이 있는지 확인하세요. 다음과 같은 조언도 볼 수 있습니다.
HTTP에서 HTTPS로 - 11
여기서는 JKS가 독점 형식이라는 것을 알 수 있습니다. 독점이란 작성자의 사유 재산이며 Java에서만 사용하도록 의도되었음을 의미합니다. 타사 유틸리티를 사용할 때 충돌이 발생할 수 있으므로 경고를 받습니다. 또한 다음과 같은 오류가 발생할 수 있습니다 The destination pkcs12 keystore has different storepass and keypass. 이 오류는 키 저장소 항목의 비밀번호와 키 저장소 자체의 비밀번호가 다르기 때문에 발생합니다. keytool 문서에 따르면 "예를 들어 대부분의 타사 도구는 PKCS #12 키 저장소의 storepass와 keypass가 동일해야 합니다." 키를 직접 지정할 수 있습니다(예: -destkeypassentrypassw). 하지만 요구 사항을 위반하지 않고 동일한 비밀번호를 설정하는 것이 좋습니다. 따라서 가져오기는 다음과 같습니다.
keytool -importkeystore -srckeystore C:/keystore.jks -destkeystore C:/keystore.jks -deststoretype pkcs12
성공 사례:
HTTP에서 HTTPS로 - 12
인증서를 파일로 내보내려면 다음을 실행할 수 있습니다.
keytool -export -alias ssl -storepass passw0rd -file C:/server.cer -keystore C:/keystore.jks
또한 다음과 같은 Keystore 콘텐츠를 얻을 수 있습니다.
keytool -list -v -keystore C:/keystore.jks -storepass passw0rd
좋습니다. 이제 인증서가 포함된 키 저장소가 생겼습니다. 이제 코드에서 가져올 수 있습니다.
public KeyStore getKeyStore() {
	// Согласно https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#KeyStore
	try(FileInputStream fis = new FileInputStream("C:/keystore.jks")){
		KeyStore keyStore = KeyStore.getInstance("pkcs12");
		keyStore.load(fis, "passw0rd".toCharArray());
		return keyStore;
	} catch (IOException ioe) {
		throw new IllegalStateException(ioe);
	} catch (KeyStoreException | NoSuchAlgorithmException | CertificateException e) {
		throw new IllegalStateException(e);
	}
}
KeyStore가 있으면 KeyManager를 초기화할 수 있습니다.
public KeyManager[] getKeyManagers(KeyStore keyStore) {
	String keyManagerAlgo = KeyManagerFactory.getDefaultAlgorithm();
	KeyManagerFactory keyManagerFactory = null;
	try {
		keyManagerFactory = KeyManagerFactory.getInstance(keyManagerAlgo);
		keyManagerFactory.init(keyStore, "passw0rd".toCharArray());
		return keyManagerFactory.getKeyManagers();
	} catch (NoSuchAlgorithmException e) {
		throw new IllegalStateException(e);
	} catch (UnrecoverableKeyException | KeyStoreException e) {
		throw new IllegalStateException(e);
	}
}
우리의 첫 번째 목표가 달성되었습니다. TrustManager가 무엇인지 알아내는 것이 남아 있습니다. TrustManager는 JSSE 문서의 " TrustManager 인터페이스 " 섹션에 설명되어 있습니다 . KeyManager와 매우 유사하지만 연결을 요청하는 사람을 신뢰할 수 있는지 확인하는 것이 목적입니다. 직설적으로 말하면 이것은 반대의 KeyManager입니다. =) TrustManager는 필요하지 않으므로 null을 전달하겠습니다. 그러면 서버에 요청하는 최종 사용자를 확인하지 않는 기본 TrustManager가 생성됩니다. 문서에는 "기본 구현이 사용됩니다"라고 나와 있습니다. SecureRandom과 동일합니다. null을 지정하면 기본 구현이 사용됩니다. SecureRandom은 JCA 클래스이고 JCA 문서의 " The SecureRandom Class " 섹션에 설명되어 있다는 점을 기억해두세요 . 위에서 설명한 모든 방법을 고려한 준비는 전체적으로 다음과 같습니다.
public static void main(String[] args) {
	// 1. Подготавливаем приложение к работе по HTTPS
	App app = new App();
	SSLContext sslContext = app.getSSLContext();
	KeyStore keyStore = app.getKeyStore();
	KeyManager[] keyManagers = app.getKeyManagers(keyStore);
	try {
		sslContext.init(keyManagers, null, null);
	} catch (KeyManagementException e) {
		throw new IllegalStateException(e);
	}
남은 것은 서버를 시작하는 것뿐입니다.
// 2. Поднимаем server
 	int httpsPort = 443;
	Undertow server = Undertow.builder()
            .addHttpsListener(httpsPort, "localhost", sslContext)
            .setHandler(new HttpHandler() {
                @Override
                public void handleRequest(final HttpServerExchange exchange) throws Exception {
                    exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/plain");
                    exchange.getResponseSender().send("Hello World");
                }
            }).build();
	server.start();
}
이번에는 다음 주소에서 서버를 사용할 수 있습니다. https://localhost:443 그러나 이 리소스를 신뢰할 수 없다는 오류가 계속 표시됩니다.
HTTP에서 HTTPS로 - 13
인증서에 어떤 문제가 있고 어떻게 해야 하는지 알아봅시다.
HTTP에서 HTTPS로 - 14

인증서 관리

따라서 우리 서버는 이미 HTTPS를 통해 작동할 준비가 되어 있지만 클라이언트는 이를 신뢰하지 않습니다. 왜? 살펴보자:
HTTP에서 HTTPS로 - 15
그 이유는 이 인증서가 자체 서명된 인증서이기 때문입니다. 자체 서명 SSL 인증서는 식별되는 동일한 사람이 발급하고 서명한 공개 키 인증서를 의미합니다. 즉, 존경받는 인증 기관(CA, 인증 기관이라고도 함)에서 발급되지 않았습니다. 인증 기관은 수탁자 역할을 하며 일상 생활에서 공증인과 유사합니다. 그는 자신이 발급한 인증서가 신뢰할 수 있다고 확신합니다. 해당 CA에서 인증서를 발급하는 서비스는 유료이므로 누구도 신뢰 상실이나 평판 위험을 겪을 필요가 없습니다. 기본적으로 신뢰할 수 있는 여러 인증 기관이 있습니다. 이 목록은 편집 가능합니다. 그리고 각 운영 체제에는 인증 기관 목록에 대한 자체 관리 기능이 있습니다. 예를 들어 Windows에서 이 목록을 관리하는 방법은 " Windows에서 신뢰할 수 있는 루트 인증서 관리 " 에서 읽을 수 있습니다 . 오류 메시지에 표시된 대로 신뢰할 수 있는 인증서에 인증서를 추가해 보겠습니다. 이렇게 하려면 먼저 인증서를 다운로드하세요.
HTTP에서 HTTPS로 - 16
OS Windows에서는 Win+R을 누르고 실행하여 mmc제어 콘솔을 호출합니다. 그런 다음 Ctrl+M을 눌러 "인증서" 섹션을 현재 콘솔에 추가합니다. 다음으로 "신뢰할 수 있는 루트 인증 기관" 하위 섹션에서 Действия / Все задачи / Импорт. 앞서 다운로드한 파일을 해당 파일로 가져와 보겠습니다. 브라우저가 인증서의 과거 신뢰 상태를 기억했을 수 있습니다. 따라서 페이지를 열기 전에 브라우저를 다시 시작해야 합니다. 예를 들어 Google Chrome의 주소 표시줄에서 를 실행해야 합니다 chrome://restart. OS Windows에서는 유틸리티를 사용하여 인증서를 볼 수도 있습니다 certmgr.msc.
HTTP에서 HTTPS로 - 17
모든 작업을 올바르게 수행했다면 HTTPS를 통해 서버에 대한 성공적인 호출이 표시됩니다.
HTTP에서 HTTPS로 - 18
보시다시피 이제 인증서는 유효한 것으로 간주되고 리소스를 사용할 수 있으며 오류가 없습니다.
HTTP에서 HTTPS로 - 19

결론

그래서 우리는 웹 서버에서 HTTPS 프로토콜을 활성화하는 체계가 무엇인지, 그리고 이에 필요한 것이 무엇인지 알아냈습니다. 암호화를 담당하는 JCA(Java Cryptography Architecture)와 Java 측에서 TLS 구현을 제공하는 JSSE(Java Secure Socket Extension)의 상호 작용을 통해 지원이 제공된다는 것이 이 시점에서 명확해지기를 바랍니다. JDK에 포함된 keytool 유틸리티를 사용하여 KeyStore 키 및 인증서 저장소를 사용하는 방법을 살펴보았습니다. 또한 우리는 HTTPS가 보안을 위해 SSL/TLS 프로토콜을 사용한다는 것을 깨달았습니다. 이를 강화하려면 이 주제에 대한 훌륭한 기사를 읽어 보시기 바랍니다. 이 간단한 검토 후에 HTTPS가 좀 더 투명해지기를 바랍니다. 그리고 HTTPS를 활성화해야 하는 경우 애플리케이션 서버 및 프레임워크 설명서에서 용어를 쉽게 이해할 수 있습니다. #비아체슬라프
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION