👥🚨 수정시간 및 수정하기 작성자 권한 변경

2026. 2. 14. 16:57·프로젝트/스프링 부트 3 백엔드 개발자 되기 Blog + 선착순 강의 프로젝트

트러블슈팅: “작성자만 게시글 수정/삭제” 구현하기 (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") 확인

❌ 버튼이 계속 안 보임

대부분 아래 중 하나:

  1. author-email hidden input이 html에 없음
  2. DTO에 authorEmail을 안 넣어서 Thymeleaf에 값이 null
  3. 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
'프로젝트/스프링 부트 3 백엔드 개발자 되기 Blog + 선착순 강의 프로젝트' 카테고리의 다른 글
  • 👥💡 Spring Data JPA + Querydsl 커스텀 Repository 구조 정리
  • 👥💡 Querydsl 페이징: fetchResults()가 비권장(Deprecated)된 이유와 실무 정석 패턴
  • 👥🚨 수정은 됐는데 화면에는 “실패”라고 뜬다?
  • 👥💡 N+1 리팩토링 과정
hak0622
hak0622
개발하면서 “이게 뭐지?”라는 순간마다 궁금한 점을 바탕으로 정리한 개발 블로그입니다.
  • hak0622
    궁금한 개발 이야기 Why?
    hak0622
  • 전체
    오늘
    어제
    • 분류 전체보기 (68)
      • 공부 (36)
        • 1. 자바 ORM 표준 JPA 프로그래밍 - 기본.. (35)
        • 시험 (1)
      • 프로젝트 (32)
        • 스프링 부트 3 백엔드 개발자 되기 Blog + .. (32)
  • 인기 글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.5
hak0622
👥🚨 수정시간 및 수정하기 작성자 권한 변경
상단으로

티스토리툴바