CSS & BootStrap
.classList.add("클래스명")
.classList.remove("클래스명")
.classList.toggle("클래스명")
<div class="mb-3">
<label for="userName" class="form-label">아이디</label>
<input type="text" id="userName" class="form-control" />
<div class="invalid-feedback">5글자 이상 입력하세요</div>
<div class="valid-feedback">사용 가능한 아이디입니다</div>
</div>
// userName 입력란의 참조값
const userNameInput = document.querySelector("#userName");
userNameInput.addEventListener("input", ()=>{
// 입력한 userName 을 읽어온다
const userName = userNameInput.value;
// 입력한 값을 검증한다
const userNameValid = userName.length >= 5 ? true : false;
// 일단 모든 클래스를 제거하고
userNameInput.classList.remove("is-valid", "is-invalid");
// 검증 결과에 맞는 클래스를 추가한다
userNameInput.classList.add(userNameValid ? "is-valid":"is-invalid");
});
regular expression 정규 표현식을 사용하면 더 간단하게 가능
<div class="mb-3">
<label for="pwd" class="form-label">비밀번호</label>
<input type="password" id="pwd" class="form-control" />
<div class="form-text text-muted">
비밀번호는 8글자 이상 입력하세요
</div>
<div class="invalid-feedback">비밀번호를 확인하세요</div>
<div class="valid-feedback">사용 가능한 비밀번호입니다</div>
</div>
// pwd 입력란의 참조값
const pwdInput = document.querySelector("#pwd");
pwdInput.addEventListener("input", ()=>{
// 입력한 pwd 을 읽어온다
const pwd = pwdInput.value;
// 입력한 값을 검증한다
const pwdValid = pwd.length >= 8 ? true : false;
// 일단 모든 클래스를 제거하고
pwdInput.classList.remove("is-valid", "is-invalid");
// 검증 결과에 맞는 클래스를 추가한다
pwdInput.classList.add(pwdValid ? "is-valid":"is-invalid");
});
<div class="mb-3">
<label for="pwd2" class="form-label">비밀번호 확인</label>
<input type="password" id="pwd2" class="form-control" />
<div class="invalid-feedback">비밀번호가 일치하지 않습니다</div>
<div class="valid-feedback">비밀번호가 일치합니다</div>
</div>
// pwd2 입력란의 참조값
const pwd2Input = document.querySelector("#pwd2");
pwd2Input.addEventListener("input", ()=>{
pwdEqualValidate();
});
function pwdEqualValidate(){
// 입력한 2개의 비밀번호를 읽어와서
const pwd = pwd2Input.value;
const pwd2 = pwdInput.value;
// 일단 모든 클래스를 제거하고
pwd2Input.classList.remove("is-valid", "is-invalid");
// 두 비밀번호가 같은지 확인해서 적절한 클래스를 추가한다
pwd2Input.classList.add(pwd == pwd2 ? "is-valid" : "is-invalid");
}
pwdEqualValidate();
JAVA & JSP
<div class="d-none form-div">
<form action="save-comment.jsp" method="post">
<!-- 원글의 글번호, 댓글 대상자의 userName, 댓글의 그룹번호도 같이 전송해야 한다 -->
<input type="hidden" name="parentNum" value="<%=dto.getNum()%>"/>
<input type="hidden" name="targetWriter" value="<%= tmp.getWriter()%>"/>
<input type="hidden" name="groupNum" value="<%=getGroupNum()%>"/>
<textarea name="content" class="form-control mb-2" rows="2"
placeholder="댓글을 입력하세요"></textarea>
<button type="submit" class="btn btn-sm btn-outline-primary">등록</button>
<button type="reset" class="btn btn-sm btn-outline-secondary cancel-reply-btn">취소</button>
</form>
</div>
⭐ 댓글의 글번호가 안 넘어오면 댓글, 넘어오면 대댓글
<%
// 댓글의 그룹번호가 넘어오는지 읽어와 본다 (null 이면 원글의 댓글 저장 요청이다)
String strGroupNum = request.getParameter("groupNum");
// 댓글의 그룹번호를 담을 변수를 미리 만들고
int groupNum = 0;
// 만일 댓글의 그룹번호가 넘어 온다면 (댓글의 댓글이라면)
if(strGroupNum != null){
groupNum = Integer.parseInt(strGroupNum);
}
// 만일 원글의 댓글이면
if(groupNum == 0){
dto.setGroupNum(num); // 원글의 댓글은 자신의 글번호가 댓글의 글번호이다
} else{
dto.setGroupNum(groupNum); // 대댓글은 전송된 그룹번호가 댓글의 그룹 번호이다
}
%>
<div class="comments">
<%for(CommentDto tmp : commentList) {%>
<!-- 대댓글은 자신의 글번호와 댓글의 그룹번호가 다르다, 그런 경우 왼쪽 마진을 부여 -->
<div class="card mb-3 <%=tmp.getNum() == tmp.getGroupNum() ? "":"ms-5"%>">
<%if(tmp.getDeleted().equals("yes")) {%>
<!-- 삭제된 댓글 -->
<div class="card-body bg-light text-muted rounded">삭제된 댓글입니다</div>
<%}else {%>
<!-- 삭제되지 않은 댓글 -->
<div class="card-body d-flex flex-column flex-sm-row position-relative">
<%if(tmp.getNum() != tmp.getGroupNum()){ %>
<i class="bi bi-arrow-return-right position-absolute" style="top:0;left:-30px"></i>
<%} %>
.
.
.
</div>
⭐ 원글의 댓글을 자신의 num 과 groupNum 이 같지만
댓글의 댓글은 자신의 num 과 groupNum 이 다르다
➜ groupNum이 모두 같다
<%
// role 정보도 저장한다
session.setAttribute("role", dto.getRole());
%>
<%
// 세션 영역에 저장된 모든 정보 삭제
session.invalidate();
%>
isAuthorized 메소드 추가// role 정보 얻어오기
String role = (String)session.getAttribute("role");
// 권한 체크
// 만일 Authorized() 메소드가 false 를 리턴한다면 (접근 불가하다고 판정이 된다면)
if(!isAuthorized(path, role)) {
// 금지된 요청이라고 응답하고
res.sendError(HttpServletResponse.SC_FORBIDDEN, "접근 권한이 없습니다");
return; // 메소드를 여기서 끝낸다
}
// 역할 기반 권한 검사
// 클라이언트의 요청 경로와 role 정보를 넣어서 접근 가능한지 여부를 리턴하는 메소드
private boolean isAuthorized(String path, String role) {
if ("ROLE_ADMIN".equals(role)) {
return true; // 모든 경로 접근 허용
} else if ("ROLE_STAFF".equals(role)) {
// "/admin/" 하위 경로를 제외한 모든 경로 접근 허용
return !path.startsWith("/admin/");
} else if ("ROLE_USER".equals(role)) {
// "/admin/" 하위와 "/staff/" 하위를 제외한 모든 경로 접근 허용
return !path.startsWith("/admin/") && !path.startsWith("/staff/");
}
return false; // unknown role
}
⚠️ webapp 폴더 안에 admin, staff 폴더 생성
admin 폴더에서 index.jsp 파일 생성<div class="container">
<h1>관리자 인덱스 페이지</h1>
<p>role이 <strong>ROLE_ADMIN</strong> 인 사용자만 이 페이지를 볼 수 있습니다</p>
</div>
staff 폴더에서 index.jsp 파일 생성<div class="container">
<h1>직원 인덱스 페이지</h1>
<p>
role이 <strong>ROLE_STAFF</strong> 또는
<strong>ROLE_ADMIN</strong> 인 사용자만 이 페이지를 볼 수 있습니다
</p>
</div>
<ul>
<li><a href="${pageContext.request.contextPath}/admin/index.jsp">관리자</a></li>
<li><a href="${pageContext.request.contextPath}/staff/index.jsp">직원</a></li>
</ul>
UPDATE users
SET role='ROLE_ADMIN'
WHERE userName='superman';
UPDATE users
SET role='ROLE_STAFF'
WHERE userName='batman';
⭐ 관리자 페이지는 role 이 ROLE_ADMIN 인 사용자만 볼 수 있고
직원 페이지는 role 이 ROLE_ADMIN, ROLE_STAFF 인 사용자가 볼 수 있다