모의 해킹에 사용할 게시판 회원가입 구현
formdata
를 생성한 뒤, fetch
함수로 signupprocess
컨트롤러에 전달한다.
const form = document.getElementById("signup-form");
form.addEventListener("submit", (event) => {
event.preventDefault(); // 기본 제출 동작 막기
const formData = new FormData(form);
const username = formData.get("user_id");
const name = formData.get("name");
const password = formData.get("user_pw");
const address = formData.get("address");
fetch("/hackthebox/signupprocess", {
method: "POST",
headers: [["Content-Type", "application/json; charset=utf-8"]],
body: JSON.stringify({
user_id: username,
name: name,
user_pw: password,
address: address,
}),
})
.then((response) => {
if (!response.ok) {
throw new Error("Network response was not ok");
}
return response.json();
})
.then((result) => {
if (result.signup === "success") {
location.href = "/hackthebox/community";
} else if (result.signup === "duplication") {
alert("이미 가입한 아이디입니다.");
}
})
.catch((error) => {
console.error(error);
alert("회원가입 중 오류가 발생했습니다. 다시 시도해주세요.");
});
});
회원가입에 필요한 파라미터 중에 입력 값이 없으면 에러를 발생한다. 데이터를 정상적으로 받아왔다면 userdao의 UserInsert 메소드로 회원가입을 처리한다.
아이디가 중복되면 JSON
으로 "signup", "duplication"을 전달한다.
@WebServlet("/signupprocess")
public class UserInsertController extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String userId;
String name;
String userPwd;
String address;
JSONObject json;
// JSON 파싱
BufferedReader br = req.getReader();
StringBuilder sb = new StringBuilder();
String line;
while ((line = br.readLine()) != null) {
sb.append(line);
}
br.close();
json = new JSONObject(sb.toString());
userId = json.getString("user_id");
name = json.getString("name");
userPwd = json.getString("user_pw");
address = json.getString("address");
// 입력 값 없을 경우 에러 발생
try {
if (!userId.isEmpty() && !name.isEmpty() && !userPwd.isEmpty() && !address.isEmpty()) {
UserDTO uservo = new UserDTO();
uservo.setUser_id(userId);
uservo.setName(name);
uservo.setPassword(userPwd);
uservo.setAddress(address);
UserDAO userdao = new UserDAO();
int result = userdao.UserInsert(uservo);
if (result == 1) {
// 회원가입 성공 -> 로그인 처리 컨트롤러에서 세션 생성 후 community.jsp 리다이렉트
req.setAttribute("vo", uservo);
req.getRequestDispatcher("loginprocess").forward(req, resp);
} else if (result == 4) {
// 아이디 중복
json = new JSONObject();
json.put("signup", "duplication");
PrintWriter out = resp.getWriter();
resp.setContentType("application/json");
resp.setCharacterEncoding("UTF-8");
out.print(json.toString());
out.flush();
} else {
throw new ServletException("Insert Error");
}
}
} catch (NullPointerException e) {
resp.sendRedirect("api/signup.jsp");
e.printStackTrace();
}
}
}
회원 가입 기능과 함께 구현해야 하는 기능이 아이디 중복 검사 기능이다.
내 생각에 중복 검사 기능을 설계하는 방법은 크게 2가지가 있다.
users
테이블에 존재하는지 쿼리를 사용해 확인한다. UNIQUE
제약 조건 또는 PRIMARY KEY
를 사용한다. 위 방법 외에도 여러 방법이 있겠지만 난 2번 방법으로 결정했다. 처음 테이블을 생성할 때 이미 user_id
컬럼을 PRIMARY KEY
로 설정했기 때문이다.. 이 이유 뿐 아니라 보안적으로도 2번이 더 안전한 설계이기 때문이다. 1번 같은 경우는 테이블에 user_id
컬럼을 UNIQUE
제약 조건 또는 PRIMARY KEY
를 사용하지 않고 설계했을 때 매우 위험하다. 예를 들어 프록시 도구인 burp suite
또는 개발자 도구를 사용하면 클라이언트 측에서 중복 버튼 코드를 제거해 우회가 가능하다.
나는 테이블을 생성할 때 중복을 방지하고자 user_id
컬럼을 PRIMARY KEY
로 지정했다. 따라서 회원 가입 시 중복된 아이디라면 예외가 발생한다. 예외 명은 아래와 같다.
java.sql.SQLIntegrityConstraintViolationException: ORA-00001: unique constraint (INMO.SYS_C007164) violated
아래 코드는 회원 가입 코드이다. 예외가 발생하므로 예외 처리를 하여 중복 검사 기능을 구현하겠다.
public int UserInsert(UserVO vo) {
String query = "INSERT INTO users(user_id, name, password, address) VALUES('" + vo.getUser_id() + "', '" + vo.getName() + "', '" + vo.getPassword() + "', + '" + vo.getAddress() + "')";
getConnect()
int cnt = -1;
try {
stmt = conn.createStatement();
cnt = stmt.executeUpdate(query);
} catch (SQLException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
} finally {
dbClose();
}
SQLIntegrityConstraintViolationException
예외 클래스를 캐치해 Exception
내용이 unique constraint
일 경우 클라이언트 측 에러 코드를 의미하는 숫자 4를 리턴하겠다.
public int UserInsert(UserVO vo) {
String query = "INSERT INTO users(user_id, name, password, address) VALUES('" + vo.getUser_id() + "', '" + vo.getName() + "', '" + vo.getPassword() + "', + '" + vo.getAddress() + "')"; // Oracle query
getConnect();
int cnt = -1;
try {
stmt = conn.createStatement();
cnt = stmt.executeUpdate(query);
} catch (SQLIntegrityConstraintViolationException e) {
e.printStackTrace();
String message = e.getMessage();
if (message.contains("ORA-00001: unique constraint")) {
cnt = 4;
}
} catch (SQLException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
} finally {
dbClose();
}
return cnt; // 1 or 0 or 4
}
UserInsert
메서드는 UserInsertController
에서 실행된다. 따라서 컨트롤러는 메서드가 1
을 리턴하면 로그인 성공을 안내하고 4
를 리턴하면 이미 가입한 아이디라고 안내한다. 만약 0
을 리턴한다면 Insert Error
Exception을 발생시키겠다.
아래 코드는 UserInsertController
의 로그인 성공을 판별하는 코드이다. result
변수가 “4” 라면 클라이언트 측에 이미 가입한 아이디라고 안내한 뒤, 회원가입 페이지로 리다이렉트한다.
UserDAO userdao = new UserDAO();
int result = userdao.UserInsert(uservo); // 리턴 값을 result 변수에 담는다.
if (result == 1) {
req.setAttribute("vo", uservo);
req.getRequestDispatcher("loginprocess").forward(req, resp);
} else if (result == 4) {
resp.setContentType("text/html;charset=utf-8");
PrintWriter out = resp.getWriter();
out.println("<script>alert(\"이미 가입한 아이디입니다.\"); location.href='api/signup.html';</script>");
} else {
throw new ServletException("Insert Error");
}
추후에 해당 코드는 클라이언트 측에 JSON 객체를 응답하는 코드로 수정할 것이다.
public int UserInsert(UserDTO dto) {
String query = "INSERT INTO users(user_id, name, password, address) VALUES(?, ?, ?, ?)";
int cnt = -1; // query 실행 결과
// Connecrtion 객체 생성
conn = driver.getConnect();
try {
psmt = conn.prepareStatement(query);
psmt.setString(1, dto.getUser_id());
psmt.setString(2, dto.getName());
psmt.setString(3, dto.getPassword());
psmt.setString(4, dto.getAddress());
cnt = psmt.executeUpdate(); // 전송(SQL 실행) -> 정상적으로 Insert 되었다면 성공한 행은 1개이므로 1이 리턴된다. Insert 실패하면 0을 리턴한다.
} catch (SQLIntegrityConstraintViolationException e) {
e.printStackTrace();
String message = e.getMessage();
if (message.contains("ORA-00001: unique constraint")) {
cnt = 4;
}
} catch (SQLException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
} finally {
driver.dbClose(psmt, conn);
}
return cnt; // 1 or 0 or 4
}