diff --git "a/.run/MissionStepTest.\354\202\274\353\213\250\352\263\204.run.xml" "b/.run/MissionStepTest.\354\202\274\353\213\250\352\263\204.run.xml"
new file mode 100644
index 000000000..80b29a46e
--- /dev/null
+++ "b/.run/MissionStepTest.\354\202\274\353\213\250\352\263\204.run.xml"
@@ -0,0 +1,31 @@
+
+  
+    
+      
+      
+      
+      
+      
+      
+      
+      
+    
+    false
+    true
+    false
+    true
+    
+  
+
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
index 8d52aebc6..f6df4186f 100644
--- a/build.gradle
+++ b/build.gradle
@@ -15,7 +15,7 @@ repositories {
 dependencies {
     implementation 'org.springframework.boot:spring-boot-starter-web'
     implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
-    implementation 'org.springframework.boot:spring-boot-starter-jdbc'
+    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
 
     implementation 'dev.akkinoc.spring.boot:logback-access-spring-boot-starter:4.0.0'
 
@@ -26,7 +26,12 @@ dependencies {
     testImplementation 'org.springframework.boot:spring-boot-starter-test'
     testImplementation 'io.rest-assured:rest-assured:5.3.1'
 
+    implementation 'com.h2database:h2'
     runtimeOnly 'com.h2database:h2'
+
+    implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.1.0'
+    implementation 'jakarta.servlet:jakarta.servlet-api:6.0.0'
+
 }
 
 test {
diff --git a/src/main/java/jwt/JwtConfiguration.java b/src/main/java/jwt/JwtConfiguration.java
new file mode 100644
index 000000000..ad7242bba
--- /dev/null
+++ b/src/main/java/jwt/JwtConfiguration.java
@@ -0,0 +1,17 @@
+package jwt;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class JwtConfiguration {
+
+    @Value("${jwt.secret}")
+    private String secretKey;
+
+    @Bean
+    public JwtUtils jwtUtils() {
+        return new JwtUtils();
+    }
+}
diff --git a/src/main/java/jwt/JwtService.java b/src/main/java/jwt/JwtService.java
new file mode 100644
index 000000000..cfb44da58
--- /dev/null
+++ b/src/main/java/jwt/JwtService.java
@@ -0,0 +1,35 @@
+package jwt;
+
+import io.jsonwebtoken.Claims;
+import org.springframework.stereotype.Service;
+import roomescape.member.Member;
+import roomescape.member.MemberRepository;
+
+@Service
+public class JwtService {
+    private final JwtUtils jwtUtils;
+    private final MemberRepository memberRepository;
+
+    public JwtService(JwtUtils jwtUtils, MemberRepository memberRepository) {
+        this.jwtUtils = jwtUtils;
+        this.memberRepository = memberRepository;
+    }
+
+    public Long getUserIdFromToken(String token) {
+        Claims claims = jwtUtils.getClaimsFromToken(token);
+        try {
+            return Long.valueOf(claims.getSubject());
+        } catch (Exception e) {
+            throw new IllegalArgumentException("Claims에서 User ID를 추출할 수 없습니다.", e);
+        }
+    }
+
+    public Member getMemberFromToken(String token) {
+        Long userId = getUserIdFromToken(token);
+        Member member = memberRepository.findById(userId).get();
+        if (member == null) {
+            throw new IllegalArgumentException("토큰으로부터 유저를 찾을 수 없습니다.");
+        }
+        return member;
+    }
+}
diff --git a/src/main/java/jwt/JwtUtils.java b/src/main/java/jwt/JwtUtils.java
new file mode 100644
index 000000000..61d465487
--- /dev/null
+++ b/src/main/java/jwt/JwtUtils.java
@@ -0,0 +1,49 @@
+package jwt;
+
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.SignatureAlgorithm;
+import org.springframework.beans.factory.annotation.Value;
+import roomescape.member.Role;
+
+import javax.crypto.spec.SecretKeySpec;
+import java.security.Key;
+import java.util.Date;
+
+public class JwtUtils {
+    private static final long EXPIRATION_TIME = 86400000;
+    private String secretKey;
+
+    @Value("${jwt.secret}")
+    public void setSecretKey(String secretKey) {
+        this.secretKey = secretKey;
+    }
+
+    private Key generateKey() {
+        return new SecretKeySpec(secretKey.getBytes(), SignatureAlgorithm.HS256.getJcaName());
+    }
+
+    public String generateToken(Long userId, Role role) {
+        Key key = generateKey();
+        return Jwts.builder()
+                .setSubject(String.valueOf(userId))
+                .claim("role", role.name())
+                .setIssuedAt(new Date())
+                .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
+                .signWith(key, SignatureAlgorithm.HS256)
+                .compact();
+    }
+
+    public Claims getClaimsFromToken(String token) {
+        try {
+            Key key = generateKey();
+            return Jwts.parserBuilder()
+                    .setSigningKey(key)
+                    .build()
+                    .parseClaimsJws(token)
+                    .getBody();
+        } catch (Exception e) {
+            throw new IllegalArgumentException("유효하지 않은 토큰입니다.", e);
+        }
+    }
+}
diff --git a/src/main/java/roomescape/ExceptionController.java b/src/main/java/roomescape/ExceptionController.java
index 4e2450f9e..e452ff0cb 100644
--- a/src/main/java/roomescape/ExceptionController.java
+++ b/src/main/java/roomescape/ExceptionController.java
@@ -1,14 +1,33 @@
 package roomescape;
 
+import org.springframework.http.HttpStatus;
 import org.springframework.http.ResponseEntity;
 import org.springframework.web.bind.annotation.ControllerAdvice;
 import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.server.ResponseStatusException;
+
+import java.util.HashMap;
+import java.util.Map;
 
 @ControllerAdvice
 public class ExceptionController {
+
+    @ExceptionHandler(ResponseStatusException.class)
+    public ResponseEntity handleResponseStatusException(ResponseStatusException e) {
+        return ResponseEntity.status(e.getStatusCode()).build();
+    }
+
+    @ExceptionHandler(IllegalArgumentException.class)
+    public ResponseEntity