From 0c79c6520dcd27a1d765682fbbfed0118933d9d1 Mon Sep 17 00:00:00 2001 From: haeyoon1 Date: Fri, 10 Jan 2025 06:50:53 +0900 Subject: [PATCH 01/12] =?UTF-8?q?feat:=20JWT=20=ED=86=A0=ED=81=B0=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/roomescape/jwt/JwtProvider.java | 10 ++++ .../java/roomescape/jwt/JwtProviderImpl.java | 47 +++++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 src/main/java/roomescape/jwt/JwtProvider.java create mode 100644 src/main/java/roomescape/jwt/JwtProviderImpl.java diff --git a/src/main/java/roomescape/jwt/JwtProvider.java b/src/main/java/roomescape/jwt/JwtProvider.java new file mode 100644 index 000000000..297d41492 --- /dev/null +++ b/src/main/java/roomescape/jwt/JwtProvider.java @@ -0,0 +1,10 @@ +package roomescape.jwt; + +import roomescape.member.Member; + +public interface JwtProvider { + + String generateToken(Member member); + boolean isValidToken(String token); + Long extractSubject(String token); +} diff --git a/src/main/java/roomescape/jwt/JwtProviderImpl.java b/src/main/java/roomescape/jwt/JwtProviderImpl.java new file mode 100644 index 000000000..c036079e6 --- /dev/null +++ b/src/main/java/roomescape/jwt/JwtProviderImpl.java @@ -0,0 +1,47 @@ +package roomescape.jwt; + +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.security.Keys; +import roomescape.member.Member; + +import java.nio.charset.StandardCharsets; + + +public class JwtProviderImpl implements JwtProvider{ + + private static final String SECRET_KEY="Yn2kjibddFAWtnPJ2AFlL8WXmohJMCvigQggaEypa5E="; + // private static final long EXPRIATION_TIME = 86400000; // 1일 + + @Override + public String generateToken(Member member) { + return Jwts.builder() + .setSubject(member.getId().toString()) + .claim("name", member.getName()) + .claim("role", member.getRole()) + .signWith(Keys.hmacShaKeyFor(SECRET_KEY.getBytes(StandardCharsets.UTF_8))) + .compact(); + } + + @Override + public boolean isValidToken(String token) { + try { + Jwts.parserBuilder() + .setSigningKey(Keys.hmacShaKeyFor(SECRET_KEY.getBytes(StandardCharsets.UTF_8))) + .build() + .parseClaimsJws(token); + return true; + } catch (Exception e){ + return false; + } + } + + @Override + public Long extractSubject(String token) { + Long memberId = Long.valueOf(Jwts.parserBuilder() + .setSigningKey(Keys.hmacShaKeyFor(SECRET_KEY.getBytes())) + .build() + .parseClaimsJws(token) + .getBody().getSubject()); + return memberId; + } +} From c09fda447f290eeea9cd2f62e8b044c811ad374a Mon Sep 17 00:00:00 2001 From: haeyoon1 Date: Fri, 10 Jan 2025 06:54:02 +0900 Subject: [PATCH 02/12] =?UTF-8?q?feat:=20JWT=20=ED=86=A0=ED=81=B0=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/roomescape/jwt/JwtProvider.java | 1 + src/main/java/roomescape/jwt/JwtProviderImpl.java | 1 + 2 files changed, 2 insertions(+) diff --git a/src/main/java/roomescape/jwt/JwtProvider.java b/src/main/java/roomescape/jwt/JwtProvider.java index 297d41492..06dbc2324 100644 --- a/src/main/java/roomescape/jwt/JwtProvider.java +++ b/src/main/java/roomescape/jwt/JwtProvider.java @@ -7,4 +7,5 @@ public interface JwtProvider { String generateToken(Member member); boolean isValidToken(String token); Long extractSubject(String token); + } diff --git a/src/main/java/roomescape/jwt/JwtProviderImpl.java b/src/main/java/roomescape/jwt/JwtProviderImpl.java index c036079e6..90073474d 100644 --- a/src/main/java/roomescape/jwt/JwtProviderImpl.java +++ b/src/main/java/roomescape/jwt/JwtProviderImpl.java @@ -44,4 +44,5 @@ public Long extractSubject(String token) { .getBody().getSubject()); return memberId; } + } From 2d2c452c6bd8c7c3003019e4b353dbe10714ef24 Mon Sep 17 00:00:00 2001 From: haeyoon1 Date: Fri, 10 Jan 2025 07:00:58 +0900 Subject: [PATCH 03/12] =?UTF-8?q?feat:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5,=20=ED=86=A0=ED=81=B0=20=EB=B0=9C=EA=B8=89,?= =?UTF-8?q?=20=EC=82=AC=EC=9A=A9=EC=9E=90=20=EC=A0=95=EB=B3=B4=20=EA=B2=80?= =?UTF-8?q?=EC=A6=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/roomescape/login/LoginService.java | 9 +++++ .../roomescape/login/LoginServiceImpl.java | 38 +++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 src/main/java/roomescape/login/LoginService.java create mode 100644 src/main/java/roomescape/login/LoginServiceImpl.java diff --git a/src/main/java/roomescape/login/LoginService.java b/src/main/java/roomescape/login/LoginService.java new file mode 100644 index 000000000..2f458ec1f --- /dev/null +++ b/src/main/java/roomescape/login/LoginService.java @@ -0,0 +1,9 @@ +package roomescape.login; + +import roomescape.member.MemberResponse; + +public interface LoginService { + + String login(String email, String password); + MemberResponse validateToken(String token); +} diff --git a/src/main/java/roomescape/login/LoginServiceImpl.java b/src/main/java/roomescape/login/LoginServiceImpl.java new file mode 100644 index 000000000..c9a057690 --- /dev/null +++ b/src/main/java/roomescape/login/LoginServiceImpl.java @@ -0,0 +1,38 @@ +package roomescape.login; + +import org.springframework.stereotype.Service; +import roomescape.jwt.JwtProvider; +import roomescape.member.Member; +import roomescape.member.MemberDao; +import roomescape.member.MemberResponse; + +@Service +public class LoginServiceImpl implements LoginService{ + + private final MemberDao memberDao; + private final JwtProvider jwtProvider; + + public LoginServiceImpl(MemberDao memberDao, JwtProvider jwtProvider) { + this.memberDao = memberDao; + this.jwtProvider = jwtProvider; + } + + @Override + public String login(String email, String password) { + Member member = memberDao.findByEmailAndPassword(email, password); + return jwtProvider.generateToken(member); + } + + @Override + public MemberResponse validateToken(String token) { + + if (!jwtProvider.isValidToken(token)) { + throw new IllegalArgumentException("Invalid token"); + } + + Long memberId = Long.valueOf(jwtProvider.extractSubject(token)); + Member member = memberDao.findByName(memberId.toString()); + + return new MemberResponse(member.getId(), member.getName(), member.getEmail()); + } +} From 64685a7e2defd8677a8e46811cde678759cce506 Mon Sep 17 00:00:00 2001 From: haeyoon1 Date: Fri, 10 Jan 2025 08:36:32 +0900 Subject: [PATCH 04/12] =?UTF-8?q?feat:=20http=20=EC=9A=94=EC=B2=AD=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC=20-=20=EC=BF=A0=ED=82=A4=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=EB=B0=8F=20=EC=A1=B0=ED=9A=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../roomescape/login/LoginController.java | 55 +++++++++++++++++++ .../java/roomescape/login/LoginRequest.java | 14 +++++ 2 files changed, 69 insertions(+) create mode 100644 src/main/java/roomescape/login/LoginController.java create mode 100644 src/main/java/roomescape/login/LoginRequest.java diff --git a/src/main/java/roomescape/login/LoginController.java b/src/main/java/roomescape/login/LoginController.java new file mode 100644 index 000000000..b539082c1 --- /dev/null +++ b/src/main/java/roomescape/login/LoginController.java @@ -0,0 +1,55 @@ +package roomescape.login; + +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; +import roomescape.jwt.JwtUtil; +import roomescape.member.MemberResponse; + +@RestController +public class LoginController { + + private final LoginService loginService; + + public LoginController(LoginService loginService) { + this.loginService = loginService; + } + + @PostMapping("/login") + public ResponseEntity login(@RequestBody LoginRequest loginRequest, HttpServletResponse response){ + + String token = loginService.login(loginRequest.getEmail(), loginRequest.getPassword()); + //쿠키 생성 + Cookie cookie = new Cookie("token", token); + cookie.setHttpOnly(true); + cookie.setPath("/"); + response.addCookie(cookie); + + return ResponseEntity.ok().build(); + } + + @GetMapping("/login/check") //쿠키 조회 + public ResponseEntity checkLogin(HttpServletRequest request) { + + Cookie[] cookies = request.getCookies(); + String token = extractTokenFromCookies(cookies); + MemberResponse memberResponse = loginService.validateToken(token); + + return ResponseEntity.ok(memberResponse); + } + + private String extractTokenFromCookies(Cookie[] cookies) { + for (Cookie cookie: cookies){ + if ("token".equals(cookie.getName())){ + return cookie.getValue(); + } + } + return ""; + } +} diff --git a/src/main/java/roomescape/login/LoginRequest.java b/src/main/java/roomescape/login/LoginRequest.java new file mode 100644 index 000000000..c9ffb9cbe --- /dev/null +++ b/src/main/java/roomescape/login/LoginRequest.java @@ -0,0 +1,14 @@ +package roomescape.login; + +public class LoginRequest { + private String email; + private String password; + + public String getEmail() { + return email; + } + + public String getPassword() { + return password; + } +} From 3c6bb14ffb51d2ba6b1e07328ac9a068fe27dc99 Mon Sep 17 00:00:00 2001 From: haeyoon1 Date: Fri, 10 Jan 2025 08:47:01 +0900 Subject: [PATCH 05/12] =?UTF-8?q?feat:=20JwtProviderImpl=20=EC=8A=A4?= =?UTF-8?q?=ED=94=84=EB=A7=81=EB=B9=88=EC=9C=BC=EB=A1=9C=20=EB=93=B1?= =?UTF-8?q?=EB=A1=9D=20-=20=EC=96=B4=EB=85=B8=ED=85=8C=EC=9D=B4=EC=85=98?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/roomescape/jwt/JwtProviderImpl.java | 3 ++- src/main/java/roomescape/login/LoginController.java | 2 -- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/roomescape/jwt/JwtProviderImpl.java b/src/main/java/roomescape/jwt/JwtProviderImpl.java index 90073474d..160c2829d 100644 --- a/src/main/java/roomescape/jwt/JwtProviderImpl.java +++ b/src/main/java/roomescape/jwt/JwtProviderImpl.java @@ -2,11 +2,12 @@ import io.jsonwebtoken.Jwts; import io.jsonwebtoken.security.Keys; +import org.springframework.stereotype.Component; import roomescape.member.Member; import java.nio.charset.StandardCharsets; - +@Component public class JwtProviderImpl implements JwtProvider{ private static final String SECRET_KEY="Yn2kjibddFAWtnPJ2AFlL8WXmohJMCvigQggaEypa5E="; diff --git a/src/main/java/roomescape/login/LoginController.java b/src/main/java/roomescape/login/LoginController.java index b539082c1..512193d37 100644 --- a/src/main/java/roomescape/login/LoginController.java +++ b/src/main/java/roomescape/login/LoginController.java @@ -4,12 +4,10 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.http.ResponseEntity; -import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; -import roomescape.jwt.JwtUtil; import roomescape.member.MemberResponse; @RestController From d5288a3d73dd50ac13c73cdd707cf2b211725d7f Mon Sep 17 00:00:00 2001 From: haeyoon1 Date: Fri, 10 Jan 2025 09:16:05 +0900 Subject: [PATCH 06/12] =?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=20=EC=BD=94=EB=93=9C=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 --- src/test/java/roomescape/MissionStepTest.java | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/test/java/roomescape/MissionStepTest.java b/src/test/java/roomescape/MissionStepTest.java index 6add784bd..3a43eb8f3 100644 --- a/src/test/java/roomescape/MissionStepTest.java +++ b/src/test/java/roomescape/MissionStepTest.java @@ -7,6 +7,7 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.annotation.DirtiesContext; +import roomescape.reservation.ReservationResponse; import java.util.HashMap; import java.util.Map; @@ -35,4 +36,40 @@ public class MissionStepTest { assertThat(token).isNotBlank(); } + + + @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("브라운"); + } + } \ No newline at end of file From 62d7822b42692b8ec8f0772d93b951ca5a133444 Mon Sep 17 00:00:00 2001 From: haeyoon1 Date: Fri, 10 Jan 2025 16:31:08 +0900 Subject: [PATCH 07/12] =?UTF-8?q?feat:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=EB=90=9C=20=EC=82=AC=EC=9A=A9=EC=9E=90=20=EA=B4=80=EB=A0=A8=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 2 + .../java/roomescape/auth/LoginMember.java | 19 +++++++ .../auth/LoginMemberArgumentResolver.java | 53 +++++++++++++++++++ .../roomescape/login/LoginServiceImpl.java | 2 +- 4 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 src/main/java/roomescape/auth/LoginMember.java create mode 100644 src/main/java/roomescape/auth/LoginMemberArgumentResolver.java diff --git a/build.gradle b/build.gradle index 8d52aebc6..81dffc709 100644 --- a/build.gradle +++ b/build.gradle @@ -27,6 +27,8 @@ dependencies { testImplementation 'io.rest-assured:rest-assured:5.3.1' runtimeOnly 'com.h2database:h2' + + implementation 'org.projectlombok:lombok' } test { diff --git a/src/main/java/roomescape/auth/LoginMember.java b/src/main/java/roomescape/auth/LoginMember.java new file mode 100644 index 000000000..ab48fc9ad --- /dev/null +++ b/src/main/java/roomescape/auth/LoginMember.java @@ -0,0 +1,19 @@ +package roomescape.auth; + +import lombok.Getter; + +@Getter +public class LoginMember { + + private Long id; + private String name; + private String email; + private String role; + + public LoginMember(Long id, String name, String email, String role) { + this.id = id; + this.name = name; + this.email = email; + this.role = role; + } +} diff --git a/src/main/java/roomescape/auth/LoginMemberArgumentResolver.java b/src/main/java/roomescape/auth/LoginMemberArgumentResolver.java new file mode 100644 index 000000000..dd022c98b --- /dev/null +++ b/src/main/java/roomescape/auth/LoginMemberArgumentResolver.java @@ -0,0 +1,53 @@ +package roomescape.auth; + +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.core.MethodParameter; +import org.springframework.web.bind.support.WebDataBinderFactory; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.method.support.ModelAndViewContainer; +import roomescape.jwt.JwtProvider; +import roomescape.member.Member; +import roomescape.member.MemberDao; + +public class LoginMemberArgumentResolver implements HandlerMethodArgumentResolver { + + private final MemberDao memberDao; + private final JwtProvider jwtProvider; + + public LoginMemberArgumentResolver(MemberDao memberDao, JwtProvider jwtProvider) { + this.memberDao = memberDao; + this.jwtProvider = jwtProvider; + } + + + @Override + public boolean supportsParameter(MethodParameter parameter) { + return parameter.getParameterType().equals(LoginMember.class); + } + + @Override + public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) { + + HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest(); + Cookie[] cookies = request.getCookies(); + String token = extractTokenFromCookies(cookies); + + if (jwtProvider.isValidToken(token)) { + Long memberId = jwtProvider.extractSubject(token); + Member member = memberDao.findByName(memberId.toString()); + return new LoginMember(member.getId(), member.getName(), member.getEmail(), member.getRole()); + } + throw new IllegalArgumentException("Invalid token"); + } + + private String extractTokenFromCookies(Cookie[] cookies) { + for (Cookie cookie : cookies) { + if ("token".equals(cookie.getName())) { + return cookie.getValue(); + } + } + return ""; + } +} \ No newline at end of file diff --git a/src/main/java/roomescape/login/LoginServiceImpl.java b/src/main/java/roomescape/login/LoginServiceImpl.java index c9a057690..d181e923c 100644 --- a/src/main/java/roomescape/login/LoginServiceImpl.java +++ b/src/main/java/roomescape/login/LoginServiceImpl.java @@ -7,7 +7,7 @@ import roomescape.member.MemberResponse; @Service -public class LoginServiceImpl implements LoginService{ +public class LoginServiceImpl implements LoginService { private final MemberDao memberDao; private final JwtProvider jwtProvider; From 96725879698213f9db1589bc5d9e3f5855b4f628 Mon Sep 17 00:00:00 2001 From: haeyoon1 Date: Fri, 10 Jan 2025 16:44:18 +0900 Subject: [PATCH 08/12] =?UTF-8?q?feat:=20WebConfig=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/roomescape/config/WebConfig.java | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 src/main/java/roomescape/config/WebConfig.java diff --git a/src/main/java/roomescape/config/WebConfig.java b/src/main/java/roomescape/config/WebConfig.java new file mode 100644 index 000000000..424541e30 --- /dev/null +++ b/src/main/java/roomescape/config/WebConfig.java @@ -0,0 +1,25 @@ +package roomescape.config; + +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import roomescape.auth.LoginMemberArgumentResolver; +import roomescape.jwt.JwtProvider; +import roomescape.member.MemberDao; + +import java.util.List; + +public class WebConfig implements WebMvcConfigurer { + + private final JwtProvider jwtProvider; + private final MemberDao memberDao; + + public WebConfig(JwtProvider jwtProvider, MemberDao memberDao) { + this.jwtProvider = jwtProvider; + this.memberDao = memberDao; + } + + @Override + public void addArgumentResolvers(List resolvers) { + resolvers.add(new LoginMemberArgumentResolver(memberDao, jwtProvider)); + } +} From b71a876fe30f3d6910eb7f0c95aa69598c33f366 Mon Sep 17 00:00:00 2001 From: haeyoon1 Date: Sun, 12 Jan 2025 09:43:53 +0900 Subject: [PATCH 09/12] =?UTF-8?q?fix:=202=EB=8B=A8=EA=B3=84=20=EC=98=A4?= =?UTF-8?q?=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/LoginMemberArgumentResolver.java | 14 ++++++++++--- src/main/java/roomescape/jwt/JwtProvider.java | 3 ++- .../java/roomescape/jwt/JwtProviderImpl.java | 21 ++++++++++++++----- .../roomescape/login/LoginServiceImpl.java | 8 +++++-- src/test/java/roomescape/MissionStepTest.java | 16 ++++++++++++++ 5 files changed, 51 insertions(+), 11 deletions(-) diff --git a/src/main/java/roomescape/auth/LoginMemberArgumentResolver.java b/src/main/java/roomescape/auth/LoginMemberArgumentResolver.java index dd022c98b..8a2ea11f7 100644 --- a/src/main/java/roomescape/auth/LoginMemberArgumentResolver.java +++ b/src/main/java/roomescape/auth/LoginMemberArgumentResolver.java @@ -21,7 +21,6 @@ public LoginMemberArgumentResolver(MemberDao memberDao, JwtProvider jwtProvider) this.jwtProvider = jwtProvider; } - @Override public boolean supportsParameter(MethodParameter parameter) { return parameter.getParameterType().equals(LoginMember.class); @@ -34,9 +33,18 @@ public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer m Cookie[] cookies = request.getCookies(); String token = extractTokenFromCookies(cookies); + if (token == null || token.isEmpty()) { + throw new IllegalArgumentException("Token not found in cookies"); + } + if (jwtProvider.isValidToken(token)) { - Long memberId = jwtProvider.extractSubject(token); - Member member = memberDao.findByName(memberId.toString()); + String email = jwtProvider.extractEmail(token); // 이메일 추출 + Member member = memberDao.findByEmailAndPassword(email, null); // 비밀번호는 검증 단계에서 사용하지 않음 + + if (member == null) { + throw new IllegalArgumentException("Member not found for email: " + email); + } + return new LoginMember(member.getId(), member.getName(), member.getEmail(), member.getRole()); } throw new IllegalArgumentException("Invalid token"); diff --git a/src/main/java/roomescape/jwt/JwtProvider.java b/src/main/java/roomescape/jwt/JwtProvider.java index 06dbc2324..5100f01c4 100644 --- a/src/main/java/roomescape/jwt/JwtProvider.java +++ b/src/main/java/roomescape/jwt/JwtProvider.java @@ -6,6 +6,7 @@ public interface JwtProvider { String generateToken(Member member); boolean isValidToken(String token); - Long extractSubject(String token); +// Long extractSubject(String token); + String extractEmail(String token); } diff --git a/src/main/java/roomescape/jwt/JwtProviderImpl.java b/src/main/java/roomescape/jwt/JwtProviderImpl.java index 160c2829d..044e0cdb3 100644 --- a/src/main/java/roomescape/jwt/JwtProviderImpl.java +++ b/src/main/java/roomescape/jwt/JwtProviderImpl.java @@ -18,6 +18,7 @@ public String generateToken(Member member) { return Jwts.builder() .setSubject(member.getId().toString()) .claim("name", member.getName()) + .claim("email", member.getEmail()) .claim("role", member.getRole()) .signWith(Keys.hmacShaKeyFor(SECRET_KEY.getBytes(StandardCharsets.UTF_8))) .compact(); @@ -36,14 +37,24 @@ public boolean isValidToken(String token) { } } +// @Override +// public Long extractSubject(String token) { +// Long memberId = Long.valueOf(Jwts.parserBuilder() +// .setSigningKey(Keys.hmacShaKeyFor(SECRET_KEY.getBytes())) +// .build() +// .parseClaimsJws(token) +// .getBody().getSubject()); +// return memberId; +// } + @Override - public Long extractSubject(String token) { - Long memberId = Long.valueOf(Jwts.parserBuilder() - .setSigningKey(Keys.hmacShaKeyFor(SECRET_KEY.getBytes())) + public String extractEmail(String token) { + return Jwts.parserBuilder() + .setSigningKey(Keys.hmacShaKeyFor(SECRET_KEY.getBytes(StandardCharsets.UTF_8))) .build() .parseClaimsJws(token) - .getBody().getSubject()); - return memberId; + .getBody() + .get("email", String.class); } } diff --git a/src/main/java/roomescape/login/LoginServiceImpl.java b/src/main/java/roomescape/login/LoginServiceImpl.java index d181e923c..c5b8237c5 100644 --- a/src/main/java/roomescape/login/LoginServiceImpl.java +++ b/src/main/java/roomescape/login/LoginServiceImpl.java @@ -30,8 +30,12 @@ public MemberResponse validateToken(String token) { throw new IllegalArgumentException("Invalid token"); } - Long memberId = Long.valueOf(jwtProvider.extractSubject(token)); - Member member = memberDao.findByName(memberId.toString()); + String email = jwtProvider.extractEmail(token); + Member member = memberDao.findByEmailAndPassword(email, null); // 비밀번호는 로그인에서만 검증 + + if (member == null) { + throw new IllegalArgumentException("Member not found for email: " + email); + } return new MemberResponse(member.getId(), member.getName(), member.getEmail()); } diff --git a/src/test/java/roomescape/MissionStepTest.java b/src/test/java/roomescape/MissionStepTest.java index 3a43eb8f3..d64608d8a 100644 --- a/src/test/java/roomescape/MissionStepTest.java +++ b/src/test/java/roomescape/MissionStepTest.java @@ -37,6 +37,21 @@ public class MissionStepTest { assertThat(token).isNotBlank(); } + private String createToken(String email, String password) { + Map params = new HashMap<>(); + params.put("email", email); + params.put("password", password); + + ExtractableResponse response = RestAssured.given().log().all() + .contentType(ContentType.JSON) + .body(params) + .when().post("/login") + .then().log().all() + .statusCode(200) // 로그인 성공 응답 코드 확인 + .extract(); + + return response.headers().get("Set-Cookie").getValue().split(";")[0].split("=")[1]; + } @Test void 이단계() { @@ -72,4 +87,5 @@ public class MissionStepTest { assertThat(adminResponse.as(ReservationResponse.class).getName()).isEqualTo("브라운"); } + } \ No newline at end of file From 169fc7b85d73ec58b15ff22085a33466cc7a1865 Mon Sep 17 00:00:00 2001 From: haeyoon1 Date: Sun, 12 Jan 2025 09:44:48 +0900 Subject: [PATCH 10/12] =?UTF-8?q?feat:=203=EB=8B=A8=EA=B3=84=20test=20code?= =?UTF-8?q?=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/java/roomescape/MissionStepTest.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/test/java/roomescape/MissionStepTest.java b/src/test/java/roomescape/MissionStepTest.java index d64608d8a..e8c9b53e6 100644 --- a/src/test/java/roomescape/MissionStepTest.java +++ b/src/test/java/roomescape/MissionStepTest.java @@ -87,5 +87,23 @@ private String createToken(String email, String password) { 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 5472b46c9daf006c3abb7b6b49943c996f4110b6 Mon Sep 17 00:00:00 2001 From: haeyoon1 Date: Sun, 12 Jan 2025 09:54:02 +0900 Subject: [PATCH 11/12] =?UTF-8?q?feat:=203=EB=8B=A8=EA=B3=84-=EA=B4=80?= =?UTF-8?q?=EB=A6=AC=EC=9E=90=20=EA=B6=8C=ED=95=9C=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../roomescape/auth/AdminInterceptor.java | 56 +++++++++++++++++++ .../java/roomescape/config/WebConfig.java | 12 +++- src/main/resources/application.properties | 2 +- 3 files changed, 68 insertions(+), 2 deletions(-) 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 000000000..0f10e0cb8 --- /dev/null +++ b/src/main/java/roomescape/auth/AdminInterceptor.java @@ -0,0 +1,56 @@ +package roomescape.auth; + +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.web.servlet.HandlerInterceptor; +import roomescape.jwt.JwtProvider; +import roomescape.member.Member; +import roomescape.member.MemberDao; + +public class AdminInterceptor implements HandlerInterceptor { + + private final JwtProvider jwtProvider; + private final MemberDao memberDao; + + public AdminInterceptor(JwtProvider jwtProvider, MemberDao memberDao) { + this.jwtProvider = jwtProvider; + this.memberDao = memberDao; + } + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + + Cookie[] cookies = request.getCookies(); + if (cookies == null) { + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + return false; + } + + String token = extractTokenFromCookies(cookies); + if (token == null || token.isEmpty() || !jwtProvider.isValidToken(token)) { + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + return false; + } + + String email = jwtProvider.extractEmail(token); + Member member = memberDao.findByEmailAndPassword(email, null); // 비밀번호 검증은 생략 + + + if (member == null || !"ADMIN".equals(member.getRole())) { //관리자 권한 + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + return false; + } + + return true; + } + + private String extractTokenFromCookies(Cookie[] cookies) { + for (Cookie cookie : cookies) { + if ("token".equals(cookie.getName())) { + return cookie.getValue(); + } + } + return null; + } +} \ No newline at end of file diff --git a/src/main/java/roomescape/config/WebConfig.java b/src/main/java/roomescape/config/WebConfig.java index 424541e30..28de3a9cb 100644 --- a/src/main/java/roomescape/config/WebConfig.java +++ b/src/main/java/roomescape/config/WebConfig.java @@ -1,13 +1,17 @@ package roomescape.config; +import org.springframework.context.annotation.Configuration; import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import roomescape.auth.AdminInterceptor; import roomescape.auth.LoginMemberArgumentResolver; import roomescape.jwt.JwtProvider; import roomescape.member.MemberDao; import java.util.List; +@Configuration public class WebConfig implements WebMvcConfigurer { private final JwtProvider jwtProvider; @@ -18,8 +22,14 @@ public WebConfig(JwtProvider jwtProvider, MemberDao memberDao) { this.memberDao = memberDao; } - @Override + @Override //3단계 public void addArgumentResolvers(List resolvers) { resolvers.add(new LoginMemberArgumentResolver(memberDao, jwtProvider)); } + + @Override //3단계 + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(new AdminInterceptor(jwtProvider, memberDao)) + .addPathPatterns("/admin/**"); + } } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index a0f33bbab..612d9890e 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -8,4 +8,4 @@ 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= \ No newline at end of file From d3da9bff76befc851def91333c6389ef7df3260e Mon Sep 17 00:00:00 2001 From: haeyoon1 Date: Tue, 14 Jan 2025 00:28:28 +0900 Subject: [PATCH 12/12] =?UTF-8?q?feat:=20Expriationtime=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 --- src/main/java/roomescape/jwt/JwtProviderImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/roomescape/jwt/JwtProviderImpl.java b/src/main/java/roomescape/jwt/JwtProviderImpl.java index 044e0cdb3..5395661e3 100644 --- a/src/main/java/roomescape/jwt/JwtProviderImpl.java +++ b/src/main/java/roomescape/jwt/JwtProviderImpl.java @@ -11,7 +11,7 @@ public class JwtProviderImpl implements JwtProvider{ private static final String SECRET_KEY="Yn2kjibddFAWtnPJ2AFlL8WXmohJMCvigQggaEypa5E="; - // private static final long EXPRIATION_TIME = 86400000; // 1일 + private static final long EXPRIATION_TIME = 86400000; // 1일 @Override public String generateToken(Member member) {