[spring][react][SSL] 인증서 발급하기
Https와 SSL인증서
HTTP는 HTML을 전송하기 위한 통신규약이다. HTTPS는 데이터를 전송할 때 암호화를 사용하는 통신규약이다. HTTPS는 SSL프로토콜 위에서 돌아가는 프로토콜이다. SSL인증서는 클라이언트와 서버간의 통신을 제3자가 보증해주는 전자화된 문서다. 공인인증서가 그 예이다. SSL인증서를 통해 HTTPS서버로 접속할 수 있다.
CA
인증서의 역할은 클라이언트가 접속한 서버가 클라이언트가 의도한 서버가 맞는지 보장하는 역할을 한다. 이 역할을 하는 민간기업들이 있는데 이런 기업들을 CA라고 한다.
사설 인증기관
사설 CA의 인증서를 이용한다면 브라우저는 경고를 출력한다.
공인된 CA의 인증서를 사용한다면 경고를 출력하지 않는다.
프로세스
- 사용자 데이터 세팅
2. 사용자 신원 확인
따로 주민등록번호가 실제 주민등록번호인지 확인할 권한이 없으므로 아이디가 db에 존재한다면 인증서를 발급한다.
사용자는 id와 주민등록번호를 입력한다.
crt인증서와 개인키 파일이 C:/cert폴더에 생성된다.
ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream("C:\\\\cert\\\\temisone.key"));
//비밀키 공개키 쌍 생성
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
kpg.initialize(1024);
KeyPair mKeyPair = kpg.genKeyPair();
os.writeObject(mKeyPair);
os.close();
//crt인증서 생성 공급자는 temisone이다.
X509V3CertificateGenerator v3CertGen = new X509V3CertificateGenerator();
v3CertGen.setSerialNumber(new BigInteger("1234"));
v3CertGen.setIssuerDN(new X509Principal("CN=temisone, OU=temisone, O=Company, L=Seoul, C=KR"));
v3CertGen.setNotBefore(new Date());
v3CertGen.setNotAfter(new Date(System.currentTimeMillis()+1000L*60*60*24*365*10));
v3CertGen.setSubjectDN(new X509Principal("CN=temisone, OU=temisone, O=Company, L=Seoul, C=KR"));
v3CertGen.setPublicKey(mKeyPair.getPublic());//키쌍의 공개키를 인증서에 저장한다.
v3CertGen.setSignatureAlgorithm("SHA1withRSA");
//개인키로 인증서를 증명할 수 있도록 한다.
X509Certificate pKCertificate = v3CertGen.generate(mKeyPair.getPrivate());
//로컬에 인증서와 키쌍을 저장한다.
FileOutputStream fos = new FileOutputStream("C:\\\\cert\\\\temisone.crt");
fos.write(pKCertificate.getEncoded());
fos.close();
System.out.println("키저장완료");
3. 에러 처리
- 인증서가 이미 존재한다면
- 이미 인증서가 존재한다는 알림을 출력한다.
- 회원이 아니라면
- 아이디가 존재하지 않는다고 알림을 출력한다.
서버에 인증서 등록하기
현재까지 찾아본 바로는 수동으로 등록하는 방법뿐이었다.
- java
jdk나 jre는 keystore을 자체적으로 관리해서 인증서를 keystore에 등록할 수 있다. 경로는 jre/library/security/cacerts다.
docker 명령어로 키파일을 저장시킨다.
keytool -import -keystore [키스토어 경로] -file [키파일 경로]
tomcat서버에 keystore를 등록해준다.
server.xml
<Connector port="443" protocol="org.apache.coyote.http11.Http11NioProtocol"
maxThreads="150" SSLEnabled="true" scheme="https" secure="true"
defaultSSLHostConfigName="localhost">
<SSLHostConfig hostName="localhost" protocols="TLSv1.2,+TLSv1.1,+TLSv1">
<Certificate certificateKeystoreFile="C:\\java_hyj\\jre\\bin\\temisoneKeyfile"
type="RSA" certificateKeystorePassword="temisone1245"/>
</SSLHostConfig>
</Connector>
이렇게 하면 인증서를 가지고 https접속이 된다.
동적으로 키파일이 keystore에 존재하는지 검증할 수 있다.
public class HttpsURLConnection {
static String urlString= "<https://localhost:443>";
static String line = null;
static InputStream in = null;
static BufferedReader reader = null;
static javax.net.ssl.HttpsURLConnection httpsConn = null;
public HttpsURLConnection() throws IOException, NoSuchAlgorithmException, KeyManagementException, KeyStoreException, CertificateException, UnrecoverableKeyException {
URL url = new URL(urlString);
httpsConn = (javax.net.ssl.HttpsURLConnection) url.openConnection();
// Set Hostname verification
httpsConn.setHostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
// Ignore host name verification. It always returns true.
return true;
}
});
// Input setting
httpsConn.setDoInput(true);
// Output setting
//httpsConn.setDoOutput(true);
// Caches setting
httpsConn.setUseCaches(false);
// Read Timeout Setting
httpsConn.setReadTimeout(1000);
// Connection Timeout setting
httpsConn.setConnectTimeout(1000);
// Method Setting(GET/POST)
httpsConn.setRequestMethod("GET");
// Header Setting
httpsConn.setRequestProperty("HeaderKey","HeaderValue");
//로컬의 키파일을 가져온다.
KeyStore clientStore = KeyStore.getInstance("jks");
clientStore.load(new FileInputStream("C:/cert/temisone.crt"),"temisone1245".toCharArray());
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(clientStore,"temisone1245".toCharArray());
KeyManager[] kms = kmf.getKeyManagers();
//서버의 키스토어를 가져온다.
KeyStore trustStore = KeyStore.getInstance("jks");
trustStore.load(new FileInputStream("C:/java_hyj/jre/lib/security/cacerts"),"temisone1245".toCharArray() );
//사설 인증서를 인증해주지 않아서 무조건 인증을 사용한다.
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(trustStore);
TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkClientTrusted(X509Certificate[] certs, String authType){
}
public void checkServerTrusted(X509Certificate[] certs, String authType) {
}
} };
SSLContext sslContext = null;
sslContext = SSLContext.getInstance("TLS");
sslContext.init(kms, trustAllCerts,new SecureRandom());
httpsConn.setSSLSocketFactory(sslContext.getSocketFactory());
System.out.println("끝");
httpsConn.connect();
httpsConn.setInstanceFollowRedirects(false);
}
}
- react
react도 자체적인 서버를 가지기 때문에 https인증을 해주어야 한다.
mkcert를 사용하여 key를 등록해준다
mkcert -key-file [개인키 경로] -cert-file [인증서 경로]
package.json에 사용할 인증서를 등록해준다.
"scripts": {
"start": "cross-env HTTPS=ture SSL_CRT_FILE=C:/cert/temisone.crt react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
아쉬운점
자체적으로 발급한 인증서라 두가지 방법을 사용해도 제대로 인증이 되지 않는다.