From 8b1f9fda575bb45811bf1aebbe6984dd70f76990 Mon Sep 17 00:00:00 2001 From: nyeroni Date: Wed, 5 Jun 2024 21:15:09 +0900 Subject: [PATCH 01/20] =?UTF-8?q?feat:=20findById=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/member/MemberDao.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/main/java/roomescape/member/MemberDao.java b/src/main/java/roomescape/member/MemberDao.java index 81f77f4c..114fd342 100644 --- a/src/main/java/roomescape/member/MemberDao.java +++ b/src/main/java/roomescape/member/MemberDao.java @@ -52,4 +52,17 @@ public Member findByName(String name) { name ); } + + public Member findById(Long id) { + 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") + ), + id + ); + } } From 242b03992cc34bd2ceb812478e5d145983d8dddb Mon Sep 17 00:00:00 2001 From: nyeroni Date: Wed, 5 Jun 2024 21:15:24 +0900 Subject: [PATCH 02/20] =?UTF-8?q?feat:=20findById=20=EB=B0=8F=20findByEmai?= =?UTF-8?q?lAndPassword=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/member/MemberService.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/main/java/roomescape/member/MemberService.java b/src/main/java/roomescape/member/MemberService.java index ccaa8cba..a1592064 100644 --- a/src/main/java/roomescape/member/MemberService.java +++ b/src/main/java/roomescape/member/MemberService.java @@ -14,4 +14,14 @@ 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()); } + + public ViewMemberResponse findMemberByEmailAndPassword (String email, String password) { + Member member = memberDao.findByEmailAndPassword(email, password); + return new ViewMemberResponse(member.getId(), member.getName(), member.getEmail(), member.getRole()); + } + + public ViewMemberResponse findMemberById (Long id) { + Member member = memberDao.findById(id); + return new ViewMemberResponse(member.getId(), member.getName(), member.getEmail(), member.getRole()); + } } From 47c331a7881ace67d86f267f333fa40ad9457b2c Mon Sep 17 00:00:00 2001 From: nyeroni Date: Wed, 5 Jun 2024 21:15:36 +0900 Subject: [PATCH 03/20] =?UTF-8?q?feat:=20viewMemberResponse=20Dto=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../roomescape/member/ViewMemberResponse.java | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 src/main/java/roomescape/member/ViewMemberResponse.java diff --git a/src/main/java/roomescape/member/ViewMemberResponse.java b/src/main/java/roomescape/member/ViewMemberResponse.java new file mode 100644 index 00000000..aad33156 --- /dev/null +++ b/src/main/java/roomescape/member/ViewMemberResponse.java @@ -0,0 +1,33 @@ +package roomescape.member; + +public class ViewMemberResponse { + + private Long id; + private String name; + private String email; + private String role; + + public ViewMemberResponse(Long id, String name, String email, String role) { + this.id = id; + this.name = name; + this.email = email; + this.role = role; + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public String getEmail() { + return email; + } + + public String getRole (){ + return role; + } + +} From 4001434e768b15e4f8bebdda50edfa7880ccb416 Mon Sep 17 00:00:00 2001 From: nyeroni Date: Wed, 5 Jun 2024 21:15:48 +0900 Subject: [PATCH 04/20] =?UTF-8?q?feat:=201=EB=8B=A8=EA=B3=84=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 | 65 ++++++++++++++++++- 1 file changed, 64 insertions(+), 1 deletion(-) diff --git a/src/main/java/roomescape/member/MemberController.java b/src/main/java/roomescape/member/MemberController.java index 881ae5e0..297f1e2b 100644 --- a/src/main/java/roomescape/member/MemberController.java +++ b/src/main/java/roomescape/member/MemberController.java @@ -1,5 +1,9 @@ package roomescape.member; +import io.jsonwebtoken.Claims; +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; @@ -10,14 +14,18 @@ import org.springframework.web.bind.annotation.RestController; import java.net.URI; +import java.security.Key; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; @RestController public class MemberController { private MemberService memberService; - public MemberController(MemberService memberService) { this.memberService = memberService; } + private static final String SECRET_KEY = "Yn2kjibddFAWtnPJ2AFlL8WXmohJMCvigQggaEypa5E="; @PostMapping("/members") public ResponseEntity createMember(@RequestBody MemberRequest memberRequest) { @@ -25,6 +33,61 @@ public ResponseEntity createMember(@RequestBody MemberRequest memberRequest) { return ResponseEntity.created(URI.create("/members/" + member.getId())).body(member); } + @PostMapping("/login") + public ResponseEntity login(@RequestBody Map params, HttpServletResponse response) { + + String email = params.get("email"); + String password = params.get("password"); + + ViewMemberResponse viewMemberResponse = memberService.findMemberByEmailAndPassword(email, password); + + String assessToken = Jwts.builder() + .setSubject(viewMemberResponse.getId().toString()) + .claim("name", viewMemberResponse.getName()) + .claim("role", viewMemberResponse.getRole()) + .signWith(SignatureAlgorithm.HS256, SECRET_KEY.getBytes()) + .compact(); + + Cookie cookie = new Cookie("token", assessToken); + cookie.setHttpOnly(true); + cookie.setPath("/"); + response.addCookie(cookie); + + return ResponseEntity.ok().build(); + } + + @GetMapping("/login/check") + public ResponseEntity checkLogin(HttpServletRequest request) { + Cookie[] cookies = request.getCookies(); + if (cookies != null) { + String token = extractTokenFromCookie(cookies); + if (!token.isEmpty()) { + try { + Long memberId = Long.valueOf(Jwts.parserBuilder() + .setSigningKey(Keys.hmacShaKeyFor(SECRET_KEY.getBytes())) + .build() + .parseClaimsJws(token) + .getBody().getSubject()); + + ViewMemberResponse memberResponse = memberService.findMemberById(memberId); + return ResponseEntity.ok(memberResponse); + } catch (Exception e) { + return ResponseEntity.status(401).build(); + } + } + } + return ResponseEntity.status(401).build(); + } + 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", ""); From fad29adf5bde6972f6f9cb20e013656a1e308453 Mon Sep 17 00:00:00 2001 From: nyeroni Date: Tue, 25 Jun 2024 16:31:14 +0900 Subject: [PATCH 05/20] =?UTF-8?q?feat:=202=EB=8B=A8=EA=B3=84=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 --- .../java/roomescape/auth/JwtProvider.java | 26 ++++++++++ .../java/roomescape/auth/LoginMember.java | 50 ++++++++++++++++++ .../auth/LoginMemberArgumentResolver.java | 50 ++++++++++++++++++ src/main/java/roomescape/auth/LoginUser.java | 11 ++++ .../java/roomescape/config/WebConfig.java | 22 ++++++++ .../reservation/ReservationController.java | 12 +++-- .../reservation/ReservationRequest.java | 4 ++ src/main/resources/application.properties | 11 ++-- src/test/java/roomescape/MissionStepTest.java | 52 +++++++++++++++++++ 9 files changed, 230 insertions(+), 8 deletions(-) create mode 100644 src/main/java/roomescape/auth/JwtProvider.java create mode 100644 src/main/java/roomescape/auth/LoginMember.java create mode 100644 src/main/java/roomescape/auth/LoginMemberArgumentResolver.java create mode 100644 src/main/java/roomescape/auth/LoginUser.java create mode 100644 src/main/java/roomescape/config/WebConfig.java diff --git a/src/main/java/roomescape/auth/JwtProvider.java b/src/main/java/roomescape/auth/JwtProvider.java new file mode 100644 index 00000000..a77a2339 --- /dev/null +++ b/src/main/java/roomescape/auth/JwtProvider.java @@ -0,0 +1,26 @@ +package roomescape.auth; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.io.Decoders; +import io.jsonwebtoken.security.Keys; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import java.nio.charset.StandardCharsets; + +@Component +public class JwtProvider { + @Value("${roomescape.auth.jwt.secret}") + private String secretKey; + @Value("${roomescape.auth.jwt.expiration}") + private Long expiration; + + public Claims getClaimsFromToken(String token) { + return Jwts.parserBuilder() + .setSigningKey(secretKey.getBytes()) + .build() + .parseClaimsJws(token) + .getBody(); + } +} diff --git a/src/main/java/roomescape/auth/LoginMember.java b/src/main/java/roomescape/auth/LoginMember.java new file mode 100644 index 00000000..e3eeac16 --- /dev/null +++ b/src/main/java/roomescape/auth/LoginMember.java @@ -0,0 +1,50 @@ +package roomescape.auth; + +import java.nio.file.attribute.UserPrincipal; + +public class LoginMember implements UserPrincipal { + + 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; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getRole() { + return role; + } + + public void setRole(String role) { + 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 00000000..ed8af262 --- /dev/null +++ b/src/main/java/roomescape/auth/LoginMemberArgumentResolver.java @@ -0,0 +1,50 @@ +package roomescape.auth; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwt; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpSession; +import org.springframework.core.MethodParameter; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; +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.member.MemberService; +import roomescape.member.ViewMemberResponse; + +@Component +public class LoginMemberArgumentResolver implements HandlerMethodArgumentResolver { + + private final MemberService memberService; + private final JwtProvider jwtProvider; + public LoginMemberArgumentResolver(MemberService memberService, JwtProvider jwtProvider) { + this.memberService = memberService; + this.jwtProvider = jwtProvider; + } + @Override + public boolean supportsParameter(MethodParameter parameter) { + boolean isLoginUserAnnotation = parameter.getParameterAnnotation(LoginUser.class) != null; + boolean isUserClass = LoginMember.class.equals(parameter.getParameterType()); + return isLoginUserAnnotation && isUserClass; } + + @Override + public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { + HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest(); + Cookie [] cookies = request.getCookies(); + if(cookies != null ) { + for(Cookie cookie : cookies) { + if(cookie.getName().equals("token")) { + String token = cookie.getValue(); + Claims claims = jwtProvider.getClaimsFromToken(token); + ViewMemberResponse member = memberService.findMemberById(Long.parseLong(claims.getSubject())); + + return new LoginMember(member.getId(), member.getName(), member.getEmail(), member.getRole()); + } + } + } + throw new IllegalArgumentException("유효한 인증 정보가 없습니다."); + } +} diff --git a/src/main/java/roomescape/auth/LoginUser.java b/src/main/java/roomescape/auth/LoginUser.java new file mode 100644 index 00000000..df47530d --- /dev/null +++ b/src/main/java/roomescape/auth/LoginUser.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; + +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.RUNTIME) +public @interface LoginUser { +} diff --git a/src/main/java/roomescape/config/WebConfig.java b/src/main/java/roomescape/config/WebConfig.java new file mode 100644 index 00000000..a0762f9d --- /dev/null +++ b/src/main/java/roomescape/config/WebConfig.java @@ -0,0 +1,22 @@ +package roomescape.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import roomescape.auth.LoginMemberArgumentResolver; + +import java.util.List; + +@Configuration +public class WebConfig implements WebMvcConfigurer { + private final LoginMemberArgumentResolver loginMemberArgumentResolver; + + public WebConfig(LoginMemberArgumentResolver loginMemberArgumentResolver) { + this.loginMemberArgumentResolver = loginMemberArgumentResolver; + } + + @Override + public void addArgumentResolvers(List resolvers) { + resolvers.add(loginMemberArgumentResolver); + } +} diff --git a/src/main/java/roomescape/reservation/ReservationController.java b/src/main/java/roomescape/reservation/ReservationController.java index b3bef399..6e3c107f 100644 --- a/src/main/java/roomescape/reservation/ReservationController.java +++ b/src/main/java/roomescape/reservation/ReservationController.java @@ -1,12 +1,15 @@ package roomescape.reservation; import org.springframework.http.ResponseEntity; +import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; +import roomescape.auth.LoginMember; +import roomescape.auth.LoginUser; import java.net.URI; import java.util.List; @@ -26,13 +29,16 @@ public List list() { } @PostMapping("/reservations") - public ResponseEntity create(@RequestBody ReservationRequest reservationRequest) { - if (reservationRequest.getName() == null - || reservationRequest.getDate() == null + public ResponseEntity create(@RequestBody ReservationRequest reservationRequest, @LoginUser LoginMember member) { + + if (reservationRequest.getDate() == null || reservationRequest.getTheme() == null || reservationRequest.getTime() == null) { return ResponseEntity.badRequest().build(); } + if (reservationRequest.getName() == null) { + reservationRequest.setName(member.getName()); + } ReservationResponse reservation = reservationService.save(reservationRequest); return ResponseEntity.created(URI.create("/reservations/" + reservation.getId())).body(reservation); diff --git a/src/main/java/roomescape/reservation/ReservationRequest.java b/src/main/java/roomescape/reservation/ReservationRequest.java index 19f44124..a07a8483 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(String name) { + this.name = name; + } } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index a0f33bba..65c9a63d 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -3,9 +3,10 @@ spring.h2.console.enabled=true spring.h2.console.path=/h2-console spring.datasource.url=jdbc:h2:mem:database -#spring.jpa.show-sql=true -#spring.jpa.properties.hibernate.format_sql=true -#spring.jpa.ddl-auto=create-drop -#spring.jpa.defer-datasource-initialization=true +spring.jpa.show-sql=true +spring.jpa.properties.hibernate.format_sql=true +spring.jpa.ddl-auto=create-drop +spring.jpa.defer-datasource-initialization=true -#roomescape.auth.jwt.secret= Yn2kjibddFAWtnPJ2AFlL8WXmohJMCvigQggaEypa5E= \ No newline at end of file +roomescape.auth.jwt.secret= Yn2kjibddFAWtnPJ2AFlL8WXmohJMCvigQggaEypa5E= +roomescape.auth.jwt.expiration=86400 \ No newline at end of file diff --git a/src/test/java/roomescape/MissionStepTest.java b/src/test/java/roomescape/MissionStepTest.java index 6add784b..bcfbb8d3 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,55 @@ 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("브라운"); + } + + private String createToken(String mail, String password) { + Map params = new HashMap<>(); + params.put("email", "admin@email.com"); + 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]; + } } \ No newline at end of file From 2c3afbf24501c3301196233919cad95fa48f96bd Mon Sep 17 00:00:00 2001 From: nyeroni Date: Tue, 25 Jun 2024 17:33:29 +0900 Subject: [PATCH 06/20] =?UTF-8?q?feat:=203=EB=8B=A8=EA=B3=84=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 --- .../auth/AdminAccessInterceptor.java | 53 +++++++++++++++++++ .../auth/AuthMemberArgumentResolver.java | 34 ++++++++++++ .../auth/LoginMemberArgumentResolver.java | 34 +++++++++--- .../java/roomescape/config/WebConfig.java | 21 +++++++- src/test/java/roomescape/MissionStepTest.java | 22 +++++++- 5 files changed, 154 insertions(+), 10 deletions(-) create mode 100644 src/main/java/roomescape/auth/AdminAccessInterceptor.java create mode 100644 src/main/java/roomescape/auth/AuthMemberArgumentResolver.java diff --git a/src/main/java/roomescape/auth/AdminAccessInterceptor.java b/src/main/java/roomescape/auth/AdminAccessInterceptor.java new file mode 100644 index 00000000..59567180 --- /dev/null +++ b/src/main/java/roomescape/auth/AdminAccessInterceptor.java @@ -0,0 +1,53 @@ +package roomescape.auth; + +import io.jsonwebtoken.Claims; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.HandlerInterceptor; +import org.springframework.web.servlet.ModelAndView; +import roomescape.member.MemberService; +import roomescape.member.ViewMemberResponse; + +import java.lang.reflect.Array; +import java.util.Arrays; +import java.util.Optional; + +@Component +public class AdminAccessInterceptor implements HandlerInterceptor { + + private final JwtProvider jwtProvider; + private final MemberService memberService; + + public AdminAccessInterceptor(JwtProvider jwtProvider, MemberService memberService) { + this.jwtProvider = jwtProvider; + this.memberService = memberService; + } + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + + Optional token = Arrays.stream(request.getCookies()) + .filter(cookie -> cookie.getName().equals("token")) + .findFirst() + .map(Cookie::getValue); + + if(token.isEmpty()) { + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + return false; + } + + String tokenValue = token.get(); + Claims claims = jwtProvider.getClaimsFromToken(tokenValue); + String role = claims.get("role", String.class); + ViewMemberResponse member = memberService.findMemberById(Long.parseLong(claims.getSubject())); + + if(member == null || !member.getRole().equalsIgnoreCase("admin")) { + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + return false; + } + + return true; + } +} diff --git a/src/main/java/roomescape/auth/AuthMemberArgumentResolver.java b/src/main/java/roomescape/auth/AuthMemberArgumentResolver.java new file mode 100644 index 00000000..ea463c4c --- /dev/null +++ b/src/main/java/roomescape/auth/AuthMemberArgumentResolver.java @@ -0,0 +1,34 @@ +package roomescape.auth; + +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.core.MethodParameter; +import org.springframework.stereotype.Component; +import org.springframework.web.bind.support.WebDataBinderFactory; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.method.support.ModelAndViewContainer; + +import java.util.Arrays; + +@Component +public class AuthMemberArgumentResolver implements HandlerMethodArgumentResolver { + @Override + public boolean supportsParameter(MethodParameter parameter) { + boolean isLoginUserAnnotation = parameter.getParameterAnnotation(LoginUser.class) != null; + boolean isUserClass = LoginMember.class.equals(parameter.getParameterType()); + return isLoginUserAnnotation && isUserClass; + } + + @Override + public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { + HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest(); + Arrays.stream(request.getCookies()) + .filter(cookie -> cookie.getName().equals("token")) + .findFirst() + .map(Cookie::getValue) + .orElseThrow(()-> new IllegalArgumentException("로그인이 필요합니다.")); + LoginMember loginUser = (LoginMember) request.getAttribute("loginUser"); + return loginUser; + } +} diff --git a/src/main/java/roomescape/auth/LoginMemberArgumentResolver.java b/src/main/java/roomescape/auth/LoginMemberArgumentResolver.java index ed8af262..6ded4b07 100644 --- a/src/main/java/roomescape/auth/LoginMemberArgumentResolver.java +++ b/src/main/java/roomescape/auth/LoginMemberArgumentResolver.java @@ -28,20 +28,40 @@ public LoginMemberArgumentResolver(MemberService memberService, JwtProvider jwtP public boolean supportsParameter(MethodParameter parameter) { boolean isLoginUserAnnotation = parameter.getParameterAnnotation(LoginUser.class) != null; boolean isUserClass = LoginMember.class.equals(parameter.getParameterType()); - return isLoginUserAnnotation && isUserClass; } + return isLoginUserAnnotation && isUserClass; + } @Override public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest(); - Cookie [] cookies = request.getCookies(); - if(cookies != null ) { - for(Cookie cookie : cookies) { - if(cookie.getName().equals("token")) { + Cookie[] cookies = request.getCookies(); + + if (cookies != null) { + for (Cookie cookie : cookies) { + if (cookie.getName().equals("token")) { String token = cookie.getValue(); + System.out.println("=========="); + System.out.println("token == " + token); + System.out.println("=========="); + Claims claims = jwtProvider.getClaimsFromToken(token); - ViewMemberResponse member = memberService.findMemberById(Long.parseLong(claims.getSubject())); + if (claims != null && claims.getSubject() != null) { + System.out.println("claims == " + claims.getSubject()); + Long memberId = Long.parseLong(claims.getSubject()); + ViewMemberResponse member = memberService.findMemberById(memberId); + + if (member != null) { + System.out.println("=========="); + System.out.println("member == " + member.getEmail()); + System.out.println("=========="); - return new LoginMember(member.getId(), member.getName(), member.getEmail(), member.getRole()); + return new LoginMember(member.getId(), member.getName(), member.getEmail(), member.getRole()); + } else { + throw new IllegalArgumentException("해당 ID의 회원을 찾을 수 없습니다."); + } + } else { + throw new IllegalArgumentException("토큰에서 유효한 클레임을 추출할 수 없습니다."); + } } } } diff --git a/src/main/java/roomescape/config/WebConfig.java b/src/main/java/roomescape/config/WebConfig.java index a0762f9d..9c81aa56 100644 --- a/src/main/java/roomescape/config/WebConfig.java +++ b/src/main/java/roomescape/config/WebConfig.java @@ -1,22 +1,41 @@ package roomescape.config; +import org.springframework.beans.factory.annotation.Autowired; 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.AdminAccessInterceptor; +import roomescape.auth.AuthMemberArgumentResolver; import roomescape.auth.LoginMemberArgumentResolver; import java.util.List; @Configuration public class WebConfig implements WebMvcConfigurer { + private final LoginMemberArgumentResolver loginMemberArgumentResolver; + private final AuthMemberArgumentResolver authMemberArgumentResolver; + private final AdminAccessInterceptor adminAccessInterceptor; - public WebConfig(LoginMemberArgumentResolver loginMemberArgumentResolver) { + @Autowired + public WebConfig(LoginMemberArgumentResolver loginMemberArgumentResolver, + AuthMemberArgumentResolver authMemberArgumentResolver, + AdminAccessInterceptor adminAccessInterceptor) { this.loginMemberArgumentResolver = loginMemberArgumentResolver; + this.authMemberArgumentResolver = authMemberArgumentResolver; + this.adminAccessInterceptor = adminAccessInterceptor; } @Override public void addArgumentResolvers(List resolvers) { resolvers.add(loginMemberArgumentResolver); + resolvers.add(authMemberArgumentResolver); + } + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(adminAccessInterceptor) + .addPathPatterns("/admin"); } } diff --git a/src/test/java/roomescape/MissionStepTest.java b/src/test/java/roomescape/MissionStepTest.java index bcfbb8d3..8b984285 100644 --- a/src/test/java/roomescape/MissionStepTest.java +++ b/src/test/java/roomescape/MissionStepTest.java @@ -74,8 +74,8 @@ public class MissionStepTest { private String createToken(String mail, 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) @@ -87,4 +87,22 @@ private String createToken(String mail, String password) { return response.headers().get("Set-Cookie").getValue().split(";")[0].split("=")[1]; } + @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 1407d21d5dbc6467d4c88cf2361cb582de3b72bc Mon Sep 17 00:00:00 2001 From: nyeroni Date: Wed, 3 Jul 2024 00:13:14 +0900 Subject: [PATCH 07/20] =?UTF-8?q?feat:=20git=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 ++ src/main/resources/application.properties | 5 +---- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index c2065bc2..94c92c7e 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,5 @@ out/ ### VS Code ### .vscode/ + +src/main/resources/application-oauth.properties \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 65c9a63d..6fbe42b9 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -2,11 +2,8 @@ spring.sql.init.encoding=utf-8 spring.h2.console.enabled=true spring.h2.console.path=/h2-console spring.datasource.url=jdbc:h2:mem:database - +spring.profiles.include=oauth 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=86400 \ No newline at end of file From 88f673cb723b32cbc2ebb3d68de6eb2b8373c582 Mon Sep 17 00:00:00 2001 From: nyeroni Date: Wed, 3 Jul 2024 00:13:30 +0900 Subject: [PATCH 08/20] =?UTF-8?q?feat:=20jpa=20=EC=9D=98=EC=A1=B4=EC=84=B1?= =?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 --- 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 5e65b1f5a7e63b8e94c69252debbc72c20be5947 Mon Sep 17 00:00:00 2001 From: nyeroni Date: Wed, 3 Jul 2024 00:18:15 +0900 Subject: [PATCH 09/20] =?UTF-8?q?feat:=20ddl=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/schema.sql | 39 ----------------------------------- 1 file changed, 39 deletions(-) diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql index 75c947a5..7dc9a8b5 100644 --- a/src/main/resources/schema.sql +++ b/src/main/resources/schema.sql @@ -1,42 +1,3 @@ -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'); From 1f4b2f0928c78bd38300bf84882cc6510dd44a43 Mon Sep 17 00:00:00 2001 From: nyeroni Date: Wed, 3 Jul 2024 00:21:38 +0900 Subject: [PATCH 10/20] =?UTF-8?q?feat:=20time=20jpa=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/roomescape/time/Time.java | 10 +++++ src/main/java/roomescape/time/TimeDao.java | 41 ------------------- .../java/roomescape/time/TimeRepository.java | 6 +++ .../java/roomescape/time/TimeService.java | 21 +++++----- 4 files changed, 27 insertions(+), 51 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..c4f9c6f9 100644 --- a/src/main/java/roomescape/time/Time.java +++ b/src/main/java/roomescape/time/Time.java @@ -1,6 +1,16 @@ package roomescape.time; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; + +import static jakarta.persistence.GenerationType.IDENTITY; + +@Entity public class Time { + @Id + @GeneratedValue(strategy = IDENTITY) private Long id; private String value; 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