Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

전남대 BE_유보민 2주차 과제 (3단계) #448

Open
wants to merge 87 commits into
base: rbm0524
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
87 commits
Select commit Hold shift + click to select a range
20ff0b0
Move code from rbm0524 branch of spring-gift-product repository
rbm0524 Jul 2, 2024
e3239ff
docx : README.md 1단계 작성
rbm0524 Jul 3, 2024
01b085a
fix : validation 추가
rbm0524 Jul 3, 2024
517bd97
fix : name에 유효성 검사 Pattern 추가
rbm0524 Jul 3, 2024
494b433
fix : 에러 메시지 표시할 부분 추가
rbm0524 Jul 3, 2024
73ea46c
fix : create, update에 @Valid 어노테이션 추가
rbm0524 Jul 3, 2024
81b35f6
feat : Exceptionhandler를 통한 예외 처리
rbm0524 Jul 3, 2024
b501dc6
docs : step2 README.md 작성
rbm0524 Jul 4, 2024
f690bba
fix : Token 사용 위한 의존성 추가
rbm0524 Jul 4, 2024
ac7beb8
fix : 관리자/사용자에 따라 보여주는 페이지를 다르게 함.
rbm0524 Jul 4, 2024
f8d071f
fix : 테이블의 price를 integer로 수정
rbm0524 Jul 4, 2024
ee5d5a1
fix : Qualifer 붙이기
rbm0524 Jul 4, 2024
66f00b7
fix : h2 데이터베이스 관련 properties 수정
rbm0524 Jul 4, 2024
759b272
feat : Token 발급 위한 클래스 작성
rbm0524 Jul 4, 2024
49f9e90
feat : 로그인 페이지 구현
rbm0524 Jul 4, 2024
9a7a963
feat : 회원가입 페이지 구현
rbm0524 Jul 4, 2024
d38460f
feat : 회원 모델 작성
rbm0524 Jul 4, 2024
2255336
feat : 관리자가 아닌 사용자에게 보여줄 상품 페이지 구현
rbm0524 Jul 4, 2024
32afa44
feat : UserController 작성
rbm0524 Jul 4, 2024
2802bc3
feat : UserRepository 작성
rbm0524 Jul 4, 2024
2d5292b
feat : UserRowMapper 작성
rbm0524 Jul 4, 2024
01a5222
feat : UserService 작성
rbm0524 Jul 4, 2024
2376e85
fix : ExceptionHandler 추가
rbm0524 Jul 5, 2024
2e695cc
fix : 잘 작동하지 않아 전체 주석 처리
rbm0524 Jul 5, 2024
a4a17ec
fix : 장바구니에 담는 기능 추가
rbm0524 Jul 5, 2024
fb80ec7
fix : 인증 과정 추가
rbm0524 Jul 5, 2024
4939a95
fix : 전체 attribute 가져오는 것으로 수정
rbm0524 Jul 5, 2024
e0a412d
fix : findByEmail 추가
rbm0524 Jul 5, 2024
8bf8288
feat : Basic 인증하는 AuthUtil 작성
rbm0524 Jul 5, 2024
a8903b0
feat : 예외 작성
rbm0524 Jul 5, 2024
624a12d
feat : wishlist 화면 작성
rbm0524 Jul 5, 2024
cbd3c96
feat : WishListController 작성
rbm0524 Jul 5, 2024
61862a3
feat : WishListitem 작성
rbm0524 Jul 5, 2024
05c5035
feat : WishListRepository 작성
rbm0524 Jul 5, 2024
1433f8d
feat : WishListService 작성
rbm0524 Jul 5, 2024
fca376c
Merge branch 'rbm0524' into step3
rbm0524 Jul 5, 2024
667f8e2
remove : AuthUtil 삭제
rbm0524 Jul 7, 2024
ca227e6
fix : jjwt 버전 수정
rbm0524 Jul 7, 2024
25a6dac
remove : DBConnection 클래스 삭제
rbm0524 Jul 7, 2024
86992fd
fix : 이름 수정, 코드 decode 추가
rbm0524 Jul 7, 2024
f79a43a
fix : getPrice 반환타입 int로 수정
rbm0524 Jul 7, 2024
929ee5b
fix : 위시 리스트 보기 버튼 추가, 구현
rbm0524 Jul 7, 2024
4893140
fix : RequestMapping을 Get/Post로 쪼개기
rbm0524 Jul 7, 2024
f416adf
fix : sout 삭제
rbm0524 Jul 7, 2024
abafd0b
fix : Repository 패키지로 옮기기
rbm0524 Jul 7, 2024
5980b3a
fix : authenticate 메서드 추가
rbm0524 Jul 7, 2024
cfbcf03
fix : 위시 리스트 항목 제거 기능 추가
rbm0524 Jul 7, 2024
fa113bd
fix : getWishlist JWT 사용으로 수정
rbm0524 Jul 7, 2024
c272930
fix : 필드 수정, 생성자 수정, getter/setter 수정
rbm0524 Jul 7, 2024
31a9f49
fix : addWishlistItem, removeWishlistItem 수정
rbm0524 Jul 7, 2024
44d927b
fix : 인자를 wishListItem으로 수정
rbm0524 Jul 7, 2024
fcdc4d9
feat : WebConfig 작성
rbm0524 Jul 7, 2024
ebad18a
feat : WishListResolver 어노테이션 작성
rbm0524 Jul 7, 2024
530e882
feat : WishListResolverHandlerMethodArgumentResolver 작성
rbm0524 Jul 7, 2024
daed829
Merge remote-tracking branch 'origin/step3' into step3
rbm0524 Jul 7, 2024
d21978b
fix : price를 int로 수정
rbm0524 Jul 7, 2024
e7a4afc
fix : conflict된 부분 수정
rbm0524 Jul 7, 2024
f0d9fe8
fix : Google Code Style 적용
rbm0524 Jul 7, 2024
cf7b629
fix : 마지막에 비어있는 한 줄 추가
rbm0524 Jul 7, 2024
b30ede9
fix : 3단계 요구사항 추가
rbm0524 Jul 7, 2024
b75543c
fix : secretKey를 properties에서 관리하도록 수정
rbm0524 Jul 7, 2024
79ce0d2
fix : Utils 패키지로 refactor, @Value로 secretKey와 연결
rbm0524 Jul 7, 2024
fd1f4ed
fix : RequestBody로 값을 전달할 수 있게 수정
rbm0524 Jul 7, 2024
5e613c7
fix : import 경로 수정
rbm0524 Jul 7, 2024
4d96c5c
fix : LoginMemberArgumentResolver를 사용하도록 수정
rbm0524 Jul 7, 2024
2d954c5
fix : JSON으로 보낼 때 ProductName 추가
rbm0524 Jul 7, 2024
9121d3a
fix : @Value 추가, add/remove 메서드 로직 수정
rbm0524 Jul 7, 2024
f73c44a
fix : 주석 추가
rbm0524 Jul 7, 2024
6565cc4
fix : sql문 수정
rbm0524 Jul 7, 2024
f671234
fix : WishListResolver 대신 LoginMemberArgumentResolver를 사용하기 위해 주석처리
rbm0524 Jul 7, 2024
25d1d26
fix : 줄바꿈 수정
rbm0524 Jul 7, 2024
58f0a3a
feat: LoginMemberArgumentResolver 작성
rbm0524 Jul 7, 2024
68cc4b2
feat: LoginMemberResolver 어노테이션 작성
rbm0524 Jul 7, 2024
5f74dfe
Merge branch 'rbm0524' into step3
rbm0524 Jul 7, 2024
31a51dc
fix : 클래스 삭제
rbm0524 Jul 8, 2024
1bb9e7a
fix : Service에서 분기하도록 수정
rbm0524 Jul 8, 2024
5485798
fix : 메서드 2개로 나누기
rbm0524 Jul 8, 2024
bd7b4bc
fix : 필드 추가
rbm0524 Jul 8, 2024
1b6e0a7
fix : 주석 삭제
rbm0524 Jul 8, 2024
13e9a1a
fix : count값도 JSON에 포함하도록 수정
rbm0524 Jul 8, 2024
13ddc95
fix : 주석 삭제
rbm0524 Jul 8, 2024
3168c2e
fix : 쓰지 않은 메서드 삭제
rbm0524 Jul 8, 2024
c68ec42
fix : name 수정
rbm0524 Jul 8, 2024
bfb1b3f
fix : 주석 삭제
rbm0524 Jul 8, 2024
a58bee7
Merge remote-tracking branch 'origin/step3' into step3
rbm0524 Jul 8, 2024
7d37e6d
fix : 사용하지 않는 클래스 삭제
rbm0524 Jul 9, 2024
e5f51a5
refactor : 중복 제거
rbm0524 Jul 9, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,12 @@
### 프로그래밍 요구 사항
1. Authorization 헤더가 유효하지 않거나 토큰이 유효하지 않은 경우 401 Unauthorized를 반환한다.
2. 잘못된 로그인, 비밀번호 찾기, 비밀번호 변경 요청은 403 Forbidden을 반환한다.

# 3단계 위시 리스트

## 요구사항
### 기능 요구사항
이전 단계에서 로그인 후 받은 토큰을 사용하여 사용자별 위시 리스트 기능을 구현한다.
1. 위시 리스트에 등록된 상품 목록을 조회할 수 있다.
2. 위시 리스트에 상품을 추가할 수 있다.
3. 위시 리스트에 담긴 상품을 삭제할 수 있다.
8 changes: 4 additions & 4 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,13 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'javax.xml.bind:jaxb-api:2.3.1'
implementation 'org.springframework.boot:spring-boot-starter-validation'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.6'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.6'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'io.jsonwebtoken:jjwt-impl:0.11.2'
implementation 'io.jsonwebtoken:jjwt-jackson:0.11.2'
runtimeOnly 'com.h2database:h2'
compileOnly 'io.jsonwebtoken:jjwt-api:0.12.6'
compileOnly 'io.jsonwebtoken:jjwt-api:0.11.2'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'

}

tasks.named('test') {
Expand Down
56 changes: 56 additions & 0 deletions src/main/java/gift/Annotation/LoginMemberArgumentResolver.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package gift.Annotation;

import gift.Model.User;
import gift.Service.UserService;
import gift.Utils.JwtUtil;
import io.jsonwebtoken.Claims;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
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 UserService userService;
private final JwtUtil jwtUtil;

@Autowired
public LoginMemberArgumentResolver(UserService userService, JwtUtil jwtUtil) {
this.userService = userService;
this.jwtUtil = jwtUtil;
}

@Override
public boolean supportsParameter(MethodParameter methodParameter) {
return methodParameter.getParameterType().equals(User.class);
}

@Override
public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception {
HttpServletRequest request = (HttpServletRequest) nativeWebRequest.getNativeRequest();

// 쿠키에서 JWT 토큰 추출
String token = null;
if (request.getCookies() != null) {
for (Cookie cookie : request.getCookies()) {
if ("token".equals(cookie.getName())) {
token = cookie.getValue();
break;
}
}
}

if (token == null) {
throw new IllegalArgumentException("JWT token not found in cookies");
}

Claims claims = jwtUtil.decodeToken(token); //decode
String userEmail = claims.getSubject(); // subject를 email로 설정했기 때문에 userEmail로 사용
return userService.findByEmail(userEmail); //null이라면 인증된 것이 아닐 것이고 null이 아니라면 인증된 것
}
}
11 changes: 11 additions & 0 deletions src/main/java/gift/Annotation/LoginMemberResolver.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package gift.Annotation;

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 LoginMemberResolver {
}
22 changes: 22 additions & 0 deletions src/main/java/gift/Config/WebConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package gift.Config;

import gift.Annotation.LoginMemberArgumentResolver;
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.WebMvcConfigurer;

import java.util.List;

@Configuration
public class WebConfig implements WebMvcConfigurer {

@Autowired
private LoginMemberArgumentResolver loginMemberResolverHandlerMethodArgumentResolver;

@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(loginMemberResolverHandlerMethodArgumentResolver);
}

}
16 changes: 0 additions & 16 deletions src/main/java/gift/Connection/DBConnection.java

This file was deleted.

21 changes: 18 additions & 3 deletions src/main/java/gift/Controller/GlobalExceptionHandler.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
package gift.Controller;

import gift.Exception.ForbiddenException;
import gift.Exception.UnauthorizedException;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.ui.Model;
import org.springframework.validation.FieldError;
Expand All @@ -20,11 +25,21 @@ public String handleMethodArgumentNotValid(MethodArgumentNotValidException ex, M

// 에러가 발생한 URL에 따라 다시 해당 페이지로 돌아가기
String requestUrl = request.getRequestURI();
if (requestUrl.equals("/api/products/create")) {
return "product_form"; // product_form 뷰로 포워딩
} else if (requestUrl.startsWith("/api/products/update")) {
if (requestUrl.equals("/api/products/create") || requestUrl.startsWith("/api/products/update")) {
return "product_form"; // product_form 뷰로 포워딩
}

return "redirect:/api/products";
}

@ExceptionHandler(UnauthorizedException.class)
public ResponseEntity<String> handleUnauthorizedException(UnauthorizedException ex) {
return new ResponseEntity<>(ex.getMessage(), HttpStatus.UNAUTHORIZED);
}

@ExceptionHandler(ForbiddenException.class)
public ResponseEntity<String> handleForbiddenException(ForbiddenException ex) {
return new ResponseEntity<>(ex.getMessage(), HttpStatus.FORBIDDEN);
}

}
11 changes: 7 additions & 4 deletions src/main/java/gift/Controller/ProductController.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

import gift.Model.Product;
import gift.Service.ProductService;

import java.util.Optional;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.Valid;
import jakarta.servlet.http.HttpServletRequest;
Expand Down Expand Up @@ -39,10 +41,10 @@ public String getAllProductsByUser(Model model) {

@RequestMapping(value = "/api/products/create", method = {RequestMethod.GET, RequestMethod.POST})
public String createProduct(@Valid @ModelAttribute Product product, HttpServletRequest request, Model model) {
if("GET".equalsIgnoreCase(request.getMethod())) {
if ("GET".equalsIgnoreCase(request.getMethod())) {
model.addAttribute("product", new Product());
return "product_form";
} else if("POST".equalsIgnoreCase(request.getMethod())) {
} else if ("POST".equalsIgnoreCase(request.getMethod())) {
model.addAttribute("product", product);
productService.saveProduct(product);
return "redirect:/api/products";
Expand All @@ -52,11 +54,11 @@ public String createProduct(@Valid @ModelAttribute Product product, HttpServletR

@RequestMapping(value = "/api/products/update/{id}", method = {RequestMethod.GET, RequestMethod.POST})
public String updateProductById(@PathVariable Long id, @Valid @ModelAttribute Product productDetails, HttpServletRequest request, Model model) {
if("GET".equalsIgnoreCase(request.getMethod())) {
if ("GET".equalsIgnoreCase(request.getMethod())) {
Optional<Product> optionalProduct = productService.getProductById(id);
model.addAttribute("product", optionalProduct.get());
return "product_form";
} else if("POST".equalsIgnoreCase(request.getMethod())) {
} else if ("POST".equalsIgnoreCase(request.getMethod())) {
productService.updateProduct(id, productDetails);
return "redirect:/api/products";
}
Expand All @@ -70,4 +72,5 @@ public String deleteProduct(@PathVariable Long id, Model model) {
productService.deleteProduct(id);
return "redirect:/api/products"; // 제품 목록 페이지로 리디렉션
}

}
59 changes: 33 additions & 26 deletions src/main/java/gift/Controller/UserController.java
Original file line number Diff line number Diff line change
@@ -1,52 +1,57 @@
package gift.Controller;

import gift.Model.Product;
import gift.Model.User;
import gift.Service.UserService;
import jakarta.servlet.http.HttpServletRequest;
import gift.Utils.JwtUtil;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import java.util.List;
import org.springframework.web.bind.annotation.*;
import jakarta.servlet.http.HttpServletRequest;

@Controller
public class UserController {

private UserService userService;
private JwtTokenProvider tokenProvider;
private final UserService userService;
private final JwtUtil jwtUtil;

@Autowired
public UserController(UserService userService, JwtTokenProvider tokenProvider) {
public UserController(UserService userService, JwtUtil jwtUtil) {
this.userService = userService;
this.tokenProvider = tokenProvider;
this.jwtUtil = jwtUtil;
}

@RequestMapping(value = "/login", method = {RequestMethod.GET, RequestMethod.POST})
public String login(@ModelAttribute User user, Model model, HttpServletRequest request) {
if ("GET".equalsIgnoreCase(request.getMethod())) {
model.addAttribute("user", new User());
return "login";
} else if ("POST".equalsIgnoreCase(request.getMethod())) {
model.addAttribute("user", user);
List<User> users = userService.login(user);
if (users.isEmpty()) {
model.addAttribute("error", "유효하지 않은 아이디거나 비밀번호입니다.");
return "login";
}
@GetMapping("/login")
public String login(Model model) {
model.addAttribute("user", new User());
return "login";
}

@PostMapping(value = "/login")
public String login(@ModelAttribute User user, Model model, HttpServletResponse response) {
String email = user.getEmail();
String password = user.getPassword();

String email = user.getEmail();
boolean isAuthenticated = userService.authenticate(email, password);
if (isAuthenticated) {
boolean isAdmin = userService.isAdmin(email);
String jwt = tokenProvider.generateToken(user, isAdmin);
model.addAttribute("token", jwt);
User authenticatedUser = userService.findByEmail(email);
String token = jwtUtil.generateToken(authenticatedUser, isAdmin);
// Set token in HttpOnly cookie
Cookie cookie = new Cookie("token", token);
cookie.setHttpOnly(true);
cookie.setPath("/");
response.addCookie(cookie);

if (isAdmin) {
return "redirect:/api/products";
}
return "redirect:/products";
}

model.addAttribute("error", "Authentication failed");
return "login";
}

Expand All @@ -58,6 +63,8 @@ public String register(@ModelAttribute User user, Model model, HttpServletReques
} else if ("POST".equalsIgnoreCase(request.getMethod())) {
model.addAttribute("user", user);
userService.register(user);
model.addAttribute("message", "회원가입에 성공했습니다.");
return "login";
}
return "login";
}
Expand Down
50 changes: 50 additions & 0 deletions src/main/java/gift/Controller/WishListController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package gift.Controller;

import gift.Annotation.LoginMemberResolver;
import gift.Model.User;
import gift.Model.WishListItem;
import gift.Service.WishListService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@Controller
public class WishListController {
private final WishListService wishlistService;

@Autowired
public WishListController(WishListService wishlistService) {
this.wishlistService = wishlistService;
}

@GetMapping("/wishlist")
public String getWishlist(@LoginMemberResolver User user, Model model) {
List<WishListItem> wishlist = wishlistService.getWishlist(user.getId());
model.addAttribute("wishlist", wishlist);
return "wishlist";
}

@PostMapping("/wishlist/add")
public String addWishlistItem(@LoginMemberResolver User user, @RequestBody WishListItem wishListItem) {
if(user == null) {
return "redirect:/login";
}
wishListItem.setUserId(user.getId());
wishlistService.addWishlistItem(wishListItem);
return "redirect:/products";
}

@PostMapping("/wishlist/remove/{productId}")
public String removeWishlistItem(@LoginMemberResolver User user, @RequestBody WishListItem wishListItem) {
if(user == null) {
return "redirect:/login";
}

wishListItem.setUserId(user.getId());
wishlistService.removeWishlistItem(wishListItem);
return "redirect:/wishlist";
}
}
11 changes: 11 additions & 0 deletions src/main/java/gift/Exception/ForbiddenException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package gift.Exception;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

@ResponseStatus(HttpStatus.FORBIDDEN)
public class ForbiddenException extends RuntimeException {
public ForbiddenException(String message) {
super(message);
}
}
11 changes: 11 additions & 0 deletions src/main/java/gift/Exception/UnauthorizedException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package gift.Exception;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

@ResponseStatus(HttpStatus.UNAUTHORIZED)
public class UnauthorizedException extends RuntimeException {
public UnauthorizedException(String message) {
super(message);
}
}
Loading