@Configuration
@Slf4j
public class ApiAuthInterceptor implements HandlerInterceptor {
// 비동기 통신시 인가 처리
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HttpSession session = request.getSession();
if (!LoginUtil.isLoggedIn(session)) {
// 로그인하지 않은 경우 403상태코드를 전송
log.info("인가되지 않은 접근입니다. : {}", request.getRequestURI());
response.sendError(403);
return false;
}
return true;
}
}
// 만들어 놓은 인터셉터들을 스프링 컨텍스트에 등록하는 설정 파일
@Configuration // 설정파일을 의미
@RequiredArgsConstructor
public class InterceptorConfig implements WebMvcConfigurer {
private final AfterLoginInterceptor afterLoginInterceptor;
private final BoardInterceptor boardInterceptor;
private final AutoLoginInterceptor autoLoginInterceptor;
private final ApiAuthInterceptor apiAuthInterceptor;
// 설정 메서드
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 로그인 인터셉터 등록
registry
.addInterceptor(afterLoginInterceptor)
// 해당 인터셉터가 동작할 URL을 설정
.addPathPatterns("/members/sign-up", "/members/sign-in")
;
// 게시판 인터셉터 등록
registry
.addInterceptor(boardInterceptor)
.addPathPatterns("/board/*")
.excludePathPatterns("/board/list", "/board/detail"
, "/board/like", "/board/dislike")
;
// 자동로그인 인터셉터 등록
registry
.addInterceptor(autoLoginInterceptor)
.addPathPatterns("/**"); // 어디로 들어오든 자동로그인
↓ 추가
// REST API 인가 처리 인터셉터 등록
registry
.addInterceptor(apiAuthInterceptor)
.addPathPatterns("/api/v1/**")
.excludePathPatterns("/api/v1/replies/*/page/*");
}
}
// 서버 api에 호출을 수행하는 함수
export const callApi = async (url, method = 'GET', payload = null) => {
const requestInfo = {
method,
};
if (payload) {
requestInfo.headers = {'content-type': 'application/json'};
requestInfo.body = JSON.stringify(payload);
}
const res = await fetch(url, requestInfo);
if (res.status === 403) {
alert('접근 권한이 없습니다.');
window.location.href = '/members/sign-in';
return null;
}
return await res.json();
};
<c:if test="${login == null}">
<a href="/members/sign-in">댓글은 로그인 후 작성해주세요!!</a>
</c:if>
<c:if test="${login != null}">
<div class="row">
<div class="col-md-9">
<div class="form-group">
<label for="newReplyText" hidden>댓글 내용</label>
<textarea rows="3" id="newReplyText" name="replyText" class="form-control"
placeholder="댓글을 입력해주세요."></textarea>
</div>
</div>
<div class="col-md-3">
<div class="form-group">
<label for="newReplyWriter" hidden>댓글 작성자</label>
<input id="newReplyWriter" name="replyWriter" type="text" value="${login.nickname}"
class="form-control" placeholder="작성자 이름" style="margin-bottom: 6px;" readonly>
<button id="replyAddBtn" type="button" class="btn btn-dark form-control">등록
</button>
</div>
</div>
</div>
</c:if>
-> 계정이 필요
// db와 1대1 매칭되는 객체
public class Reply {
private long replyNo;
@Setter
private String replyText;
private String replyWriter;
private LocalDateTime replyDate;
private long boardNo;
private String account;
}
<insert id="save">
INSERT INTO tbl_reply
(reply_text, reply_writer, board_no, account)
VALUES
(#{replyText}, #{replyWriter}, #{boardNo}, #{account})
</insert>
// 댓글 입력
public boolean register(ReplyPostDto dto, HttpSession session) {
Reply reply = Reply.builder()
.replyText(dto.getText())
.replyWriter(dto.getAuthor())
.boardNo(dto.getBno())
↓ 추가
.account(LoginUtil.getLoggedInUserAccount(session))
.build();
boolean flag = replyMapper.save(reply);
if (flag) log.info("댓글 등록 성공 - {}", dto);
else log.warn("댓글 등록 실패");
return flag;
}
public class ReplyDetailDto {
private long rno;
private String text;
private String writer;
// @JsonFormat(pattern = "yyyy년 MM월 dd일 HH:mm")
private LocalDateTime createAt;
private String account; // 댓글 작성자 계정명
// 엔터티를 DTO로 변환하는 생성자
public ReplyDetailDto(Reply r) {
this.rno = r.getReplyNo();
this.text = r.getReplyText();
this.writer = r.getReplyWriter();
this.createAt = r.getReplyDate();
this.account = r.getAccount();
}
}
public class ReplyListDto {
// ReplyDetailDto의 형태
// ReplyDetailDto를 필드로 만들면서 배열의 이름이 생김 (replies)
/*
[
{}, {}, {}
]
*/
@Setter
private LoginUserInfoDto loginUser;
private PageMaker pageInfo;
private List<ReplyDetailDto> replies;
}
function appendReplies({ replies, loginUser }) {
// 댓글 목록 렌더링
console.log(replies);
let tag = '';
if (replies && replies.length > 0) {
replies.forEach(({ rno, writer, text, createAt, account: replyAccount }) => {
tag += `
<div id='replyContent' class='card-body' data-reply-id='${rno}'>
<div class='row user-block'>
<span class='col-md-3'>
<b>${writer}</b>
</span>
<span class='offset-md-6 col-md-3 text-right'><b>${getRelativeTime(
createAt
)}</b></span>
</div><br>
<div class='row'>
<div class='col-md-9'>${text}</div>
<div class='col-md-3 text-right'>
`;
// 관리자이거나 내가 쓴 댓글일 경우만 조건부 렌더링
// 로그인한 회원 권한, 로그인한 회원 계정명, 해당 댓글의 계정명
if (loginUser) { // 로그인 유저가 존재하면~
const {auth, account: loginUserAccount} = loginUser;
if (auth === 'ADMIN' || replyAccount === loginUserAccount) {
tag += `<a id='replyModBtn' class='btn btn-sm btn-outline-dark' data-bs-toggle='modal' data-bs-target='#replyModifyModal'>수정</a>
<a id='replyDelBtn' class='btn btn-sm btn-outline-dark' href='#'>삭제</a>
`;
}
}
tag += `</div>
</div>
</div>
`;
});
} else {
tag = `<div id='replyContent' class='card-body'>댓글이 아직 없습니다! ㅠㅠ</div>`;
}
document.getElementById('replyData').innerHTML += tag;
console.log('append replies');
// 로드된 댓글 수 업데이트
loadedReplies += replies.length;
}