From 6cf4a6d49a77462e98e20903e74872ae44960c1d Mon Sep 17 00:00:00 2001 From: TaeyeonRoyce Date: Wed, 5 Jun 2024 20:51:16 +0900 Subject: [PATCH 01/42] =?UTF-8?q?test:=20[1=EB=8B=A8=EA=B3=84]=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/java/roomescape/MissionStepTest.java | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/test/java/roomescape/MissionStepTest.java b/src/test/java/roomescape/MissionStepTest.java index 6add784b..510711ae 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 d0f6595bbd525883b5230a210816c2d0112edb11 Mon Sep 17 00:00:00 2001 From: TaeyeonRoyce Date: Wed, 5 Jun 2024 21:12:59 +0900 Subject: [PATCH 02/42] =?UTF-8?q?chore:=20[1=EB=8B=A8=EA=B3=84]=20jwt=20co?= =?UTF-8?q?nfig=20=EA=B0=92=20=EC=84=A4=EC=A0=95?= 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 a0f33bba..97d1e4b8 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.expiration=86400000 From e21365bc0c9f8946d8998d9ab063de0f85f26c5d Mon Sep 17 00:00:00 2001 From: TaeyeonRoyce Date: Wed, 5 Jun 2024 21:15:36 +0900 Subject: [PATCH 03/42] =?UTF-8?q?feat:=20[1=EB=8B=A8=EA=B3=84]=20payload?= =?UTF-8?q?=EC=97=90=20=EB=94=B0=EB=A5=B8=20jwt=20=EC=83=9D=EC=84=B1=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../infrastructure/jwt/JwtProvider.java | 37 +++++++++++++++++++ .../infrastructure/jwt/JwtTokenInfo.java | 6 +++ .../infrastructure/jwt/JwtProviderTest.java | 24 ++++++++++++ 3 files changed, 67 insertions(+) create mode 100644 src/main/java/roomescape/infrastructure/jwt/JwtProvider.java create mode 100644 src/main/java/roomescape/infrastructure/jwt/JwtTokenInfo.java create mode 100644 src/test/java/roomescape/infrastructure/jwt/JwtProviderTest.java diff --git a/src/main/java/roomescape/infrastructure/jwt/JwtProvider.java b/src/main/java/roomescape/infrastructure/jwt/JwtProvider.java new file mode 100644 index 00000000..09d40ba8 --- /dev/null +++ b/src/main/java/roomescape/infrastructure/jwt/JwtProvider.java @@ -0,0 +1,37 @@ + package roomescape.infrastructure.jwt; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.io.Decoders; +import io.jsonwebtoken.security.Keys; +import java.util.Date; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +@Component +public class JwtProvider { + + private final String jwtSecret; + private final Long validityInMilliseconds; + + public JwtProvider( + @Value("${roomescape.auth.jwt.secret}") String jwtSecret, + @Value("${roomescape.auth.jwt.expiration}") Long expireMilliseconds + ) { + this.jwtSecret = jwtSecret; + this.validityInMilliseconds = expireMilliseconds; + } + + public JwtTokenInfo createToken(String payload) { + Claims claims = Jwts.claims().setSubject(payload); + Date now = new Date(); + Date validity = new Date(now.getTime() + validityInMilliseconds); + String tokenValue = Jwts.builder() + .setClaims(claims) + .setIssuedAt(now) + .setExpiration(validity) + .signWith(Keys.hmacShaKeyFor(Decoders.BASE64URL.decode(jwtSecret))) + .compact(); + return new JwtTokenInfo(tokenValue); + } +} diff --git a/src/main/java/roomescape/infrastructure/jwt/JwtTokenInfo.java b/src/main/java/roomescape/infrastructure/jwt/JwtTokenInfo.java new file mode 100644 index 00000000..c53bac38 --- /dev/null +++ b/src/main/java/roomescape/infrastructure/jwt/JwtTokenInfo.java @@ -0,0 +1,6 @@ +package roomescape.infrastructure.jwt; + +public record JwtTokenInfo( + String accessToken +) { +} diff --git a/src/test/java/roomescape/infrastructure/jwt/JwtProviderTest.java b/src/test/java/roomescape/infrastructure/jwt/JwtProviderTest.java new file mode 100644 index 00000000..30e439c7 --- /dev/null +++ b/src/test/java/roomescape/infrastructure/jwt/JwtProviderTest.java @@ -0,0 +1,24 @@ +package roomescape.infrastructure.jwt; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; + +@SpringBootTest(webEnvironment = WebEnvironment.NONE) +class JwtProviderTest { + + @Autowired + private JwtProvider jwtProvider; + + @Test + void create_token() { + String payload = "test"; + + JwtTokenInfo token = jwtProvider.createToken(payload); + + assertThat(token.accessToken()).isNotNull(); + } +} From 76666e2e36c559ccc5992e68449a778baf5bea90 Mon Sep 17 00:00:00 2001 From: TaeyeonRoyce Date: Wed, 5 Jun 2024 21:22:20 +0900 Subject: [PATCH 04/42] =?UTF-8?q?refactor:=20[1=EB=8B=A8=EA=B3=84]=20?= =?UTF-8?q?=ED=9A=8C=EC=9B=90=20=EC=9D=B8=EC=A6=9D=20=EA=B5=AC=ED=98=84=20?= =?UTF-8?q?=EB=B0=A9=EC=8B=9D=20=EC=B6=94=EC=83=81=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/roomescape/auth/AuthorizationProvider.java | 6 ++++++ src/main/java/roomescape/auth/MemberAuthorization.java | 6 ++++++ .../roomescape/infrastructure/jwt/JwtProvider.java | 10 ++++++---- .../roomescape/infrastructure/jwt/JwtProviderTest.java | 5 +++-- 4 files changed, 21 insertions(+), 6 deletions(-) create mode 100644 src/main/java/roomescape/auth/AuthorizationProvider.java create mode 100644 src/main/java/roomescape/auth/MemberAuthorization.java diff --git a/src/main/java/roomescape/auth/AuthorizationProvider.java b/src/main/java/roomescape/auth/AuthorizationProvider.java new file mode 100644 index 00000000..262d96ad --- /dev/null +++ b/src/main/java/roomescape/auth/AuthorizationProvider.java @@ -0,0 +1,6 @@ +package roomescape.auth; + +public interface AuthorizationProvider { + + MemberAuthorization createByPayload(String payload); +} diff --git a/src/main/java/roomescape/auth/MemberAuthorization.java b/src/main/java/roomescape/auth/MemberAuthorization.java new file mode 100644 index 00000000..712fea93 --- /dev/null +++ b/src/main/java/roomescape/auth/MemberAuthorization.java @@ -0,0 +1,6 @@ +package roomescape.auth; + +public record MemberAuthorization( + String authorization +) { +} diff --git a/src/main/java/roomescape/infrastructure/jwt/JwtProvider.java b/src/main/java/roomescape/infrastructure/jwt/JwtProvider.java index 09d40ba8..9c4d4d89 100644 --- a/src/main/java/roomescape/infrastructure/jwt/JwtProvider.java +++ b/src/main/java/roomescape/infrastructure/jwt/JwtProvider.java @@ -7,9 +7,11 @@ import java.util.Date; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; +import roomescape.auth.AuthorizationProvider; +import roomescape.auth.MemberAuthorization; -@Component -public class JwtProvider { + @Component +public class JwtProvider implements AuthorizationProvider { private final String jwtSecret; private final Long validityInMilliseconds; @@ -22,7 +24,7 @@ public JwtProvider( this.validityInMilliseconds = expireMilliseconds; } - public JwtTokenInfo createToken(String payload) { + public MemberAuthorization createByPayload(String payload) { Claims claims = Jwts.claims().setSubject(payload); Date now = new Date(); Date validity = new Date(now.getTime() + validityInMilliseconds); @@ -32,6 +34,6 @@ public JwtTokenInfo createToken(String payload) { .setExpiration(validity) .signWith(Keys.hmacShaKeyFor(Decoders.BASE64URL.decode(jwtSecret))) .compact(); - return new JwtTokenInfo(tokenValue); + return new MemberAuthorization(tokenValue); } } diff --git a/src/test/java/roomescape/infrastructure/jwt/JwtProviderTest.java b/src/test/java/roomescape/infrastructure/jwt/JwtProviderTest.java index 30e439c7..9fb2437c 100644 --- a/src/test/java/roomescape/infrastructure/jwt/JwtProviderTest.java +++ b/src/test/java/roomescape/infrastructure/jwt/JwtProviderTest.java @@ -6,6 +6,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import roomescape.auth.MemberAuthorization; @SpringBootTest(webEnvironment = WebEnvironment.NONE) class JwtProviderTest { @@ -17,8 +18,8 @@ class JwtProviderTest { void create_token() { String payload = "test"; - JwtTokenInfo token = jwtProvider.createToken(payload); + MemberAuthorization token = jwtProvider.createByPayload(payload); - assertThat(token.accessToken()).isNotNull(); + assertThat(token.authorization()).isNotNull(); } } From 3e1fb846e7041434c01f20ce0c506a1c85c02dba Mon Sep 17 00:00:00 2001 From: TaeyeonRoyce Date: Fri, 7 Jun 2024 15:09:42 +0900 Subject: [PATCH 05/42] =?UTF-8?q?feat:=20[1=EB=8B=A8=EA=B3=84]=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20API=20=EA=B0=9C=EB=B0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../roomescape/member/MemberController.java | 22 +++++++++++++++---- .../roomescape/member/MemberLoginRequest.java | 7 ++++++ 2 files changed, 25 insertions(+), 4 deletions(-) create mode 100644 src/main/java/roomescape/member/MemberLoginRequest.java diff --git a/src/main/java/roomescape/member/MemberController.java b/src/main/java/roomescape/member/MemberController.java index 881ae5e0..ca631e00 100644 --- a/src/main/java/roomescape/member/MemberController.java +++ b/src/main/java/roomescape/member/MemberController.java @@ -1,22 +1,24 @@ 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; import java.net.URI; +import roomescape.auth.AuthorizationProvider; +import roomescape.auth.MemberAuthorization; @RestController public class MemberController { - private MemberService memberService; + private final MemberService memberService; + private final AuthorizationProvider authorizationProvider; - public MemberController(MemberService memberService) { + public MemberController(MemberService memberService, AuthorizationProvider authorizationProvider) { this.memberService = memberService; + this.authorizationProvider = authorizationProvider; } @PostMapping("/members") @@ -34,4 +36,16 @@ public ResponseEntity logout(HttpServletResponse response) { response.addCookie(cookie); return ResponseEntity.ok().build(); } + + @PostMapping("/login") + public void login( + @RequestBody MemberLoginRequest memberLoginRequest, + HttpServletResponse response + ) { + MemberAuthorization memberAuthorization = authorizationProvider.createByPayload(memberLoginRequest.email()); + Cookie cookie = new Cookie("token", memberAuthorization.authorization()); + cookie.setHttpOnly(true); + cookie.setPath("/"); + response.addCookie(cookie); + } } diff --git a/src/main/java/roomescape/member/MemberLoginRequest.java b/src/main/java/roomescape/member/MemberLoginRequest.java new file mode 100644 index 00000000..f47171e0 --- /dev/null +++ b/src/main/java/roomescape/member/MemberLoginRequest.java @@ -0,0 +1,7 @@ +package roomescape.member; + +public record MemberLoginRequest( + String email, + String password +) { +} From 61b3924fed85abcbe3ad7191d0edded60fb7fe9a Mon Sep 17 00:00:00 2001 From: TaeyeonRoyce Date: Fri, 7 Jun 2024 15:17:45 +0900 Subject: [PATCH 06/42] =?UTF-8?q?feat:=20[1=EB=8B=A8=EA=B3=84]=20=ED=86=A0?= =?UTF-8?q?=ED=81=B0=20parsing=20=EB=A1=9C=EC=A7=81=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../roomescape/auth/AuthorizationProvider.java | 2 ++ .../infrastructure/jwt/JwtProvider.java | 18 +++++++++++++++--- .../infrastructure/jwt/JwtProviderTest.java | 11 +++++++++++ 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/main/java/roomescape/auth/AuthorizationProvider.java b/src/main/java/roomescape/auth/AuthorizationProvider.java index 262d96ad..bb85cc37 100644 --- a/src/main/java/roomescape/auth/AuthorizationProvider.java +++ b/src/main/java/roomescape/auth/AuthorizationProvider.java @@ -3,4 +3,6 @@ public interface AuthorizationProvider { MemberAuthorization createByPayload(String payload); + + String parseAuthorization(String token); } diff --git a/src/main/java/roomescape/infrastructure/jwt/JwtProvider.java b/src/main/java/roomescape/infrastructure/jwt/JwtProvider.java index 9c4d4d89..4cdc5973 100644 --- a/src/main/java/roomescape/infrastructure/jwt/JwtProvider.java +++ b/src/main/java/roomescape/infrastructure/jwt/JwtProvider.java @@ -1,4 +1,4 @@ - package roomescape.infrastructure.jwt; +package roomescape.infrastructure.jwt; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; @@ -10,9 +10,11 @@ import roomescape.auth.AuthorizationProvider; import roomescape.auth.MemberAuthorization; - @Component +@Component public class JwtProvider implements AuthorizationProvider { + private static final String USER_IDX = "email"; + private final String jwtSecret; private final Long validityInMilliseconds; @@ -29,11 +31,21 @@ public MemberAuthorization createByPayload(String payload) { Date now = new Date(); Date validity = new Date(now.getTime() + validityInMilliseconds); String tokenValue = Jwts.builder() - .setClaims(claims) + .claim(USER_IDX, payload) .setIssuedAt(now) .setExpiration(validity) .signWith(Keys.hmacShaKeyFor(Decoders.BASE64URL.decode(jwtSecret))) .compact(); return new MemberAuthorization(tokenValue); } + + @Override + public String parseAuthorization(String token) { + return Jwts.parserBuilder() + .setSigningKey(Keys.hmacShaKeyFor(Decoders.BASE64URL.decode(jwtSecret))) + .build() + .parseClaimsJws(token) + .getBody() + .get(USER_IDX, String.class); + } } diff --git a/src/test/java/roomescape/infrastructure/jwt/JwtProviderTest.java b/src/test/java/roomescape/infrastructure/jwt/JwtProviderTest.java index 9fb2437c..e000d25b 100644 --- a/src/test/java/roomescape/infrastructure/jwt/JwtProviderTest.java +++ b/src/test/java/roomescape/infrastructure/jwt/JwtProviderTest.java @@ -2,6 +2,7 @@ import static org.assertj.core.api.Assertions.assertThat; +import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @@ -22,4 +23,14 @@ void create_token() { assertThat(token.authorization()).isNotNull(); } + + @Test + void parse_token() { + String payload = "test"; + MemberAuthorization token = jwtProvider.createByPayload(payload); + + String actual = jwtProvider.parseAuthorization(token.authorization()); + + Assertions.assertThat(actual).isEqualTo(payload); + } } From 2a82b2f0dd02d96a32ef88bf3edb64a5129baef6 Mon Sep 17 00:00:00 2001 From: TaeyeonRoyce Date: Fri, 7 Jun 2024 15:44:28 +0900 Subject: [PATCH 07/42] =?UTF-8?q?refactor:=20[1=EB=8B=A8=EA=B3=84]=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EA=B2=80=EC=A6=9D=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/AuthorizationProvider.java | 4 +- .../roomescape/auth/MemberAuthContext.java | 7 ++ ...thorization.java => MemberCredential.java} | 2 +- .../infrastructure/jwt/JwtProvider.java | 28 ++++---- .../java/roomescape/member/MemberRequest.java | 21 ++---- .../java/roomescape/member/MemberService.java | 20 +++++- .../infrastructure/jwt/JwtProviderTest.java | 23 +++++-- .../java/roomescape/member/MemberFixture.java | 8 +++ .../roomescape/member/MemberServiceTest.java | 64 +++++++++++++++++++ 9 files changed, 138 insertions(+), 39 deletions(-) create mode 100644 src/main/java/roomescape/auth/MemberAuthContext.java rename src/main/java/roomescape/auth/{MemberAuthorization.java => MemberCredential.java} (63%) create mode 100644 src/test/java/roomescape/member/MemberFixture.java create mode 100644 src/test/java/roomescape/member/MemberServiceTest.java diff --git a/src/main/java/roomescape/auth/AuthorizationProvider.java b/src/main/java/roomescape/auth/AuthorizationProvider.java index bb85cc37..306ac2c7 100644 --- a/src/main/java/roomescape/auth/AuthorizationProvider.java +++ b/src/main/java/roomescape/auth/AuthorizationProvider.java @@ -2,7 +2,7 @@ public interface AuthorizationProvider { - MemberAuthorization createByPayload(String payload); + MemberCredential create(MemberAuthContext member); - String parseAuthorization(String token); + MemberAuthContext parseCredential(MemberCredential token); } diff --git a/src/main/java/roomescape/auth/MemberAuthContext.java b/src/main/java/roomescape/auth/MemberAuthContext.java new file mode 100644 index 00000000..364a775f --- /dev/null +++ b/src/main/java/roomescape/auth/MemberAuthContext.java @@ -0,0 +1,7 @@ +package roomescape.auth; + +public record MemberAuthContext( + String name, + String role +) { +} diff --git a/src/main/java/roomescape/auth/MemberAuthorization.java b/src/main/java/roomescape/auth/MemberCredential.java similarity index 63% rename from src/main/java/roomescape/auth/MemberAuthorization.java rename to src/main/java/roomescape/auth/MemberCredential.java index 712fea93..b27ec13e 100644 --- a/src/main/java/roomescape/auth/MemberAuthorization.java +++ b/src/main/java/roomescape/auth/MemberCredential.java @@ -1,6 +1,6 @@ package roomescape.auth; -public record MemberAuthorization( +public record MemberCredential( String authorization ) { } diff --git a/src/main/java/roomescape/infrastructure/jwt/JwtProvider.java b/src/main/java/roomescape/infrastructure/jwt/JwtProvider.java index 4cdc5973..ac6b571b 100644 --- a/src/main/java/roomescape/infrastructure/jwt/JwtProvider.java +++ b/src/main/java/roomescape/infrastructure/jwt/JwtProvider.java @@ -8,12 +8,14 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import roomescape.auth.AuthorizationProvider; -import roomescape.auth.MemberAuthorization; +import roomescape.auth.MemberAuthContext; +import roomescape.auth.MemberCredential; @Component public class JwtProvider implements AuthorizationProvider { - private static final String USER_IDX = "email"; + private static final String USER_NAME = "name"; + private static final String USER_ROLE = "role"; private final String jwtSecret; private final Long validityInMilliseconds; @@ -26,26 +28,30 @@ public JwtProvider( this.validityInMilliseconds = expireMilliseconds; } - public MemberAuthorization createByPayload(String payload) { - Claims claims = Jwts.claims().setSubject(payload); + public MemberCredential create(MemberAuthContext context) { Date now = new Date(); Date validity = new Date(now.getTime() + validityInMilliseconds); String tokenValue = Jwts.builder() - .claim(USER_IDX, payload) + .claim(USER_NAME, context.name()) + .claim(USER_ROLE, context.role()) .setIssuedAt(now) .setExpiration(validity) .signWith(Keys.hmacShaKeyFor(Decoders.BASE64URL.decode(jwtSecret))) .compact(); - return new MemberAuthorization(tokenValue); + return new MemberCredential(tokenValue); } @Override - public String parseAuthorization(String token) { - return Jwts.parserBuilder() + public MemberAuthContext parseCredential(MemberCredential token) { + Claims claims = Jwts.parserBuilder() .setSigningKey(Keys.hmacShaKeyFor(Decoders.BASE64URL.decode(jwtSecret))) .build() - .parseClaimsJws(token) - .getBody() - .get(USER_IDX, String.class); + .parseClaimsJws(token.authorization()) + .getBody(); + + return new MemberAuthContext( + claims.get(USER_NAME, String.class), + claims.get(USER_ROLE, String.class) + ); } } diff --git a/src/main/java/roomescape/member/MemberRequest.java b/src/main/java/roomescape/member/MemberRequest.java index cafb79f1..d3258382 100644 --- a/src/main/java/roomescape/member/MemberRequest.java +++ b/src/main/java/roomescape/member/MemberRequest.java @@ -1,19 +1,8 @@ package roomescape.member; -public class MemberRequest { - private String name; - private String email; - private String password; - - public String getName() { - return name; - } - - public String getEmail() { - return email; - } - - public String getPassword() { - return password; - } +public record MemberRequest( + String name, + String email, + String password +) { } diff --git a/src/main/java/roomescape/member/MemberService.java b/src/main/java/roomescape/member/MemberService.java index ccaa8cba..f80eb93b 100644 --- a/src/main/java/roomescape/member/MemberService.java +++ b/src/main/java/roomescape/member/MemberService.java @@ -1,17 +1,33 @@ package roomescape.member; +import org.springframework.dao.DataAccessException; import org.springframework.stereotype.Service; +import roomescape.auth.MemberAuthContext; @Service public class MemberService { - private MemberDao memberDao; + private final MemberDao memberDao; public MemberService(MemberDao memberDao) { this.memberDao = memberDao; } public MemberResponse createMember(MemberRequest memberRequest) { - Member member = memberDao.save(new Member(memberRequest.getName(), memberRequest.getEmail(), memberRequest.getPassword(), "USER")); + Member member = memberDao.save( + new Member(memberRequest.name(), memberRequest.email(), memberRequest.password(), "USER")); return new MemberResponse(member.getId(), member.getName(), member.getEmail()); } + + public MemberAuthContext loginByEmailAndPassword(MemberLoginRequest request) { + try { + Member member = memberDao.findByEmailAndPassword(request.email(), request.password()); + return new MemberAuthContext(member.getName(), member.getEmail()); + } catch (DataAccessException exception) { + throw new IllegalArgumentException("로그인 정보가 불일치 합니다."); + } + } + + public Member checkLogin(MemberAuthContext authContext) { + + } } diff --git a/src/test/java/roomescape/infrastructure/jwt/JwtProviderTest.java b/src/test/java/roomescape/infrastructure/jwt/JwtProviderTest.java index e000d25b..a6c772ac 100644 --- a/src/test/java/roomescape/infrastructure/jwt/JwtProviderTest.java +++ b/src/test/java/roomescape/infrastructure/jwt/JwtProviderTest.java @@ -7,9 +7,14 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; -import roomescape.auth.MemberAuthorization; +import org.springframework.test.annotation.DirtiesContext; +import roomescape.auth.MemberAuthContext; +import roomescape.auth.MemberCredential; +import roomescape.member.Member; +import roomescape.member.MemberFixture; @SpringBootTest(webEnvironment = WebEnvironment.NONE) +@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD) class JwtProviderTest { @Autowired @@ -17,20 +22,24 @@ class JwtProviderTest { @Test void create_token() { - String payload = "test"; + String memberName = "test"; + Member member = MemberFixture.memberWithName(memberName); + MemberAuthContext authContext = new MemberAuthContext(member.getName(), member.getRole()); - MemberAuthorization token = jwtProvider.createByPayload(payload); + MemberCredential token = jwtProvider.create(authContext); assertThat(token.authorization()).isNotNull(); } @Test void parse_token() { - String payload = "test"; - MemberAuthorization token = jwtProvider.createByPayload(payload); + String memberName = "test"; + Member member = MemberFixture.memberWithName(memberName); + MemberAuthContext authContext = new MemberAuthContext(member.getName(), member.getRole()); + MemberCredential token = jwtProvider.create(authContext); - String actual = jwtProvider.parseAuthorization(token.authorization()); + MemberAuthContext actual = jwtProvider.parseCredential(token); - Assertions.assertThat(actual).isEqualTo(payload); + Assertions.assertThat(actual).isEqualTo(authContext); } } diff --git a/src/test/java/roomescape/member/MemberFixture.java b/src/test/java/roomescape/member/MemberFixture.java new file mode 100644 index 00000000..0ea2d514 --- /dev/null +++ b/src/test/java/roomescape/member/MemberFixture.java @@ -0,0 +1,8 @@ +package roomescape.member; + +public class MemberFixture { + + public static Member memberWithName(String name) { + return new Member(name, "email", "password", "USER"); + } +} diff --git a/src/test/java/roomescape/member/MemberServiceTest.java b/src/test/java/roomescape/member/MemberServiceTest.java new file mode 100644 index 00000000..7b363794 --- /dev/null +++ b/src/test/java/roomescape/member/MemberServiceTest.java @@ -0,0 +1,64 @@ +package roomescape.member; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.test.annotation.DirtiesContext; +import roomescape.auth.MemberAuthContext; + +@SpringBootTest(webEnvironment = WebEnvironment.NONE) +@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD) +class MemberServiceTest { + + @Autowired + private MemberService memberService; + + @Test + void create_member() { + Member member = MemberFixture.memberWithName("name"); + MemberRequest request = new MemberRequest( + member.getName(), + member.getEmail(), + member.getPassword() + ); + + assertThatCode(() -> memberService.createMember(request)).doesNotThrowAnyException(); + } + + + @Test + void login_by_email_and_password() { + Member member = MemberFixture.memberWithName("name"); + MemberRequest saveRequest = new MemberRequest( + member.getName(), + member.getEmail(), + member.getPassword() + ); + memberService.createMember(saveRequest); + MemberLoginRequest loginRequest = new MemberLoginRequest( + member.getEmail(), + member.getPassword() + ); + + MemberAuthContext loginMember = memberService.loginByEmailAndPassword(loginRequest); + + assertThat(loginMember.name()).isEqualTo(member.getName()); + } + + @Test + void throw_when_try_to_login_with_invalid_info() { + MemberLoginRequest loginRequest = new MemberLoginRequest( + "email", + "password" + ); + + assertThatThrownBy(() -> memberService.loginByEmailAndPassword(loginRequest)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("로그인 정보가 불일치 합니다."); + } +} From 226f8c1c916e389f44c7f359ae5ff708cdc6a7c2 Mon Sep 17 00:00:00 2001 From: TaeyeonRoyce Date: Fri, 7 Jun 2024 16:33:04 +0900 Subject: [PATCH 08/42] =?UTF-8?q?feat:=20[1=EB=8B=A8=EA=B3=84]=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20=EA=B2=80=EC=A6=9D=20API=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../roomescape/member/MemberController.java | 19 ++++++++++++++++--- .../java/roomescape/member/MemberService.java | 9 +++++++-- src/main/resources/schema.sql | 2 +- src/test/java/roomescape/MissionStepTest.java | 9 ++++----- 4 files changed, 28 insertions(+), 11 deletions(-) diff --git a/src/main/java/roomescape/member/MemberController.java b/src/main/java/roomescape/member/MemberController.java index ca631e00..a447a42c 100644 --- a/src/main/java/roomescape/member/MemberController.java +++ b/src/main/java/roomescape/member/MemberController.java @@ -3,13 +3,16 @@ import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletResponse; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.CookieValue; +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 java.net.URI; import roomescape.auth.AuthorizationProvider; -import roomescape.auth.MemberAuthorization; +import roomescape.auth.MemberAuthContext; +import roomescape.auth.MemberCredential; @RestController public class MemberController { @@ -42,10 +45,20 @@ public void login( @RequestBody MemberLoginRequest memberLoginRequest, HttpServletResponse response ) { - MemberAuthorization memberAuthorization = authorizationProvider.createByPayload(memberLoginRequest.email()); - Cookie cookie = new Cookie("token", memberAuthorization.authorization()); + MemberAuthContext memberAuthContext = memberService.loginByEmailAndPassword(memberLoginRequest); + MemberCredential memberCredential = authorizationProvider.create(memberAuthContext); + Cookie cookie = new Cookie("token", memberCredential.authorization()); cookie.setHttpOnly(true); cookie.setPath("/"); response.addCookie(cookie); } + + @GetMapping("/login/check") + public MemberResponse loginCheck( + @CookieValue("token") String token + ) { + MemberCredential memberCredential = new MemberCredential(token); + MemberAuthContext authContext = authorizationProvider.parseCredential(memberCredential); + return memberService.checkLogin(authContext); + } } diff --git a/src/main/java/roomescape/member/MemberService.java b/src/main/java/roomescape/member/MemberService.java index f80eb93b..adc19630 100644 --- a/src/main/java/roomescape/member/MemberService.java +++ b/src/main/java/roomescape/member/MemberService.java @@ -27,7 +27,12 @@ public MemberAuthContext loginByEmailAndPassword(MemberLoginRequest request) { } } - public Member checkLogin(MemberAuthContext authContext) { - + public MemberResponse checkLogin(MemberAuthContext authContext) { + try { + Member member = memberDao.findByName(authContext.name()); + return new MemberResponse(member.getId(), member.getName(), member.getEmail()); + } catch (DataAccessException exception) { + throw new IllegalArgumentException("존재하지 않는 회원입니다."); + } } } diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql index 75c947a5..b20c6f1b 100644 --- a/src/main/resources/schema.sql +++ b/src/main/resources/schema.sql @@ -57,4 +57,4 @@ VALUES ('10:00'), INSERT INTO reservation (name, date, time_id, theme_id) VALUES ('어드민', '2024-03-01', 1, 1), ('어드민', '2024-03-01', 2, 2), - ('어드민', '2024-03-01', 3, 3); \ No newline at end of file + ('어드민', '2024-03-01', 3, 3); diff --git a/src/test/java/roomescape/MissionStepTest.java b/src/test/java/roomescape/MissionStepTest.java index 510711ae..2413ff5a 100644 --- a/src/test/java/roomescape/MissionStepTest.java +++ b/src/test/java/roomescape/MissionStepTest.java @@ -1,18 +1,17 @@ package roomescape; +import static org.assertj.core.api.Assertions.assertThat; + import io.restassured.RestAssured; import io.restassured.http.ContentType; import io.restassured.response.ExtractableResponse; import io.restassured.response.Response; +import java.util.HashMap; +import java.util.Map; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.annotation.DirtiesContext; -import java.util.HashMap; -import java.util.Map; - -import static org.assertj.core.api.Assertions.assertThat; - @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) @DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD) public class MissionStepTest { From c556cfe1aceacc650206d6141f4cbbf7bc06ece3 Mon Sep 17 00:00:00 2001 From: TaeyeonRoyce Date: Sun, 23 Jun 2024 16:55:35 +0900 Subject: [PATCH 09/42] =?UTF-8?q?test:=20[1=EB=8B=A8=EA=B3=84]=20Test?= =?UTF-8?q?=EC=8B=9C=20Context=20=EB=A7=88=EB=8B=A4=20embedded=20DB?= =?UTF-8?q?=EB=A5=BC=20=EA=B0=96=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/resources/application.properties | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 src/test/resources/application.properties diff --git a/src/test/resources/application.properties b/src/test/resources/application.properties new file mode 100644 index 00000000..05339c84 --- /dev/null +++ b/src/test/resources/application.properties @@ -0,0 +1,7 @@ +#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.expiration=86400000 From 563aedfa7447479c97f21dbd6215970d9b10233d Mon Sep 17 00:00:00 2001 From: TaeyeonRoyce Date: Sun, 23 Jun 2024 17:19:22 +0900 Subject: [PATCH 10/42] =?UTF-8?q?test:=20[2=EB=8B=A8=EA=B3=84]=20=EC=9D=B4?= =?UTF-8?q?=EB=8B=A8=EA=B3=84=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/java/roomescape/MissionStepTest.java | 55 ++++++++++++++++--- 1 file changed, 47 insertions(+), 8 deletions(-) diff --git a/src/test/java/roomescape/MissionStepTest.java b/src/test/java/roomescape/MissionStepTest.java index 2413ff5a..33307445 100644 --- a/src/test/java/roomescape/MissionStepTest.java +++ b/src/test/java/roomescape/MissionStepTest.java @@ -11,6 +11,7 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.annotation.DirtiesContext; +import roomescape.reservation.ReservationResponse; @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) @DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD) @@ -18,9 +19,24 @@ public class MissionStepTest { @Test void 일단계() { + String token = createToken("admin@email.com", "password"); + 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("어드민"); + } + + private String createToken(String email, String password) { Map params = new HashMap<>(); - params.put("email", "admin@email.com"); - params.put("password", "password"); + params.put("email", email); + params.put("password", password); ExtractableResponse response = RestAssured.given().log().all() .contentType(ContentType.JSON) @@ -30,17 +46,40 @@ public class MissionStepTest { .statusCode(200) .extract(); - String token = response.headers().get("Set-Cookie").getValue().split(";")[0].split("=")[1]; - assertThat(token).isNotBlank(); + return response.headers().get("Set-Cookie").getValue().split(";")[0].split("=")[1]; + } - ExtractableResponse checkResponse = RestAssured.given().log().all() + @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) - .when().get("/login/check") + .contentType(ContentType.JSON) + .post("/reservations") .then().log().all() - .statusCode(200) .extract(); - assertThat(checkResponse.body().jsonPath().getString("name")).isEqualTo("어드민"); + assertThat(adminResponse.statusCode()).isEqualTo(201); + assertThat(adminResponse.as(ReservationResponse.class).getName()).isEqualTo("브라운"); } } From 99517ca0ef3f750bf0377daf2aac6ed090a4d829 Mon Sep 17 00:00:00 2001 From: TaeyeonRoyce Date: Sun, 23 Jun 2024 17:20:21 +0900 Subject: [PATCH 11/42] =?UTF-8?q?feat:=20[2=EB=8B=A8=EA=B3=84]=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20=ED=9A=8C=EC=9B=90=20=EA=B3=B5=ED=86=B5=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/AuthMemberArgumentResolver.java | 49 +++++++++++++++++++ .../java/roomescape/auth/Authentication.java | 11 +++++ 2 files changed, 60 insertions(+) create mode 100644 src/main/java/roomescape/auth/AuthMemberArgumentResolver.java create mode 100644 src/main/java/roomescape/auth/Authentication.java diff --git a/src/main/java/roomescape/auth/AuthMemberArgumentResolver.java b/src/main/java/roomescape/auth/AuthMemberArgumentResolver.java new file mode 100644 index 00000000..866a3a34 --- /dev/null +++ b/src/main/java/roomescape/auth/AuthMemberArgumentResolver.java @@ -0,0 +1,49 @@ +package roomescape.auth; + +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import java.util.Arrays; +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.context.request.ServletWebRequest; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.method.support.ModelAndViewContainer; + +@Component +public class AuthMemberArgumentResolver implements HandlerMethodArgumentResolver { + + private final AuthorizationProvider authorizationProvider; + + public AuthMemberArgumentResolver(AuthorizationProvider authorizationProvider) { + this.authorizationProvider = authorizationProvider; + } + + @Override + public boolean supportsParameter(MethodParameter parameter) { + return parameter.hasParameterAnnotation(Authentication.class) + && parameter.getParameterType().equals(MemberAuthContext.class); + } + + @Override + public Object resolveArgument( + MethodParameter parameter, + ModelAndViewContainer mavContainer, + NativeWebRequest webRequest, + WebDataBinderFactory binderFactory + ) { + String token = parseTokenFromNativeRequest((ServletWebRequest) webRequest); + MemberCredential memberCredential = new MemberCredential(token); + return authorizationProvider.parseCredential(memberCredential); + } + + private String parseTokenFromNativeRequest(ServletWebRequest webRequest) { + HttpServletRequest httpServletRequest = webRequest.getRequest(); + return Arrays.stream(httpServletRequest.getCookies()) + .filter(cookie -> cookie.getName().equals("token")) + .findFirst() + .map(Cookie::getValue) + .orElseThrow(() -> new IllegalArgumentException("로그인이 필요합니다.")); + } +} diff --git a/src/main/java/roomescape/auth/Authentication.java b/src/main/java/roomescape/auth/Authentication.java new file mode 100644 index 00000000..4485dd66 --- /dev/null +++ b/src/main/java/roomescape/auth/Authentication.java @@ -0,0 +1,11 @@ +package roomescape.auth; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.PARAMETER) +public @interface Authentication { +} From cd4bc3a775ed8db98030ef5506e69dcdb208f3e2 Mon Sep 17 00:00:00 2001 From: TaeyeonRoyce Date: Sun, 23 Jun 2024 17:26:19 +0900 Subject: [PATCH 12/42] =?UTF-8?q?feat:=20[2=EB=8B=A8=EA=B3=84]=20=EC=9D=B8?= =?UTF-8?q?=EC=A6=9D=20ArgumentResolver=20=EB=93=B1=EB=A1=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../roomescape/auth/AuthWebConfiguration.java | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 src/main/java/roomescape/auth/AuthWebConfiguration.java diff --git a/src/main/java/roomescape/auth/AuthWebConfiguration.java b/src/main/java/roomescape/auth/AuthWebConfiguration.java new file mode 100644 index 00000000..0d8cb4cf --- /dev/null +++ b/src/main/java/roomescape/auth/AuthWebConfiguration.java @@ -0,0 +1,21 @@ +package roomescape.auth; + +import java.util.List; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class AuthWebConfiguration implements WebMvcConfigurer { + + private final AuthMemberArgumentResolver authMemberArgumentResolver; + + public AuthWebConfiguration(AuthMemberArgumentResolver authMemberArgumentResolver) { + this.authMemberArgumentResolver = authMemberArgumentResolver; + } + + @Override + public void addArgumentResolvers(List resolvers) { + resolvers.add(authMemberArgumentResolver); + } +} From 9710322e52dfa25721c78b718d73d90f3258ea7d Mon Sep 17 00:00:00 2001 From: TaeyeonRoyce Date: Sun, 23 Jun 2024 17:26:57 +0900 Subject: [PATCH 13/42] =?UTF-8?q?refactor:=20[2=EB=8B=A8=EA=B3=84]=20Dto?= =?UTF-8?q?=20Record=20class=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../reservation/ReservationDao.java | 16 +++++------ .../reservation/ReservationRequest.java | 27 +++++-------------- .../reservation/ReservationService.java | 2 +- 3 files changed, 15 insertions(+), 30 deletions(-) diff --git a/src/main/java/roomescape/reservation/ReservationDao.java b/src/main/java/roomescape/reservation/ReservationDao.java index a4972430..d75201e3 100644 --- a/src/main/java/roomescape/reservation/ReservationDao.java +++ b/src/main/java/roomescape/reservation/ReservationDao.java @@ -47,25 +47,25 @@ 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()); + ps.setString(1, reservationRequest.date()); + ps.setString(2, reservationRequest.name()); + ps.setLong(3, reservationRequest.theme()); + ps.setLong(4, reservationRequest.time()); 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()); + reservationRequest.time()); Theme theme = jdbcTemplate.queryForObject("SELECT * FROM theme WHERE id = ?", (rs, rowNum) -> new Theme(rs.getLong("id"), rs.getString("name"), rs.getString("description")), - reservationRequest.getTheme()); + reservationRequest.theme()); return new Reservation( keyHolder.getKey().longValue(), - reservationRequest.getName(), - reservationRequest.getDate(), + reservationRequest.name(), + reservationRequest.date(), time, theme ); diff --git a/src/main/java/roomescape/reservation/ReservationRequest.java b/src/main/java/roomescape/reservation/ReservationRequest.java index 19f44124..55da337b 100644 --- a/src/main/java/roomescape/reservation/ReservationRequest.java +++ b/src/main/java/roomescape/reservation/ReservationRequest.java @@ -1,24 +1,9 @@ package roomescape.reservation; -public class ReservationRequest { - private String name; - private String date; - private Long theme; - private Long time; - - public String getName() { - return name; - } - - public String getDate() { - return date; - } - - public Long getTheme() { - return theme; - } - - public Long getTime() { - return time; - } +public record ReservationRequest( + String name, + String date, + Long theme, + Long time +) { } diff --git a/src/main/java/roomescape/reservation/ReservationService.java b/src/main/java/roomescape/reservation/ReservationService.java index bd331332..0350dc30 100644 --- a/src/main/java/roomescape/reservation/ReservationService.java +++ b/src/main/java/roomescape/reservation/ReservationService.java @@ -15,7 +15,7 @@ public ReservationService(ReservationDao reservationDao) { public ReservationResponse save(ReservationRequest reservationRequest) { Reservation reservation = reservationDao.save(reservationRequest); - return new ReservationResponse(reservation.getId(), reservationRequest.getName(), reservation.getTheme().getName(), reservation.getDate(), reservation.getTime().getValue()); + return new ReservationResponse(reservation.getId(), reservationRequest.name(), reservation.getTheme().getName(), reservation.getDate(), reservation.getTime().getValue()); } public void deleteById(Long id) { From 05a63f2639544fe24ea3290baa320417f84f1d37 Mon Sep 17 00:00:00 2001 From: TaeyeonRoyce Date: Sun, 23 Jun 2024 17:27:21 +0900 Subject: [PATCH 14/42] =?UTF-8?q?feat:=20[2=EB=8B=A8=EA=B3=84]=20=ED=9A=8C?= =?UTF-8?q?=EC=9B=90=20=EC=9D=B4=EB=A6=84=20=EC=97=AC=EB=B6=80=EC=97=90=20?= =?UTF-8?q?=EB=94=B0=EB=A5=B8=20=EC=98=88=EC=95=BD=20=EC=83=9D=EC=84=B1=20?= =?UTF-8?q?API=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../reservation/ReservationController.java | 29 +++++++++++++------ 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/src/main/java/roomescape/reservation/ReservationController.java b/src/main/java/roomescape/reservation/ReservationController.java index b3bef399..60cd7082 100644 --- a/src/main/java/roomescape/reservation/ReservationController.java +++ b/src/main/java/roomescape/reservation/ReservationController.java @@ -1,5 +1,7 @@ package roomescape.reservation; +import java.net.URI; +import java.util.List; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; @@ -7,9 +9,8 @@ import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; - -import java.net.URI; -import java.util.List; +import roomescape.auth.Authentication; +import roomescape.auth.MemberAuthContext; @RestController public class ReservationController { @@ -26,15 +27,25 @@ public List list() { } @PostMapping("/reservations") - public ResponseEntity create(@RequestBody ReservationRequest reservationRequest) { - if (reservationRequest.getName() == null - || reservationRequest.getDate() == null - || reservationRequest.getTheme() == null - || reservationRequest.getTime() == null) { + public ResponseEntity create( + @Authentication MemberAuthContext authContext, + @RequestBody ReservationRequest reservationRequest + ) { + if (reservationRequest.date() == null + || reservationRequest.theme() == null + || reservationRequest.time() == null) { return ResponseEntity.badRequest().build(); } - ReservationResponse reservation = reservationService.save(reservationRequest); + if (reservationRequest.name() == null) { + reservationRequest = new ReservationRequest( + authContext.name(), + reservationRequest.date(), + reservationRequest.theme(), + reservationRequest.time() + ); + } + ReservationResponse reservation = reservationService.save(reservationRequest); return ResponseEntity.created(URI.create("/reservations/" + reservation.getId())).body(reservation); } From 3042ff50e8c46640eb8ef39b5642591926367dcd Mon Sep 17 00:00:00 2001 From: TaeyeonRoyce Date: Sun, 23 Jun 2024 17:34:50 +0900 Subject: [PATCH 15/42] =?UTF-8?q?test:=20[3=EB=8B=A8=EA=B3=84]=20=EC=82=BC?= =?UTF-8?q?=EB=8B=A8=EA=B3=84=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= 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 33307445..81584245 100644 --- a/src/test/java/roomescape/MissionStepTest.java +++ b/src/test/java/roomescape/MissionStepTest.java @@ -82,4 +82,23 @@ private String createToken(String email, String password) { 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 e5219ad9fbff4057627935e778f287c274ced139 Mon Sep 17 00:00:00 2001 From: TaeyeonRoyce Date: Sun, 23 Jun 2024 17:40:34 +0900 Subject: [PATCH 16/42] =?UTF-8?q?feat:=20[3=EB=8B=A8=EA=B3=84]=20=EC=96=B4?= =?UTF-8?q?=EB=93=9C=EB=AF=BC=20=EC=9D=B8=EA=B0=80=20=EC=9D=B8=ED=84=B0?= =?UTF-8?q?=EC=85=89=ED=84=B0=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../roomescape/auth/AdminInterceptor.java | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 src/main/java/roomescape/auth/AdminInterceptor.java diff --git a/src/main/java/roomescape/auth/AdminInterceptor.java b/src/main/java/roomescape/auth/AdminInterceptor.java new file mode 100644 index 00000000..e5731060 --- /dev/null +++ b/src/main/java/roomescape/auth/AdminInterceptor.java @@ -0,0 +1,44 @@ +package roomescape.auth; + +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.util.Arrays; +import java.util.Optional; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.HandlerInterceptor; + +@Component +public class AdminInterceptor implements HandlerInterceptor { + + private final AuthorizationProvider authorizationProvider; + + public AdminInterceptor(AuthorizationProvider authorizationProvider) { + this.authorizationProvider = authorizationProvider; + } + + @Override + public boolean preHandle( + HttpServletRequest request, + HttpServletResponse response, + Object handler + ) { + Optional token = Arrays.stream(request.getCookies()) + .filter(cookie -> cookie.getName().equals("token")) + .findFirst() + .map(Cookie::getValue); + if (token.isEmpty()) { + response.setStatus(401); + return false; + } + + MemberCredential memberCredential = new MemberCredential(token.get()); + MemberAuthContext memberAuthContext = authorizationProvider.parseCredential(memberCredential); + if (!memberAuthContext.role().equalsIgnoreCase("admin")) { + response.setStatus(401); + return false; + } + + return true; + } +} From a9260e934d94b060f64f3336e8c382ed4352ec62 Mon Sep 17 00:00:00 2001 From: TaeyeonRoyce Date: Sun, 23 Jun 2024 17:45:42 +0900 Subject: [PATCH 17/42] =?UTF-8?q?feat:=20[3=EB=8B=A8=EA=B3=84]=20=EC=96=B4?= =?UTF-8?q?=EB=93=9C=EB=AF=BC=20=EA=B6=8C=ED=95=9C=20=EC=B2=B4=ED=81=AC=20?= =?UTF-8?q?=EC=9D=B8=ED=84=B0=EC=85=89=ED=84=B0=20=EB=93=B1=EB=A1=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/roomescape/auth/AuthWebConfiguration.java | 14 +++++++++++++- src/main/java/roomescape/member/MemberService.java | 2 +- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/main/java/roomescape/auth/AuthWebConfiguration.java b/src/main/java/roomescape/auth/AuthWebConfiguration.java index 0d8cb4cf..a33b29ed 100644 --- a/src/main/java/roomescape/auth/AuthWebConfiguration.java +++ b/src/main/java/roomescape/auth/AuthWebConfiguration.java @@ -3,19 +3,31 @@ import java.util.List; 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; @Configuration public class AuthWebConfiguration implements WebMvcConfigurer { private final AuthMemberArgumentResolver authMemberArgumentResolver; + private final AdminInterceptor adminInterceptor; - public AuthWebConfiguration(AuthMemberArgumentResolver authMemberArgumentResolver) { + public AuthWebConfiguration( + AuthMemberArgumentResolver authMemberArgumentResolver, + AdminInterceptor adminInterceptor + ) { this.authMemberArgumentResolver = authMemberArgumentResolver; + this.adminInterceptor = adminInterceptor; } @Override public void addArgumentResolvers(List resolvers) { resolvers.add(authMemberArgumentResolver); } + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(adminInterceptor) + .addPathPatterns("/admin"); + } } diff --git a/src/main/java/roomescape/member/MemberService.java b/src/main/java/roomescape/member/MemberService.java index adc19630..ee3aca12 100644 --- a/src/main/java/roomescape/member/MemberService.java +++ b/src/main/java/roomescape/member/MemberService.java @@ -21,7 +21,7 @@ public MemberResponse createMember(MemberRequest memberRequest) { public MemberAuthContext loginByEmailAndPassword(MemberLoginRequest request) { try { Member member = memberDao.findByEmailAndPassword(request.email(), request.password()); - return new MemberAuthContext(member.getName(), member.getEmail()); + return new MemberAuthContext(member.getName(), member.getRole()); } catch (DataAccessException exception) { throw new IllegalArgumentException("로그인 정보가 불일치 합니다."); } From 1a11963a384b92111f15f93af5cff844c6283555 Mon Sep 17 00:00:00 2001 From: kokodak Date: Mon, 1 Jul 2024 17:58:20 +0900 Subject: [PATCH 18/42] =?UTF-8?q?build:=20Spring=20Data=20JPA=20=EC=9D=98?= =?UTF-8?q?=EC=A1=B4=EC=84=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 8d52aebc..9bc129a0 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' From c799603cb0e8f777faa1f6626df7b5e051b92b19 Mon Sep 17 00:00:00 2001 From: kokodak Date: Tue, 2 Jul 2024 01:56:11 +0900 Subject: [PATCH 19/42] =?UTF-8?q?chore:=20JPA=20=EA=B4=80=EB=A0=A8=20prope?= =?UTF-8?q?rties=20=EB=82=B4=EC=9A=A9=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application.properties | 8 ++++---- src/test/resources/application.properties | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 97d1e4b8..e98869e4 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.hibernate.ddl-auto=create-drop +spring.jpa.defer-datasource-initialization=true roomescape.auth.jwt.secret= Yn2kjibddFAWtnPJ2AFlL8WXmohJMCvigQggaEypa5E= roomescape.auth.jwt.expiration=86400000 diff --git a/src/test/resources/application.properties b/src/test/resources/application.properties index 05339c84..ee8369b9 100644 --- a/src/test/resources/application.properties +++ b/src/test/resources/application.properties @@ -1,7 +1,7 @@ -#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.hibernate.ddl-auto=create-drop +spring.jpa.defer-datasource-initialization=true roomescape.auth.jwt.secret= Yn2kjibddFAWtnPJ2AFlL8WXmohJMCvigQggaEypa5E= roomescape.auth.jwt.expiration=86400000 From 66727918c673cb807abc485150722961f63bfc48 Mon Sep 17 00:00:00 2001 From: kokodak Date: Tue, 2 Jul 2024 01:56:49 +0900 Subject: [PATCH 20/42] =?UTF-8?q?chore:=20sql=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EC=9D=B4=EB=A6=84=20=EB=B3=80=EA=B2=BD=20=EB=B0=8F=20DDL=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 --- src/main/resources/data.sql | 21 ++++++++++++ src/main/resources/schema.sql | 60 ----------------------------------- 2 files changed, 21 insertions(+), 60 deletions(-) create mode 100644 src/main/resources/data.sql delete mode 100644 src/main/resources/schema.sql diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql new file mode 100644 index 00000000..45d0d21c --- /dev/null +++ b/src/main/resources/data.sql @@ -0,0 +1,21 @@ +INSERT INTO member (name, email, password, role) +VALUES ('어드민', 'admin@email.com', 'password', 'ADMIN'), + ('브라운', 'brown@email.com', 'password', 'USER'); + +INSERT INTO theme (name, description) +VALUES ('테마1', '테마1입니다.'), + ('테마2', '테마2입니다.'), + ('테마3', '테마3입니다.'); + +INSERT INTO time (time_value) +VALUES ('10:00'), + ('12:00'), + ('14:00'), + ('16:00'), + ('18:00'), + ('20:00'); + +INSERT INTO reservation (name, date, time_id, theme_id) +VALUES ('어드민', '2024-03-01', 1, 1), + ('어드민', '2024-03-01', 2, 2), + ('어드민', '2024-03-01', 3, 3); diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql deleted file mode 100644 index b20c6f1b..00000000 --- a/src/main/resources/schema.sql +++ /dev/null @@ -1,60 +0,0 @@ -CREATE TABLE time -( - id BIGINT NOT NULL AUTO_INCREMENT, - time_value VARCHAR(20) NOT NULL, - deleted BOOLEAN NOT NULL DEFAULT FALSE, - PRIMARY KEY (id) -); - -CREATE TABLE theme -( - id BIGINT NOT NULL AUTO_INCREMENT, - name VARCHAR(255) NOT NULL, - description VARCHAR(255) NOT NULL, - deleted BOOLEAN NOT NULL DEFAULT FALSE, - PRIMARY KEY (id) -); - -CREATE TABLE member -( - id BIGINT NOT NULL AUTO_INCREMENT, - name VARCHAR(255) NOT NULL, - email VARCHAR(255) UNIQUE NOT NULL, - password VARCHAR(255) NOT NULL, - role VARCHAR(255) NOT NULL, - PRIMARY KEY (id) -); - -CREATE TABLE reservation -( - id BIGINT NOT NULL AUTO_INCREMENT, - date VARCHAR(255) NOT NULL, - name VARCHAR(255) NOT NULL, - time_id BIGINT, - theme_id BIGINT, - PRIMARY KEY (id), - FOREIGN KEY (time_id) REFERENCES time (id), - FOREIGN KEY (theme_id) REFERENCES theme (id) -); - -INSERT INTO member (name, email, password, role) -VALUES ('어드민', 'admin@email.com', 'password', 'ADMIN'), - ('브라운', 'brown@email.com', 'password', 'USER'); - -INSERT INTO theme (name, description) -VALUES ('테마1', '테마1입니다.'), - ('테마2', '테마2입니다.'), - ('테마3', '테마3입니다.'); - -INSERT INTO time (time_value) -VALUES ('10:00'), - ('12:00'), - ('14:00'), - ('16:00'), - ('18:00'), - ('20:00'); - -INSERT INTO reservation (name, date, time_id, theme_id) -VALUES ('어드민', '2024-03-01', 1, 1), - ('어드민', '2024-03-01', 2, 2), - ('어드민', '2024-03-01', 3, 3); From 56f3d96b2241dbd1918350fee9b280cd3b780873 Mon Sep 17 00:00:00 2001 From: kokodak Date: Tue, 2 Jul 2024 01:57:35 +0900 Subject: [PATCH 21/42] =?UTF-8?q?feat:=20Time=20=EB=8F=84=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=EC=9D=84=20JPA=EB=A1=9C=20=EB=A7=88=EC=9D=B4=EA=B7=B8?= =?UTF-8?q?=EB=A0=88=EC=9D=B4=EC=85=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/roomescape/time/Time.java | 13 +++++- src/main/java/roomescape/time/TimeDao.java | 41 ------------------- .../java/roomescape/time/TimeRepository.java | 7 ++++ .../java/roomescape/time/TimeService.java | 41 ++++++++++--------- 4 files changed, 40 insertions(+), 62 deletions(-) delete mode 100644 src/main/java/roomescape/time/TimeDao.java create mode 100644 src/main/java/roomescape/time/TimeRepository.java diff --git a/src/main/java/roomescape/time/Time.java b/src/main/java/roomescape/time/Time.java index 008ed93c..681d983d 100644 --- a/src/main/java/roomescape/time/Time.java +++ b/src/main/java/roomescape/time/Time.java @@ -1,7 +1,19 @@ package roomescape.time; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; + +@Entity public class Time { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; + + @Column(name = "time_value") private String value; public Time(Long id, String value) { @@ -14,7 +26,6 @@ public Time(String value) { } public Time() { - } public Long getId() { diff --git a/src/main/java/roomescape/time/TimeDao.java b/src/main/java/roomescape/time/TimeDao.java deleted file mode 100644 index f39a9a32..00000000 --- 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