<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>hyewkim_dev.log</title>
        <link>https://velog.io/</link>
        <description>볼일 없는 상태에서 별볼일 있는 블로그로 키우는 중입니다.</description>
        <lastBuildDate>Mon, 19 Jan 2026 08:54:25 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>hyewkim_dev.log</title>
            <url>https://images.velog.io/images/hyew-kim/profile/182bc287-c23f-4b2e-8add-8d30c41b1200/social.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. hyewkim_dev.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/hyew-kim" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[AWS] 보안 체크 리스트]]></title>
            <link>https://velog.io/@hyew-kim/AWS-%EB%B3%B4%EC%95%88-%EC%B2%B4%ED%81%AC-%EB%A6%AC%EC%8A%A4%ED%8A%B8</link>
            <guid>https://velog.io/@hyew-kim/AWS-%EB%B3%B4%EC%95%88-%EC%B2%B4%ED%81%AC-%EB%A6%AC%EC%8A%A4%ED%8A%B8</guid>
            <pubDate>Mon, 19 Jan 2026 08:54:25 GMT</pubDate>
            <description><![CDATA[<h2 id="aws-인스턴스-생성-시-보안-주의사항들털리기-싫으면-필독">AWS 인스턴스 생성 시 보안 주의사항들(털리기 싫으면 필독)</h2>
<blockquote>
<p>오늘 작업하면서 AI 끼고 AWS 인스턴스 새로 파는데, 보안 관련해서 &quot;이건 진짜 지키자&quot; 싶은 것들 정리함. 
나중에 까먹고 대충 하다가 사고 치지 말고 미리미리 챙기자!!</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/hyew-kim/post/4d10a8e6-d14f-4a81-8ace-0d356f69aaa1/image.png" alt=""></p>
<h3 id="1-절대절대절대-root-계정-access-key-만들지-마라">1. 절대절대절대 Root 계정 Access Key 만들지 마라</h3>
<ul>
<li>방금 만든 따끈따끈한 그 <strong>Root 계정</strong> 말하는 거다.</li>
<li>AWS에서도 제발 만들지 말라고 사정하는데, 심지어 Root 키를 만든다? 탈취되는 순간 내 AWS 계정은 그냥 해커 놀이터 되는 거임. (과금 폭탄은 덤)</li>
<li><strong>Action:</strong> Root는 MFA(OTP) 빡세게 걸어두고 봉인해라. 작업은 무조건 권한 쪼개진 계정으로 하는 거다. &#39;최소 권한 원칙&#39; 알지?</li>
</ul>
<h3 id="2-iam-계정-생성-vs-sso-설정-대세는-sso다">2. IAM 계정 생성 vs. SSO 설정 (대세는 SSO다)</h3>
<ul>
<li>아직도 <code>IAM User</code> 만들어서 <code>Access Key</code> 파일 다운받아 쓰나? 요즘 권장 방식은 <strong>IAM Identity Center(SSO)</strong>임.</li>
<li>&quot;진짜 계정&quot;을 하나 파주는 게 IAM이라면, SSO는 &quot;임시 출입증&quot;만 발급해주는 거다.</li>
<li><strong>Why?:</strong> 로컬에 영구적인 키 파일(<code>credentials</code>)이 안 남는다. 해킹 당할 건덕지 자체가 줄어든다는 소리. 1인 개발이라도 습관 들이자.</li>
</ul>
<h3 id="3-db랑-백엔드는-퍼블릭-서브넷에-두는-거-아니다">3. DB랑 백엔드는 퍼블릭 서브넷에 두는 거 아니다</h3>
<ul>
<li>DB를 퍼블릭에 둔다? ㅎㅎ 그냥 &quot;어서오세요&quot; 보안 대문 열어주는 꼴임.</li>
<li><strong>정석(3-Tier):</strong> 웹 서버만 퍼블릭에 두고, WAS랑 DB는 프라이빗 서브넷에 꽁꽁 숨겨야 함.</li>
<li>외부에서 접속 필요하면 어떡하냐고? <strong>Bastion Host(점프 서버)</strong> 하나 뚫어서 들어가거나 VPN 쓰는 게 맞다. 귀찮아도 지킬 건 지키자.</li>
</ul>
<h3 id="4-계정-정보-비밀번호-하드코딩-금지">4. 계정 정보, 비밀번호 하드코딩 금지</h3>
<ul>
<li>&quot;테스트만 해보고 지울 건데?&quot; ← 이러다가 까먹고 깃허브에 올리는 순간 나락 감.</li>
<li>코드에 설정값 박아 버리는 거 한 번 하면 습관 된다. 절대 금지.</li>
<li><strong>Action:</strong> <code>.env</code> 파일 써서 <code>dotenv</code>로 불러오거나, AWS <strong>Parameter Store</strong>나 <strong>Secrets Manager</strong> 써서 우아하게 관리하자.</li>
</ul>
<h3 id="5-terraform-쓸-때-gitignore-안-하면-망함">5. Terraform 쓸 때 <code>.gitignore</code> 안 하면 망함</h3>
<p>원격 저장소에 절대 올라가면 안 되는 파일들 있다. 프로젝트 파자마자 <code>.gitignore</code>부터 세팅해라.</p>
<ul>
<li><code>.terraform/</code>: 바이너리랑 캐시 덩어리들. 굳이 올릴 필요 없음.</li>
<li><code>*.tfstate</code>, <code>*.tfstate.*</code>: <strong>(중요)</strong> 여기 인프라 상태랑 민감 정보 다 평문으로 들어있음. 이거 털리면 인프라 지도 갖다 바치는 거.</li>
<li><code>infra/terraform.tfvars</code>: 여기에 IP랑 비번 들어갈 수 있음. 주의.</li>
</ul>
<h3 id="6-보안-그룹security-group--ip-화이트리스팅">6. 보안 그룹(Security Group) = IP 화이트리스팅</h3>
<ul>
<li>퍼블릭 액세스 <code>Yes</code>로 열어두고 보안 그룹 인바운드를 <code>0.0.0.0/0</code> (전 세계 허용)으로 둔다? 그냥 &quot;내 DB 공공재입니다&quot; 선언하는 거임.</li>
<li><strong>Action:</strong> 소스(Source)는 무조건 <strong><code>내 IP (My IP)</code></strong>로 설정해라. CIDR 표기법으로 <code>/32</code> 붙이는 거 잊지 말고 (예: <code>58.126.xx.xx/32</code>).</li>
<li><strong>내 공인 IP 확인법 (Mac/Linux):</strong><pre><code class="language-bash">curl ifconfig.me</code></pre>
<em>터미널에 <code>ifconfig</code> 쳐서 나오는 건 내부 IP니까 헷갈리지 말 것.</em></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[백엔드 개발자에게 필요해보이는 AI 지식 정리(매우 주관적인 기준으로 정리중)]]></title>
            <link>https://velog.io/@hyew-kim/%EB%B0%B1%EC%97%94%EB%93%9C-%EA%B0%9C%EB%B0%9C%EC%9E%90%EC%97%90%EA%B2%8C-%ED%95%84%EC%9A%94%ED%95%B4%EB%B3%B4%EC%9D%B4%EB%8A%94-AI-%EC%A7%80%EC%8B%9D-%EC%A0%95%EB%A6%AC%EB%A7%A4%EC%9A%B0-%EC%A3%BC%EA%B4%80%EC%A0%81%EC%9D%B8-%EA%B8%B0%EC%A4%80%EC%9C%BC%EB%A1%9C-%EC%A0%95%EB%A6%AC%EC%A4%91</link>
            <guid>https://velog.io/@hyew-kim/%EB%B0%B1%EC%97%94%EB%93%9C-%EA%B0%9C%EB%B0%9C%EC%9E%90%EC%97%90%EA%B2%8C-%ED%95%84%EC%9A%94%ED%95%B4%EB%B3%B4%EC%9D%B4%EB%8A%94-AI-%EC%A7%80%EC%8B%9D-%EC%A0%95%EB%A6%AC%EB%A7%A4%EC%9A%B0-%EC%A3%BC%EA%B4%80%EC%A0%81%EC%9D%B8-%EA%B8%B0%EC%A4%80%EC%9C%BC%EB%A1%9C-%EC%A0%95%EB%A6%AC%EC%A4%91</guid>
            <pubDate>Fri, 02 Jan 2026 10:59:16 GMT</pubDate>
            <description><![CDATA[<p>채용공고에 심심치 않게 보이는 AI 활용 어쩌구 사항들을 더이상은 모른체 할 수가 없어서 정리해보는 관련 용어 및 지식 정리 글이다.</p>
<h2 id="mcp">MCP</h2>
<p>나의 퍼플렉시티 친구에게 질문해보았다. MCP가 도대체 뭐니?? 
돌아오는 딱딱한 대답은 이러했다.</p>
<blockquote>
<p>MCP는 <strong>&quot;AI 서비스(클라이언트)와 외부 데이터/도구(서버)를 연결하는 표준 &#39;USB 포트&#39; 같은 약속&quot;</strong>입니다. AI 서비스 이름이 아니라, AI가 일을 더 잘하게 만드는 <strong>기술 표준(프로토콜)</strong>입니다.</p>
</blockquote>
<p>오.. 하나도 모르겠고... Anthropic이 제안한 오픈소스 표준이라고 하는데 정의만 들었을때 딱히 모르겠다.
프로토콜 같은 개념인거 같은데 그래서 API 명세서와 비교해서 정리해봤다. </p>
<h3 id="api-명세-vs-mcp">API 명세 vs. MCP</h3>
<blockquote>
<p>&quot;설명(Description)&quot;이 곧 명세서다.</p>
</blockquote>
<ul>
<li>기존 API는 파라미터 타입(String, Int)이 중요했다면, MCP(그리고 AI용 Tool)에서는 <strong>자연어 설명</strong>이 가장 중요하다. AI는 코드를 읽는 게 아니라 설명을 읽고 호출 여부를 결정하기 때문이다.</li>
</ul>
<table>
<thead>
<tr>
<th>구분</th>
<th>기존 REST API</th>
<th>MCP Server (Tool)</th>
</tr>
</thead>
<tbody><tr>
<td>누구를 위해 만드나?</td>
<td>프론트엔드 개발자 / 앱</td>
<td>LLM (AI 모델)</td>
</tr>
<tr>
<td>소통 방식</td>
<td>&quot;HTTP GET /api/settlement?id=user1 날려주세요.&quot;</td>
<td>&quot;나는 getSettlement(userId)라는 함수가 있어. 설명은 &#39;정산 조회&#39;야.&quot;</td>
</tr>
<tr>
<td>요청 시점</td>
<td>사용자가 버튼을 클릭했을 때</td>
<td>AI가 대화하다가 &quot;어? 이거 필요하겠는데?&quot;라고 판단했을 때</td>
</tr>
<tr>
<td>비유</td>
<td>자판기 (버튼 누르면 나옴)</td>
<td>공구함 (AI가 필요할 때 꺼내 씀)</td>
</tr>
</tbody></table>
<p>아래의 한 문장으로 MCP가 어떤 개념인지 머릿속에서 정리할 수 있겠다.</p>
<blockquote>
<p>&quot;AI 너, 우리 데이터 필요하면 내가 정해둔 규격(MCP)대로 요청해. 그럼 다 줄게.&quot; </p>
</blockquote>
<p>그래서 앞으로 서비스 개발 시 MCP/Tool 개발을 할 때는 코드를 잘 짜는 것만큼이나 &#39;함수 설명(Description)&#39;을 잘 적는 게 중요해보인다. 이걸 <strong>Prompt Engineering의 일부</strong>라고도 여겨지는 듯 하다. </p>
<p>Spring AI를 사용한 간단한 예시로 친절한 설명의 중요성에 대해 알아보자.</p>
<blockquote>
<p>Sping AI는 <code>@Tool</code> 어노테이션만 붙이면 <strong>MCP 서버</strong>가 된다.</p>
</blockquote>
<ul>
<li><p>나쁜 예: <code>@Tool(description = &quot;정산 조회&quot;)</code>
=&gt; AI가 헷갈릴 수 있음. &quot;예정 금액 조회인가? 확정 금액인가? 언제 쓰는 거지?&quot;</p>
</li>
<li><p>좋은 예: <code>@Tool(description = &quot;사용자의 &#39;확정된&#39; 정산 금액을 조회합니다. &#39;예정&#39; 금액을 물을 때는 사용하지 마세요.&quot;)</code>
=&gt; AI가 훨씬 똑똑하게 작동함.</p>
</li>
</ul>
<h3 id="ai-에이전트와-spring-서버-간의-실행-흐름-정리">AI 에이전트와 Spring 서버 간의 실행 흐름 정리</h3>
<p>Spring AI를 활용하여 정산 서비스 MCP를 구축한다고 가정했을때 예상되는 AI 에이전트와 서버간의 실행 흐름을 정리해보았다. </p>
<p><strong>핵심 포인트 3가지:</strong></p>
<ol>
<li><strong>초기화 (맨 위):</strong> 서버가 켜질 때 AI에게 <strong>나 이런 기술(<code>@Tool</code>) 있다</strong>고 미리 명함(명세서)을 건네줍니다.</li>
<li><strong>판단 (AI 내부):</strong> 사용자의 <strong>돈 질문</strong>과 도구의 <strong>설명</strong>을 매칭해서 AI가 스스로 선택합니다.</li>
<li><strong>실행 (중간):</strong> AI는 <strong>요청</strong>만 하고, 실제 DB 조회는 <strong>스프링 서버</strong>가 수행합니다.</li>
</ol>
<p><img src="https://velog.velcdn.com/images/hyew-kim/post/83fc42bb-b0e3-485c-ae36-2c51119537a3/image.png" alt=""></p>
<blockquote>
<p>AI 에이전트가 직접 자바 코드를 실행하는 게 아니라 <strong>실행해달라고 요청(Request)</strong>을 보내면 <strong>스프링 서버가 실행</strong>하고 결과만 돌려주는 구조이다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[Notebook LM과 함께한 정보처리기사 1트에 합격하기]]></title>
            <link>https://velog.io/@hyew-kim/Notebook-LM%EA%B3%BC-%ED%95%A8%EA%BB%98%ED%95%9C-%EC%A0%95%EB%B3%B4%EC%B2%98%EB%A6%AC%EA%B8%B0%EC%82%AC-1%ED%8A%B8%EC%97%90-%ED%95%A9%EA%B2%A9%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@hyew-kim/Notebook-LM%EA%B3%BC-%ED%95%A8%EA%BB%98%ED%95%9C-%EC%A0%95%EB%B3%B4%EC%B2%98%EB%A6%AC%EA%B8%B0%EC%82%AC-1%ED%8A%B8%EC%97%90-%ED%95%A9%EA%B2%A9%ED%95%98%EA%B8%B0</guid>
            <pubDate>Wed, 24 Dec 2025 07:44:57 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/hyew-kim/post/7d6855a5-a9d8-4fb6-ae68-2d91340a668f/image.png" alt=""></p>
<p>이 글은 25년 크리스마스 이브에 기사 합격이라는 달달한 선물을 얻게되어 쓰는 회고록이다.</p>
<p>올해 7월부로 퇴사 후 쉬었음 청년이 된 후 어떤걸 해볼까 고민했을때 제일 먼저 생각난 것은 정보처리 기사 취득하기 였다.</p>
<p>2년 11개월 동안 다니다가 자유의 도비가 된 사람이 제일 먼저 하고 싶었던게 자격증 따기라니 조금 아이러니 하지만 하고 싶은게 생기면 실행력은 그 누구보다 뒤지지 않았기에 바로 3회차 기사 필기 접수하러 Qnet 사이트에 뛰어갔다.</p>
<p>19년도에 화공기사 이후에 오랜만에 방문한 Qnet이라 조금 해매면서 접수를 시도했는데 이미 정기 접수는 지난지 오래였고 다행히 나 같은 늦장러들을 위해 빈 자리 접수를 받고 있어서 막차로 신청을 완료했다.</p>
<h2 id="필기-시험-준비">필기 시험 준비</h2>
<p>일주일 기간으로 촉박하게 준비를 했다. 이전에 기사 시험을 준비해본 경험이 있으니 당연히 시작은 주구장창 기출 문제를 파면서 시험 공부를 시작했다.</p>
<h3 id="기출문제">기출문제</h3>
<p>[맞춘다] (<a href="https://www.machuda.kr/learning/certification/%EC%A0%95%EB%B3%B4%EC%B2%98%EB%A6%AC%EA%B8%B0%EC%82%AC/written">https://www.machuda.kr/learning/certification/%EC%A0%95%EB%B3%B4%EC%B2%98%EB%A6%AC%EA%B8%B0%EC%82%AC/written</a>) 사이트로 CBT 형식 시험을 대비했다. 기존 CBT 사이트는 UI도 보기 불편하고 해설도 별도로 찾아봐야해서 맞춘다 사이트 3일 체험으로 편하게 공부했다. </p>
<h3 id="오답노트">오답노트</h3>
<p>백지 상태인 내 머리에 가장 도움된 것은 문제 마다 나오는 개념들이 파편화되지 않고 그룹화되어 저장되도록 학습했다. </p>
<p>주로 노트 앱으로 Obsidian을 사용하고 있는데, Obsidian의 canverse를 활용하여 기출문제를 풀고 정확하게 알지 못하는 용어를 카드 형식으로 적고 연관되는 개념들을 그룹화하거나 화살표로 연관지었다. 이 canver는 시험 전 날과 당일 날 개념 훑을 때도 아주 유용했다!!
<img src="https://velog.velcdn.com/images/hyew-kim/post/8674002b-658b-463c-bf85-62a172829098/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/hyew-kim/post/11c773d7-953c-4f4e-aad7-4f818ba5ca26/image.png" alt=""></p>
<h2 id="실기-시험-준비">실기 시험 준비</h2>
<p>넉넉하게 3주 정도 시험대비를 했다.
최근 실기 시험 후기를 분석해봤을때 실기 프로그래밍 문제로 계산 문제 및 손 코딩 같은 문제들이 킬러 문항으로 많이 나와서 해당 부분 대비를 50% 나머지 분야 대비 50% 비중으로 대비했다. </p>
<h3 id="기출문제-1">기출문제</h3>
<p>실기 시험은 이전에 전공 시험처럼 답안지를 자필로 작성하여 제출하는 형식이길래 ebook 문제집이 편리할 것 같아 시나공 실기 기출 문제집을 구매하여 공부했다.
해설 강의도 별도로 유튜브로 제공하는데 2024년도 기출문제 해설부터 개념 문제가 출제될 경우 관련 개념을 같이 정리해줘서 아주 유용했다!!
<img src="https://velog.velcdn.com/images/hyew-kim/post/85e51fbf-d6db-4cb4-8fec-1e0bcbdab6bd/image.png" alt=""></p>
<h3 id="오답노트-1">오답노트</h3>
<p>실기 시험인 경우 프로그래밍 문제 / 그 외 분야로 나눠서 오답노트 정리를 했다. 프로그래밍 문제 외 분야는 필기 시험처럼 개념 정리를 Obsidian canverse로 그룹화 하면서 암기했다.</p>
<p><img src="https://velog.velcdn.com/images/hyew-kim/post/fc57a7ff-266d-41d4-89d5-efd00fee3ae5/image.png" alt=""></p>
<p>손 코딩 문항은 내 머릿속이 디버거다 생각하고 한 줄씩 실행해가며 이해 안 가는 함수 및 메서드는 따로 정리했다. 복잡한 재귀나 상속 문제는 IDE로 실행해가며 실행 과정을 머리 속에 익혔다. </p>
<p>계산 문제로 많이 나오는 네트워크 서브네팅, 페이지 교체 알고리즘, 프로세스 스케쥴링 문제는 AI한테 기출문제 별도로 요청해서 문항별로 다 풀 수 있도록 대비했다. (하지만.. 3회차 실기에는 출제되지 않았다 🥲)</p>
<h2 id="notebook-lm-활용하기">Notebook LM 활용하기</h2>
<p>내 자격증 학습 메이트였던 notebook LM ,,,
정보처리기사 자료는 인터넷 상에 많이 나와있어 시중 자료들을 notebook LM에게 출처로 입력했고, 시나공에 올라와있는 필수 정리집 이런 자료들을 주로 활용할 수 있도록 소스를 준비했다.<br><a href="https://notebooklm.google.com/notebook/e1e6d5b0-1ea7-4082-b30e-4e4897268373">나의 정처기 Notebook LM</a></p>
<h3 id="활용방법">활용방법</h3>
<p>나에게 도움되었던 방법을 몇개 공유해보자면 아래와 같다. 기출문제를 냅다 풀다보면 초반에는 문제에 나오는 용어나 개념도 모르겠고 해설도 뭐라는지 이해하기 어려운 부분이 나올텐데 그때 나의 notebook LM에게 질문하다보니 질문자체도 좋은 학습 경험이 되었던 것 같다.</p>
<ol>
<li>notebook LM을 이제부터 과외선생님 처럼 사용한다.</li>
<li>기출문제 해설을 보고도 이해가 안 되면 해당 설명을 가져와서 관련 개념과 상세 설명을 요구한다. </li>
<li>기출문제와 오답노트 그룹화를 하다보면 카드가 자주 모이는 부분이 있을텐데 해당 부분에 대해 쪽집게 정리 요청하기
ex) 프로세스 스케줄링 기법 비교 분석해줘</li>
</ol>
<p><img src="https://velog.velcdn.com/images/hyew-kim/post/da723a80-a281-4483-81f9-6ca95196d281/image.png" alt=""></p>
<h2 id="글을-마치며">글을 마치며,,</h2>
<p>나의 작은 2025년 회고이자 <del>자랑글이자</del> 학습 기록을 남기고 싶어서 적기 시작했는데 AI랑 시험대비하는 걸 정말 정말 추천하는 바이다.
특히 notebook LM은 학습 메이트로 정말 효율적인 도구인것 같다. 스터디나 시험 준비를 할때 친구와 모르는걸 의논하다가 나도 모르게 생각 정리가 된 경험이 있을텐데, 그 과정을 똑똑이 친구와 핑퐁한다고 생각하면 좋을 것 같다!
이 일기에 가까운 글을 여기까지 읽어주셨다면 정말 감사하며 따뜻한 크리스마스 보내시길 🎄</p>
<p><img src="https://velog.velcdn.com/images/hyew-kim/post/e9b6ffee-c16a-4c7c-9546-b1e3c2fe5fb8/image.jpeg" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[디버깅 기록 - JDK 배포판 확인의 중요성]]></title>
            <link>https://velog.io/@hyew-kim/%EB%94%94%EB%B2%84%EA%B9%85-%EA%B8%B0%EB%A1%9D-JDK-%EB%B0%B0%ED%8F%AC%ED%8C%90-%ED%99%95%EC%9D%B8%EC%9D%98-%EC%A4%91%EC%9A%94%EC%84%B1</link>
            <guid>https://velog.io/@hyew-kim/%EB%94%94%EB%B2%84%EA%B9%85-%EA%B8%B0%EB%A1%9D-JDK-%EB%B0%B0%ED%8F%AC%ED%8C%90-%ED%99%95%EC%9D%B8%EC%9D%98-%EC%A4%91%EC%9A%94%EC%84%B1</guid>
            <pubDate>Mon, 10 Mar 2025 06:36:26 GMT</pubDate>
            <description><![CDATA[<p>여느때와 다름없이 평범한 조회 API 구현 후 개발 배포를 진행했다. 흠.. 하지만 로컬 환경과 또 뭐가 다른건지 다음과 같은 오류를 만났다.</p>
<pre><code>Failed to obtain JDBC Connection; nested exception is com.microsoft.sqlserver.jdbc.SQLServerException:
The driver could not establish a secure connection to SQL Server by using Secure Sockets Layer (SSL) encryption.
Error: &quot;Certificates do not conform to algorithm constraints&quot;</code></pre><p>기존 개발 서버에서는 동일한 코드와 설정으로 문제가 없었지만, 신규 개발 서버에서만 위와 같은 오류가 발생했다. 문제의 원인을 분석한 결과, <strong>기존 서버와 신규 서버에서 사용된 Java 버전은 동일했지만(JDK 17), JDK의 벤더(vendor)가 달라 발생한 문제</strong>임을 확인했다. JDK 배포판 별 차이점을 기록해두고자 이 글을 작성하게되었다.</p>
<hr>
<h3 id="🔍-오류-원인-분석">🔍 <strong>오류 원인 분석</strong></h3>
<ol>
<li><p><strong>Java의 보안 정책 차이</strong></p>
<ul>
<li>기존 서버: Oracle JDK 사용</li>
<li>신규 서버: Red Hat OpenJDK 사용</li>
<li>두 JDK는 동일한 버전(17.0.x)이었지만, 각 벤더의 <code>java.security</code> 파일 내 보안 설정(<code>jdk.tls.disabledAlgorithms</code>)이 달랐습니다.</li>
<li>Red Hat OpenJDK는 더 엄격한 보안 정책을 적용하여 특정 암호화 알고리즘(DH 키 크기 제한, ECDH 등)을 기본적으로 비활성화했습니다.</li>
</ul>
</li>
<li><p><strong>SQL Server 인증서와 Java 보안 정책의 불일치</strong></p>
<ul>
<li>SQL Server에서 사용하는 SSL 인증서가 Java 보안 정책에서 허용하지 않는 알고리즘(예: DH keySize &lt; 1024 또는 EC keySize &lt; 224)을 포함하고 있었습니다.</li>
<li>Oracle JDK는 해당 알고리즘을 허용했지만, Red Hat OpenJDK는 이를 허용하지 않아 연결이 거부되었습니다.</li>
</ul>
</li>
</ol>
<hr>
<h3 id="📊-oracle-jdk-vs-openjdk-비교">📊 <strong>Oracle JDK vs OpenJDK 비교</strong></h3>
<table>
<thead>
<tr>
<th>항목</th>
<th>Oracle JDK</th>
<th>OpenJDK</th>
</tr>
</thead>
<tbody><tr>
<td><strong>라이선스</strong></td>
<td>상업용 라이선스 (유료)</td>
<td>오픈소스 (GPL v2 + Classpath Exception)</td>
</tr>
<tr>
<td><strong>보안 업데이트 주기</strong></td>
<td>정기적이고 예측 가능한 업데이트 제공</td>
<td>커뮤니티 기여에 따라 업데이트 주기가 다름</td>
</tr>
<tr>
<td><strong>보안 정책</strong></td>
<td>상대적으로 유연</td>
<td>더 엄격한 기본 보안 정책 적용</td>
</tr>
<tr>
<td><strong>성능 최적화</strong></td>
<td>엔터프라이즈 환경에 맞춘 추가 최적화 제공</td>
<td>표준 JVM 성능 유지</td>
</tr>
<tr>
<td><strong>지원 및 유지보수</strong></td>
<td>Oracle의 공식 지원 제공</td>
<td>커뮤니티 또는 벤더별 지원</td>
</tr>
<tr>
<td><strong>비용</strong></td>
<td>상업적 사용 시 유료</td>
<td>무료</td>
</tr>
</tbody></table>
<hr>
<h3 id="🚀-결론-및-교훈">🚀 <strong>결론 및 교훈</strong></h3>
<ul>
<li>동일한 Java 버전이라도 벤더에 따라 보안 정책과 설정이 다를 수 있습니다. 특히 <code>java.security</code> 파일 내의 설정은 벤더별로 차이가 있으므로 주의해야 합니다.</li>
<li>신규 서버를 설정할 때 기존 환경과 동일한 JDK 벤더를 사용하는 것이 예상치 못한 문제를 방지하는 데 효과적입니다.</li>
</ul>
<p>이번 이슈를 해결하며 운영 중인 서비스의 JDK 선택이 얼마나 중요한지 깨닳았다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Springboot 이해하기 - @ModelAttribute, @ParameterObject]]></title>
            <link>https://velog.io/@hyew-kim/Springboot-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-ModelAttribute-ParameterObject</link>
            <guid>https://velog.io/@hyew-kim/Springboot-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-ModelAttribute-ParameterObject</guid>
            <pubDate>Wed, 13 Nov 2024 11:10:59 GMT</pubDate>
            <description><![CDATA[<h2 id="modelattribute">@ModelAttribute</h2>
<blockquote>
<p>@ModelAttribute는 폼 데이터를 Java 객체에 바인딩하기 위해 사용됩니다.</p>
</blockquote>
<h3 id="modelattribute의-동작-방식">@ModelAttribute의 동작 방식</h3>
<ul>
<li><p>객체의 필드에 값을 설정하기 위해 <strong>setter 메서드가 필요</strong>합니다.</p>
</li>
<li><p>객체 생성 및 필드 설정</p>
<ul>
<li><code>@ModelAttribute</code>는 요청 파라미터를 기반으로 새<strong>로운 객체를 생성하고, 각 필드에 값을 설정</strong>합니다. 이 과정에서 리플렉션이 사용되지만, 필드에 직접 접근하는 대신 setter 메서드를 통해 값을 설정합니다. 이는 *<em>Java의 접근 제어 원칙에 따라 필드에 직접 접근하는 것을 피하기 위해서 입니다. *</em></li>
<li>캡슐화: 객체 지향 프로그래밍에서 캡슐화 원칙을 준수하기 위해, 필드에 직접 접근하기보다는 setter 메서드를 통해 값을 설정하는 것이 일반적입니다. 따라서, @ModelAttribute를 사용할 때는 setter 메서드가 필요합니다.</li>
</ul>
</li>
<li><p>예시</p>
</li>
</ul>
<pre><code class="language-java">public class User {
    private String name;
    private int age;

    // Setter 메서드가 필요합니다.
    public void setName(String name) {
        this.name = name;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

@RestController
public class UserController {

    @PostMapping(&quot;/register&quot;)
    public ResponseEntity&lt;String&gt; register(@ModelAttribute User user) {
        return ResponseEntity.ok(&quot;Registered: &quot; + user.getName() + &quot;, Age: &quot; + user.getAge());
    }
}</code></pre>
<blockquote>
<p>@RequestParam과 @ResponseBody는 리플렉션을 통해 직접 매개변수에 값을 할당할 수 있지만, @ModelAttribute는 객체의 필드에 값을 설정하기 위해 setter 메서드를 요구합니다. 이는 객체 지향 프로그래밍의 캡슐화 원칙을 준수하기 위한 설계입니다.</p>
</blockquote>
<h2 id="parameterobject">@ParameterObject</h2>
<blockquote>
<p>@ParameterObject는 메서드의 매개변수로 객체를 사용하여 여러 개의 요청 파라미터를 묶어 전달할 수 있도록 해주는 어노테이션입니다. 이 어노테이션을 사용하면, 여러 개의 쿼리 파라미터를 하나의 객체로 매핑할 수 있어 코드가 더 깔끔해지고 가독성이 향상됩니다.</p>
</blockquote>
<h2 id="parameterobject-vs-modelattribute">@ParameterObject vs. @ModelAttribute</h2>
<ol>
<li>복잡한 파라미터 구조 처리 용이</li>
</ol>
<ul>
<li>GET 메서드에서 여러 개의 쿼리 파라미터가 필요할 때,         <code>@ParameterObject</code>를 사용하면 이러한 파라미터를 하나의 객체로 묶어 관리할 수 있습니다. 이는 코드의 가독성을 높이고, 유지보수를 용이하게 합니다.</li>
</ul>
<ol start="2">
<li>명확한 문서화</li>
</ol>
<ul>
<li><code>@ParameterObject</code>를 사용하면 OpenAPI에서 해당 객체의 모든 필드를 문서화할 수 있습니다. 이는 API 소비자에게 각 필드의 의미와 형식을 명확하게 전달하는 데 도움이 됩니다.</li>
</ul>
<ol start="3">
<li>Swagger UI에서의 표현</li>
</ol>
<ul>
<li><code>@ParameterObject</code>를 사용하면 Swagger UI에서 <strong>요청 파라미터가 객체로 표현</strong>되므로 사용자에게 더 나은 경험을 제공합니다. 사용자들은 객체의 각 필드에 대해 이해하고 입력할 수 있습니다.</li>
<li>예시<blockquote>
<ul>
<li><code>@ModelAttribute</code> 사용 시 Swagger UI에서 Object 형식으로 제공된다.
<img src="https://velog.velcdn.com/images/hyew-kim/post/84bee6a4-b7b0-471f-906b-1a6d51e74d98/image.png" alt=""></li>
<li><code>@ParameterObject</code>사용 시 Reqeust 객체 정보가 자세하게 표기된다.
<img src="https://velog.velcdn.com/images/hyew-kim/post/dc0cf685-080c-4161-b44f-1af2fc5d2453/image.png" alt=""></li>
</ul>
</blockquote>
</li>
</ul>
<ol start="4">
<li>파라미터 재사용</li>
</ol>
<ul>
<li>OpenAPI 3.0의 components 섹션을 활용하여, 여러 엔드포인트에서 재사용할 수 있는 파라미터 객체를 정의할 수 있습니다. 이는 중복을 줄이고 API의 일관성을 높이는 데 기여합니다.</li>
</ul>
<blockquote>
<p>OpenAPI 3.0에서는 @ParameterObject를 사용하는 것이 권장됩니다. 이는 복잡한 요청 파라미터를 처리하고, API 문서의 가독성을 높이며, 사용자에게 더 나은 경험을 제공합니다. 특히 GET 메서드에서 여러 쿼리 파라미터를 다룰 때, @ParameterObject는 매우 유용한 선택입니다.</p>
</blockquote>
<h2 id="참고문헌">참고문헌</h2>
<ul>
<li><a href="https://www.baeldung.com/spring-mvc-and-the-modelattribute-annotation">https://www.baeldung.com/spring-mvc-and-the-modelattribute-annotation</a></li>
<li><a href="https://velog.io/@serringg/DTO%EC%97%90%EC%84%9C-%EC%83%9D%EC%84%B1%EC%9E%90-%EC%A3%BC%EC%9E%85%ED%95%B4%EC%A4%98%EC%95%BC-%ED%95%98%EB%8A%94-%EC%9D%B4%EC%9C%A0">https://velog.io/@serringg/DTO%EC%97%90%EC%84%9C-%EC%83%9D%EC%84%B1%EC%9E%90-%EC%A3%BC%EC%9E%85%ED%95%B4%EC%A4%98%EC%95%BC-%ED%95%98%EB%8A%94-%EC%9D%B4%EC%9C%A0</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Springboot 이해하기 - @RequestParam]]></title>
            <link>https://velog.io/@hyew-kim/Springboot-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-RequestParam</link>
            <guid>https://velog.io/@hyew-kim/Springboot-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-RequestParam</guid>
            <pubDate>Wed, 13 Nov 2024 10:11:09 GMT</pubDate>
            <description><![CDATA[<h2 id="requestparam의-처리-과정">@RequestParam의 처리 과정</h2>
<ol>
<li>HTTP 요청 수신</li>
</ol>
<ul>
<li>클라이언트가 쿼리 파라미터를 포함한 HTTP 요청을 서버로 보냅니다.
e.g., <code>/process?name=John&amp;age=30</code></li>
</ul>
<ol start="2">
<li>메서드 매핑</li>
</ol>
<ul>
<li><code>Spring MVC</code>는 해당 요청을 처리하는<code>@RequestParam</code> 어노테이션이 붙은 컨트롤러 메서드를 찾습니다.</li>
</ul>
<ol start="3">
<li>리플렉션 사용</li>
</ol>
<ul>
<li>리플렉션을 통해 <strong>메서드의 매개변수 정보를 얻습니다.</strong> 쿼리 파라미터의 타입과 이름을 확인하여 실제 쿼리 파라미터 값을 읽고 적절한 타입으로 변환합니다.</li>
</ul>
<p>4.형변환(Casting)</p>
<ul>
<li>쿼리 파라미터는 기본적으로 문자열(String)로 수신되므로, 리플렉션을 통해 얻은 값을 메서드의 매개변수 타입에 맞게 형변환합니다. 이 과정에서 자바의 기본형 타입(예: int, boolean 등)으로도 자동 변환이 이루어집니다.</li>
</ul>
<p>5.파라미터 바인딩</p>
<ul>
<li>변환된 값들이 메서드의 매개변수로 전달됩니다. 이 과정에서 <strong>@Setter가 없어도 리플렉션이 메서드의 매개변수에 적절하게 값을 할당</strong>해줍니다.</li>
</ul>
<blockquote>
<p>@RequestParam도 @RequestBody와 유사하게 리플렉션을 통해 처리되며, 개발자는 별도의 setter 메서드를 작성하지 않아도 됩니다.
Spring MVC가 자동으로 쿼리 파라미터를 읽어서 적절한 타입으로 변환하고, 메서드에 전달하는 방식으로 동작합니다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[Springboot 이해하기
- @RequestBody 처리]]></title>
            <link>https://velog.io/@hyew-kim/Springboot-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-RequestBody-%EC%B2%98%EB%A6%AC</link>
            <guid>https://velog.io/@hyew-kim/Springboot-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-RequestBody-%EC%B2%98%EB%A6%AC</guid>
            <pubDate>Wed, 13 Nov 2024 09:50:03 GMT</pubDate>
            <description><![CDATA[<h2 id="requestbody">@RequestBody</h2>
<blockquote>
<p>@RequestBody는 Spring MVC에서 HTTP 요청의 본문(body)을 Java 객체로 변환해주는 어노테이션입니다. 클라이언트가 JSON 형식으로 데이터를 보내면, Spring은 이를 자동으로 Java 객체로 변환합니다.</p>
</blockquote>
<h3 id="리플렉션을-통한-객체-처리-과정">리플렉션을 통한 객체 처리 과정</h3>
<ol>
<li>HTTP 요청 수신</li>
</ol>
<ul>
<li>클라이언트가 JSON 데이터를 포함한 HTTP 요청을 서버로 전달.</li>
</ul>
<ol start="2">
<li>메서드 매핑</li>
</ol>
<ul>
<li>Spring MVC는 해당 요청을 처리할 <code>@RequestBody</code> 어노테이션이 붙은 컨트롤러 메서드를 찾습니다.</li>
</ul>
<ol start="3">
<li><code>MappingJacksonHttpMessageConverter</code> 호출</li>
</ol>
<ul>
<li>Spring은 <code>MappingJacksonHttpMessageConverter</code>를 사용하여 JSON 데이터를 객체로 변환(역직렬화)합니다.</li>
</ul>
<ol start="4">
<li>리플렉션을 통해 메서드의 매개변수 처리</li>
</ol>
<ul>
<li><code>@RequestBody</code> 가 붙은 매개변수의 타입을 <strong>리플랙션을 통해</strong> 알아냅니다.
이 정보를 바탕으로 적절한 객체를 생성하거나, 필요한 필드를 설정할 수 있습니다.</li>
</ul>
<ol start="5">
<li>Jackson의 <code>ObjectMapper</code> 사용</li>
</ol>
<ul>
<li><code>ObjectMapper</code>를 사용하여 JSON 문자열을 해당 객체 타입으로 역직렬화합니다.이렇게 생성된 객체는 메서드의 매개변수로 전달됩니다.</li>
</ul>
<ol start="6">
<li>형변환(Casting)</li>
</ol>
<ul>
<li>리플렉션을 통해 얻은 객체는 일반적으로 <strong>Object 타입</strong>이므로, 실제 사용하려는 타입으로 형변환을 해야 합니다. </li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[20220929 - page not found error ]]></title>
            <link>https://velog.io/@hyew-kim/20220929-page-not-found-error</link>
            <guid>https://velog.io/@hyew-kim/20220929-page-not-found-error</guid>
            <pubDate>Thu, 29 Sep 2022 00:58:26 GMT</pubDate>
            <description><![CDATA[<h1 id="에러-log">에러 log</h1>
<h2 id="issue">issue</h2>
<ul>
<li>@Getmapping으로 설정한 page가 404 error인 상황</li>
</ul>
<h2 id="solution">solution</h2>
<ul>
<li>스프링 부트에서 스프링이 관리하는 객체로 등록하는 코드(@Controller, @Service, @Repository, @Configuration, @Component를 선언한 코드)는
시작 애플리케이션 (hello.learningspring)을 포함한 위치 혹은 그 하위에 존재해야합니다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/hyew-kim/post/de4e324f-b712-4c26-9249-7e4cb1310f30/image.png" alt=""></p>
<p>-
<a href="https://www.inflearn.com/questions/662266">https://www.inflearn.com/questions/662266</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[TIL] 20220523]]></title>
            <link>https://velog.io/@hyew-kim/TIL-20220523</link>
            <guid>https://velog.io/@hyew-kim/TIL-20220523</guid>
            <pubDate>Thu, 26 May 2022 10:21:54 GMT</pubDate>
            <description><![CDATA[<h1 id="변수-선언">변수 선언</h1>
<h2 id="전역-영역">전역 영역</h2>
<h3 id="var">var</h3>
<blockquote>
<ul>
<li>전역 객체의 프로퍼티로 등록</li>
</ul>
</blockquote>
<ul>
<li>undefine값으로 초기화되어 호이스팅됨</li>
</ul>
<h3 id="const-let">const, let</h3>
<blockquote>
<ul>
<li>호이스팅은 되지만 선언 되기 전까지 tdz 구간</li>
</ul>
</blockquote>
<ul>
<li>선언 전에 참조하려하면 reference error</li>
</ul>
<h2 id="함수-영역">함수 영역</h2>
<ul>
<li>같은 환경에 저장되지만 초기화 되는 과정이 다름</li>
</ul>
<p>참고) <a href="https://noogoonaa.tistory.com/78">https://noogoonaa.tistory.com/78</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[TIL 20220518]]]></title>
            <link>https://velog.io/@hyew-kim/TIL-20220518</link>
            <guid>https://velog.io/@hyew-kim/TIL-20220518</guid>
            <pubDate>Wed, 18 May 2022 07:18:30 GMT</pubDate>
            <description><![CDATA[<h1 id="arrow-function">Arrow function</h1>
<h3 id="기존-function과-다른-점">기존 function과 다른 점</h3>
<ol>
<li><p>this가 없다</p>
<blockquote>
<p>자체 this가 없기 때문에 arrow function 내에서 this를 참조하면 스코프 체인을 타고 찾는다.</p>
</blockquote>
<pre><code>function func() {
 return ()=&gt;console.log(this);
}
console.log(func()());
//return window 객체
//전역 영역에서 func를 실행시켜서 func의 this는 window 객체</code></pre></li>
<li><p>argument 객체가 없음</p>
<pre><code>const func = (a, a, a) =&gt; {...} // error!
function func (a, a, a) {...} //중복되게 인자를 넣어도 argument 객체에 각각 저장되어 argument[0]형식으로 접근하여 상관 없음</code></pre></li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[TIL] 20220517]]></title>
            <link>https://velog.io/@hyew-kim/TIL-20220517</link>
            <guid>https://velog.io/@hyew-kim/TIL-20220517</guid>
            <pubDate>Tue, 17 May 2022 16:02:06 GMT</pubDate>
            <description><![CDATA[<h1 id="event-loop">Event loop</h1>
<blockquote>
<p>콜 스택이 비었을때 콜백 큐에서 콜백 함수를 콜스택으로 옮김</p>
</blockquote>
<h1 id="callback">Callback</h1>
<blockquote>
<p>다른 함수의 인자로 전달되는 함수</p>
</blockquote>
<h2 id="동기-콜백">동기 콜백</h2>
<blockquote>
<p>호출 즉시 실행</p>
</blockquote>
<h2 id="비동기-콜백">비동기 콜백</h2>
<blockquote>
<ul>
<li>나중에 특정 조건 만족시 실행</li>
</ul>
</blockquote>
<ul>
<li>setTimeout, eventListener</li>
</ul>
<h1 id="react에서-return-문에-조건-달기">React에서 return 문에 조건 달기</h1>
<blockquote>
<p>{} 안에는 js 문법이 적용된다. </p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[TIL] 20220510]]></title>
            <link>https://velog.io/@hyew-kim/TIL-20220510</link>
            <guid>https://velog.io/@hyew-kim/TIL-20220510</guid>
            <pubDate>Tue, 10 May 2022 17:06:54 GMT</pubDate>
            <description><![CDATA[<h1 id="object-spread-문법">Object spread (...)문법</h1>
<blockquote>
<p>객체에서 sepread문법 사용시 값 복사 혹은 override 된다. </p>
</blockquote>
<ul>
<li>state object는 복사되고, next object는 selectedIndex값이 업데이트 됨</li>
</ul>
<pre><code>let state = { fetchedLanguages: [],
        selectedLanguages: [],
        selectedIndex: 0,
    }

let next = {selectedIndex : 1}

state = {...state, ...next}

//result
{fetchedLanguages: Array(0), selectedLanguages: Array(0), selectedIndex: 1}
fetchedLanguages: []
selectedIndex: 1
selectedLanguages: []
[[Prototype]]: Object</code></pre><h1 id="atomic-design">Atomic design</h1>
<ul>
<li>원자: HTML tag</li>
<li>분자: HTML tag를 모아 하나의 기능을 하는 컴포넌트</li>
<li>유기체: 여러 컴포넌트가 모인 header, main, footer영역</li>
</ul>
<h1 id="script-type--module">script type = &quot;module&quot;</h1>
<blockquote>
<p>HTML element를 return하는 (rendering하는) JS file 이다라는 의미</p>
</blockquote>
<ul>
<li>type을 module이라고 지정을 하지 않으면 error</li>
</ul>
]]></description>
        </item>
    </channel>
</rss>