From fa4b93d3a2c14b18abb0f2005e69ad166e8ec375 Mon Sep 17 00:00:00 2001 From: bingle625 Date: Sat, 28 Dec 2024 14:41:13 +0900 Subject: [PATCH 1/5] =?UTF-8?q?feat:=20=EC=9D=BC=EB=8B=A8=EA=B3=84=20?= =?UTF-8?q?=ED=86=B5=EA=B3=BC=ED=95=98=EB=8F=84=EB=A1=9D=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../roomescape/member/MemberController.java | 73 ++++++++++++++++++- .../java/roomescape/member/MemberDao.java | 12 +++ src/test/java/roomescape/MissionStepTest.java | 23 ++++-- 3 files changed, 100 insertions(+), 8 deletions(-) diff --git a/src/main/java/roomescape/member/MemberController.java b/src/main/java/roomescape/member/MemberController.java index 881ae5e0d..a8303c0e3 100644 --- a/src/main/java/roomescape/member/MemberController.java +++ b/src/main/java/roomescape/member/MemberController.java @@ -1,8 +1,14 @@ package roomescape.member; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.security.Keys; import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import java.security.Key; +import java.util.Date; +import java.util.Map; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; @@ -13,10 +19,16 @@ @RestController public class MemberController { + + private final MemberDao memberDao; private MemberService memberService; - public MemberController(MemberService memberService) { + private Key key; + + public MemberController(MemberService memberService, MemberDao memberDao) { this.memberService = memberService; + this.key = Keys.secretKeyFor(SignatureAlgorithm.HS512); + this.memberDao = memberDao; } @PostMapping("/members") @@ -25,6 +37,65 @@ public ResponseEntity createMember(@RequestBody MemberRequest memberRequest) { return ResponseEntity.created(URI.create("/members/" + member.getId())).body(member); } + @PostMapping("/login") + public ResponseEntity login(@RequestBody Map body) { + // HttpServletRequest 가 Request 객체가 아닌가? 일단 Map으로 대체 + String email = body.get("email"); + String password = body.get("password"); + Date now = new Date(); + + // duration 1시간으로 가정 + int durationSecond = 60 * 60; + Date expirationDate = new Date(now.getTime() + 1000L * durationSecond); + + // TODO: member가 존재하지 않을때의 처리 + Member member = this.memberDao.findByEmailAndPassword(email, password); + + String token = Jwts.builder() + .setSubject(member.getId().toString()) + .claim("name", member.getName()) + .claim("role", member.getName()) + .signWith(this.key) + .setIssuedAt(now) + .setExpiration(expirationDate) + .compact(); + + + // TODO: 쿠키를 header 에 정상적으로 넣도록 수정 + Cookie cookie = new Cookie("token", token); + cookie.setHttpOnly(true); + cookie.setPath("/"); + + return ResponseEntity.ok().header("Set-Cookie", "token=" + token + ";").build(); + } + + @GetMapping("/login/check") + public ResponseEntity checkLogin(HttpServletRequest request) { + Cookie[] cookies = request.getCookies(); + + String token = this.extractTokenFromCookie(cookies); + + Long memberId = Long.valueOf(Jwts.parserBuilder() + .setSigningKey(this.key) + .build() + .parseClaimsJws(token) + .getBody().getSubject()); + + Member member = this.memberDao.findById(memberId); + + return ResponseEntity.ok().body(Map.of("name", member.getName())); + } + + private String extractTokenFromCookie(Cookie[] cookies) { + for (Cookie cookie : cookies) { + if (cookie.getName().equals("token")) { + return cookie.getValue(); + } + } + + return ""; + } + @PostMapping("/logout") public ResponseEntity logout(HttpServletResponse response) { Cookie cookie = new Cookie("token", ""); diff --git a/src/main/java/roomescape/member/MemberDao.java b/src/main/java/roomescape/member/MemberDao.java index 81f77f4cd..df94f59f6 100644 --- a/src/main/java/roomescape/member/MemberDao.java +++ b/src/main/java/roomescape/member/MemberDao.java @@ -52,4 +52,16 @@ public Member findByName(String name) { name ); } + + public Member findById(final Long memberId) { + return jdbcTemplate.queryForObject("SELECT id, name, email, role FROM member WHERE id = ?", + (rs, rowNum) -> new Member( + rs.getLong("id"), + rs.getString("name"), + rs.getString("email"), + rs.getString("role") + ), + memberId + ); + } } diff --git a/src/test/java/roomescape/MissionStepTest.java b/src/test/java/roomescape/MissionStepTest.java index 6add784bd..b7926769e 100644 --- a/src/test/java/roomescape/MissionStepTest.java +++ b/src/test/java/roomescape/MissionStepTest.java @@ -24,15 +24,24 @@ public class MissionStepTest { params.put("password", "password"); ExtractableResponse response = RestAssured.given().log().all() - .contentType(ContentType.JSON) - .body(params) - .when().post("/login") - .then().log().all() - .statusCode(200) - .extract(); + .contentType(ContentType.JSON) + .body(params) + .when().post("/login") + .then().log().all() + .statusCode(200) + .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 864d9fe3a3e7322994a5b8e31bb82009f11e9a13 Mon Sep 17 00:00:00 2001 From: bingle625 Date: Sat, 28 Dec 2024 14:52:02 +0900 Subject: [PATCH 2/5] =?UTF-8?q?test:=20=EC=9D=B4=EB=8B=A8=EA=B3=84=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/java/roomescape/MissionStepTest.java | 57 ++++++++++++++++--- 1 file changed, 49 insertions(+), 8 deletions(-) diff --git a/src/test/java/roomescape/MissionStepTest.java b/src/test/java/roomescape/MissionStepTest.java index b7926769e..ad2fcc1c1 100644 --- a/src/test/java/roomescape/MissionStepTest.java +++ b/src/test/java/roomescape/MissionStepTest.java @@ -10,6 +10,7 @@ import java.util.HashMap; import java.util.Map; +import roomescape.reservation.ReservationResponse; import static org.assertj.core.api.Assertions.assertThat; @@ -19,9 +20,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 static String createToken(final String mail, final String password) { Map params = new HashMap<>(); - params.put("email", "admin@email.com"); - params.put("password", "password"); + params.put("email", mail); + params.put("password", password); ExtractableResponse response = RestAssured.given().log().all() .contentType(ContentType.JSON) @@ -31,17 +47,42 @@ 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("브라운"); } + + } \ No newline at end of file From 18311394b4107166cf332ea98d9b809a44093891 Mon Sep 17 00:00:00 2001 From: bingle625 Date: Sat, 28 Dec 2024 16:19:55 +0900 Subject: [PATCH 3/5] =?UTF-8?q?feat:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=EC=8B=9C=20=EC=9D=B4=EB=A6=84=20=EC=97=86=EC=9D=84=20=EB=95=8C?= =?UTF-8?q?=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=EC=A0=95=EB=B3=B4=EB=A1=9C=20?= =?UTF-8?q?=EC=98=88=EC=95=BD=EA=B0=80=EB=8A=A5=ED=95=98=EB=8F=84=EB=A1=9D?= =?UTF-8?q?=20=EC=88=98=EC=A0=95=20(2=EB=8B=A8=EA=B3=84)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ArgumentResolver를 이용한 LoginMember 바인딩 - TokenUtil 분리 --- .../java/roomescape/config/WebConfig.java | 22 +++ .../java/roomescape/member/LoginMember.java | 8 + .../member/LoginMemberArgumentResolver.java | 42 ++++++ .../roomescape/member/MemberController.java | 141 +++++++----------- .../java/roomescape/member/MemberService.java | 4 + .../java/roomescape/member/TokenUtil.java | 58 +++++++ .../reservation/ReservationController.java | 52 ++++--- .../reservation/ReservationRequest.java | 4 + .../reservation/ReservationService.java | 11 ++ 9 files changed, 228 insertions(+), 114 deletions(-) create mode 100644 src/main/java/roomescape/config/WebConfig.java create mode 100644 src/main/java/roomescape/member/LoginMember.java create mode 100644 src/main/java/roomescape/member/LoginMemberArgumentResolver.java create mode 100644 src/main/java/roomescape/member/TokenUtil.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..e98025e69 --- /dev/null +++ b/src/main/java/roomescape/config/WebConfig.java @@ -0,0 +1,22 @@ +package roomescape.config; + +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; +import roomescape.member.LoginMemberArgumentResolver; + +@Configuration +public class WebConfig implements WebMvcConfigurer { + + private final LoginMemberArgumentResolver loginMemberArgumentResolver; + + public WebConfig(LoginMemberArgumentResolver loginMemberArgumentResolver) { + this.loginMemberArgumentResolver = loginMemberArgumentResolver; + } + + @Override + public void addArgumentResolvers(final List resolvers) { + resolvers.add(this.loginMemberArgumentResolver); + } +} diff --git a/src/main/java/roomescape/member/LoginMember.java b/src/main/java/roomescape/member/LoginMember.java new file mode 100644 index 000000000..36a8bfc07 --- /dev/null +++ b/src/main/java/roomescape/member/LoginMember.java @@ -0,0 +1,8 @@ +package roomescape.member; + +public class LoginMember extends Member { + + public LoginMember(final Long id, final String name, final String email, final String role) { + super(id, name, email, role); + } +} diff --git a/src/main/java/roomescape/member/LoginMemberArgumentResolver.java b/src/main/java/roomescape/member/LoginMemberArgumentResolver.java new file mode 100644 index 000000000..520677d5b --- /dev/null +++ b/src/main/java/roomescape/member/LoginMemberArgumentResolver.java @@ -0,0 +1,42 @@ +package roomescape.member; + +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; + +@Component +public class LoginMemberArgumentResolver implements HandlerMethodArgumentResolver { + + private final MemberService memberService; + private final TokenUtil tokenUtil; + + public LoginMemberArgumentResolver(MemberService memberService, TokenUtil tokenUtil) { + this.memberService = memberService; + this.tokenUtil = tokenUtil; + } + + @Override + public boolean supportsParameter(final MethodParameter parameter) { + return parameter.getParameterType().equals(LoginMember.class); + + } + + @Override + public Object resolveArgument( + final MethodParameter parameter, + final ModelAndViewContainer mavContainer, + final NativeWebRequest webRequest, + final WebDataBinderFactory binderFactory) + throws Exception { + HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest(); + Long memberId = tokenUtil.getMemberId(request.getCookies()); + + Member member = memberService.find(memberId); + return new LoginMember(member.getId(), member.getName(), member.getEmail(), member.getRole()); + + } +} diff --git a/src/main/java/roomescape/member/MemberController.java b/src/main/java/roomescape/member/MemberController.java index a8303c0e3..40a2b693f 100644 --- a/src/main/java/roomescape/member/MemberController.java +++ b/src/main/java/roomescape/member/MemberController.java @@ -1,13 +1,8 @@ package roomescape.member; -import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.SignatureAlgorithm; -import io.jsonwebtoken.security.Keys; import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import java.security.Key; -import java.util.Date; import java.util.Map; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; @@ -20,89 +15,55 @@ @RestController public class MemberController { - private final MemberDao memberDao; - private MemberService memberService; - - private Key key; - - public MemberController(MemberService memberService, MemberDao memberDao) { - this.memberService = memberService; - this.key = Keys.secretKeyFor(SignatureAlgorithm.HS512); - this.memberDao = memberDao; - } - - @PostMapping("/members") - public ResponseEntity createMember(@RequestBody MemberRequest memberRequest) { - MemberResponse member = memberService.createMember(memberRequest); - return ResponseEntity.created(URI.create("/members/" + member.getId())).body(member); - } - - @PostMapping("/login") - public ResponseEntity login(@RequestBody Map body) { - // HttpServletRequest 가 Request 객체가 아닌가? 일단 Map으로 대체 - String email = body.get("email"); - String password = body.get("password"); - Date now = new Date(); - - // duration 1시간으로 가정 - int durationSecond = 60 * 60; - Date expirationDate = new Date(now.getTime() + 1000L * durationSecond); - - // TODO: member가 존재하지 않을때의 처리 - Member member = this.memberDao.findByEmailAndPassword(email, password); - - String token = Jwts.builder() - .setSubject(member.getId().toString()) - .claim("name", member.getName()) - .claim("role", member.getName()) - .signWith(this.key) - .setIssuedAt(now) - .setExpiration(expirationDate) - .compact(); - - - // TODO: 쿠키를 header 에 정상적으로 넣도록 수정 - Cookie cookie = new Cookie("token", token); - cookie.setHttpOnly(true); - cookie.setPath("/"); - - return ResponseEntity.ok().header("Set-Cookie", "token=" + token + ";").build(); - } - - @GetMapping("/login/check") - public ResponseEntity checkLogin(HttpServletRequest request) { - Cookie[] cookies = request.getCookies(); - - String token = this.extractTokenFromCookie(cookies); - - Long memberId = Long.valueOf(Jwts.parserBuilder() - .setSigningKey(this.key) - .build() - .parseClaimsJws(token) - .getBody().getSubject()); - - Member member = this.memberDao.findById(memberId); - - return ResponseEntity.ok().body(Map.of("name", member.getName())); - } - - private String extractTokenFromCookie(Cookie[] cookies) { - for (Cookie cookie : cookies) { - if (cookie.getName().equals("token")) { - return cookie.getValue(); - } - } - - return ""; - } - - @PostMapping("/logout") - public ResponseEntity logout(HttpServletResponse response) { - Cookie cookie = new Cookie("token", ""); - cookie.setHttpOnly(true); - cookie.setPath("/"); - cookie.setMaxAge(0); - response.addCookie(cookie); - return ResponseEntity.ok().build(); - } + private final MemberDao memberDao; + private final TokenUtil tokenUtil; + private MemberService memberService; + + public MemberController(MemberService memberService, MemberDao memberDao, TokenUtil tokenUtil) { + this.memberService = memberService; + this.memberDao = memberDao; + this.tokenUtil = tokenUtil; + } + + @PostMapping("/members") + public ResponseEntity createMember(@RequestBody MemberRequest memberRequest) { + MemberResponse member = memberService.createMember(memberRequest); + return ResponseEntity.created(URI.create("/members/" + member.getId())).body(member); + } + + @PostMapping("/login") + public ResponseEntity login(@RequestBody Map body) { + // HttpServletRequest 가 Request 객체가 아닌가? 일단 Map으로 대체 + String email = body.get("email"); + String password = body.get("password"); + + // TODO: member가 존재하지 않을때의 처리 + Member member = memberDao.findByEmailAndPassword(email, password); + String token = tokenUtil.generate(member); + + // TODO: 쿠키를 header 에 정상적으로 넣도록 수정 + Cookie cookie = new Cookie("token", token); + cookie.setHttpOnly(true); + cookie.setPath("/"); + + return ResponseEntity.ok().header("Set-Cookie", "token=" + token + ";").build(); + } + + @GetMapping("/login/check") + public ResponseEntity> checkLogin(HttpServletRequest request) { + Long memberId = tokenUtil.getMemberId(request.getCookies()); + Member member = memberDao.findById(memberId); + + return ResponseEntity.ok().body(Map.of("name", member.getName())); + } + + @PostMapping("/logout") + public ResponseEntity logout(HttpServletResponse response) { + Cookie cookie = new Cookie("token", ""); + cookie.setHttpOnly(true); + cookie.setPath("/"); + cookie.setMaxAge(0); + response.addCookie(cookie); + return ResponseEntity.ok().build(); + } } diff --git a/src/main/java/roomescape/member/MemberService.java b/src/main/java/roomescape/member/MemberService.java index ccaa8cba5..6e55617af 100644 --- a/src/main/java/roomescape/member/MemberService.java +++ b/src/main/java/roomescape/member/MemberService.java @@ -10,6 +10,10 @@ public MemberService(MemberDao memberDao) { this.memberDao = memberDao; } + public Member find(Long memberId) { + return this.memberDao.findById(memberId); + } + public MemberResponse createMember(MemberRequest memberRequest) { Member member = memberDao.save(new Member(memberRequest.getName(), memberRequest.getEmail(), memberRequest.getPassword(), "USER")); return new MemberResponse(member.getId(), member.getName(), member.getEmail()); diff --git a/src/main/java/roomescape/member/TokenUtil.java b/src/main/java/roomescape/member/TokenUtil.java new file mode 100644 index 000000000..4b3da3536 --- /dev/null +++ b/src/main/java/roomescape/member/TokenUtil.java @@ -0,0 +1,58 @@ +package roomescape.member; + +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.security.Keys; +import jakarta.servlet.http.Cookie; +import java.security.Key; +import java.util.Date; +import org.springframework.stereotype.Component; + + +@Component +public class TokenUtil { + + private Key key; + + public TokenUtil() { + this.key = Keys.secretKeyFor(SignatureAlgorithm.HS512); + } + + public String generate(final Member member) { + Date now = new Date(); + + // duration 1시간으로 가정 + int durationSecond = 60 * 60; + Date expirationDate = new Date(now.getTime() + 1000L * durationSecond); + + return Jwts.builder() + .setSubject(member.getId().toString()) + .claim("name", member.getName()) + .claim("role", member.getName()) + .signWith(this.key) + .setIssuedAt(now) + .setExpiration(expirationDate) + .compact(); + } + + public Long getMemberId(final Cookie[] cookies) { + + String token = this.extractTokenFromCookie(cookies); + + return Long.valueOf(Jwts.parserBuilder() + .setSigningKey(this.key) + .build() + .parseClaimsJws(token) + .getBody().getSubject()); + } + + private String extractTokenFromCookie(Cookie[] cookies) { + for (Cookie cookie : cookies) { + if (cookie.getName().equals("token")) { + return cookie.getValue(); + } + } + + return ""; + } +} diff --git a/src/main/java/roomescape/reservation/ReservationController.java b/src/main/java/roomescape/reservation/ReservationController.java index b3bef3990..6c6277ca5 100644 --- a/src/main/java/roomescape/reservation/ReservationController.java +++ b/src/main/java/roomescape/reservation/ReservationController.java @@ -10,37 +10,41 @@ import java.net.URI; import java.util.List; +import roomescape.member.LoginMember; +import roomescape.member.Member; @RestController public class ReservationController { - private final ReservationService reservationService; + private final ReservationService reservationService; - public ReservationController(ReservationService reservationService) { - this.reservationService = reservationService; - } + public ReservationController(ReservationService reservationService) { + this.reservationService = reservationService; + } - @GetMapping("/reservations") - public List list() { - return reservationService.findAll(); - } + @GetMapping("/reservations") + public List list() { + return reservationService.findAll(); + } - @PostMapping("/reservations") - public ResponseEntity create(@RequestBody ReservationRequest reservationRequest) { - if (reservationRequest.getName() == null - || reservationRequest.getDate() == null - || reservationRequest.getTheme() == null - || reservationRequest.getTime() == null) { - return ResponseEntity.badRequest().build(); - } - ReservationResponse reservation = reservationService.save(reservationRequest); - - return ResponseEntity.created(URI.create("/reservations/" + reservation.getId())).body(reservation); + @PostMapping("/reservations") + public ResponseEntity create(@RequestBody ReservationRequest reservationRequest, LoginMember member) { + if ((member == null && reservationRequest.getName() == null) + || reservationRequest.getDate() == null + || reservationRequest.getTheme() == null + || reservationRequest.getTime() == null) { + return ResponseEntity.badRequest().build(); } - @DeleteMapping("/reservations/{id}") - public ResponseEntity delete(@PathVariable Long id) { - reservationService.deleteById(id); - return ResponseEntity.noContent().build(); - } + ReservationResponse reservation = reservationService.save(member, reservationRequest); + + return ResponseEntity.created(URI.create("/reservations/" + reservation.getId())) + .body(reservation); + } + + @DeleteMapping("/reservations/{id}") + public ResponseEntity delete(@PathVariable Long id) { + reservationService.deleteById(id); + return ResponseEntity.noContent().build(); + } } diff --git a/src/main/java/roomescape/reservation/ReservationRequest.java b/src/main/java/roomescape/reservation/ReservationRequest.java index 19f441246..ee369362a 100644 --- a/src/main/java/roomescape/reservation/ReservationRequest.java +++ b/src/main/java/roomescape/reservation/ReservationRequest.java @@ -21,4 +21,8 @@ public Long getTheme() { public Long getTime() { return time; } + + public void setName(final String name) { + this.name = name; + } } diff --git a/src/main/java/roomescape/reservation/ReservationService.java b/src/main/java/roomescape/reservation/ReservationService.java index bd3313328..118fa6c3a 100644 --- a/src/main/java/roomescape/reservation/ReservationService.java +++ b/src/main/java/roomescape/reservation/ReservationService.java @@ -3,6 +3,8 @@ import org.springframework.stereotype.Service; import java.util.List; +import roomescape.member.LoginMember; +import roomescape.member.Member; @Service public class ReservationService { @@ -12,6 +14,15 @@ public ReservationService(ReservationDao reservationDao) { this.reservationDao = reservationDao; } + public ReservationResponse save(LoginMember member, ReservationRequest reservationRequest) { + if (reservationRequest.getName() == null) { + reservationRequest.setName(member.getName()); + } + Reservation reservation = reservationDao.save(reservationRequest); + + return new ReservationResponse(reservation.getId(), reservationRequest.getName(), reservation.getTheme().getName(), reservation.getDate(), reservation.getTime().getValue()); + } + public ReservationResponse save(ReservationRequest reservationRequest) { Reservation reservation = reservationDao.save(reservationRequest); From f596c1f2b6b68286c51b7343c6126c68204bf059 Mon Sep 17 00:00:00 2001 From: bingle625 Date: Sat, 28 Dec 2024 16:39:15 +0900 Subject: [PATCH 4/5] =?UTF-8?q?feat:=20admin=20=ED=8E=98=EC=9D=B4=EC=A7=80?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=ED=95=9C=20?= =?UTF-8?q?=EB=A9=A4=EB=B2=84=EC=9D=98=20=EA=B6=8C=ED=95=9C=20=ED=99=95?= =?UTF-8?q?=EC=9D=B8=ED=95=98=EB=8A=94=20=EA=B8=B0=EB=8A=A5=20(3=EB=8B=A8?= =?UTF-8?q?=EA=B3=84)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/roomescape/PageController.java | 13 +- src/test/java/roomescape/MissionStepTest.java | 152 ++++++++++-------- 2 files changed, 97 insertions(+), 68 deletions(-) diff --git a/src/main/java/roomescape/PageController.java b/src/main/java/roomescape/PageController.java index ac8ef9408..30ea0fe9a 100644 --- a/src/main/java/roomescape/PageController.java +++ b/src/main/java/roomescape/PageController.java @@ -1,12 +1,23 @@ package roomescape; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ResponseStatus; +import roomescape.member.LoginMember; +import roomescape.member.Member; @Controller public class PageController { @GetMapping("/admin") - public String admin() { + public String admin(LoginMember member, HttpServletResponse response) throws IOException { + if (member.getRole().equals("USER")) { + response.sendError(401); + return null; // 상태 코드만 반환, 템플릿 렌더링 방지 + } return "admin/index"; } diff --git a/src/test/java/roomescape/MissionStepTest.java b/src/test/java/roomescape/MissionStepTest.java index ad2fcc1c1..f0b41a6e8 100644 --- a/src/test/java/roomescape/MissionStepTest.java +++ b/src/test/java/roomescape/MissionStepTest.java @@ -18,71 +18,89 @@ @DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD) 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 static String createToken(final String mail, final String password) { - Map params = new HashMap<>(); - params.put("email", mail); - 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 response.headers().get("Set-Cookie").getValue().split(";")[0].split("=")[1]; - } - - @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("브라운"); - } - - + @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 static String createToken(final String mail, final String password) { + Map params = new HashMap<>(); + params.put("email", mail); + 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 response.headers().get("Set-Cookie").getValue().split(";")[0].split("=")[1]; + } + + @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("브라운"); + } + + @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); + } } \ No newline at end of file From e2f80bccb276fbcdad4f02b542f444b0327ed75a Mon Sep 17 00:00:00 2001 From: bingle625 Date: Sat, 28 Dec 2024 16:46:29 +0900 Subject: [PATCH 5/5] =?UTF-8?q?feat:=20admin=20=ED=8E=98=EC=9D=B4=EC=A7=80?= =?UTF-8?q?=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EB=A9=A4=EB=B2=84=20=EC=A0=91?= =?UTF-8?q?=EA=B7=BC=20=EA=B6=8C=ED=95=9C=20=ED=99=95=EC=9D=B8=20=EC=9D=B8?= =?UTF-8?q?=ED=84=B0=EC=85=89=ED=84=B0=EB=A1=9C=20=EB=8C=80=EC=B2=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/roomescape/PageController.java | 6 +--- .../java/roomescape/config/WebConfig.java | 13 ++++++-- .../member/LoginMemberInterceptor.java | 31 +++++++++++++++++++ 3 files changed, 43 insertions(+), 7 deletions(-) create mode 100644 src/main/java/roomescape/member/LoginMemberInterceptor.java diff --git a/src/main/java/roomescape/PageController.java b/src/main/java/roomescape/PageController.java index 30ea0fe9a..5e8aab01a 100644 --- a/src/main/java/roomescape/PageController.java +++ b/src/main/java/roomescape/PageController.java @@ -13,11 +13,7 @@ @Controller public class PageController { @GetMapping("/admin") - public String admin(LoginMember member, HttpServletResponse response) throws IOException { - if (member.getRole().equals("USER")) { - response.sendError(401); - return null; // 상태 코드만 반환, 템플릿 렌더링 방지 - } + public String admin() { return "admin/index"; } diff --git a/src/main/java/roomescape/config/WebConfig.java b/src/main/java/roomescape/config/WebConfig.java index e98025e69..2bdb1f982 100644 --- a/src/main/java/roomescape/config/WebConfig.java +++ b/src/main/java/roomescape/config/WebConfig.java @@ -3,20 +3,29 @@ 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; import roomescape.member.LoginMemberArgumentResolver; +import roomescape.member.LoginMemberInterceptor; @Configuration public class WebConfig implements WebMvcConfigurer { private final LoginMemberArgumentResolver loginMemberArgumentResolver; + private final LoginMemberInterceptor loginMemberInterceptor; - public WebConfig(LoginMemberArgumentResolver loginMemberArgumentResolver) { + public WebConfig(LoginMemberArgumentResolver loginMemberArgumentResolver, LoginMemberInterceptor loginMemberInterceptor) { this.loginMemberArgumentResolver = loginMemberArgumentResolver; + this.loginMemberInterceptor = loginMemberInterceptor; } @Override public void addArgumentResolvers(final List resolvers) { - resolvers.add(this.loginMemberArgumentResolver); + resolvers.add(loginMemberArgumentResolver); + } + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(loginMemberInterceptor).addPathPatterns("/admin"); } } diff --git a/src/main/java/roomescape/member/LoginMemberInterceptor.java b/src/main/java/roomescape/member/LoginMemberInterceptor.java new file mode 100644 index 000000000..53f07f93f --- /dev/null +++ b/src/main/java/roomescape/member/LoginMemberInterceptor.java @@ -0,0 +1,31 @@ +package roomescape.member; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.HandlerInterceptor; + +@Component +public class LoginMemberInterceptor implements HandlerInterceptor { + + private final TokenUtil tokenUtil; + private final MemberService memberService; + + public LoginMemberInterceptor(TokenUtil tokenUtil, MemberService memberService) { + this.tokenUtil = tokenUtil; + this.memberService = memberService; + } + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + Long memberId = this.tokenUtil.getMemberId(request.getCookies()); + + Member member = this.memberService.find(memberId); + + if (member == null || !member.getRole().equals("ADMIN")) { + response.setStatus(401); + return false; + } + + return true; + } +}