From 5a89792a1464d34918cb0375b32faa56b883820c Mon Sep 17 00:00:00 2001 From: sinji Date: Tue, 24 Dec 2024 16:45:27 +0900 Subject: [PATCH 01/91] =?UTF-8?q?refactor(MissionStepTest):=20[1=EB=8B=A8?= =?UTF-8?q?=EA=B3=84]=20=EC=9D=BC=EB=8B=A8=EA=B3=84=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/java/roomescape/MissionStepTest.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/test/java/roomescape/MissionStepTest.java b/src/test/java/roomescape/MissionStepTest.java index 6add784bd..765c287d7 100644 --- a/src/test/java/roomescape/MissionStepTest.java +++ b/src/test/java/roomescape/MissionStepTest.java @@ -32,7 +32,16 @@ public class MissionStepTest { .extract(); String token = response.headers().get("Set-Cookie").getValue().split(";")[0].split("=")[1]; - assertThat(token).isNotBlank(); + + ExtractableResponse checkResponse = RestAssured.given().log().all() + .contentType(ContentType.JSON) + .cookie("token", token) + .when().get("/login/check") + .then().log().all() + .statusCode(200) + .extract(); + + assertThat(checkResponse.body().jsonPath().getString("name")).isEqualTo("어드민"); } } \ No newline at end of file From acd8813fda4db9691dd4c81ffec5322dc4c28617 Mon Sep 17 00:00:00 2001 From: sinji Date: Sun, 29 Dec 2024 20:11:08 +0900 Subject: [PATCH 02/91] =?UTF-8?q?feat(TokenRequest):=20[1=EB=8B=A8?= =?UTF-8?q?=EA=B3=84]=20=EC=82=AC=EC=9A=A9=EC=9E=90=20=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=9D=B8=20=EC=9A=94=EC=B2=AD=20DTO=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/roomescape/dto/TokenRequest.java | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 src/main/java/roomescape/dto/TokenRequest.java diff --git a/src/main/java/roomescape/dto/TokenRequest.java b/src/main/java/roomescape/dto/TokenRequest.java new file mode 100644 index 000000000..8430a85c1 --- /dev/null +++ b/src/main/java/roomescape/dto/TokenRequest.java @@ -0,0 +1,22 @@ +package roomescape.dto; + +public class TokenRequest { + private String email; + private String password; + + public TokenRequest() { + } + + public TokenRequest(String email, String password) { + this.email = email; + this.password = password; + } + + public String getEmail() { + return email; + } + + public String getPassword() { + return password; + } +} From 013c8a83b143e2eb0bc5a252e5cd6a707c5785d7 Mon Sep 17 00:00:00 2001 From: sinji Date: Mon, 30 Dec 2024 01:26:35 +0900 Subject: [PATCH 03/91] =?UTF-8?q?feat(TokenLoginController):=20[1=EB=8B=A8?= =?UTF-8?q?=EA=B3=84]=20=EC=82=AC=EC=9A=A9=EC=9E=90=20=EC=9D=B8=EC=A6=9D?= =?UTF-8?q?=20=EB=B0=8F=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EC=9A=94=EC=B2=AD?= =?UTF-8?q?=20=EC=B2=98=EB=A6=AC=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= =?UTF-8?q?(tokenLogin)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../roomescape/UI/TokenLoginController.java | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 src/main/java/roomescape/UI/TokenLoginController.java diff --git a/src/main/java/roomescape/UI/TokenLoginController.java b/src/main/java/roomescape/UI/TokenLoginController.java new file mode 100644 index 000000000..5b8278318 --- /dev/null +++ b/src/main/java/roomescape/UI/TokenLoginController.java @@ -0,0 +1,38 @@ +package roomescape.UI; + +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; +import roomescape.application.AuthService; +import roomescape.dto.TokenRequest; +import roomescape.dto.TokenResponse; +import roomescape.infrastructure.AuthorizationExtractor; +import roomescape.infrastructure.BearerAuthorizationExtractor; +import roomescape.member.MemberResponse; + +@RestController +public class TokenLoginController { + private final AuthService authService; + private final AuthorizationExtractor authorizationExtractor; + + public TokenLoginController(AuthService authService) { + this.authService = authService; + this.authorizationExtractor = new BearerAuthorizationExtractor(); + } + + @PostMapping("/login") + public ResponseEntity tokenLogin(@RequestBody TokenRequest tokenRequest, HttpServletResponse response){ + TokenResponse tokenResponse = authService.createToken(tokenRequest); + Cookie cookie = new Cookie("token", tokenResponse.getAccessToken()); + cookie.setHttpOnly(true); + cookie.setPath("/"); + response.addCookie(cookie); + return ResponseEntity.ok().body(tokenResponse); + } +} From 3569c1377814c2622bf4df91e92aef30448b0910 Mon Sep 17 00:00:00 2001 From: sinji Date: Mon, 30 Dec 2024 01:33:59 +0900 Subject: [PATCH 04/91] =?UTF-8?q?feat(AuthService):=20[1=EB=8B=A8=EA=B3=84?= =?UTF-8?q?]=20=EC=82=AC=EC=9A=A9=EC=9E=90=20=EC=9D=B8=EC=A6=9D=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC=20=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../roomescape/application/AuthService.java | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 src/main/java/roomescape/application/AuthService.java diff --git a/src/main/java/roomescape/application/AuthService.java b/src/main/java/roomescape/application/AuthService.java new file mode 100644 index 000000000..eacdc1f56 --- /dev/null +++ b/src/main/java/roomescape/application/AuthService.java @@ -0,0 +1,53 @@ +package roomescape.application; + +import org.springframework.stereotype.Service; +import roomescape.dto.TokenRequest; +import roomescape.dto.TokenResponse; +import roomescape.infrastructure.JwtTokenProvider; +import roomescape.member.Member; +import roomescape.member.MemberDao; +import roomescape.member.MemberResponse; + +@Service +public class AuthService { + private final JwtTokenProvider jwtTokenProvider; + private final MemberDao memberDao; + + public AuthService(JwtTokenProvider jwtTokenProvider, MemberDao memberDao) { + this.jwtTokenProvider = jwtTokenProvider; + this.memberDao = memberDao; + } + + public MemberResponse findMemberByEmail(String email) { + Member member = memberDao.findByEmail(email); + if (member == null) { + throw new IllegalArgumentException("사용자를 찾을 수 없습니다."); + } + return new MemberResponse(member.getId(), member.getName(), member.getEmail()); + } + + public String getEmailFromToken(String token) { + return jwtTokenProvider.getPayload(token); + } + + public TokenResponse createToken(TokenRequest tokenRequest) { + if (checkInvalidLogin(tokenRequest.getEmail(), tokenRequest.getPassword())) { + throw new AuthorizationException(); + } + String accessToken = jwtTokenProvider.createToken(tokenRequest.getEmail()); + return new TokenResponse(accessToken); + } + + public boolean checkInvalidLogin(String email, String password) { + Member member = memberDao.findByEmailAndPassword(email, password); + return member == null; + } + + public boolean verifyToken(String token) { + try { + return jwtTokenProvider.validateToken(token); + } catch (Exception e) { + return false; + } + } +} From 1ddb5a4c8981daca434c47a0f4123cd74f0ff944 Mon Sep 17 00:00:00 2001 From: sinji Date: Mon, 30 Dec 2024 01:35:38 +0900 Subject: [PATCH 05/91] =?UTF-8?q?feat(JwtTokenProvider):=20[1=EB=8B=A8?= =?UTF-8?q?=EA=B3=84]=20=ED=86=A0=ED=81=B0=20=EC=83=9D=EC=84=B1=20?= =?UTF-8?q?=EB=B0=8F=20=EA=B2=80=EC=A6=9D=20=ED=81=B4=EB=9E=98=EC=8A=A4=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../infrastructure/JwtTokenProvider.java | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 src/main/java/roomescape/infrastructure/JwtTokenProvider.java diff --git a/src/main/java/roomescape/infrastructure/JwtTokenProvider.java b/src/main/java/roomescape/infrastructure/JwtTokenProvider.java new file mode 100644 index 000000000..bbba35cb4 --- /dev/null +++ b/src/main/java/roomescape/infrastructure/JwtTokenProvider.java @@ -0,0 +1,41 @@ +package roomescape.infrastructure; + +import io.jsonwebtoken.*; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import java.util.Date; + +@Component +public class JwtTokenProvider { + @Value("${roomescape.auth.jwt.secret}") + private String secretKey; + + @Value("${roomescape.auth.jwt.expire-length}") + private long validityInMilliseconds; + + public String createToken(String payload) { + Claims claims = Jwts.claims().setSubject(payload); + Date now = new Date(); + Date validity = new Date(now.getTime() + validityInMilliseconds); + return Jwts.builder() + .setClaims(claims) + .setIssuedAt(now) + .setExpiration(validity) + .signWith(SignatureAlgorithm.HS256, secretKey) + .compact(); + } + + public String getPayload(String token) { + return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody().getSubject(); + } + + public boolean validateToken(String token) { + try { + Jws claims = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token); + return !claims.getBody().getExpiration().before(new Date()); + } catch (JwtException | IllegalArgumentException e) { + return false; + } + } +} From 6cf51d43512f075c88d844bc1e733f27211fdca4 Mon Sep 17 00:00:00 2001 From: sinji Date: Mon, 30 Dec 2024 01:36:49 +0900 Subject: [PATCH 06/91] =?UTF-8?q?refactor(MemberDao):=20[1=EB=8B=A8?= =?UTF-8?q?=EA=B3=84]=20email=EB=A1=9C=20=EC=82=AC=EC=9A=A9=EC=9E=90?= =?UTF-8?q?=EB=A5=BC=20=EC=B0=BE=EB=8A=94=20=EB=A9=94=EC=84=9C=EB=93=9C=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80(findByEmail)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/roomescape/member/MemberDao.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/main/java/roomescape/member/MemberDao.java b/src/main/java/roomescape/member/MemberDao.java index 81f77f4cd..529ed5430 100644 --- a/src/main/java/roomescape/member/MemberDao.java +++ b/src/main/java/roomescape/member/MemberDao.java @@ -1,5 +1,6 @@ package roomescape.member; +import com.fasterxml.jackson.databind.deser.std.FromStringDeserializer; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.support.GeneratedKeyHolder; import org.springframework.jdbc.support.KeyHolder; @@ -52,4 +53,17 @@ public Member findByName(String name) { name ); } + + public Member findByEmail(String email) { + return jdbcTemplate.queryForObject( + "SELECT id, name, email, role FROM member WHERE email = ?", + (rs, rowNum) -> new Member( + rs.getLong("id"), + rs.getString("name"), + rs.getString("email"), + rs.getString("role") + ), + email + ); + } } From 1f22ed7bf23b844bafef7d1e2893e285af7f1088 Mon Sep 17 00:00:00 2001 From: sinji Date: Mon, 30 Dec 2024 01:39:08 +0900 Subject: [PATCH 07/91] =?UTF-8?q?feat(TokenResponse):=20[1=EB=8B=A8?= =?UTF-8?q?=EA=B3=84]=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EC=9D=91=EB=8B=B5?= =?UTF-8?q?=20=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/roomescape/dto/TokenResponse.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 src/main/java/roomescape/dto/TokenResponse.java diff --git a/src/main/java/roomescape/dto/TokenResponse.java b/src/main/java/roomescape/dto/TokenResponse.java new file mode 100644 index 000000000..4b12509d8 --- /dev/null +++ b/src/main/java/roomescape/dto/TokenResponse.java @@ -0,0 +1,13 @@ +package roomescape.dto; + +public class TokenResponse { + private String accessToken; + + public TokenResponse(String accessToken) { + this.accessToken = accessToken; + } + + public String getAccessToken() { + return accessToken; + } +} From ee6dac0824db7b512dbb6803c09f8a3791df88c5 Mon Sep 17 00:00:00 2001 From: sinji Date: Mon, 30 Dec 2024 01:43:35 +0900 Subject: [PATCH 08/91] =?UTF-8?q?refactor(TokenLoginController):=20[1?= =?UTF-8?q?=EB=8B=A8=EA=B3=84]=20=EC=82=AC=EC=9A=A9=EC=9E=90=20=EC=9D=B8?= =?UTF-8?q?=EC=A6=9D=20=EC=83=81=ED=83=9C=20=ED=99=95=EC=9D=B8=20=EC=9A=94?= =?UTF-8?q?=EC=B2=AD=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80(checkLogin)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../roomescape/UI/TokenLoginController.java | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/main/java/roomescape/UI/TokenLoginController.java b/src/main/java/roomescape/UI/TokenLoginController.java index 5b8278318..4122a0ac3 100644 --- a/src/main/java/roomescape/UI/TokenLoginController.java +++ b/src/main/java/roomescape/UI/TokenLoginController.java @@ -35,4 +35,25 @@ public ResponseEntity tokenLogin(@RequestBody TokenRequest tokenR response.addCookie(cookie); return ResponseEntity.ok().body(tokenResponse); } + + @GetMapping("/login/check") + public ResponseEntity checkLogin(HttpServletRequest request) { + Cookie[] cookies = request.getCookies(); + if (cookies != null) { + for (Cookie cookie : cookies) { + if ("token".equals(cookie.getName())) { + String token = cookie.getValue(); + boolean isValid = authService.verifyToken(token); + if (isValid) { + String email = authService.getEmailFromToken(token); + MemberResponse memberResponse = authService.findMemberByEmail(email); + return ResponseEntity.ok(memberResponse); + } else { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); + } + } + } + } + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); + } } From 91bd50f977269f3f101e6d991d2dc4be4a738ded Mon Sep 17 00:00:00 2001 From: sinji Date: Mon, 30 Dec 2024 01:47:09 +0900 Subject: [PATCH 09/91] =?UTF-8?q?feat(BearerAuthorizationExtractor):=20[1?= =?UTF-8?q?=EB=8B=A8=EA=B3=84]=20Bearer=20=ED=86=A0=ED=81=B0=20=EC=B6=94?= =?UTF-8?q?=EC=B6=9C=20=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BearerAuthorizationExtractor.java | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 src/main/java/roomescape/infrastructure/BearerAuthorizationExtractor.java diff --git a/src/main/java/roomescape/infrastructure/BearerAuthorizationExtractor.java b/src/main/java/roomescape/infrastructure/BearerAuthorizationExtractor.java new file mode 100644 index 000000000..6f88fc1e9 --- /dev/null +++ b/src/main/java/roomescape/infrastructure/BearerAuthorizationExtractor.java @@ -0,0 +1,28 @@ +package roomescape.infrastructure; + +import jakarta.servlet.http.HttpServletRequest; + +import java.util.Enumeration; + +public class BearerAuthorizationExtractor implements AuthorizationExtractor { + private static final String BEARER_TYPE = "Bearer"; + private static final String ACCESS_TOKEN_TYPE = BearerAuthorizationExtractor.class.getSimpleName() + ".ACCESS_TOKEN_TYPE"; + + @Override + public String extract(HttpServletRequest request) { + Enumeration headers = request.getHeaders(AUTHORIZATION); + while (headers.hasMoreElements()) { + String value = headers.nextElement(); + if ((value.toLowerCase().startsWith(BEARER_TYPE.toLowerCase()))) { + String authHeaderValue = value.substring(BEARER_TYPE.length()).trim(); + request.setAttribute(ACCESS_TOKEN_TYPE, value.substring(0, BEARER_TYPE.length()).trim()); + int commaIndex = authHeaderValue.indexOf(','); + if (commaIndex > 0) { + authHeaderValue = authHeaderValue.substring(0, commaIndex); + } + return authHeaderValue; + } + } + return null; + } +} From e7c9b8747c6ac04dd5eef8235336410f34dd57e1 Mon Sep 17 00:00:00 2001 From: sinji Date: Mon, 30 Dec 2024 01:51:30 +0900 Subject: [PATCH 10/91] =?UTF-8?q?feat(AuthorizationExtractor):=20[1?= =?UTF-8?q?=EB=8B=A8=EA=B3=84]=20=EC=9D=B8=EC=A6=9D=20=EC=A0=95=EB=B3=B4?= =?UTF-8?q?=20=EC=B6=94=EC=B6=9C=EC=9D=84=20=EC=9C=84=ED=95=9C=20=EC=9D=B8?= =?UTF-8?q?=ED=84=B0=ED=8E=98=EC=9D=B4=EC=8A=A4=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../infrastructure/AuthorizationExtractor.java | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 src/main/java/roomescape/infrastructure/AuthorizationExtractor.java diff --git a/src/main/java/roomescape/infrastructure/AuthorizationExtractor.java b/src/main/java/roomescape/infrastructure/AuthorizationExtractor.java new file mode 100644 index 000000000..f2c8b4d98 --- /dev/null +++ b/src/main/java/roomescape/infrastructure/AuthorizationExtractor.java @@ -0,0 +1,9 @@ +package roomescape.infrastructure; + +import jakarta.servlet.http.HttpServletRequest; + +public interface AuthorizationExtractor { + String AUTHORIZATION = "Authorization"; + + T extract(HttpServletRequest request); +} From 7773f89f963b195ba84210b3a094077bebea3a06 Mon Sep 17 00:00:00 2001 From: sinji Date: Mon, 30 Dec 2024 01:54:25 +0900 Subject: [PATCH 11/91] =?UTF-8?q?feat(AuthorizationExtractor):=20[1?= =?UTF-8?q?=EB=8B=A8=EA=B3=84]=20=EC=9D=B8=EC=A6=9D=20=EA=B4=80=EB=A0=A8?= =?UTF-8?q?=20=EC=98=88=EC=99=B8=20=EC=B2=98=EB=A6=AC=20=ED=81=B4=EB=9E=98?= =?UTF-8?q?=EC=8A=A4=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/AuthorizationException.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 src/main/java/roomescape/application/AuthorizationException.java diff --git a/src/main/java/roomescape/application/AuthorizationException.java b/src/main/java/roomescape/application/AuthorizationException.java new file mode 100644 index 000000000..bc22dda5c --- /dev/null +++ b/src/main/java/roomescape/application/AuthorizationException.java @@ -0,0 +1,14 @@ +package roomescape.application; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(HttpStatus.UNAUTHORIZED) +public class AuthorizationException extends RuntimeException { + public AuthorizationException() { + } + + public AuthorizationException(String message) { + super(message); + } +} From aa6384d4408969562b32ef418fb5501a6f3ecda7 Mon Sep 17 00:00:00 2001 From: sinji Date: Mon, 30 Dec 2024 01:56:27 +0900 Subject: [PATCH 12/91] =?UTF-8?q?refactor(AuthService):=20[1=EB=8B=A8?= =?UTF-8?q?=EA=B3=84]=20=EC=9D=B8=EC=A6=9D=20=EC=8B=A4=ED=8C=A8=20?= =?UTF-8?q?=EC=98=88=EC=99=B8=EC=B2=98=EB=A6=AC=20=ED=86=B5=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/roomescape/application/AuthService.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/java/roomescape/application/AuthService.java b/src/main/java/roomescape/application/AuthService.java index eacdc1f56..78b844b70 100644 --- a/src/main/java/roomescape/application/AuthService.java +++ b/src/main/java/roomescape/application/AuthService.java @@ -21,7 +21,7 @@ public AuthService(JwtTokenProvider jwtTokenProvider, MemberDao memberDao) { public MemberResponse findMemberByEmail(String email) { Member member = memberDao.findByEmail(email); if (member == null) { - throw new IllegalArgumentException("사용자를 찾을 수 없습니다."); + throw new AuthorizationException("사용자를 찾을 수 없습니다."); } return new MemberResponse(member.getId(), member.getName(), member.getEmail()); } @@ -40,7 +40,10 @@ public TokenResponse createToken(TokenRequest tokenRequest) { public boolean checkInvalidLogin(String email, String password) { Member member = memberDao.findByEmailAndPassword(email, password); - return member == null; + if(member == null){ + throw new AuthorizationException("이메일 또는 비밀번호가 잘못되었습니다."); + } + return false; } public boolean verifyToken(String token) { From 42c9296036a8c4aaa62008deb544d385775f5c6d Mon Sep 17 00:00:00 2001 From: sinji Date: Mon, 30 Dec 2024 01:57:18 +0900 Subject: [PATCH 13/91] =?UTF-8?q?refactor(application.properties):=20[1?= =?UTF-8?q?=EB=8B=A8=EA=B3=84]=20jwt=20properties=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application.properties | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index a0f33bbab..a476badc6 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -8,4 +8,5 @@ spring.datasource.url=jdbc:h2:mem:database #spring.jpa.ddl-auto=create-drop #spring.jpa.defer-datasource-initialization=true -#roomescape.auth.jwt.secret= Yn2kjibddFAWtnPJ2AFlL8WXmohJMCvigQggaEypa5E= \ No newline at end of file +roomescape.auth.jwt.secret= Yn2kjibddFAWtnPJ2AFlL8WXmohJMCvigQggaEypa5E= +roomescape.auth.jwt.expire-length=3600000 \ No newline at end of file From e39fa6e4f618a80cd208ea439fc039fa1aba759b Mon Sep 17 00:00:00 2001 From: sinji Date: Mon, 30 Dec 2024 12:22:30 +0900 Subject: [PATCH 14/91] =?UTF-8?q?refactor(TokenLoginController):=20[1?= =?UTF-8?q?=EB=8B=A8=EA=B3=84]=20=EC=BD=94=EB=93=9C=20=EB=AC=B8=EB=B2=95?= =?UTF-8?q?=20=EC=88=98=EC=A0=95(=EB=A1=9C=EC=A7=81=20=EB=8F=99=EC=9D=BC)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/roomescape/UI/TokenLoginController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/roomescape/UI/TokenLoginController.java b/src/main/java/roomescape/UI/TokenLoginController.java index 4122a0ac3..67f974776 100644 --- a/src/main/java/roomescape/UI/TokenLoginController.java +++ b/src/main/java/roomescape/UI/TokenLoginController.java @@ -41,7 +41,7 @@ public ResponseEntity checkLogin(HttpServletRequest request) { Cookie[] cookies = request.getCookies(); if (cookies != null) { for (Cookie cookie : cookies) { - if ("token".equals(cookie.getName())) { + if (cookie.getName().equals("token")) { String token = cookie.getValue(); boolean isValid = authService.verifyToken(token); if (isValid) { From 12e4981f8cfd7d979f1c2640fea37eb47ee3f48b Mon Sep 17 00:00:00 2001 From: sinji Date: Wed, 1 Jan 2025 20:47:11 +0900 Subject: [PATCH 15/91] =?UTF-8?q?refactor(MissionStepTest):=20[2=EB=8B=A8?= =?UTF-8?q?=EA=B3=84]=20=EC=9D=B4=EB=8B=A8=EA=B3=84=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=B6=94=EA=B0=80=20=EB=B0=8F?= =?UTF-8?q?=20=ED=86=A0=ED=81=B0=20=EC=B6=94=EC=B6=9C=20=EB=A9=94=EC=84=9C?= =?UTF-8?q?=EB=93=9C=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/java/roomescape/MissionStepTest.java | 59 ++++++++++++++++++- 1 file changed, 57 insertions(+), 2 deletions(-) diff --git a/src/test/java/roomescape/MissionStepTest.java b/src/test/java/roomescape/MissionStepTest.java index 765c287d7..3c2269e7f 100644 --- a/src/test/java/roomescape/MissionStepTest.java +++ b/src/test/java/roomescape/MissionStepTest.java @@ -7,6 +7,7 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.annotation.DirtiesContext; +import roomescape.reservation.ReservationResponse; import java.util.HashMap; import java.util.Map; @@ -17,6 +18,27 @@ @DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD) public class MissionStepTest { + private String createToken(String email, String password) { + Map params = new HashMap<>(); + params.put("email", email); + params.put("password", password); + + ExtractableResponse response = RestAssured.given().log().all() + .contentType(ContentType.JSON) + .body(params) + .when().post("/login") + .then().log().all() + .statusCode(200) + .extract(); + + return extractTokenFromResponse(response); + } + + private String extractTokenFromResponse(ExtractableResponse response) { + String token = response.headers().get("Set-Cookie").getValue().split(";")[0].split("=")[1]; + return token; + } + @Test void 일단계() { Map params = new HashMap<>(); @@ -41,7 +63,40 @@ public class MissionStepTest { .then().log().all() .statusCode(200) .extract(); - assertThat(checkResponse.body().jsonPath().getString("name")).isEqualTo("어드민"); } -} \ No newline at end of file + + @Test + void 이단계() { + String token = createToken("admin@email.com", "password"); // 일단계에서 토큰을 추출하는 로직을 메서드로 따로 만들어서 활용하세요. + + Map params = new HashMap<>(); + params.put("date", "2024-03-01"); + params.put("time", "1"); + params.put("theme", "1"); + + ExtractableResponse response = RestAssured.given().log().all() + .body(params) + .cookie("token", token) + .contentType(ContentType.JSON) + .post("/reservations") + .then().log().all() + .extract(); + + assertThat(response.statusCode()).isEqualTo(201); + assertThat(response.as(ReservationResponse.class).getName()).isEqualTo("어드민"); + + params.put("name", "브라운"); + + ExtractableResponse adminResponse = RestAssured.given().log().all() + .body(params) + .cookie("token", token) + .contentType(ContentType.JSON) + .post("/reservations") + .then().log().all() + .extract(); + + assertThat(adminResponse.statusCode()).isEqualTo(201); + assertThat(adminResponse.as(ReservationResponse.class).getName()).isEqualTo("브라운"); + } +} From 084f339761c18f7140d6291d9bfc0e7015197f92 Mon Sep 17 00:00:00 2001 From: sinji Date: Wed, 1 Jan 2025 20:51:35 +0900 Subject: [PATCH 16/91] =?UTF-8?q?feat(LoginMemberArgumentResolver):=20[2?= =?UTF-8?q?=EB=8B=A8=EA=B3=84]=20=EC=BF=A0=ED=82=A4=EB=A5=BC=20=ED=86=B5?= =?UTF-8?q?=ED=95=B4=20=EB=A9=A4=EB=B2=84=20=EC=A0=95=EB=B3=B4=EB=A5=BC=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=ED=95=98=EB=8A=94=20ArgumentResolver=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../web/LoginMemberArgumentResolver.java | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 src/main/java/roomescape/web/LoginMemberArgumentResolver.java diff --git a/src/main/java/roomescape/web/LoginMemberArgumentResolver.java b/src/main/java/roomescape/web/LoginMemberArgumentResolver.java new file mode 100644 index 000000000..d2ef71e82 --- /dev/null +++ b/src/main/java/roomescape/web/LoginMemberArgumentResolver.java @@ -0,0 +1,61 @@ +package roomescape.web; + +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.core.MethodParameter; +import org.springframework.stereotype.Component; +import org.springframework.web.bind.support.WebDataBinderFactory; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.method.support.ModelAndViewContainer; +import roomescape.application.AuthService; +import roomescape.application.AuthorizationException; +import roomescape.member.LoginMember; + +import java.util.Arrays; + +@Component +public class LoginMemberArgumentResolver implements HandlerMethodArgumentResolver { + private final AuthService authService; + + public LoginMemberArgumentResolver(AuthService authService) { + this.authService = authService; + } + + @Override + public boolean supportsParameter(MethodParameter parameter) { + return parameter.getParameterType().equals(LoginMember.class); + } + + @Override + public Object resolveArgument(MethodParameter parameter, + ModelAndViewContainer mavContainer, + NativeWebRequest webRequest, + WebDataBinderFactory binderFactory) throws Exception { + HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest(); + + String token = extractTokenFromCookies(request.getCookies()); + + if (!authService.verifyToken(token)) { + throw new AuthorizationException("유효하지 않은 토큰입니다."); + } + + String email = authService.getEmailFromToken(token); + + LoginMember member = authService.findLoginMemberByEmail(email); + + return new LoginMember(member.getId(), member.getName(), member.getEmail(), member.getRole()); + } + + private String extractTokenFromCookies(Cookie[] cookies) { + if (cookies == null) { + throw new AuthorizationException("쿠키가 존재하지 않습니다."); + } + + return Arrays.stream(cookies) + .filter(cookie -> "token".equals(cookie.getName())) + .findFirst() + .map(Cookie::getValue) + .orElseThrow(() -> new AuthorizationException("토큰 쿠키가 없습니다.")); + } +} From a9d0815235f926233b64bf5cacad73f48fd63e72 Mon Sep 17 00:00:00 2001 From: sinji Date: Wed, 1 Jan 2025 20:54:11 +0900 Subject: [PATCH 17/91] =?UTF-8?q?feat(LoginMember):=20[2=EB=8B=A8=EA=B3=84?= =?UTF-8?q?]=20LoginMemberArgumentResolver=20=EB=A5=BC=20=ED=86=B5?= =?UTF-8?q?=ED=95=B4=20=EC=A1=B0=ED=9A=8C=ED=95=9C=20=EB=A9=A4=EB=B2=84=20?= =?UTF-8?q?=EC=A0=95=EB=B3=B4=EB=A5=BC=20=EC=A0=84=EB=8B=AC=ED=95=98?= =?UTF-8?q?=EB=8A=94=20DTO?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/roomescape/member/LoginMember.java | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 src/main/java/roomescape/member/LoginMember.java diff --git a/src/main/java/roomescape/member/LoginMember.java b/src/main/java/roomescape/member/LoginMember.java new file mode 100644 index 000000000..fe84a571d --- /dev/null +++ b/src/main/java/roomescape/member/LoginMember.java @@ -0,0 +1,31 @@ +package roomescape.member; + +public class LoginMember { + private final Long id; + private final String name; + private final String email; + private final String role; + + public LoginMember(Long id, String name, String email, String role) { + this.id = id; + this.name = name; + this.email = email; + this.role = role; + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public String getEmail() { + return email; + } + + public String getRole() { + return role; + } +} From 75870e31d31d9341fb84c3df84c8bce8cd0c0d1b Mon Sep 17 00:00:00 2001 From: sinji Date: Wed, 1 Jan 2025 22:04:07 +0900 Subject: [PATCH 18/91] =?UTF-8?q?refactor(AuthService):=20[2=EB=8B=A8?= =?UTF-8?q?=EA=B3=84]=20=EC=9D=B4=EB=A9=94=EC=9D=BC=EB=A1=9C=20=EB=A9=A4?= =?UTF-8?q?=EB=B2=84=EB=A5=BC=20=EC=A1=B0=ED=9A=8C=ED=95=98=EB=8A=94=20?= =?UTF-8?q?=EB=A9=94=EC=84=9C=EB=93=9C=EC=9D=98=20=EB=B0=98=ED=99=98?= =?UTF-8?q?=ED=98=95=20=EB=B3=80=EA=B2=BD(MemberResponse=20->=20LoginMembe?= =?UTF-8?q?r)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/roomescape/application/AuthService.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/roomescape/application/AuthService.java b/src/main/java/roomescape/application/AuthService.java index 78b844b70..25b52e5ce 100644 --- a/src/main/java/roomescape/application/AuthService.java +++ b/src/main/java/roomescape/application/AuthService.java @@ -4,6 +4,7 @@ import roomescape.dto.TokenRequest; import roomescape.dto.TokenResponse; import roomescape.infrastructure.JwtTokenProvider; +import roomescape.member.LoginMember; import roomescape.member.Member; import roomescape.member.MemberDao; import roomescape.member.MemberResponse; @@ -18,12 +19,12 @@ public AuthService(JwtTokenProvider jwtTokenProvider, MemberDao memberDao) { this.memberDao = memberDao; } - public MemberResponse findMemberByEmail(String email) { + public LoginMember findLoginMemberByEmail(String email) { Member member = memberDao.findByEmail(email); if (member == null) { throw new AuthorizationException("사용자를 찾을 수 없습니다."); } - return new MemberResponse(member.getId(), member.getName(), member.getEmail()); + return new LoginMember(member.getId(), member.getName(), member.getEmail(), member.getRole()); } public String getEmailFromToken(String token) { From 1ad00b605723c54f9b231e0bb7c5e007b18212db Mon Sep 17 00:00:00 2001 From: sinji Date: Wed, 1 Jan 2025 22:07:53 +0900 Subject: [PATCH 19/91] =?UTF-8?q?refactor(TokenLoginController):=20[2?= =?UTF-8?q?=EB=8B=A8=EA=B3=84]=20loginMember=20=EB=A5=BC=20=ED=86=B5?= =?UTF-8?q?=ED=95=B4=20=EC=A0=84=EB=8B=AC=EB=90=9C=20=EC=A0=95=EB=B3=B4?= =?UTF-8?q?=EB=A5=BC=20=EC=9D=91=EB=8B=B5=EC=9C=BC=EB=A1=9C=20=EB=B0=94?= =?UTF-8?q?=EB=A1=9C=20=EC=82=AC=EC=9A=A9=ED=95=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../roomescape/UI/TokenLoginController.java | 34 ++++++------------- 1 file changed, 10 insertions(+), 24 deletions(-) diff --git a/src/main/java/roomescape/UI/TokenLoginController.java b/src/main/java/roomescape/UI/TokenLoginController.java index 67f974776..7c288a485 100644 --- a/src/main/java/roomescape/UI/TokenLoginController.java +++ b/src/main/java/roomescape/UI/TokenLoginController.java @@ -1,9 +1,7 @@ package roomescape.UI; import jakarta.servlet.http.Cookie; -import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; @@ -12,48 +10,36 @@ import roomescape.application.AuthService; import roomescape.dto.TokenRequest; import roomescape.dto.TokenResponse; -import roomescape.infrastructure.AuthorizationExtractor; -import roomescape.infrastructure.BearerAuthorizationExtractor; +import roomescape.member.LoginMember; import roomescape.member.MemberResponse; @RestController public class TokenLoginController { private final AuthService authService; - private final AuthorizationExtractor authorizationExtractor; public TokenLoginController(AuthService authService) { this.authService = authService; - this.authorizationExtractor = new BearerAuthorizationExtractor(); } @PostMapping("/login") public ResponseEntity tokenLogin(@RequestBody TokenRequest tokenRequest, HttpServletResponse response){ TokenResponse tokenResponse = authService.createToken(tokenRequest); + Cookie cookie = new Cookie("token", tokenResponse.getAccessToken()); cookie.setHttpOnly(true); cookie.setPath("/"); response.addCookie(cookie); + return ResponseEntity.ok().body(tokenResponse); } @GetMapping("/login/check") - public ResponseEntity checkLogin(HttpServletRequest request) { - Cookie[] cookies = request.getCookies(); - if (cookies != null) { - for (Cookie cookie : cookies) { - if (cookie.getName().equals("token")) { - String token = cookie.getValue(); - boolean isValid = authService.verifyToken(token); - if (isValid) { - String email = authService.getEmailFromToken(token); - MemberResponse memberResponse = authService.findMemberByEmail(email); - return ResponseEntity.ok(memberResponse); - } else { - return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); - } - } - } - } - return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); + public ResponseEntity checkLogin(LoginMember loginMember) { + MemberResponse memberResponse = new MemberResponse( + loginMember.getId(), + loginMember.getName(), + loginMember.getEmail() + ); + return ResponseEntity.ok(memberResponse); } } From 656234543e846b0421142f6f7ab8ad6ffb338aa6 Mon Sep 17 00:00:00 2001 From: sinji Date: Wed, 1 Jan 2025 22:09:35 +0900 Subject: [PATCH 20/91] =?UTF-8?q?refactor(ReservationController):=20[2?= =?UTF-8?q?=EB=8B=A8=EA=B3=84]=20name=EC=9D=B4=20=EC=97=86=EB=8A=94=20?= =?UTF-8?q?=EA=B2=BD=EC=9A=B0=20Cookie=EC=97=90=20=EB=8B=B4=EA=B8=B4=20?= =?UTF-8?q?=EC=A0=95=EB=B3=B4=EB=A5=BC=20=ED=99=9C=EC=9A=A9=ED=95=98?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../reservation/ReservationController.java | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/src/main/java/roomescape/reservation/ReservationController.java b/src/main/java/roomescape/reservation/ReservationController.java index b3bef3990..fa84cb71d 100644 --- a/src/main/java/roomescape/reservation/ReservationController.java +++ b/src/main/java/roomescape/reservation/ReservationController.java @@ -1,5 +1,6 @@ package roomescape.reservation; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; @@ -7,6 +8,9 @@ import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; +import roomescape.member.LoginMember; +import roomescape.member.Member; +import roomescape.member.MemberDao; import java.net.URI; import java.util.List; @@ -14,9 +18,11 @@ @RestController public class ReservationController { + private final MemberDao memberDao; private final ReservationService reservationService; - public ReservationController(ReservationService reservationService) { + public ReservationController(MemberDao memberDao, ReservationService reservationService) { + this.memberDao = memberDao; this.reservationService = reservationService; } @@ -26,18 +32,34 @@ public List list() { } @PostMapping("/reservations") - public ResponseEntity create(@RequestBody ReservationRequest reservationRequest) { + public ResponseEntity create(@RequestBody ReservationRequest reservationRequest, LoginMember loginMember) { + + if (reservationRequest.getName() == null) { + reservationRequest.setName(loginMember.getName()); + } + if (reservationRequest.getName() == null || reservationRequest.getDate() == null || reservationRequest.getTheme() == null || reservationRequest.getTime() == null) { return ResponseEntity.badRequest().build(); } + + Member member; + try { + member = memberDao.findByName(reservationRequest.getName()); + } catch (Exception e) { + return ResponseEntity.status(HttpStatus.NOT_FOUND).body("해당 이름의 예약이 없습니다.: " + reservationRequest.getName()); + } + + reservationRequest.setMemberId(member.getId()); + ReservationResponse reservation = reservationService.save(reservationRequest); return ResponseEntity.created(URI.create("/reservations/" + reservation.getId())).body(reservation); } + @DeleteMapping("/reservations/{id}") public ResponseEntity delete(@PathVariable Long id) { reservationService.deleteById(id); From c9a7b9c7d30282a6c795b5d7e0dde7a8dabf0921 Mon Sep 17 00:00:00 2001 From: sinji Date: Wed, 1 Jan 2025 22:13:02 +0900 Subject: [PATCH 21/91] =?UTF-8?q?refactor(ReservationRequest):=20[2?= =?UTF-8?q?=EB=8B=A8=EA=B3=84]=20DB=EC=97=90=EC=84=9C=20=ED=9A=8C=EC=9B=90?= =?UTF-8?q?=20=EB=8D=B0=EC=9D=B4=ED=84=B0=EB=A5=BC=20=EC=A1=B0=ED=9A=8C?= =?UTF-8?q?=ED=95=98=EA=B8=B0=20=EC=9C=84=ED=95=B4=20memberId=20=ED=95=84?= =?UTF-8?q?=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/roomescape/reservation/ReservationRequest.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/main/java/roomescape/reservation/ReservationRequest.java b/src/main/java/roomescape/reservation/ReservationRequest.java index 19f441246..846eb64ef 100644 --- a/src/main/java/roomescape/reservation/ReservationRequest.java +++ b/src/main/java/roomescape/reservation/ReservationRequest.java @@ -1,15 +1,24 @@ package roomescape.reservation; public class ReservationRequest { + private Long memberId; private String name; private String date; private Long theme; private Long time; + public void setMemberId(Long memberId) { + this.memberId = memberId; + } + public String getName() { return name; } + public void setName(String name) { + this.name = name; + } + public String getDate() { return date; } From 96df506586ee8375b3eeb7d2658f2b601cf7eebf Mon Sep 17 00:00:00 2001 From: sinji Date: Wed, 1 Jan 2025 23:34:40 +0900 Subject: [PATCH 22/91] =?UTF-8?q?refactor(MissionStepTest):=20[3=EB=8B=A8?= =?UTF-8?q?=EA=B3=84]=20=EC=82=BC=EB=8B=A8=EA=B3=84=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/java/roomescape/MissionStepTest.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/test/java/roomescape/MissionStepTest.java b/src/test/java/roomescape/MissionStepTest.java index 3c2269e7f..c39bae2c9 100644 --- a/src/test/java/roomescape/MissionStepTest.java +++ b/src/test/java/roomescape/MissionStepTest.java @@ -99,4 +99,23 @@ private String extractTokenFromResponse(ExtractableResponse response) assertThat(adminResponse.statusCode()).isEqualTo(201); assertThat(adminResponse.as(ReservationResponse.class).getName()).isEqualTo("브라운"); } + + @Test + void 삼단계() { + String brownToken = createToken("brown@email.com", "password"); + + RestAssured.given().log().all() + .cookie("token", brownToken) + .get("/admin") + .then().log().all() + .statusCode(401); + + String adminToken = createToken("admin@email.com", "password"); + + RestAssured.given().log().all() + .cookie("token", adminToken) + .get("/admin") + .then().log().all() + .statusCode(200); + } } From 1021263c7e85bff8ecf47fad477910c4bc6edbc7 Mon Sep 17 00:00:00 2001 From: sinji Date: Wed, 1 Jan 2025 23:36:30 +0900 Subject: [PATCH 23/91] =?UTF-8?q?feat(WebConfig):=20WebConfig=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80(Resolvers,=20interceptors)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/roomescape/config/WebConfig.java | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 src/main/java/roomescape/config/WebConfig.java diff --git a/src/main/java/roomescape/config/WebConfig.java b/src/main/java/roomescape/config/WebConfig.java new file mode 100644 index 000000000..db6bebe97 --- /dev/null +++ b/src/main/java/roomescape/config/WebConfig.java @@ -0,0 +1,34 @@ +package roomescape.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import roomescape.application.AuthService; +import roomescape.security.AdminAccessInterceptor; +import roomescape.web.LoginMemberArgumentResolver; + +import java.util.List; + +@Configuration +public class WebConfig implements WebMvcConfigurer { + private final AuthService authService; + + public WebConfig(AuthService authService) { + this.authService = authService; + } + + @Override + public void addArgumentResolvers(List resolvers) { + resolvers.add(new LoginMemberArgumentResolver(authService)); + } + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(new AdminAccessInterceptor(authService)) + .addPathPatterns("/admin/**") + .excludePathPatterns("/login"); + } + +} + From 0dd51e87578d70bc132dc3663e6b9250f11b7baf Mon Sep 17 00:00:00 2001 From: sinji Date: Wed, 1 Jan 2025 23:39:54 +0900 Subject: [PATCH 24/91] =?UTF-8?q?feat(AdminAccessInterceptor):=20[3?= =?UTF-8?q?=EB=8B=A8=EA=B3=84]=20Interceptor=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../security/AdminAccessInterceptor.java | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 src/main/java/roomescape/security/AdminAccessInterceptor.java diff --git a/src/main/java/roomescape/security/AdminAccessInterceptor.java b/src/main/java/roomescape/security/AdminAccessInterceptor.java new file mode 100644 index 000000000..be9a22e17 --- /dev/null +++ b/src/main/java/roomescape/security/AdminAccessInterceptor.java @@ -0,0 +1,54 @@ +package roomescape.security; + +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.HandlerInterceptor; +import roomescape.application.AuthService; +import roomescape.application.AuthorizationException; +import roomescape.member.LoginMember; + +import java.util.Arrays; + +@Component +public class AdminAccessInterceptor implements HandlerInterceptor { + private final AuthService authService; + + public AdminAccessInterceptor(AuthService authService) { + this.authService = authService; + } + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + String token = extractTokenFromCookies(request.getCookies()); + + String email = authService.getEmailFromToken(token); + LoginMember member = authService.findLoginMemberByEmail(email); + + if (member == null) { + response.setStatus(401); + return false; + } + + if (!"ADMIN".equals(member.getRole())) { + response.setStatus(401); + response.getWriter().write("권한이 없습니다."); + return false; + } + + return true; + } + + private String extractTokenFromCookies(Cookie[] cookies) { + if (cookies == null) { + throw new AuthorizationException("쿠키가 존재하지 않습니다."); + } + + return Arrays.stream(cookies) + .filter(cookie -> "token".equals(cookie.getName())) + .findFirst() + .map(Cookie::getValue) + .orElseThrow(() -> new AuthorizationException("토큰 쿠키가 없습니다.")); + } +} From 59aecdc53ff7930da55f206a89c1e86421083f36 Mon Sep 17 00:00:00 2001 From: sinji Date: Wed, 1 Jan 2025 23:40:37 +0900 Subject: [PATCH 25/91] =?UTF-8?q?[=EC=A7=88=EB=AC=B8]:=20build.gradle?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/build.gradle b/build.gradle index 8d52aebc6..31ba2ae66 100644 --- a/build.gradle +++ b/build.gradle @@ -16,6 +16,7 @@ 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-security' implementation 'dev.akkinoc.spring.boot:logback-access-spring-boot-starter:4.0.0' From 7622e3f7c116a5ff158fc9028dde0ea38efb9052 Mon Sep 17 00:00:00 2001 From: sinji Date: Wed, 1 Jan 2025 23:43:06 +0900 Subject: [PATCH 26/91] =?UTF-8?q?=EC=A3=BC=EC=84=9D=20=EB=B0=8F=20?= =?UTF-8?q?=EA=B3=B5=EB=B0=B1=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/roomescape/application/AuthService.java | 1 - src/main/java/roomescape/config/WebConfig.java | 2 -- src/main/java/roomescape/infrastructure/JwtTokenProvider.java | 1 - src/main/java/roomescape/member/MemberController.java | 2 -- src/main/java/roomescape/member/MemberDao.java | 1 - src/main/java/roomescape/security/AdminAccessInterceptor.java | 1 - 6 files changed, 8 deletions(-) diff --git a/src/main/java/roomescape/application/AuthService.java b/src/main/java/roomescape/application/AuthService.java index 25b52e5ce..13a3fd5cf 100644 --- a/src/main/java/roomescape/application/AuthService.java +++ b/src/main/java/roomescape/application/AuthService.java @@ -7,7 +7,6 @@ import roomescape.member.LoginMember; import roomescape.member.Member; import roomescape.member.MemberDao; -import roomescape.member.MemberResponse; @Service public class AuthService { diff --git a/src/main/java/roomescape/config/WebConfig.java b/src/main/java/roomescape/config/WebConfig.java index db6bebe97..5920a6199 100644 --- a/src/main/java/roomescape/config/WebConfig.java +++ b/src/main/java/roomescape/config/WebConfig.java @@ -29,6 +29,4 @@ public void addInterceptors(InterceptorRegistry registry) { .addPathPatterns("/admin/**") .excludePathPatterns("/login"); } - } - diff --git a/src/main/java/roomescape/infrastructure/JwtTokenProvider.java b/src/main/java/roomescape/infrastructure/JwtTokenProvider.java index bbba35cb4..5d2681d9e 100644 --- a/src/main/java/roomescape/infrastructure/JwtTokenProvider.java +++ b/src/main/java/roomescape/infrastructure/JwtTokenProvider.java @@ -3,7 +3,6 @@ import io.jsonwebtoken.*; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; - import java.util.Date; @Component diff --git a/src/main/java/roomescape/member/MemberController.java b/src/main/java/roomescape/member/MemberController.java index 881ae5e0d..fe009d062 100644 --- a/src/main/java/roomescape/member/MemberController.java +++ b/src/main/java/roomescape/member/MemberController.java @@ -1,10 +1,8 @@ package roomescape.member; import jakarta.servlet.http.Cookie; -import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; diff --git a/src/main/java/roomescape/member/MemberDao.java b/src/main/java/roomescape/member/MemberDao.java index 529ed5430..e3203235d 100644 --- a/src/main/java/roomescape/member/MemberDao.java +++ b/src/main/java/roomescape/member/MemberDao.java @@ -1,6 +1,5 @@ package roomescape.member; -import com.fasterxml.jackson.databind.deser.std.FromStringDeserializer; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.support.GeneratedKeyHolder; import org.springframework.jdbc.support.KeyHolder; diff --git a/src/main/java/roomescape/security/AdminAccessInterceptor.java b/src/main/java/roomescape/security/AdminAccessInterceptor.java index be9a22e17..9cdb34753 100644 --- a/src/main/java/roomescape/security/AdminAccessInterceptor.java +++ b/src/main/java/roomescape/security/AdminAccessInterceptor.java @@ -8,7 +8,6 @@ import roomescape.application.AuthService; import roomescape.application.AuthorizationException; import roomescape.member.LoginMember; - import java.util.Arrays; @Component From d09b798f7c99611b7ebbb47646d1ea26281b6c35 Mon Sep 17 00:00:00 2001 From: sinji Date: Wed, 8 Jan 2025 15:17:13 +0900 Subject: [PATCH 27/91] =?UTF-8?q?refactor(build.gradle):=20[4=EB=8B=A8?= =?UTF-8?q?=EA=B3=84]=20jdbc=20->=20jpa=20=EC=9D=98=EC=A1=B4=EC=84=B1=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 31ba2ae66..9bc129a0e 100644 --- a/build.gradle +++ b/build.gradle @@ -15,8 +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-security' + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'dev.akkinoc.spring.boot:logback-access-spring-boot-starter:4.0.0' From 999bca1bde845f0f6248a9cf790b410ee582229f Mon Sep 17 00:00:00 2001 From: sinji Date: Thu, 9 Jan 2025 01:07:49 +0900 Subject: [PATCH 28/91] =?UTF-8?q?refactor(application.properties):=20[4?= =?UTF-8?q?=EB=8B=A8=EA=B3=84]=20jpa=20=EC=9D=98=EC=A1=B4=EC=84=B1=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application.properties | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index a476badc6..bf810041b 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -3,10 +3,10 @@ spring.h2.console.enabled=true spring.h2.console.path=/h2-console spring.datasource.url=jdbc:h2:mem:database -#spring.jpa.show-sql=true -#spring.jpa.properties.hibernate.format_sql=true -#spring.jpa.ddl-auto=create-drop -#spring.jpa.defer-datasource-initialization=true +spring.jpa.show-sql=true +spring.jpa.properties.hibernate.format_sql=true +spring.jpa.ddl-auto=create-drop +spring.jpa.defer-datasource-initialization=true roomescape.auth.jwt.secret= Yn2kjibddFAWtnPJ2AFlL8WXmohJMCvigQggaEypa5E= roomescape.auth.jwt.expire-length=3600000 \ No newline at end of file From aad687f488d99e29e5eb6cd7d72bd8d6c3c90300 Mon Sep 17 00:00:00 2001 From: sinji Date: Thu, 9 Jan 2025 01:08:39 +0900 Subject: [PATCH 29/91] =?UTF-8?q?refactor(Dao):=20[4=EB=8B=A8=EA=B3=84]=20?= =?UTF-8?q?=EC=A0=84=EC=B2=B4=20DAO=20=ED=81=B4=EB=9E=98=EC=8A=A4=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/roomescape/member/MemberDao.java | 68 ---------- .../reservation/ReservationDao.java | 127 ------------------ src/main/java/roomescape/theme/ThemeDao.java | 41 ------ src/main/java/roomescape/time/TimeDao.java | 41 ------ 4 files changed, 277 deletions(-) delete mode 100644 src/main/java/roomescape/member/MemberDao.java delete mode 100644 src/main/java/roomescape/reservation/ReservationDao.java delete mode 100644 src/main/java/roomescape/theme/ThemeDao.java delete mode 100644 src/main/java/roomescape/time/TimeDao.java diff --git a/src/main/java/roomescape/member/MemberDao.java b/src/main/java/roomescape/member/MemberDao.java deleted file mode 100644 index e3203235d..000000000 --- a/src/main/java/roomescape/member/MemberDao.java +++ /dev/null @@ -1,68 +0,0 @@ -package roomescape.member; - -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.support.GeneratedKeyHolder; -import org.springframework.jdbc.support.KeyHolder; -import org.springframework.stereotype.Repository; - -@Repository -public class MemberDao { - private JdbcTemplate jdbcTemplate; - - public MemberDao(JdbcTemplate jdbcTemplate) { - this.jdbcTemplate = jdbcTemplate; - } - - public Member save(Member member) { - KeyHolder keyHolder = new GeneratedKeyHolder(); - jdbcTemplate.update(connection -> { - var ps = connection.prepareStatement("INSERT INTO member(name, email, password, role) VALUES (?, ?, ?, ?)", new String[]{"id"}); - ps.setString(1, member.getName()); - ps.setString(2, member.getEmail()); - ps.setString(3, member.getPassword()); - ps.setString(4, member.getRole()); - return ps; - }, keyHolder); - - return new Member(keyHolder.getKey().longValue(), member.getName(), member.getEmail(), "USER"); - } - - public Member findByEmailAndPassword(String email, String password) { - return jdbcTemplate.queryForObject( - "SELECT id, name, email, role FROM member WHERE email = ? AND password = ?", - (rs, rowNum) -> new Member( - rs.getLong("id"), - rs.getString("name"), - rs.getString("email"), - rs.getString("role") - ), - email, password - ); - } - - public Member findByName(String name) { - return jdbcTemplate.queryForObject( - "SELECT id, name, email, role FROM member WHERE name = ?", - (rs, rowNum) -> new Member( - rs.getLong("id"), - rs.getString("name"), - rs.getString("email"), - rs.getString("role") - ), - name - ); - } - - public Member findByEmail(String email) { - return jdbcTemplate.queryForObject( - "SELECT id, name, email, role FROM member WHERE email = ?", - (rs, rowNum) -> new Member( - rs.getLong("id"), - rs.getString("name"), - rs.getString("email"), - rs.getString("role") - ), - email - ); - } -} diff --git a/src/main/java/roomescape/reservation/ReservationDao.java b/src/main/java/roomescape/reservation/ReservationDao.java deleted file mode 100644 index a4972430c..000000000 --- a/src/main/java/roomescape/reservation/ReservationDao.java +++ /dev/null @@ -1,127 +0,0 @@ -package roomescape.reservation; - -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.support.GeneratedKeyHolder; -import org.springframework.jdbc.support.KeyHolder; -import org.springframework.stereotype.Repository; -import roomescape.theme.Theme; -import roomescape.time.Time; - -import java.sql.PreparedStatement; -import java.util.List; - -@Repository -public class ReservationDao { - - private final JdbcTemplate jdbcTemplate; - - public ReservationDao(JdbcTemplate jdbcTemplate) { - this.jdbcTemplate = jdbcTemplate; - } - - public List findAll() { - return jdbcTemplate.query( - "SELECT r.id AS reservation_id, r.name as reservation_name, r.date as reservation_date, " + - "t.id AS theme_id, t.name AS theme_name, t.description AS theme_description, " + - "ti.id AS time_id, ti.time_value AS time_value " + - "FROM reservation r " + - "JOIN theme t ON r.theme_id = t.id " + - "JOIN time ti ON r.time_id = ti.id", - - (rs, rowNum) -> new Reservation( - rs.getLong("reservation_id"), - rs.getString("reservation_name"), - rs.getString("reservation_date"), - new Time( - rs.getLong("time_id"), - rs.getString("time_value") - ), - new Theme( - rs.getLong("theme_id"), - rs.getString("theme_name"), - rs.getString("theme_description") - ))); - } - - public Reservation save(ReservationRequest reservationRequest) { - KeyHolder keyHolder = new GeneratedKeyHolder(); - jdbcTemplate.update(connection -> { - PreparedStatement ps = connection.prepareStatement("INSERT INTO reservation(date, name, theme_id, time_id) VALUES (?, ?, ?, ?)", new String[]{"id"}); - ps.setString(1, reservationRequest.getDate()); - ps.setString(2, reservationRequest.getName()); - ps.setLong(3, reservationRequest.getTheme()); - ps.setLong(4, reservationRequest.getTime()); - return ps; - }, keyHolder); - - Time time = jdbcTemplate.queryForObject("SELECT * FROM time WHERE id = ?", - (rs, rowNum) -> new Time(rs.getLong("id"), rs.getString("time_value")), - reservationRequest.getTime()); - - Theme theme = jdbcTemplate.queryForObject("SELECT * FROM theme WHERE id = ?", - (rs, rowNum) -> new Theme(rs.getLong("id"), rs.getString("name"), rs.getString("description")), - reservationRequest.getTheme()); - - return new Reservation( - keyHolder.getKey().longValue(), - reservationRequest.getName(), - reservationRequest.getDate(), - time, - theme - ); - } - - public void deleteById(Long id) { - jdbcTemplate.update("DELETE FROM reservation WHERE id = ?", id); - } - - public List findReservationsByDateAndTheme(String date, Long themeId) { - return jdbcTemplate.query( - "SELECT r.id AS reservation_id, r.name as reservation_name, r.date as reservation_date, " + - "t.id AS theme_id, t.name AS theme_name, t.description AS theme_description, " + - "ti.id AS time_id, ti.time_value AS time_value " + - "FROM reservation r " + - "JOIN theme t ON r.theme_id = t.id " + - "JOIN time ti ON r.time_id = ti.id" + - "WHERE r.date = ? AND r.theme_id = ?", - new Object[]{date, themeId}, - (rs, rowNum) -> new Reservation( - rs.getLong("reservation_id"), - rs.getString("reservation_name"), - rs.getString("reservation_date"), - new Time( - rs.getLong("time_id"), - rs.getString("time_value") - ), - new Theme( - rs.getLong("theme_id"), - rs.getString("theme_name"), - rs.getString("theme_description") - ))); - } - - public List findByDateAndThemeId(String date, Long themeId) { - return jdbcTemplate.query( - "SELECT r.id AS reservation_id, r.name as reservation_name, r.date as reservation_date, " + - "t.id AS theme_id, t.name AS theme_name, t.description AS theme_description, " + - "ti.id AS time_id, ti.time_value AS time_value " + - "FROM reservation r " + - "JOIN theme t ON r.theme_id = t.id " + - "JOIN time ti ON r.time_id = ti.id " + - "WHERE r.date = ? AND r.theme_id = ?", - new Object[]{date, themeId}, - (rs, rowNum) -> new Reservation( - rs.getLong("reservation_id"), - rs.getString("reservation_name"), - rs.getString("reservation_date"), - new Time( - rs.getLong("time_id"), - rs.getString("time_value") - ), - new Theme( - rs.getLong("theme_id"), - rs.getString("theme_name"), - rs.getString("theme_description") - ))); - } -} diff --git a/src/main/java/roomescape/theme/ThemeDao.java b/src/main/java/roomescape/theme/ThemeDao.java deleted file mode 100644 index 945341d8d..000000000 --- a/src/main/java/roomescape/theme/ThemeDao.java +++ /dev/null @@ -1,41 +0,0 @@ -package roomescape.theme; - -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.support.GeneratedKeyHolder; -import org.springframework.jdbc.support.KeyHolder; -import org.springframework.stereotype.Repository; - -import java.util.List; - -@Repository -public class ThemeDao { - private JdbcTemplate jdbcTemplate; - - public ThemeDao(JdbcTemplate jdbcTemplate) { - this.jdbcTemplate = jdbcTemplate; - } - - public List findAll() { - return jdbcTemplate.query("SELECT * FROM theme where deleted = false", (rs, rowNum) -> new Theme( - rs.getLong("id"), - rs.getString("name"), - rs.getString("description") - )); - } - - public Theme save(Theme theme) { - KeyHolder keyHolder = new GeneratedKeyHolder(); - jdbcTemplate.update(connection -> { - var ps = connection.prepareStatement("INSERT INTO theme(name, description) VALUES (?, ?)", new String[]{"id"}); - ps.setString(1, theme.getName()); - ps.setString(2, theme.getDescription()); - return ps; - }, keyHolder); - - return new Theme(keyHolder.getKey().longValue(), theme.getName(), theme.getDescription()); - } - - public void deleteById(Long id) { - jdbcTemplate.update("UPDATE theme SET deleted = true WHERE id = ?", id); - } -} diff --git a/src/main/java/roomescape/time/TimeDao.java b/src/main/java/roomescape/time/TimeDao.java deleted file mode 100644 index f39a9a328..000000000 --- a/src/main/java/roomescape/time/TimeDao.java +++ /dev/null @@ -1,41 +0,0 @@ -package roomescape.time; - -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.support.GeneratedKeyHolder; -import org.springframework.jdbc.support.KeyHolder; -import org.springframework.stereotype.Repository; - -import java.sql.PreparedStatement; -import java.util.List; - -@Repository -public class TimeDao { - private final JdbcTemplate jdbcTemplate; - - public TimeDao(JdbcTemplate jdbcTemplate) { - this.jdbcTemplate = jdbcTemplate; - } - - public List