
1. verify() 만들기
package shop.mtcoding.blog._core.utils;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import shop.mtcoding.blog.user.SessionUser;
import shop.mtcoding.blog.user.User;
import java.util.Date;
public class JwtUtil {
// 토큰 생성
public static String create(User user){ // user 객체가 필요함
String jwt = JWT.create()
.withSubject("blog")
.withExpiresAt(new Date(System.currentTimeMillis() + 1000L * 60L * 60L))
.withClaim("id", user.getId())
.withClaim("username", user.getUsername())
.sign(Algorithm.HMAC512("metacoding")); // 나중에 환경변수(OS변수)로 사용해야 함
return jwt;
}
public static void verify(String jwt){
DecodedJWT decodedJWT = JWT.require(Algorithm.HMAC512("metacoding")).build().verify(jwt);
int id = decodedJWT.getClaim("id").asInt();
// 임시 세션을 만들면 꺼내쓰기 쉬움
// String username = decodedJWT.getClaim("username").asString();
// SessionUser sessionUser = new SessionUser();
}
}
- 세션에 뭘 넣어준 적이 없으니까 더 이상 sessionUser는 필요 없음
- 토큰에 있는 정보를 사용할 때마다 인증해서 받아와야 함
인증 받지 못하면 데이터를 받아올 수 없음


2. 토큰을 세션에 저장하는 방식
- 세션 저장소 설정 : 먼저 애플리케이션의 세션 저장소를 설정
클라이언트와 서버 간의 세션 데이터를 저장하고 관리하는데 사용
주로 서버 메모리, 데이터베이스, 레디스와 같은 외부 저장소를 사용
- 토큰 생성 : 사용자가 로그인하거나 인증 프로세스를 거치면, 서버는 사용자의 정보를 기반으로 토큰을 생성
사용자의 세션을 식별하는 데 사용
- 세션에 토큰 저장: 토큰이 생성되면 세션 저장소에 저장됩니다. 이 때 세션 ID를 키로 사용하여 토큰을 저장합니다.
- 클라이언트와 서버 간의 통신: 클라이언트는 로그인 후에 서버로부터 받은 토큰을 저장합니다. 보통은 쿠키나 로컬 스토리지에 저장됩니다. 이 토큰은 클라이언트와 서버 간의 모든 통신에 사용됩니다.
- 토큰 유효성 검사: 클라이언트가 요청을 보낼 때마다 서버는 토큰을 검증하여 사용자의 세션을 확인합니다. 이를 통해 사용자의 인증 및 권한 부여를 수행할 수 있습니다.
- 토큰 갱신 및 만료 관리: 토큰은 일정 기간 후에 만료될 수 있습니다. 만료 전에는 토큰을 갱신하여 세션을 유지할 수 있습니다. 또한 만료된 토큰은 폐기되어야 하며, 사용자가 로그아웃할 때 세션에서 토큰을 제거해야 합니다.

- stateless 세션 : 요청할 때마다 임시로 만들어짐
requesst에 저장하는 것과 동일한 개념
세션 기반과 다른 점 - 세션에 잠깐 옮겨놓고 사용후 없어짐
e) 지하철 사물함 느낌
- 인터셉트 : 검증 → 검증 : user 객체 넣기 → user 객체를 꺼내서 사용
3. 임시 세션 사용하기
- builder 패턴의 생성자 추가하기
package shop.mtcoding.blog.user;
import lombok.Builder;
import lombok.Data;
import java.sql.Timestamp;
@Data
public class SessionUser {
private Integer id;
private String username;
private String password;
private String email;
private Timestamp createdAt;
public SessionUser(User user) {
this.id = user.getId();
this.username = user.getUsername();
this.password = user.getPassword();
this.email = user.getEmail();
this.createdAt = user.getCreatedAt();
}
@Builder
public SessionUser(Integer id, String username, String password, String email, Timestamp createdAt) {
this.id = id;
this.username = username;
this.password = password;
this.email = email;
this.createdAt = createdAt;
}
}
- 임시 세션 이용하기
- user 객체를 만들어서 리턴하기
package shop.mtcoding.blog._core.utils;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import shop.mtcoding.blog.user.SessionUser;
import shop.mtcoding.blog.user.User;
import java.util.Date;
public class JwtUtil {
// 토큰 생성
public static String create(User user){ // user 객체가 필요함
String jwt = JWT.create()
.withSubject("blog")
.withExpiresAt(new Date(System.currentTimeMillis() + 1000L * 60L * 60L))
.withClaim("id", user.getId())
.withClaim("username", user.getUsername())
.sign(Algorithm.HMAC512("metacoding")); // 나중에 환경변수(OS변수)로 사용해야 함
return jwt;
}
public static SessionUser verify(String jwt){
DecodedJWT decodedJWT = JWT.require(Algorithm.HMAC512("metacoding")).build().verify(jwt);
int id = decodedJWT.getClaim("id").asInt();
String username = decodedJWT.getClaim("username").asString(); // 사용자명 추출
return SessionUser.builder()
.id(id)
.username(username)
.build();
}
}
- LoginInterceptor에 토큰 검증 로직 추가하기
- 프로토콜 : Bearer jwt토큰
검증할 때는 있으면 안되서 제거해야 함

- jSessionId를 제거하는 코드를 써서 없애야하는데 없애지 않아도 오류가 나지 않음
package shop.mtcoding.blog._core.interceptor;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import shop.mtcoding.blog._core.errors.exception.Exception401;
import shop.mtcoding.blog._core.utils.JwtUtil;
import shop.mtcoding.blog.user.SessionUser;
import shop.mtcoding.blog.user.User;
// /api/** 인증 필요 주소
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 프로토콜 : Bearer jwt토큰
String jwt = request.getHeader("Authorization");
jwt = jwt.replace("Bearer", ""); // Bearer 제거하기
// 검증하기
try {
SessionUser sessionUser = JwtUtil.verify(jwt);
// 임시 세션(jSessionId가 필요 없음)
HttpSession session = request.getSession();
session.setAttribute("sessionUser", sessionUser);
return true;
} catch (Exception e) {
return false;
}
}
}
- jwt가 null일때 오류 잡기

package shop.mtcoding.blog._core.interceptor;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import shop.mtcoding.blog._core.errors.exception.Exception401;
import shop.mtcoding.blog._core.utils.JwtUtil;
import shop.mtcoding.blog.user.SessionUser;
import shop.mtcoding.blog.user.User;
// /api/** 인증 필요 주소
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 프로토콜 : Bearer jwt토큰
String jwt = request.getHeader("Authorization");
jwt = jwt.replace("Bearer", ""); // Bearer 제거하기
if(jwt == null){
throw new Exception401("jwt 토큰을 전달해주세요"); // 필터로 구성하면 throw 못함 -> 인터셉터 사용하기
}
// 검증하기
try {
SessionUser sessionUser = JwtUtil.verify(jwt);
// 임시 세션(jSessionId가 필요 없음)
HttpSession session = request.getSession();
session.setAttribute("sessionUser", sessionUser);
return true;
} catch (Exception e) {
return false;
}
}
}


package shop.mtcoding.blog._core.interceptor;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import shop.mtcoding.blog._core.errors.exception.Exception401;
import shop.mtcoding.blog._core.errors.exception.Exception500;
import shop.mtcoding.blog._core.utils.JwtUtil;
import shop.mtcoding.blog.user.SessionUser;
import shop.mtcoding.blog.user.User;
// /api/** 인증 필요 주소
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// Bearer jwt 토큰이 들어옴
String jwt = request.getHeader("Authorization");
if (jwt == null) {
throw new Exception401("jwt 토큰을 전달해주세요");
}
jwt = jwt.replace("Bearer ", "");
//검증
try {
SessionUser sessionUser = JwtUtil.verify(jwt);
// 임시 세션 (jsessionId는 필요 없음)
HttpSession session = request.getSession();
session.setAttribute("sessionUser", sessionUser);
return true;
}catch (TokenExpiredException e){
throw new Exception401("토큰 만료시간이 지났어요. 다시 로그인하세요");
}catch (JWTDecodeException e){
throw new Exception401("토큰이 유효하지 않습니다");
}catch (Exception e){
throw new Exception500(e.getMessage());
}
}
}
public String 로그인(UserRequest.LoginDTO reqDTO){
User user = userJPARepository.findByUsernameAndPassword(reqDTO.getUsername(), reqDTO.getPassword())
.orElseThrow(() -> new Exception401("인증되지 않았습니다"));
String jwt = JwtUtil.create(user);
return jwt;
}
@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody UserRequest.LoginDTO reqDTO) {
String jwt = userService.로그인(reqDTO);
//session.setAttribute("sessionUser", sessionUser);
return ResponseEntity.ok().header("Authorization", "Bearer "+jwt).body(null);
}
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJibG9nIiwiaWQiOjEsImV4cCI6MTcxMjAzODcwMSwidXNlcm5hbWUiOiJzc2FyIn0.VGGXfy8W0x0wuQ8Yz1IT7tqG9efxTm-v_Uw-h4QiiG1OshXZ-PLtRSVWREO9n6JUtVpONyuG4Hmkfmj_mMzLEQ

Share article