Post

️[매일매일]프론트엔드 기술 일기: 인증과 인가, 그리고 나의 삽질기: JWT와 401 에러를 마주하며

“아니, 분명 로그인했는데 왜 또 401이야…”

Greenwear 프로젝트를 진행하면서 가장 많이 마주쳤던 에러 중 하나가 바로 401 Unauthorized 였습니다.특히 로그인 API를 /api/login으로 호출하면서 시큐리티 에러와 JWT 토큰 관련 문제로 정말 골머리를 앓았던 기억이 나네요.더군다나 이 프로젝트에서는 Cursor AI, Gemini CLI 같은 AI 도구를 적극 활용했기 때문에 더 빠르게 개발할 수 있었지만, 동시에 중요한 부분을 당연한 것처럼 넘기거나 누락해 실수한 경우도 많았습니다.이번 글에서는 그때의 경험을 바탕으로 인증/인가 개념, 간단한 예제, 그리고 왜 AI를 써도 401을 맞았는지까지 정리해 보겠습니다.

📚 인증(Authentication)과 인가(Authorization)란?✅ 인증 (Authentication)

✅ 인가 (Authorization)

🔍 Greenwear 프로젝트에서의 삽질기🌱 Greenwear 프로젝트에서는 JWT를 활용한 로그인 인증을 구현했는데요, 초기에는 아래처럼만 생각했습니다:

if (userId && password) { return jwt.sign({ userId }, SECRET); }

로그인하면 토큰을 클라이언트에 주고, API 요청 시 헤더에 Authorization: Bearer <토큰>을 붙이는 구조였죠.그런데…로그인 후에도 계속 401 Unauthorized 에러.관리자 권한이 필요한 API에 일반 계정으로 접근해도 에러가 안 나는 문제.토큰 유효성 검증 누락으로 보안 취약점.

🚨 /api/login 삽질기특히 이 부분이 핵심이었어요.로그인 API 요청을 클라이언트에서 /api/login으로 보내면서, 백엔드에서는 /login만 처리하도록 구현해 둔 상태였습니다.결과:/login → 정상 동작/api/login → 401 혹은 404 이유는 2가지였습니다:1. 라우팅 불일치

app.post(“/login”, handler);

서버는 /login만 처리하는데, 클라이언트는 /api/login으로 요청 → 매칭 실패.2. 시큐리티 설정 문제​스프링 시큐리티 설정을 이렇게 해놨었습니다.

.antMatchers(“/login”).permitAll() .antMatchers(“/api/**”).authenticated();

즉, /api/** 경로는 인증을 요구했고, /api/login도 거기에 포함돼서 로그인조차 인증을 요구하는 웃픈 상황이 된 겁니다ㅎㅎ ^^

🤖 AI를 쓰면 더 빨라지지만 더 위험할 수도 있다이 프로젝트에서는 빠른 개발을 위해 Cursor AI와 Gemini CLI를 적극 활용했습니다.코드 스니펫을 자동 완성해 주고,시큐리티 설정도 추천해 줬고,JWT 유틸 함수도 AI가 생성해줬습니다.개발 속도는 정말 빨랐습니다.하지만 문제는…

“왜 API 요청 경로와 시큐리티 설정이 불일치하는데도 난 못 봤지?”

AI가 작성해준 코드가 잘못된 건 아니지만, 제가 리뷰를 꼼꼼히 하지 않아 중요한 부분을 간과한 게 원인이었습니다.AI를 쓸수록 기본 개념을 알고 꼼꼼히 체크해야 한다는 교훈을 얻었습니다.

🧪 실습 예제: JWT로 인증 + 인가1️⃣ 인증 예제: 로그인 & 토큰 발급

app.post(“/login”, (req, res) => { const { username, password } = req.body; if (username === “admin” && password === “secret”) { const token = jwt.sign({ username, role: “admin” }, SECRET, { expiresIn: “1h” }); res.json({ token }); } else { res.status(401).send(“Invalid credentials”); } });

2️⃣ 인가 예제: 권한 확인

const authMiddleware = (req, res, next) => { const token = req.headers.authorization?.split(“ “)[1]; if (!token) return res.status(401).send(“Unauthorized”);

try { const decoded = jwt.verify(token, SECRET); req.user = decoded; next(); } catch { res.status(401).send(“Invalid token”); } };

app.get(“/admin”, authMiddleware, (req, res) => { if (req.user.role !== “admin”) { return res.status(403).send(“Forbidden: Insufficient rights”); } res.send(“Welcome, admin!”); });

🔗 정리: 왜 헷갈렸을까?

구분

정의

Greenwear 당시 실수

인증

사용자가 본인인지 확인

토큰만 발급하고 끝낸 줄 앎

인가

권한 있는지 확인

권한 체크 로직을 누락함

경로

로그인 요청 경로가 맞는지

서버는 /login, 클라는 /api/login

AI

빠르지만 검증 필요

기본을 확인하지 않고 자동 완성만 믿음

🌟 마무리하며✅ 인증과 인가는 다르다.✅ 인증은 로그인, 인가는 권한 확인.✅ 로그인 API의 경로와 시큐리티 설정은 반드시 일치시켜야 한다.✅ 토큰에는 role 등 권한 정보를 넣고 백엔드에서 검증하자.✅ AI가 빠르다고 해서 기본 개념을 소홀히 하면 안 된다.Greenwear 프로젝트를 하며 이런 삽질을 경험한 덕분에, 지금은 시큐리티 설정을 할 때 가장 먼저 인증/인가를 분리하고 /login 경로를 명시적으로 permitAll()로 설정해 두는 습관이 생겼습니다.그리고 무엇보다, AI의 도움을 받더라도 내가 이해하고 검증하는 게 중요하다는 걸 배웠습니다.

📖 참고 자료 및 출처https://www.okta.com/ko-kr/identity-101/authentication-vs-authorization/

Authentication vs. Authorization | Okta Products Why Okta Developers Resources Authentication vs. Authorization 업데이트됨: 2024년09월02일 Time to read: ~ Topics IAM 목차 공유 Ready to make Identity a business advantage? Sign up today. Get started What’s the difference between authentication and authorization?  Authentication confirms that users are … www.okta.com

https://www.okta.com/ko-kr/identity-101/role-based-access-control-vs-attribute-based-access-control/

RBAC vs. ABAC: Definitions & When to Use | Okta 공유 Identity management techniques protect your sensitive digital assets. But what form should that protection take? Knowing the difference between role-based access control (RBAC) vs. attribute-based access control (ABAC) can help you make a smart decision. The main difference between RBAC vs. ABAC … www.okta.com

https://www.youtube.com/watch?v=xCqpxiPXnCk

​

This post is licensed under CC BY 4.0 by the author.