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