Skip to content

Commit 53febfe

Browse files
authored
고생하셨습니다.
🎉 PR 머지 완료! 🎉
1 parent f6958d2 commit 53febfe

23 files changed

+332
-194
lines changed

README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# 🔓 방탈출 예약 서비스
2+
3+
<br>
4+
<img width="800" height="413" alt="image" src="https://github.com/user-attachments/assets/9ce9fd16-4341-46d0-9f62-750021db4dd6" />
5+
<img width="800" height="248" alt="image" src="https://github.com/user-attachments/assets/46d1c290-90ad-450c-8a0b-d05cb32ae7af" />
6+
<br>
7+
8+
## 🚀 주요 기능
9+
10+
1. **로그인**: 가입한 이메일과 비밀번호로 로그인할 수 있습니다.
11+
12+
2. **예약**: '예약' 페이지에서 원하는 날짜, 테마, 시간을 선택하고 '예약하기' 버튼을 누르면 예약이 완료됩니다.
13+
14+
3. **예약 확인 및 취소**: '내 예약' 페이지에서 예약 내역을 확인하거나, 원치 않는 예약을 취소할 수 있습니다.
15+
16+
4. **예약 대기 신청**: 예약 대기를 신청하면, 취소 자리가 생겼을 때 우선적으로 예약할 기회를 드립니다.

src/main/java/auth/JwtUtils.java

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package auth;
2+
3+
import io.jsonwebtoken.Claims;
4+
import io.jsonwebtoken.Jws;
5+
import io.jsonwebtoken.JwtException;
6+
import io.jsonwebtoken.Jwts;
7+
import io.jsonwebtoken.SignatureAlgorithm;
8+
import jakarta.servlet.http.Cookie;
9+
import jakarta.servlet.http.HttpServletRequest;
10+
11+
import javax.crypto.spec.SecretKeySpec;
12+
import java.security.Key;
13+
import java.util.Arrays;
14+
import java.util.Date;
15+
16+
public class JwtUtils {
17+
18+
private final Key key;
19+
20+
public JwtUtils(String secretKey) {
21+
this.key = new SecretKeySpec(secretKey.getBytes(), SignatureAlgorithm.HS256.getJcaName());
22+
}
23+
24+
public String createToken(String id, String role) {
25+
Claims claims = Jwts.claims().setSubject(id);
26+
claims.put("role", role);
27+
Date now = new Date();
28+
Date validity = new Date(now.getTime() + 3600000);
29+
30+
return Jwts.builder()
31+
.setClaims(claims)
32+
.setIssuedAt(now)
33+
.setExpiration(validity)
34+
.signWith(key, SignatureAlgorithm.HS256)
35+
.compact();
36+
}
37+
38+
private Claims getClaims(String token) {
39+
try {
40+
Jws<Claims> claims = Jwts.parserBuilder()
41+
.setSigningKey(key)
42+
.build()
43+
.parseClaimsJws(token);
44+
return claims.getBody();
45+
} catch (JwtException | IllegalArgumentException e) {
46+
throw new IllegalArgumentException("유효하지 않은 토큰입니다.");
47+
}
48+
}
49+
50+
public String getSubject(String token) {
51+
return getClaims(token).getSubject();
52+
}
53+
54+
public String getRole(String token) {
55+
return getClaims(token).get("role", String.class);
56+
}
57+
58+
public String getTokenFromCookie(HttpServletRequest request) {
59+
Cookie[] cookies = request.getCookies();
60+
if (cookies == null) {
61+
return null;
62+
}
63+
return Arrays.stream(cookies)
64+
.filter(cookie -> "token".equals(cookie.getName()))
65+
.findFirst()
66+
.map(Cookie::getValue)
67+
.orElse(null);
68+
}
69+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package auth.annotation;
2+
3+
import java.lang.annotation.ElementType;
4+
import java.lang.annotation.Retention;
5+
import java.lang.annotation.RetentionPolicy;
6+
import java.lang.annotation.Target;
7+
8+
@Target(ElementType.PARAMETER)
9+
@Retention(RetentionPolicy.RUNTIME)
10+
public @interface Login {
11+
}
Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,33 @@
11
package roomescape;
22

3+
import org.slf4j.Logger;
4+
import org.slf4j.LoggerFactory;
5+
import org.springframework.http.HttpStatus;
36
import org.springframework.http.ResponseEntity;
47
import org.springframework.web.bind.annotation.ControllerAdvice;
58
import org.springframework.web.bind.annotation.ExceptionHandler;
9+
import roomescape.auth.exception.UnauthenticatedException;
610

711
@ControllerAdvice
812
public class ExceptionController {
13+
14+
private static final Logger log = LoggerFactory.getLogger(ExceptionController.class);
15+
916
@ExceptionHandler(Exception.class)
10-
public ResponseEntity<Void> handleRuntimeException(Exception e) {
11-
e.printStackTrace();
12-
return ResponseEntity.badRequest().build();
17+
public ResponseEntity<String> handleRuntimeException(Exception e) {
18+
log.error("예상치 못한 예외가 발생했습니다.", e);
19+
return ResponseEntity.internalServerError().body("서버 내부 오류가 발생했습니다.");
20+
}
21+
22+
@ExceptionHandler(UnauthenticatedException.class)
23+
public ResponseEntity<String> handleUnauthenticatedException(UnauthenticatedException e) {
24+
log.error("인증되지 않은 요청입니다.", e);
25+
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(e.getMessage());
26+
}
27+
28+
@ExceptionHandler(IllegalArgumentException.class)
29+
public ResponseEntity<String> handleIllegalArgumentException(IllegalArgumentException e) {
30+
log.error("잘못된 요청: {}", e.getMessage(), e);
31+
return ResponseEntity.badRequest().body(e.getMessage());
1332
}
1433
}

src/main/java/roomescape/RoomescapeApplication.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import org.springframework.boot.SpringApplication;
44
import org.springframework.boot.autoconfigure.SpringBootApplication;
55

6-
@SpringBootApplication
6+
@SpringBootApplication(scanBasePackages = {"roomescape", "auth"})
77
public class RoomescapeApplication {
88
public static void main(String[] args) {
99
SpringApplication.run(RoomescapeApplication.class, args);
Lines changed: 10 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,33 @@
11
package roomescape.auth;
22

3-
import jakarta.servlet.http.Cookie;
3+
import auth.JwtUtils;
44
import jakarta.servlet.http.HttpServletRequest;
55
import jakarta.servlet.http.HttpServletResponse;
6+
import org.jetbrains.annotations.NotNull;
67
import org.springframework.stereotype.Component;
78
import org.springframework.web.servlet.HandlerInterceptor;
89

9-
import java.util.Arrays;
10-
import java.util.Optional;
11-
1210
@Component
1311
public class AdminInterceptor implements HandlerInterceptor {
1412

15-
private final JwtTokenProvider jwtTokenProvider;
13+
private final JwtUtils jwtUtils;
1614

17-
public AdminInterceptor(JwtTokenProvider jwtTokenProvider) {
18-
this.jwtTokenProvider = jwtTokenProvider;
15+
public AdminInterceptor(JwtUtils jwtUtils) {
16+
this.jwtUtils = jwtUtils;
1917
}
2018

2119
@Override
22-
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
23-
Optional<Cookie> tokenCookie = findTokenCookie(request.getCookies());
20+
public boolean preHandle(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull Object handler) throws Exception {
21+
String token = jwtUtils.getTokenFromCookie(request);
2422

25-
if (tokenCookie.isEmpty()) {
23+
if (token == null) {
2624
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
2725
return false;
2826
}
2927

3028
try {
31-
String token = tokenCookie.get().getValue();
32-
String role = jwtTokenProvider.getRole(token);
29+
String role = jwtUtils.getRole(token);
30+
3331
if (!"ADMIN".equals(role)) {
3432
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
3533
return false;
@@ -41,13 +39,4 @@ public boolean preHandle(HttpServletRequest request, HttpServletResponse respons
4139

4240
return true;
4341
}
44-
45-
private Optional<Cookie> findTokenCookie(Cookie[] cookies) {
46-
if (cookies == null) {
47-
return Optional.empty();
48-
}
49-
return Arrays.stream(cookies)
50-
.filter(cookie -> "token".equals(cookie.getName()))
51-
.findFirst();
52-
}
5342
}

src/main/java/roomescape/auth/JwtTokenProvider.java

Lines changed: 0 additions & 51 deletions
This file was deleted.

src/main/java/roomescape/auth/LoginController.java

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
package roomescape.auth;
22

3+
import auth.JwtUtils;
4+
import auth.annotation.Login;
5+
import roomescape.auth.dto.LoginMember;
36
import jakarta.servlet.http.Cookie;
47
import jakarta.servlet.http.HttpServletResponse;
58
import org.springframework.http.ResponseEntity;
@@ -8,22 +11,26 @@
811
import org.springframework.web.bind.annotation.RequestBody;
912
import org.springframework.web.bind.annotation.RestController;
1013
import roomescape.auth.dto.LoginCheckResponse;
11-
import roomescape.auth.dto.LoginMember;
1214
import roomescape.auth.dto.LoginRequest;
15+
import roomescape.member.Member;
1316
import roomescape.member.MemberService;
1417

1518
@RestController
1619
public class LoginController {
1720

1821
private final MemberService memberService;
22+
private final JwtUtils jwtUtils;
1923

20-
public LoginController(MemberService memberService) {
24+
public LoginController(MemberService memberService, JwtUtils jwtUtils) {
2125
this.memberService = memberService;
26+
this.jwtUtils = jwtUtils;
2227
}
2328

2429
@PostMapping("/login")
2530
public ResponseEntity<Void> login(@RequestBody LoginRequest request, HttpServletResponse response) {
26-
String token = memberService.login(request);
31+
Member member = memberService.login(request);
32+
33+
String token = jwtUtils.createToken(String.valueOf(member.getId()), member.getRole());
2734

2835
Cookie cookie = new Cookie("token", token);
2936
cookie.setHttpOnly(true);
@@ -45,10 +52,7 @@ public ResponseEntity<Void> logout(HttpServletResponse response) {
4552
}
4653

4754
@GetMapping("/login/check")
48-
public ResponseEntity<LoginCheckResponse> checkLogin(LoginMember loginMember) {
49-
if (loginMember == null) {
50-
return ResponseEntity.status(401).build();
51-
}
55+
public ResponseEntity<LoginCheckResponse> checkLogin(@Login LoginMember loginMember) {
5256
return ResponseEntity.ok(new LoginCheckResponse(loginMember.getName()));
5357
}
5458
}

src/main/java/roomescape/auth/LoginMemberArgumentResolver.java

Lines changed: 0 additions & 67 deletions
This file was deleted.

0 commit comments

Comments
 (0)