로그인을 하면 백엔드 서버로부터 Access Token과 Refresh Token을 전달받는다.
그리고 전달받은 토큰들을 Cookie에 저장한다.
토큰들은 JWT 토큰인데, 이 중 Access Token을 해독해서 Store에 저장한다.
Header에 닉네임 표시- 유저의
id를 조회없이 바로 사용
위 2가지 이유로 Store에 토큰을 해독해서 토큰에 담긴 정보들을 저장해놓는 것이다.
나는 아래 VueJwtDecode를 사용했었는데 Header에 나온 한글 닉네임이 깨지는 문제가 발생했다.
import VueJwtDecode from "vue-jwt-decode";
한글이 깨지는 문제는 주로 JWT 토큰을 생성할 때 인코딩 또는 디코딩 방식에서 발생한다.
특히, JWT 토큰에 한글을 포함하여 인코딩하거나 디코딩할 때 문제가 발생할 수 있다.
이를 해결하려면, 인코딩 및 디코딩 과정에서 UTF-8을 명시적으로 사용하거나,
Base64 인코딩 및 디코딩을 처리할 때 적절한 조치를 취해야 한다.
먼저 서버에서 JWT 토큰을 생성할 때, 한글이 깨지지 않도록 UTF-8로 인코딩되었는지 확인해야 한다.
하지만 대부분의 JWT 라이브러리는 기본적으로 UTF-8을 지원하기 때문에
백엔드 서버의 문제는 아니라고 판단했다.
클라이언트 측에서 JWT 디코딩을 할 때
한글이 깨지지 않도록 인코딩된 Base64 URL을 올바르게 처리해야 한다.
나는 Vue.js에서 JWT를 디코딩할 때 VueJwtDecode 라이브러리를 사용했는데,
이 라이브러리를 사용하면 기본적으로 Base64 인코딩된 문자열을 디코딩할 때 한글이 깨질 수 있다.
그래서 아래 코드로 수정하고 나서 한글로 정상적으로 Store에 저장되고 헤더에 출력되었다.
@/utils/jwtDecode.jsfunction base64UrlDecode(str) {
try {
// Base64 URL -> Base64 변환
str = str.replace(/-/g, '+').replace(/_/g, '/');
// Base64 디코딩
const decodedStr = atob(str);
// UTF-8 디코딩
const bytes = new Uint8Array(decodedStr.split('').map(char => char.charCodeAt(0)));
const decodedText = new TextDecoder('utf-8').decode(bytes);
return JSON.parse(decodedText);
} catch (e) {
console.error("Failed to decode base64 string:", e);
return null;
}
}
// VueJwtDecode.decode() 대신에 custom decode 사용
export function customJwtDecode(token) {
const base64Url = token.split('.')[1];
return base64UrlDecode(base64Url);
}
이 코드의 목적은 JSON Web Token (JWT)의 페이로드 부분을 디코딩하여 객체로 변환하는 것이다.
JWT는 세 부분으로 구성된 문자열로, 각각은 Base64 URL 인코딩된 헤더, 페이로드, 서명이다.
이 코드에서는 페이로드 부분을 디코딩한다.
// Base64 URL -> Base64 변환
str = str.replace(/-/g, '+').replace(/_/g, '/');
JWT는 Base64 URL 인코딩을 사용한다.
URL-safe 버전의 Base64는
+->-
/->_
로 변환된다.
따라서, 이 코드에서는 먼저 이러한 문자를 다시 원래의 Base64 포맷으로 변환한다.
// Base64 디코딩
const decodedStr = atob(str);
atob() 함수는 Base64로 인코딩된 문자열을 디코딩한다.
이 함수는 브라우저에서 제공하는 기본 함수로, 디코딩된 결과는 바이너리 데이터를 표현하는 문자열이다.
각 문자 코드는 바이트 데이터(0-255)를 나타낸다.
// UTF-8 디코딩
const bytes = new Uint8Array(decodedStr.split('').map(char => char.charCodeAt(0)));
const decodedText = new TextDecoder('utf-8').decode(bytes);
Base64로 인코딩된 데이터가 UTF-8로 인코딩된 텍스트를 포함하고 있다고 가정하고,
이를 다시 UTF-8 문자열로 변환한다.
Uint8Array는 8비트 부호 없는 정수 배열을 생성하여,
각 문자 코드(charCodeAt(0))를 해당 배열의 요소로 변환한다.
TextDecoder('utf-8')는 UTF-8로 인코딩된 바이트 배열을 문자열로 디코딩하는 객체이다.
return JSON.parse(decodedText);
디코딩된 문자열은 JSON 형식이므로, JSON.parse()를 사용하여 이를 JavaScript 객체로 변환한다.
export function customJwtDecode(token) {
const base64Url = token.split('.')[1];
return base64UrlDecode(base64Url);
}
JWT는 세 부분으로 구성된 문자열이다. (header.payload.signature)
각 부분은 .으로 구분된다.
이 함수는 JWT 문자열을 .으로 분할하여 두 번째 부분인 페이로드(payload)를 가져온다.
Base64 URL 인코딩 처리
JWT는 Base64 URL 인코딩을 사용하기 때문에, 일반 Base64 디코딩 함수를 사용하기 전에URL-safe 문자(-, _)를 일반 Base64 문자(+, /)로 변환해야 한다.UTF-8 지원
JWT 페이로드는 UTF-8로 인코딩된 JSON 문자열로 되어 있기 때문에,Base64로 디코딩된 바이트 배열을 다시 UTF-8 문자열로 변환해야 한다.Uint8Array와 TextDecoder를 사용한다.JSON 파싱
JSON 형식이므로, 이를 파싱하여 JavaScript 객체로 변환한다.HeaderComponent.vue<script>
import { useMemberStore } from "/src/stores/useMemberStore";
import { getTokenFromCookie, deleteTokenCookies } from "@/utils/authCookies";
import { customJwtDecode } from "@/utils/jwtDecode";
export default {
name: "HeaderComponents",
computed: {
decodedToken() {
const store = useMemberStore();
return store.decodedToken;
},
},
created() {
const store = useMemberStore();
const accessToken = getTokenFromCookie('accessToken');
if (accessToken) {
try {
const decoded = customJwtDecode(accessToken);
// VueJwtDecode 대신 custom 디코딩 함수 사용
// ...
}
}
};
</script>

