2023. 8. 14. 15:30

웹팩 개발서버에 로컬 인증서(SSL) 설정하는 방법을 몰라서 한참 고생을....

정확하게는 로컬 인증서를 자동으로 생성하는 방법을 몰라서 고생한거긴 합니다.

 

문서를 좀더 잘읽었으면 삽질이 좀 줄긴 했을거 같은데 말이죠 ㅎㅎㅎㅎ

 

연관글 영역

 

 

1. 로컬 인증서 생성

'ASP.NET Core'의 'React'템플릿에 보면 다음과 같이 2개의 파일이 있습니다.

(참고 : github - dang-gun/AspDotNetSamples/WebpackSslTest/ClientApp/OriginalFils/ )

 

aspnetcore-https.js : 로컬 인증서를 생성해 주는 파일

aspnetcore-react.js : 생성된 경로를 '.env.development.local'에 넣어주는 파일

 

이 파일들을 실행하면 로컬인증서가 생성되고 '.env.development.local'파일에 경로가 저장됩니다.

 

이 두 개의 파일은 .NET과 관련 없이 독립적으로 동작하는 파일입니다.

하지만 자동 생성되는 경로가 'ASP.NET Core'의 경로로 지정됩니다.

 

자신에게 맞게 수정하면 'ASP.NET Core'과 상관없이 사용할 수 있습니다.

 

 

스크립트 실행

'aspnetcore-https.js'와 'aspnetcore-react.js'를 실행시키려면 'package.json'에 설정하면 됩니다.

  "scripts": {
    "prestart": "node aspnetcore-https && node aspnetcore-react",
    "start": "rimraf ./build && node scripts/start.js",
    "build": "node scripts/build.js",
    "test": "cross-env CI=true node scripts/test.js --env=jsdom",
    "lint": "eslint ./src/"
  },

 

이 코드에서는 "prestart"에 넣어 'start'가 호출되기 전에 동작하도록 했지만

한 번만 실행돼도 문제가 없기 때문에 다른 명령어로 넣고 수동으로 한 번만 호출해도 상관없습니다.

 

 

2. 수동으로 웹 팩 개발 서버에 세팅하기

웹 팩 개발 서버의 'https' 항목에 인증서를 설정하면 SSL이 동작하게 됩니다.

(참고 : webpack - DevServer - devServer.https )

const fs = require('fs');
const path = require('path');

devServer: {
    https: {
        minVersion: 'TLSv1.1',
        key: fs.readFileSync(path.join(__dirname, './server.key')),
        pfx: fs.readFileSync(path.join(__dirname, './server.pfx')),
        cert: fs.readFileSync(path.join(__dirname, './server.crt')),
        ca: fs.readFileSync(path.join(__dirname, './ca.pem')),
        passphrase: 'webpack-dev-server',
        requestCert: true,
    },
},

여기서 위에서 생성된 경로를 지정하면 SSL이 동작하게 됩니다.

 

 

3. 자동으로 웹 팩 개발 서버에 세팅하기

매번 복사 붙여넣기 하는 번거로움이 싫다면 자동으로 '.env.development.local'파일을 읽도록 구성하면 됩니다.

 

아래 코드는 'ASP.NET Core'의 'React'템플릿의 'getHttpsConfig.js'를 참고하여 만들었습니다.

(참고 : github - dang-gun/AspDotNetSamples/WebpackSslTest/ClientApp/OriginalFils/getHttpsConfig.js )

'use strict';

const fs = require('fs');
const path = require('path');
const crypto = require('crypto');
//const chalk = require('react-dev-utils/chalk');
//const paths = require('../paths');

const appDirectory = fs.realpathSync(process.cwd());
const resolveApp = relativePath => path.resolve(appDirectory, relativePath);



// Ensure the certificate and key provided are valid and if not
// throw an easy to debug error
function validateKeyAndCerts({ cert, key, keyFile, crtFile }) {
  let encrypted;
  try {
    // publicEncrypt will throw an error with an invalid cert
    encrypted = crypto.publicEncrypt(cert, Buffer.from('test'));
  } catch (err) {
    throw new Error(
      `The certificate  is invalid.\n${err.message}`
    );
  }

  try {
    // privateDecrypt will throw an error with an invalid key
    crypto.privateDecrypt(key, encrypted);
  } catch (err) {
    throw new Error(
      `The certificate key  is invalid.\n${
        err.message
      }`
    );
  }
}

// Read file and throw an error if it doesn't exist
function readEnvFile(file, type) {
  if (!fs.existsSync(file)) {
    throw new Error(
      `You specified ${chalk.cyan(
        type
      )} in your env, but the file  can't be found.`
    );
  }
  return fs.readFileSync(file);
}

// Get the https config
// Return cert files if provided in env, otherwise just true or false
function getHttpsConfig(bHttpsIs) 
{
  //const { SSL_CRT_FILE, SSL_KEY_FILE, HTTPS } = process.env;
    //const isHttps = HTTPS === 'true';

    let SSL_CRT_FILE = "";
    let SSL_KEY_FILE = "";

    if (true === bHttpsIs)
    {
        let dirEnv_D_L = path.resolve(__dirname, ".env.development.local");

        if (!fs.existsSync(dirEnv_D_L))
        {
            throw new Error(
                `You specified in your env, but the file "${dirEnv_D_L}" can't be found.`
            );
        }
        else
        {
            var array = fs.readFileSync(dirEnv_D_L).toString().split("\n");

            for (let i in array)
            {
                let arrCut = array[i].split("=");

                if (0 < arrCut.length)
                {
                    switch (arrCut[0])
                    {
                        case "SSL_CRT_FILE":
                            SSL_CRT_FILE = arrCut[1];
                            break;
                        case "SSL_KEY_FILE":
                            SSL_KEY_FILE = arrCut[1];
                            break;
                    }
                }
            }
        }//end if (!fs.existsSync(dirEnv_D_L))
    }

    if (bHttpsIs && SSL_CRT_FILE && SSL_KEY_FILE) {
      const crtFile = path.resolve(resolveApp('.'), SSL_CRT_FILE);
      const keyFile = path.resolve(resolveApp('.'), SSL_KEY_FILE);
    const config = {
      cert: readEnvFile(crtFile, 'SSL_CRT_FILE'),
      key: readEnvFile(keyFile, 'SSL_KEY_FILE'),
    };

      validateKeyAndCerts({ ...config, keyFile, crtFile });
    return config;
  }

    return bHttpsIs;
}

module.exports = getHttpsConfig;

 

이제 웹 팩 설정 파일에서 아래와 같이 선언하고

const getHttpsConfig = require('./getHttpsConfig');

 

아래와 같이 호출하면 됩니다.

devServer: {
    /** 서비스 포트 */
    port: "9503",
    https: getHttpsConfig(true),
}

 

 

4. 통합하기

위에 나온 스크립트를 통합하여 하나의 파일로 만들 수 있습니다.

참고 : github - dang-gun/AspDotNetSamples/WebpackSslTest/ClientApp/AspNetCore_HttpsConfigGet.js

 

개체 이름을 바꿨기 때문에 아래와 같이 사용합니다.

const HttpsConfigGet = require('./AspNetCore_HttpsConfigGet');


devServer: {
    https: HttpsConfigGet(true),
},

 

 

마무리

테스트 프로젝트 : github - dang-gun/AspDotNetSamples/WebpackSslTest/

 

웹 팩 설정이지만 'ASP.NET Core'를 넣은 것은 인증서 생성부분이 'ASP.NET Core'와 연관이 있어서 그렇습니다.

정확하게는 'ASP.NET Core'에서 추출한 코드라 경로가 'ASP.NET Core'로 되어있다는 것이죠.

 

이 부분은 자신이 원하는 데로 수정하여 사용할 수 있습니다.

수정하지 않아도 사용하는 데 지장없습니다.

('ASP.NET Core'의 유무와 관계없음.)