Tag Archives: 웹서버

[Linux/SSL] 리눅스 서버에 Apache 2 설치 및 SSL 설정하기(mod_ssl, openssl)

세상에서 가장 많이 사용하고 있다는 아파치 웹서버에 SSL 설정을 하여 결과적으로 HTTPS를 사용할 수 있게 하는 방법에 대해 간략하게 정리해 보겠습니다. 아래는 개인적으로 자체 키를 생성해서 사용하는 방법을 적어볼 것이며 이것은 브라우저에서 접속시에 인증되지 않은 접속으로 경고가 뜨게 됩니다. 공인 인증 기관에서 키를 발급받을 경우 키 생성 부분만 발급 기관에서 요구하는 방법대로 수행을 하시면 됩니다.

1. Apache 웹서버 다운로드 및 설치

http://httpd.apache.org/ 에 방문하여 최신버전의 웹서버를 다운받습니다. 2.2.17 버전을 예로 들어 설명해 보겠습니다. 다운받은 파일을 적절한 위치로 이동후에 다음과 같은 명령어로 SSL을 활성화 시켜줍니다.

$ ./configure --prefix=/usr/local/apache2 --enable-ssl --enable-so
$ make
$ make install

위의 설명에서는 아파치 웹서버를 기본적으로 /usr/local/apache2 위치에 설치하는것으로 하였습니다.

2. SSL 설정하기

위와 같이 설치할 경우 아파치 웹서버의 설정 파일인 httpd.conf 파일은 /usr/local/apache2/conf 에 위치하게 됩니다. /usr/local/apache2/conf/httpd.conf 설정 파일을 열어 httpd-ssl.conf 추가 설정의 주석을 해제해 줍니다.

$ vi /usr/local/apache2/conf/httpd.conf

Include conf/extra/httpd-ssl.conf

3. server.crt 및 server.key 생성하기

우선 openssl을 이용하여 server.key를 생성합니다. 계정의 루트 디렉토리에서 작업을 하도록 하겠습니다.

$ cd ~
$ openssl genrsa -des3 -out server.key 1024

위의 명령을 수행할 경우 암호를 물어보게 됩니다. 여기서 설정하는 암호를 잘 기억해 두셔야 합니다. 나중에 아파치 실행시에 물어봅니다. 여기서 암호를 지정하지 않을경우 아파치 실행시에 암호를 물어오지 않습니다.

이번에는 위에서 생성한 server.key파일을 이용하여 인증요청파일을 생성합니다.

$ openssl req -new -key server.key -out server.csr

마지막으로 위에서 생성한 server.key 및 server.csr파일을 이용하여 자체적으로 서명을 한 server.crt파일을 생성합니다.

$ openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crt

좀더 자세한 정보를 원할 경우 [다음]을 참고하도록 합시다.

이제 만들어진 인증키 파일을 아파치 설정 디렉토리로 옮기도록 합니다. 이 디렉토리는 적당한 위치를 사용하시면 됩니다.

$ cd ~
$ cp server.key /usr/local/apache2/conf/
$ cp server.crt /usr/local/apache2/conf/

4. SSL이 적용된 Apache 웹서버 구동하기

이제 설정이 끝난 아파치를 구동하도록 합시다.

$ /usr/local/apache2/bin/apachectl start

실행시에 위의 server.key를 생성할때 물어보았던 개인키 암호를 물어보게 되는데 입력해 주도록 합시다.

Apache/2.2.17 mod_ssl/2.2.17 (Pass Phrase Dialog)
Some of your private key files are encrypted for security reasons.
In order to read them you have to provide the pass phrases.

Server www.example.com:443 (RSA)
Enter pass phrase:

OK: Pass Phrase Dialog successful.

5. 가상호스트(VirtualHost) 적용하기

필요한 경우 하나의 서버에 다수의 가상호스트를 적용해야 할 수도 있습니다. 이경우 다음과 같은 가상 호스트 설정을 사용하시면 됩니다. 물론 다수의 키를 사용해도 되고 중복 사용해도 별 문제는 없습니다.

<VirtualHost *:443>
    ServerAdmin eye@example.com
    DocumentRoot /usr/local/apache2/htdocs
    ServerName example.com:443

    ...

    SSLEngine on
    SSLCipherSuite ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP:+eNULL
    SSLCertificateFile "/usr/local/apache2/conf/server.crt"
    SSLCertificateKeyFile "/usr/local/apache2/conf/server.key"
</VirtualHost>

* CC 인증을 위한 키 생성

CC 인증을 받기 위해 SSL키 설정시 제약 사항이 추가되게 됩니다. 가령 다음과 같습니다.

  • 암호알고리즘으로 AES128 이상
  • 무결성알고리즘으로 SHA-2 (SHA256)
  • 키 알고리즘(RSA 1024이상)

이 경우 다음과 같이 키를 생성하시면 됩니다.

$ openssl genrsa -aes128 -out server.key 1024 
$ openssl req -new -key server.key -out server.csr
$ openssl x509 -sha256 -req -days 365 -in server.csr -signkey server.key -out server.crt

참고 : http://www.thegeekstuff.com/2011/03/install-apache2-ssl/

[Java] 간단한 멀티쓰레드 웹서버 구현하기

학교 과제로 냈었던-_-a Java로 구현한 멀티쓰레드 웹서버입니다. Java로 구현할 수 있는 가장 기본적인 형태의 소켓 프로그래밍을 이용하여 구현하였습니다. 그냥 요청 받으면 해당 파일을 읽어서 내용을 보내는 단순한 어플리케이션입니다. Thread safe한 설계는 하지 않았습니다. 그냥 공부용으로만 사용해주세요.

* WebServer.java

class WebServer
{
    public static void main(String argv[]) throws Exception
    {
        // 서버소켓을 생성한다. 웹서버는 기본적으로 80번 포트를 사용한다.
        ServerSocket listenSocket = new ServerSocket(80);
        System.out.println("WebServer Socket Created");

        Socket connectionSocket;
        ServerThread serverThread;

        // 순환을 돌면서 클라이언트의 접속을 받는다.
        // accept()는 Blocking 메서드이다.
        while((connectionSocket = listenSocket.accept()) != null)
        {
            // 서버 쓰레드를 생성하여 실행한다.
            serverThread = new ServerThread(connectionSocket);
            serverThread.start();
        }
    }
}&nbsp;

* ServerThread.java

public class ServerThread extends Thread
{
  // 파일 요청이 없을 경우의 기본 파일
  private static final String DEFAULT_FILE_PATH = "index.html";

  // 클라이언트와의 접속 소켓
  private Socket connectionSocket;

  /**
   * <pre>
   * 기본 생성자
   * </pre>
   * 
   * @param connectionSocket 클라이언트와의 통신을 위한 소켓
   */
  public ServerThread(Socket connectionSocket)
  {
    this.connectionSocket = connectionSocket;
  }

  /* (non-Javadoc)
   * @see java.lang.Thread#run()
   */
  @Override
  public void run()
  {
    System.out.println("WebServer Thread Created");
    BufferedReader inFromClient = null;
    DataOutputStream outToClient = null;

    try
    {
      // 클라이언트와 통신을 위한 입/출력 2개의 스트림을 생성한다.
      inFromClient = new BufferedReader(

            new InputStreamReader(connectionSocket.getInputStream()));
      outToClient = new DataOutputStream(
            connectionSocket.getOutputStream());

      // 클라이언트로의 메시지중 첫번째 줄을 읽어들인다.
      String requestMessageLine = inFromClient.readLine();

      // 파싱을 위한 토큰을 생성한다.
      StringTokenizer tokenizedLine = new StringTokenizer(
            requestMessageLine);

      // 첫번째 토큰이 GET으로 시작하는가? ex) GET /green.jpg
      if(tokenizedLine.nextToken().equals("GET"))
      {
        // 다음의 토큰은 파일명이다.
        String fileName = tokenizedLine.nextToken();

        // 기본적으로 루트(/)로부터 주소가 시작하므로 제거한다.
        if(fileName.startsWith("/") == true)
        {
          if(fileName.length() > 1)
          {
            fileName = fileName.substring(1);
          }
          // 파일명을 따로 입력하지 않았을 경우 기본 파일을 출력한다.
          else
          {
            fileName = DEFAULT_FILE_PATH;
          }
        }

        File file = new File(fileName);

        // 요청한 파일이 존재하는가?
        if(file.exists())
        {
          // 존재하는 파일의 MIME타입을 분석한다.
          String mimeType = new MimetypesFileTypeMap()
            .getContentType(file);

          // 파일의 바이트수를 찾아온다.
          int numOfBytes = (int) file.length();

          // 파일을 스트림을 읽어들일 준비를 한다.
          FileInputStream inFile = new FileInputStream(fileName);
          byte[] fileInBytes = new byte[numOfBytes];
          inFile.read(fileInBytes);

          // 정상적으로 처리가 되었음을 나타내는 200 코드를 출력한다.
          outToClient.writeBytes("HTTP/1.0 200 Document Follows \r\n");
          outToClient.writeBytes("Content-Type: " + mimeType + "\r\n");

          // 출력할 컨텐츠의 길이를 출력
          outToClient.writeBytes("Content-Length: " + numOfBytes + "\r\n");
          outToClient.writeBytes("\r\n");

          // 요청 파일을 출력한다.
          outToClient.write(fileInBytes, 0, numOfBytes);
        }
        else
        {
          // 파일이 존재하지 않는다는 에러인 404 에러를 출력하고 접속을 종료한다.
          System.out.println("Requested File Not Found : " + fileName);

          outToClient.writeBytes("HTTP/1.0 404 Not Found \r\n");
          outToClient.writeBytes("Connection: close\r\n");
          outToClient.writeBytes("\r\n");
        }
      }
      else
      {
        // 잘못된 요청임을 나타내는 400 에러를 출력하고 접속을 종료한다.
        System.out.println("Bad Request");

        outToClient.writeBytes("HTTP/1.0 400 Bad Request Message \r\n");
        outToClient.writeBytes("Connection: close\r\n");
        outToClient.writeBytes("\r\n");
      }

      connectionSocket.close();
      System.out.println("Connection Closed");
    }
    // 예외 처리
    catch(IOException ioe)
    {
      ioe.printStackTrace();
    }
  }
}

* index.html

<html>
<head>
<title>웹서버 테스트</title>
</head>
<body>
<p>http://theeye.pe.kr</p>
<img src="sooji.jpg" />
</body>
</html>

 

사용자 삽입 이미지

테스트를 해보니 정상적으로 파일을 전송하고 해당 html에 딸려있는 객체들역시 정상적으로 전송됨을 알 수 있습니다. 잘 되네요~^^b

[샘플코드 다운로드]