와아아아아아아 진짜 6시간 동안 싸운 오류와의 싸움 과정 기록.
서블릿과 jsp를 이용하여 회원가입 페이지를 만들고 있다.
회원가입을 크게 이메일 가입과 SNS 가입 두 개로 나누었다.
따라서 이메일 가입을 구현하기 위해서는 이메일 인증 절차가 필요했다.
흐린 눈 하고 잠시 프론트만 할 때는 좋았는데 기능을 구현하려니까 약간 머리가 아팠다.
일단 구글 이메일 인증은 아래 블로그들을 참고 했다!!
하나 하나 참고 하다보면 된다..🚚
https://blog.naver.com/froginthesky/220834657436
https://studyforus.tistory.com/259
https://zero-week.tistory.com/87
(진짜 너무 감사합니다..)
지금부터 회원가입, 이메일 인증 관련 파일 코드와 함께 리뷰를 해보겠다.
(완전히 내것으로 만들기 위한 리뷰)
파일 구성은 webapp아래 SignUp폴더를 만들고 join.jsp / joinProcess.jsp를 넣었다.
mail 패키지를 만들어서 mailsend.java / GoogleAuthentication.java /VerifyCode.java파일
util 패키지를 만들어서 CodeGenerator.java 파일을 만들었다.
🍤Ajax
Ajax는 서버와 통신하기 위해 XMLHttpRequest 객체를 사용하며 XML 뿐만 아니라 HTML, JSON, 일반 텍스트 형식 등을 포함한 다양한 포맷을 주고 받을 수 있다.
Ajax의 가장 큰 특징과 핵심적인 부분은
웹 페이지 전체를 다시 로딩하지 않고 웹 페이지의 필요한 일부분만을 갱신할 수 있게 해주는 것이다.
Ajax는 서버의 처리가 완료될 때 까지 기다리지 않고 처리가 가능하다는 것이 장점(비동기적인 특징)
아무튼 페이지를 벗어나지 않고 내부에서 기능을 처리할 수 있다는 것은 장점이다!
1. join.jsp (회원가입 프론트엔드 UI )
//부분 발췌
<script type="text/javascript">
function email_ok(email) {
console.log("입력된 이메일: ", email);
if (!email || email === "") {
alert("이메일을 입력하세요.");
return;
}
var contextPath = '<%= request.getContextPath() %>';
$.ajax({
type: "POST",
url: contextPath + "/mailSend", // 서버의 컨텍스트 경로 + 서블릿 경로
data: { receiver: email }, // 서버로 전송할 데이터 (이메일)
dataType: "text", // 서버에서 텍스트 형식의 응답을 받을 때
success: function(response) {
console.log("서버 응답: ", response);
if (response === "이메일 전송 성공") {
// 이메일 전송 성공 메시지 화면에 표시
alert("이메일이 성공적으로 전송되었습니다. 계속해서 가입을 진행하세요.");
} else {
alert("이메일 전송에 실패했습니다. 다시 시도해주세요.");
}
},
error: function(xhr, status, error) {
console.log("AJAX 오류: ", error); // 오류 로그 출력
alert("서버 오류: " + error);
}
});
}
// 인증번호 확인 함수
function verifyCode() {
var inputCode = document.getElementById("authCode").value; // 사용자가 입력한 인증번호
var contextPath = '<%= request.getContextPath() %>';
$.ajax({
type: "POST",
url: contextPath +"/verifyCode", // VerifyCode 서블릿에 요청
data: { authCode: inputCode }, // 사용자가 입력한 인증번호 전달
success: function(response) {
if (response === "success") {
alert("인증이 완료되었습니다. 회원가입을 진행하세요.");
document.getElementById("submitBtn").disabled = false; // 회원가입 버튼 활성화
} else {
alert("인증번호가 일치하지 않습니다.");
}
},
error: function(xhr, status, error) {
alert("인증번호 확인 중 오류가 발생했습니다.");
}
});
}
function checkCode(){
var v1 = document.getElementById("code_check").value;
var v2 = document.getElementById("code").value;
if(v1 != v2){
document.getElementById('checkCode').style.color = "red";
document.getElementById('checkCode').innerHTML = "잘못된 인증번호";
makeNull();
}else{
document.getElementById('checkCode').style.color = "green";
document.getElementById('checkCode').innerHTML = "인증번호가 확인되었습니다";
makeReal();
}
}
function makeReal(){
var hi = document.getElementById("hi");
hi.type = "submit";
}
function makeNull(){
var hi = document.getElementById("hi");
hi.type = "hidden";
}
<label for="email">이메일</label>
<br>
<input type="text" id="email" name="email" placeholder="아이디로 사용할 이메일을 입력해 주세요." required>
<button type="button" onclick="email_ok(document.getElementById('email').value)">인증번호 전송</button>
<br>
<label for="authCode">인증번호</label>
<br>
<input type="text" id="authCode" name="authCode" placeholder="이메일로 받은 인증번호를 입력해 주세요." required>
<button type="button" onclick="verifyCode()">인증번호 확인</button>
<br>
email_ok(email)함수
사용자가 입력한 이메일을 받아 서버로 전송하고 서버에서 이메일 전송 결과를 받아 처리하는 함수
post방식 ajax요청으로
mailsend 서블릿에 이메일을 전송하고, 응답으로 "이메일 전송 성공" 또는 "실패" 메세지를 받아 처리
contextPath
jsp에서 request.getContextPath()로 가져와서 애플리케이션 경로를 자동으로 적용.
verifyCode() 함수
사용자가 입력한 인증번호를 서버에 전송하여,
서버에서 받은 응답에 따라 회원가입 버튼을 활성화하거나 오류 메시지 출력
Ajax 요청
verifyCode 서블릿에 인증번호를 보내고, 인증 성공 시 회원가입 버튼을 활성화
2. joinProcess.jsp (서버 측 처리 페이지)
//부분 발췌
// 세션에서 인증 코드 가져오기
String sessionAuthCode = (String) session.getAttribute("authCode"); // 세션에 저장된 인증 코드
String inputAuthCode = request.getParameter("authCode"); // 사용자가 입력한 인증 코드
System.out.println("세션 인증 코드: " + sessionAuthCode); // 로그 출력
System.out.println("사용자가 입력한 인증 코드: " + inputAuthCode); // 로그 출력
// 인증 코드가 일치하지 않을 경우 회원가입을 차단
if (inputAuthCode == null || !inputAuthCode.equals(sessionAuthCode)) {
request.setAttribute("msg", "인증 코드가 올바르지 않습니다.");
RequestDispatcher dispatcher = request.getRequestDispatcher("../SignUp/join.jsp");
dispatcher.forward(request, response);
return; // 중단
}
// 클라이언트가 전송한 회원가입 폼 데이터를 가져옴
String email = request.getParameter("email"); // 사용자가 입력한 이메일 값
String password = request.getParameter("password"); // 사용자가 입력한 비밀번호 값
String nickname = request.getParameter("nickname"); // 사용자가 입력한 닉네임 값
// 입력값 로그 출력
System.out.println("입력된 이메일: " + email);
System.out.println("입력된 닉네임: " + nickname);
// 입력값 검증
if (email == null || email.isEmpty() || password == null || password.isEmpty() || nickname == null || nickname.isEmpty()) {
request.setAttribute("msg", "모든 필드를 입력해야 합니다.");
RequestDispatcher dispatcher = request.getRequestDispatcher("../SignUp/join.jsp");
dispatcher.forward(request, response);
return; // 중단
}
// 이메일 형식 검증 (간단한 이메일 패턴을 사용한 검증)
String emailPattern = "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,6}$";
if (!email.matches(emailPattern)) {
request.setAttribute("msg", "올바른 이메일 형식을 입력해주세요.");
RequestDispatcher dispatcher = request.getRequestDispatcher("../SignUp/join.jsp");
dispatcher.forward(request, response);
return; // 중단
}
인증된 사용자 데이터를 서버에서 받아 회원 가입 처리
join.jsp에서 넘어온 데이터(이메일, 비밀번호 등)를 처리하여 데이터베이스에 저장
사용자가 이메일로 받은 인증번호("authcode")는 서버에 저장되고 이 코드는 세선에 저장된 인증 코드를 가져온다.
session.getAttribute("authCode")
서버 세션에 저장된 인증 코드를 가져옴
request.getParameter("authCode")
사용자가 입력한 인증 코드를 가져옴
사용자가 입력한 인증 코드(inputAuthCode)와
세션에 저장된 인증 코드(sessionAuthCode)를 비교하여
인증 코드가 다르거나 없을 경우 회원가입 중단
email.matches(emailPattern)
이메일이 정해진 패턴(정규식)에 맞는지 검증
3. mailsend.java (서블릿)
@WebServlet("/mailSend")
public class MailSend extends HttpServlet {
private static final long serialVersionUID = 1L;
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html; charset=UTF-8");
response.setCharacterEncoding("UTF-8");
System.out.println("MailSend 서블릿 요청 수신");
// 이메일 파라미터를 받아옴
String email = request.getParameter("receiver");
System.out.println("받은 이메일: " + email); // 이메일 값을 서버 로그에 출력
// 이메일 값이 null이거나 비어있는지 확인
if (email == null || email.isEmpty()) {
System.out.println("이메일 파라미터가 비어있습니다.");
response.getWriter().write("이메일 파라미터가 비어있습니다.");
return;
}
// SMTP 서버 설정
Properties props = new Properties();
props.put("mail.smtp.user", "parkyeseul.developer@gmail.com"); // 서버 아이디
props.put("mail.smtp.host", "smtp.gmail.com"); // 구글 smtp 서버
props.put("mail.smtp.port", "465");
props.put("mail.smtp.starttls.enable", "true");
props.put("mail.smtp.auth", "true");
props.put("mail.smtp.socketFactory.port", "465");
props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
props.put("mail.smtp.socketFactory.fallback", "false");
// 구글 SMTP 서버 인증
Authenticator auth = new GoogleAuthentication();
// 세션 생성
Session session = Session.getDefaultInstance(props, auth);
MimeMessage msg = new MimeMessage(session);
try {
// 메일 발송 정보 설정
msg.setSentDate(new Date());
// 발신자 정보 설정
InternetAddress from = new InternetAddress("parkyeseul.developer@gmail.com", "관리자");
msg.setFrom(from);
// 수신자 정보 설정
InternetAddress to = new InternetAddress(email); // 파라미터로 받은 이메일
msg.setRecipient(Message.RecipientType.TO, to);
// 인증번호 생성
String code = CodeGenerator.generateCode(); // 6자리 랜덤 숫자 생성
HttpSession httpSession = request.getSession(); // 세션에 인증번호 저장
httpSession.setAttribute("authCode", code); // 세션에 인증번호 저장
// 메일 제목 설정
msg.setSubject("이메일 인증번호", "UTF-8");
// 이메일 본문 설정 (생성된 인증번호 포함)
msg.setText("인증번호: " + code, "UTF-8");
// 메일 헤더 설정
msg.setHeader("Content-Type", "text/html");
// 메일 전송
Transport.send(msg);
System.out.println("이메일 전송 성공");
response.getWriter().write("이메일 전송 성공");
} catch (AddressException addr_e) {
addr_e.printStackTrace();
response.getWriter().write("fail");
} catch (MessagingException msg_e) {
msg_e.printStackTrace();
response.getWriter().write("fail");
}
}
}
사용자가 이메일 인증을 요청했을 때, 해당 이메일로 인증번호를 전송하는 역할
이메일을 전송하기 위한 로직이 들어 있다.
@WebServlet("/mailSend")
이 서블릿은 /mailSend 경로로 호출될 때 실행
MailSend extends HttpServlet
이 클래스는 서블릿으로, HTTP 요청 처리
사용자의 요청을 받아 인증번호를 전송하는 작업 수행
service() 메소드는 HTTP 요청이 들어왔을 때 처리되는 메소드로, 요청이 들어왔을 때 처리,
response.setContentType("text/html; charset=UTF-8")
서버에서 클라이언트로 보낼 응답 데이터의 형식을 HTML로 지정하고, 인코딩을 UTF-8 설정
SMTP 서버 설정
( https://blog.naver.com/froginthesky/220834657436) ->여기서 SMTP설정 가능!
메일을 전송하기 위해 Gmail SMTP 서버와 연결하는 설정
mail.smtp.user
이메일을 전송할 계정 정보
mail.smtp.host
Gmail SMTP 서버 주소
mail.smtp.port
465번 포트 SSL을 사용하는 SMTP 연결을 위해 사용
SSL 설정
메일 전송 시 암호화된 연결을 위해 SSL 설정이 필요
Authenticator
메일을 전송하기 위해 SMTP 인증을 처리하는 부분
GoogleAuthentication 클래스를 사용해 SMTP 인증 정보 제공
Session.getDefaultInstance(props, auth)
세션 객체를 생성하여 SMTP 서버와 연결할 준비
인증 정보와 설정을 기반으로 세션이 생성
<<흐름>>
이메일 전송 요청이 오면, 서버는 SMTP 설정을 통해 이메일 서버에 연결 ->
->사용자가 입력한 이메일로 6자리 인증번호를 생성, 전송하고, 해당 인증번호를 세션에 저장
-> 메일 전송이 완료되면 성공 메시지 반환
4. GoogleAuthentication.java
public class GoogleAuthentication extends Authenticator {
private PasswordAuthentication auth;
public GoogleAuthentication() {
// Gmail SMTP에 사용할 이메일과 비밀번호 설정
String email = "parkyeseul.developer@gmail.com";
String password = "wvcw pgwk vfxe dktu"; // 실제 비밀번호 또는 앱 비밀번호 입력
auth = new PasswordAuthentication(email, password);
}
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return auth;
}
}
Gmail 서버에 연결하기 위한 SMTP 인증 정보를 관리하는 클래스
이메일을 전송하기 위해 Gmail 계정 정보를 사용하여 SMTP 인증을 처리
(2단계 인증 -> 검색창에 앱 비밀번호 -> 앱 비밀번호 생성 후 password에 넣으면 된다!)
5. VerifyCode.java (서블릿)
@WebServlet("/verifyCode")
public class VerifyCode extends HttpServlet {
private static final long serialVersionUID = 1L;
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String inputCode = request.getParameter("authCode"); // 사용자가 입력한 인증번호
HttpSession session = request.getSession();
String sessionCode = (String) session.getAttribute("authCode"); // 세션에 저장된 인증번호
if (inputCode != null && inputCode.equals(sessionCode)) {
response.getWriter().write("success"); // 인증번호 일치
} else {
response.getWriter().write("fail"); // 인증번호 불일치
}
}
}
사용자가 입력한 인증번호와 서버에서 전송한 인증번호를 비교하여 유효성 검증
일치하면 회원가입 절차로 진행되고, 불일치하면 오류 메시지를 반환
6. CodeGenerator.java
public class CodeGenerator {
// 6자리 랜덤 인증번호를 생성하는 메서드
public static String generateCode() {
Random random = new Random();
StringBuilder code = new StringBuilder();
for (int i = 0; i < 6; i++) {
code.append(random.nextInt(10)); // 0-9 랜덤 숫자 생성
}
return code.toString(); // 생성된 인증번호 반환
}
}
인증번호를 랜덤하게 생성하는 유틸리티 클래스
6자리 숫자로 된 인증 코드를 생성하여 이메일로 전송
<대략적인 전체 흐름>
- join.jsp: 사용자가 이메일을 입력하고 인증번호를 요청.
- mailsend.java: 입력된 이메일로 인증번호 전송
- VerifyCode.java: 사용자가 입력한 인증번호와 서버에 저장된 인증번호를 비교
- joinProcess.jsp: 인증 성공 시 회원 가입 처리를 진행
왕초보가 구글링과 수업 때 배운 내용으로 열심히 작성한 코드입니다🍥🍥
일단 흐름을 아는게 중요하다고 생각이 들어서
흐름을 위주로 머리에 저장하려고 노력 중!!!!!!
이메일 인증 하나에 이렇게 많은 파일이 필요하다는 것이 넘모 신기하다.
🤖오류 극복기
잠깐 떠들자면...
어제 11시 30분부터 19:30분까지 이메일 인증 관련 기능을 구현했는데 ( ⅔ 이상 오류 잡는데 온 신경을 집중했다.)
오류는 익일 11시쯤 잡았다.
문제는 서버404에러가 뜨는데 G선생한테 물어봐도 같은 질문만 계속해서
눈이 퀭해지고,, 혹시 저처럼 오류가 생기시면 이렇게 해보세욤...
결국 해결 못하고 집 가서도 계속 404에러.. 왜 자꾸 전송은 되는데 에러가 뜨는 거니..하며 쉬는게 쉬는게 아닌 채로
바로 기절했다.
(파일은 빠진 것 없이 잘 구성되어졌다.)
Failed to load resource: the server responded with a status of 404 ()
404에러: 이 오류는 요청한 리소스(파일, 페이지 등)가 서버에 존재하지 않을 때 발생
서버가 404 오류를 반환하는 이유는 AJAX 요청이 잘못된 경로로 전송되었기 때문일 가능성이 크다고 한다.
🥊해결방법 1.
잘못된 URL이 있는지 확인하기
나는 MailSend.java안에
@WebServlet("/mailSend")로 경로를 잘 설정해두었다.
계속 이 웹서블릿 안에 경로 문제라고 g선생이 552번 이야기 했다. 진짜 아닌데..
그래서 join.jsp -> email_ok 함수(이메일을 받아 서버로 전송하고 서버에서 이메일 전송 결과를 받아 처리하는 함수)안에
var contextPath = '<%= request.getContextPath() %>';
$.ajax({
type: "POST",
url: contextPath + "/mailSend", // 서버의 컨텍스트 경로 + 서블릿 경로
컨텍스트 경로(애플리케이션이 서버 내에서 사용하는 최상위 경로)를
동적으로 가져올 수 있도록 했다.
이렇게 하면 웹 애플리케이션에서 AJAX요청을 보다 유연하게 처리 가능하다고 한다!
url 값이 서버의 서블릿 URL과 일치해야한다.
혹은 서블릿 URL 매핑이 누락 확인:
서블릿을 어노테이션이 아닌 web.xml을 통해 설정하고 있다면, 해당 서블릿이 web.xml에 제대로 매핑되었는지 확인
->나는 어노테이션을 사용하고 있기 때문에 이 방법은 Pass
🥊해결방법 2.
개발자도구 network탭에서 요청 응답 확인하기
콘솔창에 404에러가 뜨고, 알럿에는 자꾸 이메일 전송 실패 및 인증 코드번호가 알럿을 통해 나왔다. 띠용
network탭에 가서
🥊해결방법 3.
서블릿 로그 확인 및 브라우저에서 직접 URL 테스트
mailsend에서
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 인증번호 처리 코드
System.out.println("MailSend 서블릿 요청 수신");
서블릿 요청 수신을 찍고, 이메일 전송 성공 로그를 출력하도록 코드를 추가했다.
이클립스에서 MailSend 서블릿 요청 수신/ 이메일 전송 성공
이렇게 뜨는게 아닌가? 놀라서 메일함에 들어갔더니 이메일은 정상적으로 전송이 되고 있었다.
다만 회원가입 폼에서 이메일 전송 실패 알럿과 콘솔에 404에러가 뜨고 있었고 네트워크도 여전히 빨간불이었다.
이 경우 GPT답변은
<<이메일 전송은 정상적으로 이루어지지만
AJAX에서 404 오류가 발생하는 이유는, AJAX 요청 경로가 잘못되었거나
서블릿 경로가 서버에 제대로 매핑되지 않았기 때문>>
근데 위에서도 했지만 경로 문제는 아니었다.
브라우저에서 바로 URL을 테스트 했을 때, '이메일 파라미터가 비어있습니다.'는 문구가 페이지에 출력되었다.
이 문제는 mailsend서블릿에서
String email = request.getParameter("receiver");
이 부분이 try-catch문에서 한 번 더 쓰여 중복해서 받아와 지고 있었다. 그래서 중복을 제거했다.
🥊해결방법 4.
프로젝트 배포 및 서버 재시작
혹시? 혹시??하며 다시 해봤지만 뭐 역시나 그대로였다.
(프로젝트 클린: Eclipse에서 Project > Clean을 실행)
🥊해결방법 5. (해결!!!!!!!!!!!!!!!!!)
데이터 형식 맞추기 (데이터 타입과 반환타입)
와 진짜 지피티랑 구글링을 아무리 해도 다들 비슷한 해결 방법으로 해결을 하셨던데
나는 진짜 뭐가 문제일까 하면서 계속 내 코드를 들여다보고 구글링하면서 글을 엄청 읽어봤다.
응답 형태를 맞춰야 한다는 글을 발견하고 혹시 "나도 응답 객체에서 문제가 생긴 거 아닐까?" 라는 생각이 문득 들었다.
이런 생각까지 발전하는 나 진짜 칭찬해.
그래서 내 코드를 계속 들여다 보았다. 딱 보이는 문제점........🛎
<<데이터 타입과 반환 타입이 다를 때 발생하는 문제는
서버에서 반환하는 데이터 형식과 AJAX에서 기대하는 데이터 형식이 맞지 않아 생긴다.
이 경우는 서버에서 반환하는 데이터의 타입을 명확히 지정하고, 클라이언트(AJAX)에서 그 타입을 올바르게 처리하는 것이 중요
예를 들어, 서버에서 JSON 형식으로 데이터를 반환해야 하는데 텍스트 형식으로 처리하거나, 반대로 텍스트를 반환해야 하는데 JSON 형식으로 처리하려고 할 때 오류가 발생할 수 있다.>>
나의 mailsend.java 서블릿 파일을 보면 text형태로 설정했다.
(text/plain 혹은 text/html 사용 가능)
@WebServlet("/mailSend")
public class MailSend extends HttpServlet {
private static final long serialVersionUID = 1L;
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/plain; charset=UTF-8"); // 텍스트로 응답할 경우
response.setCharacterEncoding("UTF-8");
// 메일 전송 처리 로직
String email = request.getParameter("receiver");
System.out.println("받은 이메일: " + email);
if (email == null || email.isEmpty()) {
response.getWriter().write("이메일 파라미터가 비어있습니다.");
return;
}
// 메일 전송 성공 응답
response.getWriter().write("이메일 전송 성공");
}
}
join.jsp(수정)에서
원래는 data밖에 없었어서 서버에서 반환하는 데이터의 형식을 불러오지 못해 ajax에서 처리를 할 수 없었다.
dataType을 설정해서 text로 일치 시켰고, 저 코드 한 줄을 추가하고 나서..........오류가 해결되었다.😎
$.ajax({
type: "POST",
url: contextPath + "/mailSend",
data: { receiver: email },
dataType: "text", // 서버에서 텍스트 형식의 응답을 받을 때
success: function(response) {
console.log("서버 응답: ", response);
alert(response); // 텍스트 응답 처리
},
error: function(xhr, status, error) {
console.log("AJAX 오류: ", error);
alert("서버 오류: " + error);
}
});
중요❗
서버가 반환하는 데이터 타입과 AJAX에서 기대하는 데이터 타입 일치
AJAX 요청의 dataType 속성을 서버에서 반환하는 데이터의 형식에 맞게 설정.
🧚♂️
이렇게 해결했다고 한다.
너무 신나서 오바하며 팀원들에게 조잘조잘 말했다.
헤헷 ... 진짜 오늘 이거 해결 못하면 영화 안 보려고 했는데 다행이다 내 팝콘..
이 오류는 절대 안까먹을 것 같다.
이거 정리하는데도 3시간은 걸렸는데
정리하면서 한 번 더 복기해보니까 로직이 확확 들어와서 큰 수확이닷.
참 해결하고 나면 별거 아닌 경우가 많은데! 막상 그 상황에서는 눈 앞이 캄캄하다.
그럼에도 해결 못하는 오류는 없다!!!!!!!!!!!!!!!!!!!!!!!!!!!!
약간 나 성장한 것 같은 기분이 드는 데 ..진짜 맞겠지!!
참고사이트
https://velog.io/@hahan/Ajax%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%BC%EA%B9%8C
'Project > javachip' 카테고리의 다른 글
[2nd Project] 구글 API 구현하기 (7) | 2024.10.04 |
---|---|
[2nd Project] 스프링, JSP, Javascript로 네이버 API 로그인 구현하기 (5) | 2024.10.04 |
[2nd Project]³ DBCP 연결 및 DB 불러오기 실패 해결 과정 (2) | 2024.09.24 |
[2nd project] ² JSP를 활용하여 로그인/회원가입 그리고 유효성 검사하기 (9) | 2024.09.18 |
[2nd Project] ¹시작 (5) | 2024.09.18 |