트러블슈팅: “작성자만 게시글 수정/삭제” 구현하기 (Spring + Thymeleaf + JWT)
1️⃣ 목표
게시글 상세 페이지(/articles/{id})에서
- ✅ 작성자 본인이면 수정하기 / 삭제 버튼이 보인다
- ✅ 다른 사람 글이면 버튼이 아예 안 보인다
- ✅ 서버에서도 권한 체크를 해서 실제로 수정/삭제 요청이 오면 막는다 (중요)
버튼 숨기는 건 “사용자 경험(UI)”이고,
서버 권한 체크는 “보안”이라서 둘 다 해야 한다.
2️⃣ 문제 상황 (왜 버튼이 안 보이거나, 이상하게 동작했나?)
처음엔 “작성자만 수정 가능” 로직이 서버에만 있었음.
그래서 생긴 문제가 2개:
문제 A) 다른 사람 글도 수정하기 버튼은 보임
- 사용자가 버튼 누르고 수정 화면까지 들어가버림
- 실제 수정 요청을 보내면 서버에서 막힘 → "수정 실패했습니다."
➡ 사용자 입장에서는 “왜 들어가게 해놓고 실패시키지?” 라는 느낌이 듦
문제 B) 심지어 내 글도 수정하기 버튼이 안 보임
로그를 찍어보면:
loginUserEmail = anonymousUser
즉, 서버가 보는 인증 정보가 없거나(로그인 안 됨),
프론트에서 토큰이 제대로 존재하지 않거나/파싱이 안 되는 상황.
3️⃣ 해결 방향(핵심 아이디어)
상세 페이지에서 버튼을 보여줄지 말지는 프론트(UI) 에서 결정하게 하되,
- 서버에서 게시글 작성자 이메일을 내려주고
- 프론트에서 내 JWT access_token 안에 있는 이메일(sub)을 꺼내서
- 둘이 같으면 버튼 표시
즉, 작성자 이메일(authorEmail) vs 내 이메일(myEmail = token payload sub)
이 둘을 비교한다.
✅ 단계별 해결 방법
Step 1) 서버가 “작성자 이메일”을 화면에 내려주기
기존 DTO ArticleViewResponse에는 작성자 닉네임만 있었음.
하지만 버튼 제어하려면 “작성자 이메일”이 필요함.
⚒️ 변경: ArticleViewResponse 에 authorEmail 추가
public class ArticleViewResponse {
private Long id;
private String title;
private String content;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
private String author;
private String authorEmail;
public ArticleViewResponse(Article article) {
this.id = article.getId();
this.title = article.getTitle();
this.content = article.getContent();
this.createdAt = article.getCreatedAt();
this.updatedAt = article.getUpdatedAt();
this.author = article.getAuthor().getNickname();
this.authorEmail = article.getAuthor().getEmail(); //✅ 추가
}
}
Step 2) article.html에서 작성자 이메일을 hidden input으로 심기
Thymeleaf에서 JS가 읽을 수 있게 숨겨서 넣는다.
<input type="hidden"id="author-email"th:value="${article.authorEmail}">
그리고 버튼은 기본적으로 안 보이게 숨겨둔다.
<button id="modify-btn"style="display:none">수정하기</button>
<button id="delete-btn"style="display:none">삭제</button>
➡ 이렇게 해두면 JS가 조건 맞을 때만 보이게 만들 수 있음.
Step 3) 프론트에서 JWT 토큰에서 내 이메일 꺼내기
로그인을 하면 localStorage에 access_token이 저장되어 있음.
localStorage.getItem('access_token')
JWT는 점(.)으로 3부분으로 나뉨: header.payload.signature
여기서 payload를 Base64URL 디코딩하면 JSON이 나오고, 보통 sub에 이메일이 있음.
그래서 아래처럼 parseJwt()를 만들어서 sub를 뽑는다.
Step 4) 내 이메일(sub)과 작성자 이메일(authorEmail)을 비교해서 버튼 표시
핵심 로직은 이거 하나임:
- 내 이메일 == 작성자 이메일 → 버튼 보임
- 다르면 → 그대로 숨김
최종 코드 (article.js에 추가한 핵심 코드)
👉 이 코드는 상세 페이지에서만 작동하도록 요소가 없으면 그냥 종료하게 되어있음.
/**
* JWT 토큰의 Payload 부분을 디코딩하여 JSON 객체로 반환합니다.
*/
function parseJwt(token) {
try {
const base64Url = token.split('.')[1];
if (!base64Url) return null;
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
const jsonPayload = decodeURIComponent(
atob(base64)
.split('')
.map(c => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2))
.join('')
);
return JSON.parse(jsonPayload);
} catch (e) {
console.error("JWT 파싱 실패:", e);
return null;
}
}
/**
* 현재 로그인한 사용자와 게시글 작성자가 일치할 경우 수정/삭제 버튼을 노출합니다.
*/
function showButtonsIfAuthor() {
const authorEmailInput = document.getElementById('author-email');
const modifyBtn = document.getElementById('modify-btn');
const deleteBtn = document.getElementById('delete-btn');
// 필수 요소가 없으면 중단
if (!authorEmailInput || !modifyBtn || !deleteBtn) return;
const token = localStorage.getItem('access_token');
if (!token) return;
const payload = parseJwt(token);
if (!payload) return;
// JWT의 sub(subject) 또는 email 필드 확인
const myEmail = payload.sub || payload.email;
const authorEmail = authorEmailInput.value;
// 이메일이 일치하면 버튼 표시
if (myEmail === authorEmail) {
modifyBtn.style.display = 'inline-block';
deleteBtn.style.display = 'inline-block';
}
}
// DOM 로드 완료 시 실행
document.addEventListener('DOMContentLoaded', () => {
showButtonsIfAuthor();
});
4️⃣ 왜 이렇게 해야 하나?
✅ 서버에서만 막으면 안 되나?
서버만 막아도 “보안”은 지켜짐.
하지만 사용자는 버튼을 눌러서 수정 화면까지 갔다가 실패를 보게 됨.
➡ UX가 나빠서 “작성자만 버튼 보이게” 하는 처리가 필요함.
✅ 프론트에서만 막으면 안 되나?
프론트는 쉽게 뚫림.
예: 개발자 도구에서 JS를 조작해서 PUT 요청을 보내면 끝.
➡ 그래서 서버에서 authorizeArticleAuthor()로 최종 검증은 반드시 해야 함.
5️⃣ 중간에 겪은 대표 오류와 원인
❌ loginUserEmail = anonymousUser
- 로그인이 안 되어있거나
- Spring Security Context에 인증이 안 들어가있거나
- access_token이 없거나
➡ 해결: 브라우저에서 localStorage.getItem("access_token") 확인
❌ 버튼이 계속 안 보임
대부분 아래 중 하나:
- author-email hidden input이 html에 없음
- DTO에 authorEmail을 안 넣어서 Thymeleaf에 값이 null
- JWT payload에 sub가 아니라 다른 필드에 이메일이 들어감
➡ 해결: 콘솔에서 payload 출력해서 확인
6️⃣ 결론
작성자만 수정/삭제 구현은
✅ 서버 권한 체크(필수)
✅ 프론트 UI 버튼 숨김(UX 개선)
이 둘을 같이 해야 완성이다.
'프로젝트 > 스프링 부트 3 백엔드 개발자 되기 Blog + 선착순 강의 프로젝트' 카테고리의 다른 글
| 👥💡 Spring Data JPA + Querydsl 커스텀 Repository 구조 정리 (0) | 2026.02.14 |
|---|---|
| 👥💡 Querydsl 페이징: fetchResults()가 비권장(Deprecated)된 이유와 실무 정석 패턴 (0) | 2026.02.14 |
| 👥🚨 수정은 됐는데 화면에는 “실패”라고 뜬다? (1) | 2026.02.07 |
| 👥💡 N+1 리팩토링 과정 (1) | 2026.02.07 |
| 👥🚨 로컬로 실행 후 배포한 서버에도 DB가 적용되는 이유 (0) | 2026.02.07 |