From 6290b43a1a880ebb22bea9de8d4387dcee704c9b Mon Sep 17 00:00:00 2001 From: juhyeon Date: Mon, 14 Aug 2023 08:48:00 +0900 Subject: [PATCH 01/30] =?UTF-8?q?build=20:=20SpringSecurity=20=EC=9D=98?= =?UTF-8?q?=EC=A1=B4=EC=84=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/build.gradle b/app/build.gradle index c60775d0..959766ee 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -76,6 +76,8 @@ dependencies { testImplementation('org.springframework.boot:spring-boot-starter-test') { exclude group: 'org.junit.vintage', module: 'junit-vintage-engine' } + // Spring Security + implementation 'org.springframework.boot:spring-boot-starter-security' } application { From d0243f647fd7bba8874b3fb3149a81b56d0b05dc Mon Sep 17 00:00:00 2001 From: juhyeon Date: Mon, 14 Aug 2023 08:54:16 +0900 Subject: [PATCH 02/30] =?UTF-8?q?feat=20:=20SpringSecurity=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=20=EB=B0=8F=20=ED=95=84=ED=84=B0=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 인증 필터 및 인증 에러 처리를 위한 필터 추가 --- .../assignment/config/SecurityJavaConfig.java | 31 +++++++++++++ .../security/AuthenticationErrorFilter.java | 22 ++++++++++ .../security/JwtAuthenticationFilter.java | 43 +++++++++++++++++++ 3 files changed, 96 insertions(+) create mode 100644 app/src/main/java/com/codesoom/assignment/config/SecurityJavaConfig.java create mode 100644 app/src/main/java/com/codesoom/assignment/security/AuthenticationErrorFilter.java create mode 100644 app/src/main/java/com/codesoom/assignment/security/JwtAuthenticationFilter.java diff --git a/app/src/main/java/com/codesoom/assignment/config/SecurityJavaConfig.java b/app/src/main/java/com/codesoom/assignment/config/SecurityJavaConfig.java new file mode 100644 index 00000000..978d4490 --- /dev/null +++ b/app/src/main/java/com/codesoom/assignment/config/SecurityJavaConfig.java @@ -0,0 +1,31 @@ +package com.codesoom.assignment.config; + +import com.codesoom.assignment.application.AuthenticationService; +import com.codesoom.assignment.security.AuthenticationErrorFilter; +import com.codesoom.assignment.security.JwtAuthenticationFilter; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; + +import javax.servlet.Filter; + +@Configuration +public class SecurityJavaConfig extends WebSecurityConfigurerAdapter { + private final AuthenticationService authenticationService; + + public SecurityJavaConfig(AuthenticationService authenticationService) { + this.authenticationService = authenticationService; + } + + + @Override + protected void configure(HttpSecurity http) throws Exception { + Filter authenticationFilter = new JwtAuthenticationFilter(authenticationManager(), authenticationService); + Filter authenticationErrorFilter = new AuthenticationErrorFilter(); + + http + .csrf().disable() + .addFilter(authenticationFilter) + .addFilterBefore(authenticationErrorFilter, JwtAuthenticationFilter.class); + } +} diff --git a/app/src/main/java/com/codesoom/assignment/security/AuthenticationErrorFilter.java b/app/src/main/java/com/codesoom/assignment/security/AuthenticationErrorFilter.java new file mode 100644 index 00000000..f502bbd4 --- /dev/null +++ b/app/src/main/java/com/codesoom/assignment/security/AuthenticationErrorFilter.java @@ -0,0 +1,22 @@ +package com.codesoom.assignment.security; + +import com.codesoom.assignment.errors.InvalidTokenException; +import org.springframework.http.HttpStatus; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpFilter; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +public class AuthenticationErrorFilter extends HttpFilter { + @Override + protected void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { + try { + chain.doFilter(request,response); + }catch (InvalidTokenException e){ + response.sendError(HttpStatus.UNAUTHORIZED.value()); + } + } +} diff --git a/app/src/main/java/com/codesoom/assignment/security/JwtAuthenticationFilter.java b/app/src/main/java/com/codesoom/assignment/security/JwtAuthenticationFilter.java new file mode 100644 index 00000000..56889900 --- /dev/null +++ b/app/src/main/java/com/codesoom/assignment/security/JwtAuthenticationFilter.java @@ -0,0 +1,43 @@ +package com.codesoom.assignment.security; + +import com.codesoom.assignment.application.AuthenticationService; +import com.codesoom.assignment.errors.InvalidTokenException; +import org.springframework.http.HttpStatus; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +public class JwtAuthenticationFilter extends BasicAuthenticationFilter { + private final AuthenticationService authenticationService; + + public JwtAuthenticationFilter(AuthenticationManager authenticationManager, AuthenticationService authenticationService) { + super(authenticationManager); + this.authenticationService = authenticationService; + } + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { + + String authorization = request.getHeader("Authorization"); + + try { + if(authorization == null){ + throw new InvalidTokenException(""); + } + + String accessToken = authorization.substring("Bearer ".length()); + authenticationService.parseToken(accessToken); + + }catch (InvalidTokenException e){ + response.sendError(HttpStatus.UNAUTHORIZED.value()); + return; + } + + chain.doFilter(request,response); + } +} From e3d1669bcd8648959bcc2650f5515f85f78adb3b Mon Sep 17 00:00:00 2001 From: juhyeon Date: Mon, 14 Aug 2023 08:56:22 +0900 Subject: [PATCH 03/30] =?UTF-8?q?refactor=20:=20=EC=9D=B8=EC=A6=9D=20?= =?UTF-8?q?=EC=9D=B8=ED=84=B0=EC=85=89=ED=84=B0=20=EA=B4=80=EB=A0=A8=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 인증로직을 인터셉터에서 처리하는 부분에서 SpringSecurity으로 변경으로 인한 기존 인터셉터 삭제 --- .../assignment/config/WebJavaConfig.java | 18 ------ .../AuthenticationInterceptor.java | 64 ------------------- 2 files changed, 82 deletions(-) delete mode 100644 app/src/main/java/com/codesoom/assignment/config/WebJavaConfig.java delete mode 100644 app/src/main/java/com/codesoom/assignment/interceptors/AuthenticationInterceptor.java diff --git a/app/src/main/java/com/codesoom/assignment/config/WebJavaConfig.java b/app/src/main/java/com/codesoom/assignment/config/WebJavaConfig.java deleted file mode 100644 index d6ffe26f..00000000 --- a/app/src/main/java/com/codesoom/assignment/config/WebJavaConfig.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.codesoom.assignment.config; - -import com.codesoom.assignment.interceptors.AuthenticationInterceptor; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Configuration; -import org.springframework.web.servlet.config.annotation.InterceptorRegistry; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; - -@Configuration -public class WebJavaConfig implements WebMvcConfigurer { - @Autowired - private AuthenticationInterceptor authenticationInterceptor; - - @Override - public void addInterceptors(InterceptorRegistry registry) { - registry.addInterceptor(authenticationInterceptor); - } -} diff --git a/app/src/main/java/com/codesoom/assignment/interceptors/AuthenticationInterceptor.java b/app/src/main/java/com/codesoom/assignment/interceptors/AuthenticationInterceptor.java deleted file mode 100644 index c07f6f98..00000000 --- a/app/src/main/java/com/codesoom/assignment/interceptors/AuthenticationInterceptor.java +++ /dev/null @@ -1,64 +0,0 @@ -package com.codesoom.assignment.interceptors; - -import com.codesoom.assignment.application.AuthenticationService; -import org.springframework.http.HttpStatus; -import org.springframework.stereotype.Component; -import org.springframework.web.servlet.HandlerInterceptor; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; - -@Component -public class AuthenticationInterceptor implements HandlerInterceptor { - private AuthenticationService authenticationService; - - public AuthenticationInterceptor( - AuthenticationService authenticationService) { - this.authenticationService = authenticationService; - } - - @Override - public boolean preHandle(HttpServletRequest request, - HttpServletResponse response, - Object handler) throws Exception { - return filterWithPathAndMethod(request) || - doAuthentication(request, response); - } - - private boolean filterWithPathAndMethod(HttpServletRequest request) { - String path = request.getRequestURI(); - if (!path.startsWith("/products")) { - return true; - } - - String method = request.getMethod(); - if (method.equals("GET")) { - return true; - } - - if (method.equals("OPTIONS")) { - return true; - } - - return false; - } - - private boolean doAuthentication(HttpServletRequest request, - HttpServletResponse response) - throws IOException { - String authorization = request.getHeader("Authorization"); - - if (authorization == null) { - response.sendError(HttpStatus.UNAUTHORIZED.value()); - return false; - } - - String accessToken = authorization.substring("Bearer ".length()); - Long userId = authenticationService.parseToken(accessToken); - - request.setAttribute("userId", userId); - - return true; - } -} From 0974cbf8f7c882ef23e695cc9e69042be3cd3493 Mon Sep 17 00:00:00 2001 From: tmxhsk99 Date: Tue, 15 Aug 2023 06:13:49 +0900 Subject: [PATCH 04/30] =?UTF-8?q?7-1=20Spring=20security,=207-2=20?= =?UTF-8?q?=EC=9D=B8=EC=A6=9D=20=EA=B9=8C=EC=A7=80=20=EB=B3=B4=EA=B3=A0=20?= =?UTF-8?q?=EC=A7=84=ED=96=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../assignment/config/SecurityJavaConfig.java | 18 ++++++-- .../controllers/ControllerErrorAdvice.java | 6 --- .../controllers/ProductController.java | 2 + .../AuthenticationErrorFilter.java | 2 +- .../JwtAuthenticationFilter.java | 24 +++++----- .../security/UserAuthentication.java | 44 +++++++++++++++++++ 6 files changed, 75 insertions(+), 21 deletions(-) rename app/src/main/java/com/codesoom/assignment/{security => filters}/AuthenticationErrorFilter.java (94%) rename app/src/main/java/com/codesoom/assignment/{security => filters}/JwtAuthenticationFilter.java (65%) create mode 100644 app/src/main/java/com/codesoom/assignment/security/UserAuthentication.java diff --git a/app/src/main/java/com/codesoom/assignment/config/SecurityJavaConfig.java b/app/src/main/java/com/codesoom/assignment/config/SecurityJavaConfig.java index 978d4490..e499a2fa 100644 --- a/app/src/main/java/com/codesoom/assignment/config/SecurityJavaConfig.java +++ b/app/src/main/java/com/codesoom/assignment/config/SecurityJavaConfig.java @@ -1,15 +1,20 @@ package com.codesoom.assignment.config; import com.codesoom.assignment.application.AuthenticationService; -import com.codesoom.assignment.security.AuthenticationErrorFilter; -import com.codesoom.assignment.security.JwtAuthenticationFilter; +import com.codesoom.assignment.filters.AuthenticationErrorFilter; +import com.codesoom.assignment.filters.JwtAuthenticationFilter; import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpStatus; +import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.web.authentication.HttpStatusEntryPoint; import javax.servlet.Filter; @Configuration +@EnableGlobalMethodSecurity(prePostEnabled = true) public class SecurityJavaConfig extends WebSecurityConfigurerAdapter { private final AuthenticationService authenticationService; @@ -26,6 +31,13 @@ protected void configure(HttpSecurity http) throws Exception { http .csrf().disable() .addFilter(authenticationFilter) - .addFilterBefore(authenticationErrorFilter, JwtAuthenticationFilter.class); + .addFilterBefore(authenticationErrorFilter, + JwtAuthenticationFilter.class) + .sessionManagement() + .sessionCreationPolicy(SessionCreationPolicy.STATELESS) + .and() + .exceptionHandling() + .authenticationEntryPoint( + new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED)); } } diff --git a/app/src/main/java/com/codesoom/assignment/controllers/ControllerErrorAdvice.java b/app/src/main/java/com/codesoom/assignment/controllers/ControllerErrorAdvice.java index 24db2ef2..053fda86 100644 --- a/app/src/main/java/com/codesoom/assignment/controllers/ControllerErrorAdvice.java +++ b/app/src/main/java/com/codesoom/assignment/controllers/ControllerErrorAdvice.java @@ -39,12 +39,6 @@ public ErrorResponse handleLoginFailException() { return new ErrorResponse("Log-in failed"); } - @ResponseStatus(HttpStatus.UNAUTHORIZED) - @ExceptionHandler(InvalidTokenException.class) - public ErrorResponse handleInvalidAccessTokenException() { - return new ErrorResponse("Invalid access token"); - } - @ResponseBody @ResponseStatus(HttpStatus.BAD_REQUEST) @ExceptionHandler(ConstraintViolationException.class) diff --git a/app/src/main/java/com/codesoom/assignment/controllers/ProductController.java b/app/src/main/java/com/codesoom/assignment/controllers/ProductController.java index 42193f4b..f29321e2 100644 --- a/app/src/main/java/com/codesoom/assignment/controllers/ProductController.java +++ b/app/src/main/java/com/codesoom/assignment/controllers/ProductController.java @@ -9,6 +9,7 @@ import com.codesoom.assignment.domain.Product; import com.codesoom.assignment.dto.ProductData; import org.springframework.http.HttpStatus; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; import javax.validation.Valid; @@ -40,6 +41,7 @@ public Product detail(@PathVariable Long id) { @PostMapping @ResponseStatus(HttpStatus.CREATED) + @PreAuthorize("isAuthenticated() and hasAuthority('USER')") public Product create( @RequestAttribute Long userId, @RequestBody @Valid ProductData productData diff --git a/app/src/main/java/com/codesoom/assignment/security/AuthenticationErrorFilter.java b/app/src/main/java/com/codesoom/assignment/filters/AuthenticationErrorFilter.java similarity index 94% rename from app/src/main/java/com/codesoom/assignment/security/AuthenticationErrorFilter.java rename to app/src/main/java/com/codesoom/assignment/filters/AuthenticationErrorFilter.java index f502bbd4..83fd09b7 100644 --- a/app/src/main/java/com/codesoom/assignment/security/AuthenticationErrorFilter.java +++ b/app/src/main/java/com/codesoom/assignment/filters/AuthenticationErrorFilter.java @@ -1,4 +1,4 @@ -package com.codesoom.assignment.security; +package com.codesoom.assignment.filters; import com.codesoom.assignment.errors.InvalidTokenException; import org.springframework.http.HttpStatus; diff --git a/app/src/main/java/com/codesoom/assignment/security/JwtAuthenticationFilter.java b/app/src/main/java/com/codesoom/assignment/filters/JwtAuthenticationFilter.java similarity index 65% rename from app/src/main/java/com/codesoom/assignment/security/JwtAuthenticationFilter.java rename to app/src/main/java/com/codesoom/assignment/filters/JwtAuthenticationFilter.java index 56889900..a1607587 100644 --- a/app/src/main/java/com/codesoom/assignment/security/JwtAuthenticationFilter.java +++ b/app/src/main/java/com/codesoom/assignment/filters/JwtAuthenticationFilter.java @@ -1,9 +1,13 @@ -package com.codesoom.assignment.security; +package com.codesoom.assignment.filters; import com.codesoom.assignment.application.AuthenticationService; import com.codesoom.assignment.errors.InvalidTokenException; +import com.codesoom.assignment.security.UserAuthentication; import org.springframework.http.HttpStatus; import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; import javax.servlet.FilterChain; @@ -25,19 +29,17 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse String authorization = request.getHeader("Authorization"); - try { - if(authorization == null){ - throw new InvalidTokenException(""); - } - + if (authorization != null) { String accessToken = authorization.substring("Bearer ".length()); - authenticationService.parseToken(accessToken); + Long userId = authenticationService.parseToken(accessToken); + Authentication authentication = new UserAuthentication(userId); - }catch (InvalidTokenException e){ - response.sendError(HttpStatus.UNAUTHORIZED.value()); - return; + SecurityContext context = SecurityContextHolder.getContext(); + context.setAuthentication(authentication); } - chain.doFilter(request,response); + chain.doFilter(request, response); + } + } diff --git a/app/src/main/java/com/codesoom/assignment/security/UserAuthentication.java b/app/src/main/java/com/codesoom/assignment/security/UserAuthentication.java new file mode 100644 index 00000000..67efb3da --- /dev/null +++ b/app/src/main/java/com/codesoom/assignment/security/UserAuthentication.java @@ -0,0 +1,44 @@ +package com.codesoom.assignment.security; + +import org.springframework.security.authentication.AbstractAuthenticationToken; +import org.springframework.security.core.GrantedAuthority; + +import java.util.ArrayList; +import java.util.List; + +public class UserAuthentication extends AbstractAuthenticationToken { + private final Long userId; + + public UserAuthentication(Long userId) { + super(authorities()); + this.userId = userId; + } + + @Override + public Object getCredentials() { + return null; + } + + @Override + public boolean isAuthenticated() { + return true; + } + + @Override + public String toString() { + return String.format("Authentication( %s )", userId); + } + + @Override + public Object getPrincipal() { + return this.userId; + } + + private static List authorities() { + List authorities = new ArrayList<>(); + // todo : userId에 따른 다른 권한 부여 => e.g ADMIN, USER + authorities.add((GrantedAuthority) () -> "USER"); + return authorities; + } + +} From f92be3bfb13964789047ec2403daa28ff1495cde Mon Sep 17 00:00:00 2001 From: tmxhsk99 Date: Wed, 16 Aug 2023 05:54:43 +0900 Subject: [PATCH 05/30] =?UTF-8?q?=EA=B8=B0=EC=A1=B4=20TestHelper=EB=A5=BC?= =?UTF-8?q?=20=EC=82=AC=EC=9A=A9=ED=95=98=EB=8A=94=20=EA=B3=84=EC=B8=B5?= =?UTF-8?q?=ED=98=95=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EA=B5=AC=EC=A1=B0?= =?UTF-8?q?=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/AuthenticationService.java | 5 - .../assignment/config/SecurityJavaConfig.java | 11 +- .../controllers/ProductController.java | 12 +- .../assignment/dto/SessionRequestData.java | 2 + .../filters/JwtAuthenticationFilter.java | 14 +- .../codesoom/assignment/utils/JwtUtil.java | 2 +- .../AuthenticationServiceTest.java | 13 - .../controllers/ProductControllerTest.java | 560 +++++++++++------- .../codesoom/assignment/utils/TestHelper.java | 137 +++++ 9 files changed, 492 insertions(+), 264 deletions(-) create mode 100644 app/src/test/java/com/codesoom/assignment/utils/TestHelper.java diff --git a/app/src/main/java/com/codesoom/assignment/application/AuthenticationService.java b/app/src/main/java/com/codesoom/assignment/application/AuthenticationService.java index acbe601e..9956fbe1 100644 --- a/app/src/main/java/com/codesoom/assignment/application/AuthenticationService.java +++ b/app/src/main/java/com/codesoom/assignment/application/AuthenticationService.java @@ -4,7 +4,6 @@ import com.codesoom.assignment.domain.UserRepository; import com.codesoom.assignment.errors.LoginFailException; import com.codesoom.assignment.utils.JwtUtil; -import io.jsonwebtoken.Claims; import org.springframework.stereotype.Service; @Service @@ -29,8 +28,4 @@ public String login(String email, String password) { return jwtUtil.encode(1L); } - public Long parseToken(String accessToken) { - Claims claims = jwtUtil.decode(accessToken); - return claims.get("userId", Long.class); - } } diff --git a/app/src/main/java/com/codesoom/assignment/config/SecurityJavaConfig.java b/app/src/main/java/com/codesoom/assignment/config/SecurityJavaConfig.java index e499a2fa..2a36a0fc 100644 --- a/app/src/main/java/com/codesoom/assignment/config/SecurityJavaConfig.java +++ b/app/src/main/java/com/codesoom/assignment/config/SecurityJavaConfig.java @@ -1,8 +1,8 @@ package com.codesoom.assignment.config; -import com.codesoom.assignment.application.AuthenticationService; import com.codesoom.assignment.filters.AuthenticationErrorFilter; import com.codesoom.assignment.filters.JwtAuthenticationFilter; +import com.codesoom.assignment.utils.JwtUtil; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpStatus; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; @@ -16,16 +16,15 @@ @Configuration @EnableGlobalMethodSecurity(prePostEnabled = true) public class SecurityJavaConfig extends WebSecurityConfigurerAdapter { - private final AuthenticationService authenticationService; - - public SecurityJavaConfig(AuthenticationService authenticationService) { - this.authenticationService = authenticationService; + private final JwtUtil jwtUtil; + public SecurityJavaConfig(JwtUtil jwtUtil) { + this.jwtUtil = jwtUtil; } @Override protected void configure(HttpSecurity http) throws Exception { - Filter authenticationFilter = new JwtAuthenticationFilter(authenticationManager(), authenticationService); + Filter authenticationFilter = new JwtAuthenticationFilter(authenticationManager(), jwtUtil); Filter authenticationErrorFilter = new AuthenticationErrorFilter(); http diff --git a/app/src/main/java/com/codesoom/assignment/controllers/ProductController.java b/app/src/main/java/com/codesoom/assignment/controllers/ProductController.java index f29321e2..edb7c9f1 100644 --- a/app/src/main/java/com/codesoom/assignment/controllers/ProductController.java +++ b/app/src/main/java/com/codesoom/assignment/controllers/ProductController.java @@ -4,12 +4,12 @@ package com.codesoom.assignment.controllers; -import com.codesoom.assignment.application.AuthenticationService; import com.codesoom.assignment.application.ProductService; import com.codesoom.assignment.domain.Product; import com.codesoom.assignment.dto.ProductData; import org.springframework.http.HttpStatus; import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.core.Authentication; import org.springframework.web.bind.annotation.*; import javax.validation.Valid; @@ -21,12 +21,8 @@ public class ProductController { private final ProductService productService; - private final AuthenticationService authenticationService; - - public ProductController(ProductService productService, - AuthenticationService authenticationService) { + public ProductController(ProductService productService) { this.productService = productService; - this.authenticationService = authenticationService; } @GetMapping @@ -43,8 +39,8 @@ public Product detail(@PathVariable Long id) { @ResponseStatus(HttpStatus.CREATED) @PreAuthorize("isAuthenticated() and hasAuthority('USER')") public Product create( - @RequestAttribute Long userId, - @RequestBody @Valid ProductData productData + @RequestBody @Valid ProductData productData, + Authentication authentication ) { return productService.createProduct(productData); } diff --git a/app/src/main/java/com/codesoom/assignment/dto/SessionRequestData.java b/app/src/main/java/com/codesoom/assignment/dto/SessionRequestData.java index 13d1e192..a5a48387 100644 --- a/app/src/main/java/com/codesoom/assignment/dto/SessionRequestData.java +++ b/app/src/main/java/com/codesoom/assignment/dto/SessionRequestData.java @@ -1,8 +1,10 @@ package com.codesoom.assignment.dto; +import lombok.Builder; import lombok.Getter; @Getter +@Builder public class SessionRequestData { private String email; private String password; diff --git a/app/src/main/java/com/codesoom/assignment/filters/JwtAuthenticationFilter.java b/app/src/main/java/com/codesoom/assignment/filters/JwtAuthenticationFilter.java index a1607587..8ce40ac1 100644 --- a/app/src/main/java/com/codesoom/assignment/filters/JwtAuthenticationFilter.java +++ b/app/src/main/java/com/codesoom/assignment/filters/JwtAuthenticationFilter.java @@ -1,9 +1,7 @@ package com.codesoom.assignment.filters; -import com.codesoom.assignment.application.AuthenticationService; -import com.codesoom.assignment.errors.InvalidTokenException; import com.codesoom.assignment.security.UserAuthentication; -import org.springframework.http.HttpStatus; +import com.codesoom.assignment.utils.JwtUtil; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContext; @@ -17,11 +15,11 @@ import java.io.IOException; public class JwtAuthenticationFilter extends BasicAuthenticationFilter { - private final AuthenticationService authenticationService; + private JwtUtil jwtUtil; - public JwtAuthenticationFilter(AuthenticationManager authenticationManager, AuthenticationService authenticationService) { + public JwtAuthenticationFilter(AuthenticationManager authenticationManager, JwtUtil jwtUtil) { super(authenticationManager); - this.authenticationService = authenticationService; + this.jwtUtil = jwtUtil; } @Override @@ -31,9 +29,11 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse if (authorization != null) { String accessToken = authorization.substring("Bearer ".length()); - Long userId = authenticationService.parseToken(accessToken); + Long userId = jwtUtil.decode(accessToken) + .get("userId", Long.class); Authentication authentication = new UserAuthentication(userId); + SecurityContext context = SecurityContextHolder.getContext(); context.setAuthentication(authentication); } diff --git a/app/src/main/java/com/codesoom/assignment/utils/JwtUtil.java b/app/src/main/java/com/codesoom/assignment/utils/JwtUtil.java index 783dda97..a19920a4 100644 --- a/app/src/main/java/com/codesoom/assignment/utils/JwtUtil.java +++ b/app/src/main/java/com/codesoom/assignment/utils/JwtUtil.java @@ -20,7 +20,7 @@ public JwtUtil(@Value("${jwt.secret}") String secret) { public String encode(Long userId) { return Jwts.builder() - .claim("userId", 1L) + .claim("userId", userId) .signWith(key) .compact(); } diff --git a/app/src/test/java/com/codesoom/assignment/application/AuthenticationServiceTest.java b/app/src/test/java/com/codesoom/assignment/application/AuthenticationServiceTest.java index 6044b404..564ff7b8 100644 --- a/app/src/test/java/com/codesoom/assignment/application/AuthenticationServiceTest.java +++ b/app/src/test/java/com/codesoom/assignment/application/AuthenticationServiceTest.java @@ -71,17 +71,4 @@ void loginWithWrongPassword() { verify(userRepository).findByEmail("tester@example.com"); } - @Test - void parseTokenWithValidToken() { - Long userId = authenticationService.parseToken(VALID_TOKEN); - - assertThat(userId).isEqualTo(1L); - } - - @Test - void parseTokenWithInvalidToken() { - assertThatThrownBy( - () -> authenticationService.parseToken(INVALID_TOKEN) - ).isInstanceOf(InvalidTokenException.class); - } } diff --git a/app/src/test/java/com/codesoom/assignment/controllers/ProductControllerTest.java b/app/src/test/java/com/codesoom/assignment/controllers/ProductControllerTest.java index 9715830a..1b343111 100644 --- a/app/src/test/java/com/codesoom/assignment/controllers/ProductControllerTest.java +++ b/app/src/test/java/com/codesoom/assignment/controllers/ProductControllerTest.java @@ -1,13 +1,17 @@ package com.codesoom.assignment.controllers; -import com.codesoom.assignment.application.AuthenticationService; import com.codesoom.assignment.application.ProductService; import com.codesoom.assignment.domain.Product; import com.codesoom.assignment.dto.ProductData; import com.codesoom.assignment.errors.InvalidTokenException; import com.codesoom.assignment.errors.ProductNotFoundException; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; +import com.codesoom.assignment.utils.JwtUtil; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.jsonwebtoken.Claims; +import org.junit.jupiter.api.*; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.mock.mockito.MockBean; @@ -16,253 +20,361 @@ import java.util.List; +import static com.codesoom.assignment.utils.TestHelper.*; import static org.hamcrest.Matchers.containsString; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.verify; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @WebMvcTest(ProductController.class) +@SuppressWarnings({"InnerClassMayBeStatic", "NonAsciiCharacters"}) +@DisplayName("ProductController 클래스") class ProductControllerTest { - private static final String VALID_TOKEN = "eyJhbGciOiJIUzI1NiJ9." + - "eyJ1c2VySWQiOjF9.ZZ3CUl0jxeLGvQ1Js5nG2Ty5qGTlqai5ubDMXZOdaDk"; - private static final String INVALID_TOKEN = "eyJhbGciOiJIUzI1NiJ9." + - "eyJ1c2VySWQiOjF9.ZZ3CUl0jxeLGvQ1Js5nG2Ty5qGTlqai5ubDMXZOdaD0"; @Autowired private MockMvc mockMvc; + @Autowired + private ObjectMapper objectMapper; + @MockBean private ProductService productService; @MockBean - private AuthenticationService authenticationService; - - @BeforeEach - void setUp() { - Product product = Product.builder() - .id(1L) - .name("쥐돌이") - .maker("냥이월드") - .price(5000) - .build(); - given(productService.getProducts()).willReturn(List.of(product)); - - given(productService.getProduct(1L)).willReturn(product); - - given(productService.getProduct(1000L)) - .willThrow(new ProductNotFoundException(1000L)); - - given(productService.createProduct(any(ProductData.class))) - .willReturn(product); - - given(productService.updateProduct(eq(1L), any(ProductData.class))) - .will(invocation -> { - Long id = invocation.getArgument(0); - ProductData productData = invocation.getArgument(1); - return Product.builder() - .id(id) - .name(productData.getName()) - .maker(productData.getMaker()) - .price(productData.getPrice()) - .build(); - }); - - given(productService.updateProduct(eq(1000L), any(ProductData.class))) - .willThrow(new ProductNotFoundException(1000L)); - - given(productService.deleteProduct(1000L)) - .willThrow(new ProductNotFoundException(1000L)); - - given(authenticationService.parseToken(VALID_TOKEN)).willReturn(1L); - - given(authenticationService.parseToken(INVALID_TOKEN)) - .willThrow(new InvalidTokenException(INVALID_TOKEN)); + private JwtUtil jwtUtil; + + @Nested + @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) + class list_메서드는 { + + @BeforeEach + void setUp() { + given(productService.getProducts()).willReturn(List.of(TEST_PRODUCT)); + } + + @Test + @DisplayName("상품목록을 반환한다") + void It_returns_product_list() throws Exception { + mockMvc.perform(get("/products") + .accept(MediaType.APPLICATION_JSON_UTF8) + .contentType(MediaType.APPLICATION_JSON_UTF8)) + .andExpect(status().isOk()) + .andExpect(content().string(containsString(TEST_PRODUCT_NAME))); + } } - @Test - void list() throws Exception { - mockMvc.perform( - get("/products") - .accept(MediaType.APPLICATION_JSON_UTF8) - ) - .andExpect(status().isOk()) - .andExpect(content().string(containsString("쥐돌이"))); - } + @Nested + @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) + class detail_메서드는 { + + @Nested + @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) + class 요청받은_id에_해당하는_상품이_존재하는_경우 { + @BeforeEach + void setUp() { + given(productService.getProduct(1L)).willReturn(TEST_PRODUCT); + } + + @Test + @DisplayName("해당 id의 상품을 반환한다") + void It_returns_product() throws Exception { + mockMvc.perform(get("/products/1") + .accept(MediaType.APPLICATION_JSON_UTF8) + .contentType(MediaType.APPLICATION_JSON_UTF8)) + .andExpect(status().isOk()) + .andExpect(content().string(containsString(TEST_PRODUCT_NAME))); + } + } + + @Nested + @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) + class 요청받은_id에_해당하는_상품이_존재하지_않는_경우 { + @BeforeEach + void setUp() { + given(productService.getProduct(1000L)) + .willThrow(new ProductNotFoundException(1000L)); + } + + @Test + @DisplayName("에러메시지를 반환한다") + void It_returns_404_error() throws Exception { + mockMvc.perform(get("/products/1000") + .contentType(MediaType.APPLICATION_JSON_UTF8)) + .andExpect(status().isNotFound()) + .andDo(print()); + } + } - @Test - void deatilWithExsitedProduct() throws Exception { - mockMvc.perform( - get("/products/1") - .accept(MediaType.APPLICATION_JSON_UTF8) - ) - .andExpect(status().isOk()) - .andExpect(content().string(containsString("쥐돌이"))); } - @Test - void deatilWithNotExsitedProduct() throws Exception { - mockMvc.perform(get("/products/1000")) - .andExpect(status().isNotFound()); - } - - @Test - void createWithValidAttributes() throws Exception { - mockMvc.perform( - post("/products") - .accept(MediaType.APPLICATION_JSON_UTF8) - .contentType(MediaType.APPLICATION_JSON) - .content("{\"name\":\"쥐돌이\",\"maker\":\"냥이월드\"," + - "\"price\":5000}") - .header("Authorization", "Bearer " + VALID_TOKEN) - ) - .andExpect(status().isCreated()) - .andExpect(content().string(containsString("쥐돌이"))); - - verify(productService).createProduct(any(ProductData.class)); - } - - @Test - void createWithInvalidAttributes() throws Exception { - mockMvc.perform( - post("/products") - .accept(MediaType.APPLICATION_JSON_UTF8) - .contentType(MediaType.APPLICATION_JSON) - .content("{\"name\":\"\",\"maker\":\"\"," + - "\"price\":0}") - .header("Authorization", "Bearer " + VALID_TOKEN) - ) - .andExpect(status().isBadRequest()); - } - - @Test - void createWithoutAccessToken() throws Exception { - mockMvc.perform( - post("/products") - .accept(MediaType.APPLICATION_JSON_UTF8) - .contentType(MediaType.APPLICATION_JSON) - .content("{\"name\":\"쥐돌이\",\"maker\":\"냥이월드\"," + - "\"price\":5000}") - ) - .andExpect(status().isUnauthorized()); - } - - @Test - void createWithWrongAccessToken() throws Exception { - mockMvc.perform( - post("/products") - .accept(MediaType.APPLICATION_JSON_UTF8) - .contentType(MediaType.APPLICATION_JSON) - .content("{\"name\":\"쥐돌이\",\"maker\":\"냥이월드\"," + - "\"price\":5000}") - .header("Authorization", "Bearer " + INVALID_TOKEN) - ) - .andExpect(status().isUnauthorized()); - } - - @Test - void updateWithExistedProduct() throws Exception { - mockMvc.perform( - patch("/products/1") - .accept(MediaType.APPLICATION_JSON_UTF8) - .contentType(MediaType.APPLICATION_JSON) - .content("{\"name\":\"쥐순이\",\"maker\":\"냥이월드\"," + - "\"price\":5000}") - .header("Authorization", "Bearer " + VALID_TOKEN) - ) - .andExpect(status().isOk()) - .andExpect(content().string(containsString("쥐순이"))); - - verify(productService).updateProduct(eq(1L), any(ProductData.class)); - } - - @Test - void updateWithNotExistedProduct() throws Exception { - mockMvc.perform( - patch("/products/1000") - .contentType(MediaType.APPLICATION_JSON) - .content("{\"name\":\"쥐순이\",\"maker\":\"냥이월드\"," + - "\"price\":5000}") - .header("Authorization", "Bearer " + VALID_TOKEN) - ) - .andExpect(status().isNotFound()); - - verify(productService).updateProduct(eq(1000L), any(ProductData.class)); - } - - @Test - void updateWithInvalidAttributes() throws Exception { - mockMvc.perform( - patch("/products/1") - .accept(MediaType.APPLICATION_JSON_UTF8) - .contentType(MediaType.APPLICATION_JSON) - .content("{\"name\":\"\",\"maker\":\"\"," + - "\"price\":0}") - .header("Authorization", "Bearer " + VALID_TOKEN) - ) - .andExpect(status().isBadRequest()); - } - - @Test - void updateWithoutAccessToken() throws Exception { - mockMvc.perform( - patch("/products/1") - .accept(MediaType.APPLICATION_JSON_UTF8) - .contentType(MediaType.APPLICATION_JSON) - .content("{\"name\":\"쥐순이\",\"maker\":\"냥이월드\"," + - "\"price\":5000}") - ) - .andExpect(status().isUnauthorized()); - } - - @Test - void updateWithInvalidAccessToken() throws Exception { - mockMvc.perform( - patch("/products/1") - .accept(MediaType.APPLICATION_JSON_UTF8) - .contentType(MediaType.APPLICATION_JSON) - .content("{\"name\":\"쥐순이\",\"maker\":\"냥이월드\"," + - "\"price\":5000}") - .header("Authorization", "Bearer " + INVALID_TOKEN) - ) - .andExpect(status().isUnauthorized()); - } - - @Test - void destroyWithExistedProduct() throws Exception { - mockMvc.perform( - delete("/products/1") - .header("Authorization", "Bearer " + VALID_TOKEN) - ) - .andExpect(status().isNoContent()); - - verify(productService).deleteProduct(1L); + @Nested + @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) + class create_메서드는 { + + @Nested + @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) + class 유효한_토큰을_전달받은_경우 { + @BeforeEach + void setUp() { + given(jwtUtil.decode(VALID_TOKEN)).will(invocation -> { + String accessToken = invocation.getArgument(0); + Claims claims = new JwtUtil("12345678901234567890123456789010").decode(accessToken); + return claims; + }); + } + + @Nested + @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) + class 상품_요청이_정상적인_경우 { + @BeforeEach + void setUp() { + given(productService.createProduct(any(ProductData.class))) + .willReturn(TEST_PRODUCT); + } + + @Test + @DisplayName("상품을 생성하고, 생성된 상품을 반환한다") + void It_creates_product_and_returns_it() throws Exception { + mockMvc.perform(post("/products") + .header("Authorization", "Bearer " + VALID_TOKEN) + .accept(MediaType.APPLICATION_JSON_UTF8) + .contentType(MediaType.APPLICATION_JSON_UTF8) + .content(objectMapper.writeValueAsString(TEST_PRODUCT_DATA))) + .andExpect(status().isCreated()) + .andExpect(content().string(containsString(TEST_PRODUCT_NAME))); + } + } + + @Nested + @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) + class 상품요청이_비정상적인_경우 { + + @ParameterizedTest + @MethodSource("com.codesoom.assignment.utils.TestHelper#provideInvalidProductRequests") + @DisplayName("에러메시지를 반환한다") + void It_returns_400_error() throws Exception { + mockMvc.perform(post("/products") + .header("Authorization", "Bearer " + VALID_TOKEN) + .contentType(MediaType.APPLICATION_JSON_UTF8)) + .andExpect(status().isBadRequest()) + .andDo(print()); + } + } + + } + + @Nested + @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) + class 유효하지_않은_토큰을_전달받은_경우 { + @BeforeEach + void setUp() { + Mockito.reset(jwtUtil); + given(jwtUtil.decode(INVALID_TOKEN)).willThrow(new InvalidTokenException(INVALID_TOKEN)); + } + + @Test + @DisplayName("에러메시지를 반환한다") + void It_returns_401_error() throws Exception { + mockMvc.perform(post("/products") + .header("Authorization", "Bearer " + INVALID_TOKEN) + .accept(MediaType.APPLICATION_JSON_UTF8) + .contentType(MediaType.APPLICATION_JSON_UTF8) + .content(objectMapper.writeValueAsString(TEST_PRODUCT_DATA))) + .andExpect(status().isUnauthorized()) + .andDo(print()); + } + } } - @Test - void destroyWithNotExistedProduct() throws Exception { - mockMvc.perform( - delete("/products/1000") - .header("Authorization", "Bearer " + VALID_TOKEN) - ) - .andExpect(status().isNotFound()); - - verify(productService).deleteProduct(1000L); + @Nested + @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) + class update_메서드는 { + + @Nested + @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) + class 유효한_토큰을_전달받은_경우 { + @BeforeEach + void setUp() { + given(jwtUtil.decode(VALID_TOKEN)).will(invocation -> { + String accessToken = invocation.getArgument(0); + Claims claims = new JwtUtil("12345678901234567890123456789010").decode(accessToken); + return claims; + }); + } + + @Nested + @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) + class 상품_요청이_정상적인_경우 { + @BeforeEach + void setUp() { + given(productService.updateProduct(eq(1L), any(ProductData.class))) + .will(invocation -> { + Long id = invocation.getArgument(0); + ProductData productData = invocation.getArgument(1); + return Product.builder() + .id(id) + .name(productData.getName()) + .maker(productData.getMaker()) + .price(productData.getPrice()) + .build(); + }); + } + + @Test + @DisplayName("상품을 수정하고, 수정된 상품을 반환한다") + void It_updates_product_and_returns_it() throws Exception { + mockMvc.perform(patch("/products/1") + .header("Authorization", "Bearer " + VALID_TOKEN) + .accept(MediaType.APPLICATION_JSON_UTF8) + .contentType(MediaType.APPLICATION_JSON_UTF8) + .content(objectMapper.writeValueAsString(UPDATE_PRODUCT_DATA))) + .andExpect(status().isOk()) + .andExpect(content().string(containsString(TEST_UPDATE_PRODUCT_NAME))); + } + } + + @Nested + @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) + class 상품요청이_비정상적인_경우 { + @BeforeEach + void setUp() { + given(jwtUtil.decode(VALID_TOKEN)).willReturn(null); + } + + @ParameterizedTest + @MethodSource("com.codesoom.assignment.utils.TestHelper#provideInvalidProductRequests") + @DisplayName("에러메시지를 반환한다") + void It_returns_400_error() throws Exception { + mockMvc.perform(patch("/products/1") + .header("Authorization", "Bearer " + VALID_TOKEN) + .accept(MediaType.APPLICATION_JSON_UTF8) + .contentType(MediaType.APPLICATION_JSON_UTF8)) + .andExpect(status().isBadRequest()) + .andDo(print()); + } + } + + @Nested + @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) + class 수정할_상품_정보가_없는경우 { + @BeforeEach + void setUp() { + given(productService.updateProduct(eq(1000L), any(ProductData.class))) + .willThrow(new ProductNotFoundException(1000L)); + } + + @Test + @DisplayName("에러메시지를 반환한다") + void It_returns_404_error() throws Exception { + mockMvc.perform(patch("/products/1000") + .header("Authorization", "Bearer " + VALID_TOKEN) + .contentType(MediaType.APPLICATION_JSON_UTF8) + .content(objectMapper.writeValueAsString(UPDATE_PRODUCT_DATA))) + .andExpect(status().isNotFound()) + .andDo(print()); + } + } + + } + + @Nested + @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) + class 유효하지_않은_토큰을_전달받은_경우 { + @BeforeEach + void setUp() { + Mockito.reset(jwtUtil); + given(jwtUtil.decode(INVALID_TOKEN)).willThrow(new InvalidTokenException(INVALID_TOKEN)); + } + + @Test + @DisplayName("에러메시지를 반환한다") + void It_returns_401_error() throws Exception { + mockMvc.perform(patch("/products/1") + .header("Authorization", "Bearer " + INVALID_TOKEN) + .accept(MediaType.APPLICATION_JSON_UTF8) + .contentType(MediaType.APPLICATION_JSON_UTF8) + .content(objectMapper.writeValueAsString(UPDATE_PRODUCT_DATA))) + .andExpect(status().isUnauthorized()) + .andDo(print()); + } + } } - @Test - void destroyWithInvalidAccessToken() throws Exception { - mockMvc.perform( - patch("/products/1") - .accept(MediaType.APPLICATION_JSON_UTF8) - .contentType(MediaType.APPLICATION_JSON) - .content("{\"name\":\"쥐순이\",\"maker\":\"냥이월드\"," + - "\"price\":5000}") - .header("Authorization", "Bearer " + INVALID_TOKEN) - ) - .andExpect(status().isUnauthorized()); + @Nested + @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) + class destroy_메서드는 { + @Nested + @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) + class 유효한_토큰을_전달받은_경우 { + @BeforeEach + void setUp() { + given(jwtUtil.decode(VALID_TOKEN)).will(invocation -> { + String accessToken = invocation.getArgument(0); + Claims claims = new JwtUtil("12345678901234567890123456789010").decode(accessToken); + return claims; + }); + } + + @Nested + @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) + class 상품_삭제요청이_정상적인_경우 { + @Test + @DisplayName("상품을 삭제한다") + void It_delete_product() throws Exception { + mockMvc.perform(delete("/products/1") + .header("Authorization", "Bearer " + VALID_TOKEN) + .accept(MediaType.APPLICATION_JSON_UTF8) + .contentType(MediaType.APPLICATION_JSON_UTF8) + .content(objectMapper.writeValueAsString(UPDATE_PRODUCT_DATA))) + .andExpect(status().isNoContent()); + } + } + + @Nested + @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) + class 삭제할_상품_정보가_없는경우 { + @BeforeEach + void setUp() { + given(productService.deleteProduct(1000L)) + .willThrow(new ProductNotFoundException(1000L)); + } + + @Test + @DisplayName("에러메시지를 반환한다") + void It_returns_404_error() throws Exception { + mockMvc.perform(delete("/products/1000") + .header("Authorization", "Bearer " + VALID_TOKEN) + .contentType(MediaType.APPLICATION_JSON_UTF8)) + .andExpect(status().isNotFound()) + .andDo(print()); + } + } + + } + + @Nested + @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) + class 유효하지_않은_토큰을_전달받은_경우 { + @BeforeEach + void setUp() { + Mockito.reset(jwtUtil); + given(jwtUtil.decode(INVALID_TOKEN)).willThrow(new InvalidTokenException(INVALID_TOKEN)); + } + + @Test + @DisplayName("에러메시지를 반환한다") + void It_returns_401_error() throws Exception { + mockMvc.perform(delete("/products/1") + .header("Authorization", "Bearer " + INVALID_TOKEN) + .accept(MediaType.APPLICATION_JSON_UTF8) + .contentType(MediaType.APPLICATION_JSON_UTF8)) + .andExpect(status().isUnauthorized()) + .andDo(print()); + } + } } } diff --git a/app/src/test/java/com/codesoom/assignment/utils/TestHelper.java b/app/src/test/java/com/codesoom/assignment/utils/TestHelper.java new file mode 100644 index 00000000..8df1bd4e --- /dev/null +++ b/app/src/test/java/com/codesoom/assignment/utils/TestHelper.java @@ -0,0 +1,137 @@ +package com.codesoom.assignment.utils; + +import com.codesoom.assignment.domain.Product; +import com.codesoom.assignment.domain.User; +import com.codesoom.assignment.dto.ProductData; +import com.codesoom.assignment.dto.SessionRequestData; +import org.junit.jupiter.params.provider.Arguments; +import org.springframework.mock.web.MockHttpServletRequest; + +import java.util.stream.Stream; + +public class TestHelper { + + public static final String VALID_TOKEN = "eyJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOjF9.neCsyNLzy3lQ4o2yliotWT06FwSGZagaHpKdAkjnGGw"; + public static final String INVALID_TOKEN = VALID_TOKEN + "INVALID"; + public static final String AUTH_NAME = "AUTH_NAME"; + public static final String AUTH_EMAIL = "auth@foo.com"; + public static final String INVALID_EMAIL = AUTH_EMAIL + "INVALID"; + public static final String AUTH_PASSWORD = "12345678"; + public static final String TEST_PRODUCT_NAME = "쥐돌이"; + public static final String TEST_UPDATE_PRODUCT_NAME = "쥐순이"; + public static final String TEST_PRODUCT_MAKER = "냥이월드"; + public static final int TEST_PRODUCT_PRICE = 5000; + public static final String INVALID_PASSWORD = AUTH_PASSWORD + "INVALID"; + public static final MockHttpServletRequest INVALID_SERVLET_REQUEST = new MockHttpServletRequest(); + private static final String TEST_LONG_PASSWORD = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut dignissim ex vitae congue congue. Nunc fermentum tellus leo. Donec malesuada, dolor non euismod suscipit, quam elit scelerisque ligula, in finibus eros justo eu justo. Duis tempor porta odio, id finibus nibh pellentesque congue. Ut et velit eget nibh tincidunt porta et id risus. Vestibulum suscipit ullamcorper varius. Proin eget arcu quam. Cras id feugiat libero. Integer auctor sem nec tempor pellentesque. Donec tempor molestie ex in viverra. Aliquam nec purus consequat purus ullamcorper tristique eu sodales erat. Nunc vitae accumsan orci. Vestibulum dictum ante non hendrerit convallis. Ut eu interdum nisl.\n" + + "\n" + + "Vestibulum et tellus tortor. Maecenas vulputate urna eu massa mattis, eu vulputate magna pretium. Vestibulum at sapien vitae mi tempus elementum at eget ante. Morbi risus dolor, eleifend eu ante sed, commodo aliquam augue. Pellentesque aliquet, tellus ultrices fermentum bibendum, turpis urna mollis mauris, sagittis posuere dolor mi et enim. Quisque mollis vulputate est vel eleifend. Donec nec sollicitudin massa. Sed mattis posuere metus sed dictum. Pellentesque varius est a arcu vulputate sollicitudin.\n" + + "\n" + + "Cras ac diam vehicula, elementum mauris tempus, accumsan lacus. Sed lectus diam, hendrerit a consequat id, eleifend eget libero. Praesent laoreet tempor magna et imperdiet. Aenean dictum non velit id lacinia. Donec congue ante dui, id rutrum ex accumsan at. Nulla ut massa elementum, posuere nunc sit amet, ornare nisl. Pellentesque in dui ipsum. Vivamus placerat velit sit amet tempus efficitur.\n" + + "\n" + + "Donec auctor lacus sit amet neque luctus, vitae tincidunt tortor lobortis. Fusce aliquam sem ut magna sollicitudin, ac vulputate est placerat. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nullam scelerisque augue elit, at bibendum libero efficitur ac. Sed fringilla purus pretium tortor condimentum imperdiet. Praesent in nibh lacinia, euismod enim eu, bibendum felis. Aliquam quis placerat ipsum. Integer dictum volutpat."; + + public static final SessionRequestData IS_NOT_EXISTS_USER_DATA = SessionRequestData.builder() + .email(INVALID_EMAIL) + .password(AUTH_PASSWORD).build(); + + public static final SessionRequestData INVALID_PASSWORD_USER_DATA = SessionRequestData.builder() + .email(AUTH_EMAIL) + .password(INVALID_PASSWORD) + .build(); + + public static final SessionRequestData AUTH_USER_DATA = SessionRequestData.builder() + .email(AUTH_EMAIL) + .password(AUTH_PASSWORD) + .build(); + + public static final User AUTH_USER = User.builder() + .name(AUTH_NAME) + .email(AUTH_EMAIL) + .password(AUTH_PASSWORD) + .build(); + + public static final Product TEST_PRODUCT = Product.builder() + .id(1L) + .name(TEST_PRODUCT_NAME) + .maker(TEST_PRODUCT_MAKER) + .price(TEST_PRODUCT_PRICE) + .build(); + + public static final ProductData TEST_PRODUCT_DATA = ProductData.builder() + .name(TEST_PRODUCT_NAME) + .maker(TEST_PRODUCT_MAKER) + .price(TEST_PRODUCT_PRICE) + .build(); + + public static Stream provideInvalidProductRequests() { + return Stream.of( + Arguments.of(ProductData.builder().name("").maker("").price(0).build()), + Arguments.of(ProductData.builder().name("").maker(TEST_PRODUCT_MAKER).price(TEST_PRODUCT_PRICE).build()), + Arguments.of(ProductData.builder().name(TEST_PRODUCT_NAME).maker("").price(TEST_PRODUCT_PRICE).build()), + Arguments.of(ProductData.builder().name(TEST_PRODUCT_NAME).maker(TEST_PRODUCT_MAKER).price(null).build()) + ); + } + + public static final ProductData UPDATE_PRODUCT_DATA = ProductData.builder() + .name(TEST_UPDATE_PRODUCT_NAME) + .maker(TEST_PRODUCT_MAKER) + .price(TEST_PRODUCT_PRICE) + .build(); + + public static MockHttpServletRequest getInvalidTokenServletRequest() { + MockHttpServletRequest request = new MockHttpServletRequest(); + request.addHeader("Authorization", "Bearer " + INVALID_TOKEN); + return request; + } + + public static MockHttpServletRequest getValidTokenServletRequest() { + MockHttpServletRequest request = new MockHttpServletRequest(); + request.addHeader("Authorization", "Bearer " + VALID_TOKEN); + return request; + } + + public static SessionRequestData AUTH_USER_LOGIN_DATA = SessionRequestData.builder() + .email(AUTH_EMAIL) + .password(AUTH_PASSWORD) + .build(); + + public static SessionRequestData EMAIL_NULL_LOGIN_USER_DATA = SessionRequestData.builder() + .password(AUTH_PASSWORD) + .build(); + + public static SessionRequestData PASSWORD_NULL_LOGIN_USER_DATA = SessionRequestData.builder() + .email(AUTH_EMAIL) + .build(); + + public static SessionRequestData EMPTY_LOGIN_USER_DATA = SessionRequestData.builder() + .email("") + .password("") + .build(); + + public static SessionRequestData EMAIL_IS_SHORT_LOGIN_USER_DATA = SessionRequestData.builder() + .email("aa") + .password(AUTH_PASSWORD) + .build(); + + public static SessionRequestData PASSWORD_IS_TOO_SHORT_LOGIN_USER_DATA = SessionRequestData.builder() + .email(AUTH_EMAIL) + .password("111") + .build(); + + public static SessionRequestData PASSWORD_IS_TOO_LONG_LOGIN_USER_DATA = SessionRequestData.builder() + .email(AUTH_EMAIL) + .password(TEST_LONG_PASSWORD) + .build(); + + public static Stream provideInvalidUserLoginRequests() { + return Stream.of( + Arguments.of(EMAIL_NULL_LOGIN_USER_DATA), + Arguments.of(PASSWORD_NULL_LOGIN_USER_DATA), + Arguments.of(EMPTY_LOGIN_USER_DATA), + Arguments.of(EMAIL_IS_SHORT_LOGIN_USER_DATA), + Arguments.of(PASSWORD_IS_TOO_SHORT_LOGIN_USER_DATA), + Arguments.of(PASSWORD_IS_TOO_LONG_LOGIN_USER_DATA) + ); + } +} From ac873fb6b70bcda07dac9d5d28dc7438cb6517aa Mon Sep 17 00:00:00 2001 From: juhyeon Date: Wed, 16 Aug 2023 09:33:13 +0900 Subject: [PATCH 06/30] =?UTF-8?q?fix=20:=20=EC=9E=98=EB=AA=BB=EB=90=9C=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=9D=B4=EC=A0=84=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 기존에 유효한 토큰인경우인데 유효하지 않는 경우의 로직이 존재하여 삭제 --- .../assignment/controllers/ProductControllerTest.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/app/src/test/java/com/codesoom/assignment/controllers/ProductControllerTest.java b/app/src/test/java/com/codesoom/assignment/controllers/ProductControllerTest.java index 1b343111..19bceabb 100644 --- a/app/src/test/java/com/codesoom/assignment/controllers/ProductControllerTest.java +++ b/app/src/test/java/com/codesoom/assignment/controllers/ProductControllerTest.java @@ -240,10 +240,6 @@ void It_updates_product_and_returns_it() throws Exception { @Nested @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) class 상품요청이_비정상적인_경우 { - @BeforeEach - void setUp() { - given(jwtUtil.decode(VALID_TOKEN)).willReturn(null); - } @ParameterizedTest @MethodSource("com.codesoom.assignment.utils.TestHelper#provideInvalidProductRequests") From efd50a37026038199234d6dc9fb9425a43f36017 Mon Sep 17 00:00:00 2001 From: juhyeon Date: Wed, 16 Aug 2023 09:34:09 +0900 Subject: [PATCH 07/30] =?UTF-8?q?feat=20:=20claims=20=EA=B0=80=20null?= =?UTF-8?q?=EC=9D=B8=20=EA=B2=BD=EC=9A=B0=20=EC=97=90=EB=9F=AC=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../assignment/filters/JwtAuthenticationFilter.java | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/codesoom/assignment/filters/JwtAuthenticationFilter.java b/app/src/main/java/com/codesoom/assignment/filters/JwtAuthenticationFilter.java index 8ce40ac1..01604a85 100644 --- a/app/src/main/java/com/codesoom/assignment/filters/JwtAuthenticationFilter.java +++ b/app/src/main/java/com/codesoom/assignment/filters/JwtAuthenticationFilter.java @@ -1,7 +1,9 @@ package com.codesoom.assignment.filters; +import com.codesoom.assignment.errors.InvalidTokenException; import com.codesoom.assignment.security.UserAuthentication; import com.codesoom.assignment.utils.JwtUtil; +import io.jsonwebtoken.Claims; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContext; @@ -29,8 +31,13 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse if (authorization != null) { String accessToken = authorization.substring("Bearer ".length()); - Long userId = jwtUtil.decode(accessToken) - .get("userId", Long.class); + Claims claims = jwtUtil.decode(accessToken); + + if(claims == null){ + throw new InvalidTokenException(accessToken); + } + + Long userId = claims.get("userId", Long.class); Authentication authentication = new UserAuthentication(userId); From a3750b63c3cc3ad8a6b9de2f62e3fdff8d254a20 Mon Sep 17 00:00:00 2001 From: juhyeon Date: Wed, 16 Aug 2023 09:34:40 +0900 Subject: [PATCH 08/30] =?UTF-8?q?feat=20:=20=EC=88=98=EC=A0=95=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C=20=EC=9A=94=EC=B2=AD=EC=8B=9C=20Authentication=20?= =?UTF-8?q?=EB=B0=9B=EB=8F=84=EB=A1=9D=20=EB=A7=A4=EA=B0=9C=EB=B3=80?= =?UTF-8?q?=EC=88=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codesoom/assignment/controllers/ProductController.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/codesoom/assignment/controllers/ProductController.java b/app/src/main/java/com/codesoom/assignment/controllers/ProductController.java index edb7c9f1..9557bf60 100644 --- a/app/src/main/java/com/codesoom/assignment/controllers/ProductController.java +++ b/app/src/main/java/com/codesoom/assignment/controllers/ProductController.java @@ -47,7 +47,7 @@ public Product create( @PatchMapping("{id}") public Product update( - @RequestAttribute Long userId, + Authentication authentication, @PathVariable Long id, @RequestBody @Valid ProductData productData ) { @@ -57,7 +57,7 @@ public Product update( @DeleteMapping("{id}") @ResponseStatus(HttpStatus.NO_CONTENT) public void destroy( - @RequestAttribute Long userId, + Authentication authentication, @PathVariable Long id ) { productService.deleteProduct(id); From 6178caed496a48b1f281ae69ab26750b546be4d8 Mon Sep 17 00:00:00 2001 From: juhyeon Date: Wed, 16 Aug 2023 09:35:06 +0900 Subject: [PATCH 09/30] =?UTF-8?q?fix=20:=20=EA=B8=B0=EC=A1=B4=EC=97=90=20?= =?UTF-8?q?=ED=95=98=EB=93=9C=20=EC=BD=94=EB=94=A9=EB=90=98=EC=96=B4?= =?UTF-8?q?=EC=9E=88=EB=8D=98=20=EB=B6=80=EB=B6=84=EC=9D=84=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codesoom/assignment/application/AuthenticationService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/codesoom/assignment/application/AuthenticationService.java b/app/src/main/java/com/codesoom/assignment/application/AuthenticationService.java index 9956fbe1..3fba45f8 100644 --- a/app/src/main/java/com/codesoom/assignment/application/AuthenticationService.java +++ b/app/src/main/java/com/codesoom/assignment/application/AuthenticationService.java @@ -25,7 +25,7 @@ public String login(String email, String password) { throw new LoginFailException(email); } - return jwtUtil.encode(1L); + return jwtUtil.encode(user.getId()); } } From 6bb268a861afd1528c3dec13a194a65c97d970b1 Mon Sep 17 00:00:00 2001 From: juhyeon Date: Wed, 16 Aug 2023 09:35:41 +0900 Subject: [PATCH 10/30] =?UTF-8?q?feat=20:=20=EB=A9=94=EC=84=9C=EB=93=9C?= =?UTF-8?q?=EC=99=80=20=EA=B2=BD=EB=A1=9C=EC=97=90=20=EB=A7=9E=EA=B2=8C=20?= =?UTF-8?q?=EC=9D=B8=EC=A6=9D=20=EB=A1=9C=EC=A7=81=EC=9D=84=20=EC=88=98?= =?UTF-8?q?=ED=96=89=ED=95=98=EB=8F=84=EB=A1=9D=20=EA=B4=80=EB=A0=A8=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../assignment/config/SecurityJavaConfig.java | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/app/src/main/java/com/codesoom/assignment/config/SecurityJavaConfig.java b/app/src/main/java/com/codesoom/assignment/config/SecurityJavaConfig.java index 2a36a0fc..7158edca 100644 --- a/app/src/main/java/com/codesoom/assignment/config/SecurityJavaConfig.java +++ b/app/src/main/java/com/codesoom/assignment/config/SecurityJavaConfig.java @@ -12,6 +12,7 @@ import org.springframework.security.web.authentication.HttpStatusEntryPoint; import javax.servlet.Filter; +import javax.servlet.http.HttpServletRequest; @Configuration @EnableGlobalMethodSecurity(prePostEnabled = true) @@ -27,6 +28,8 @@ protected void configure(HttpSecurity http) throws Exception { Filter authenticationFilter = new JwtAuthenticationFilter(authenticationManager(), jwtUtil); Filter authenticationErrorFilter = new AuthenticationErrorFilter(); + configureAuthorizations(http); + http .csrf().disable() .addFilter(authenticationFilter) @@ -39,4 +42,69 @@ protected void configure(HttpSecurity http) throws Exception { .authenticationEntryPoint( new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED)); } + + private void configureAuthorizations(HttpSecurity http) throws Exception { + http.authorizeRequests() + .requestMatchers(this::matchesPostProductRequest).authenticated() + .and() + .authorizeRequests() + .requestMatchers(this::matchesPatchProductRequest).authenticated() + .and() + .authorizeRequests() + .requestMatchers(this::matchesDeleteProductRequest).authenticated() + .and() + .authorizeRequests() + .requestMatchers(this::matchesPostUserRequest).authenticated() + .and() + .authorizeRequests() + .requestMatchers(this::matchesDeleteUserRequest).authenticated(); + } + /** + * POST /products or /products/{상품아이디} 요청에 대해서만 인증을 요구합니다. + * @param req + * @return + */ + private boolean matchesPostProductRequest(HttpServletRequest req) { + return req.getMethod().equals("POST") && + (req.getRequestURI().matches("^/products$") || + req.getRequestURI().matches("^/products/[0-9]+$")); + } + + /** + * PATCH /products or /products/{상품아이디} 요청에 대해서만 인증을 요구합니다. + * @param req + * @return + */ + private boolean matchesPatchProductRequest(HttpServletRequest req) { + return req.getMethod().equals("PATCH") && + req.getRequestURI().matches("^/products/[0-9]+$"); + } + + /** + * DELETE /products or /products/{상품아이디} 요청에 대해서만 인증을 요구합니다. + * @param req + * @return + */ + private boolean matchesDeleteProductRequest(HttpServletRequest req) { + return req.getMethod().equals("DELETE") && + req.getRequestURI().matches("^/products/[0-9]+$"); + } + + /** + * POST /users/{유저아이디} 요청에 대해서만 인증을 요구합니다. + * @param req + * @return + */ + private boolean matchesPostUserRequest(HttpServletRequest req) { + return req.getMethod().equals("POST") && req.getRequestURI().matches("^/users/[0-9]+$"); + } + + /** + * DELETE /users/{유저아이디} 요청에 대해서만 인증을 요구합니다. + * @param req + * @return + */ + private boolean matchesDeleteUserRequest(HttpServletRequest req) { + return req.getMethod().equals("DELETE") && req.getRequestURI().matches("^/users/[0-9]+$"); + } } From 4cce0b9ac95bdff569d236d32fec190d95b65636 Mon Sep 17 00:00:00 2001 From: juhyeon Date: Thu, 17 Aug 2023 07:36:02 +0900 Subject: [PATCH 11/30] =?UTF-8?q?refactor=20:=20=EB=B6=88=ED=95=84?= =?UTF-8?q?=EC=9A=94=ED=95=9C=20=EC=A3=BC=EC=84=9D=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 코드의 내용을 그대로 주석에 표현하여 해당 주석 삭제 --- .../assignment/config/SecurityJavaConfig.java | 26 +------------------ 1 file changed, 1 insertion(+), 25 deletions(-) diff --git a/app/src/main/java/com/codesoom/assignment/config/SecurityJavaConfig.java b/app/src/main/java/com/codesoom/assignment/config/SecurityJavaConfig.java index 7158edca..2744d8e0 100644 --- a/app/src/main/java/com/codesoom/assignment/config/SecurityJavaConfig.java +++ b/app/src/main/java/com/codesoom/assignment/config/SecurityJavaConfig.java @@ -59,51 +59,27 @@ private void configureAuthorizations(HttpSecurity http) throws Exception { .authorizeRequests() .requestMatchers(this::matchesDeleteUserRequest).authenticated(); } - /** - * POST /products or /products/{상품아이디} 요청에 대해서만 인증을 요구합니다. - * @param req - * @return - */ + private boolean matchesPostProductRequest(HttpServletRequest req) { return req.getMethod().equals("POST") && (req.getRequestURI().matches("^/products$") || req.getRequestURI().matches("^/products/[0-9]+$")); } - /** - * PATCH /products or /products/{상품아이디} 요청에 대해서만 인증을 요구합니다. - * @param req - * @return - */ private boolean matchesPatchProductRequest(HttpServletRequest req) { return req.getMethod().equals("PATCH") && req.getRequestURI().matches("^/products/[0-9]+$"); } - /** - * DELETE /products or /products/{상품아이디} 요청에 대해서만 인증을 요구합니다. - * @param req - * @return - */ private boolean matchesDeleteProductRequest(HttpServletRequest req) { return req.getMethod().equals("DELETE") && req.getRequestURI().matches("^/products/[0-9]+$"); } - /** - * POST /users/{유저아이디} 요청에 대해서만 인증을 요구합니다. - * @param req - * @return - */ private boolean matchesPostUserRequest(HttpServletRequest req) { return req.getMethod().equals("POST") && req.getRequestURI().matches("^/users/[0-9]+$"); } - /** - * DELETE /users/{유저아이디} 요청에 대해서만 인증을 요구합니다. - * @param req - * @return - */ private boolean matchesDeleteUserRequest(HttpServletRequest req) { return req.getMethod().equals("DELETE") && req.getRequestURI().matches("^/users/[0-9]+$"); } From 79bc305cf02e8a07cdd4b7ddcb5f6d45790663ec Mon Sep 17 00:00:00 2001 From: tmxhsk99 Date: Fri, 18 Aug 2023 06:09:58 +0900 Subject: [PATCH 12/30] =?UTF-8?q?test=20:=20=EC=9C=A0=EC=A0=80=20=ED=8C=A8?= =?UTF-8?q?=EC=8A=A4=EC=9B=8C=EB=93=9C=20=EA=B8=B0=EB=8A=A5=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=ED=95=B4?= =?UTF-8?q?=EB=8B=B9=20=EB=B3=80=EA=B2=BD=EC=97=90=20=EB=94=B0=EB=A5=B8=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AuthenticationServiceTest.java | 10 +++-- .../application/UserServiceTest.java | 5 ++- .../codesoom/assignment/domain/UserTest.java | 41 ++++++++++++++----- 3 files changed, 42 insertions(+), 14 deletions(-) diff --git a/app/src/test/java/com/codesoom/assignment/application/AuthenticationServiceTest.java b/app/src/test/java/com/codesoom/assignment/application/AuthenticationServiceTest.java index 564ff7b8..44d87a78 100644 --- a/app/src/test/java/com/codesoom/assignment/application/AuthenticationServiceTest.java +++ b/app/src/test/java/com/codesoom/assignment/application/AuthenticationServiceTest.java @@ -7,6 +7,8 @@ import com.codesoom.assignment.utils.JwtUtil; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; import java.util.Optional; @@ -28,16 +30,18 @@ class AuthenticationServiceTest { private UserRepository userRepository = mock(UserRepository.class); + @BeforeEach void setUp() { JwtUtil jwtUtil = new JwtUtil(SECRET); - + PasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); authenticationService = new AuthenticationService( - userRepository, jwtUtil); + userRepository, jwtUtil, passwordEncoder); User user = User.builder() - .password("test") + .id(1L) .build(); + user.changePassword("test", passwordEncoder); given(userRepository.findByEmail("tester@example.com")) .willReturn(Optional.of(user)); diff --git a/app/src/test/java/com/codesoom/assignment/application/UserServiceTest.java b/app/src/test/java/com/codesoom/assignment/application/UserServiceTest.java index 4b85229c..e08ac34f 100644 --- a/app/src/test/java/com/codesoom/assignment/application/UserServiceTest.java +++ b/app/src/test/java/com/codesoom/assignment/application/UserServiceTest.java @@ -10,6 +10,8 @@ import com.github.dozermapper.core.Mapper; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; import java.util.Optional; @@ -31,8 +33,9 @@ class UserServiceTest { @BeforeEach void setUp() { Mapper mapper = DozerBeanMapperBuilder.buildDefault(); + PasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); - userService = new UserService(mapper, userRepository); + userService = new UserService(mapper, userRepository, passwordEncoder); given(userRepository.existsByEmail(EXISTED_EMAIL_ADDRESS)) .willReturn(true); diff --git a/app/src/test/java/com/codesoom/assignment/domain/UserTest.java b/app/src/test/java/com/codesoom/assignment/domain/UserTest.java index f9950e61..96682c56 100644 --- a/app/src/test/java/com/codesoom/assignment/domain/UserTest.java +++ b/app/src/test/java/com/codesoom/assignment/domain/UserTest.java @@ -1,21 +1,41 @@ package com.codesoom.assignment.domain; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; import static org.assertj.core.api.Assertions.assertThat; class UserTest { + private PasswordEncoder passwordEncoder; + + @BeforeEach + void setUp() { + passwordEncoder = new BCryptPasswordEncoder(); + } + @Test void changeWith() { + User user = User.builder().build(); user.changeWith(User.builder() .name("TEST") - .password("TEST") .build()); assertThat(user.getName()).isEqualTo("TEST"); - assertThat(user.getPassword()).isEqualTo("TEST"); + assertThat(user.getPassword()).isEqualTo(""); + } + + @Test + void changePassword() { + User user = User.builder().build(); + + user.changePassword("TEST", passwordEncoder); + + assertThat(user.getPassword()).isNotEmpty(); + assertThat(user.getPassword()).isNotEqualTo("TEST"); } @Test @@ -31,22 +51,23 @@ void destroy() { @Test void authenticate() { - User user = User.builder() - .password("test") - .build(); + User user = User.builder().build(); + user.changePassword("test", passwordEncoder); - assertThat(user.authenticate("test")).isTrue(); - assertThat(user.authenticate("xxx")).isFalse(); + assertThat(user.authenticate("test", passwordEncoder)).isTrue(); + assertThat(user.authenticate("xxx", passwordEncoder)).isFalse(); } @Test void authenticateWithDeletedUser() { User user = User.builder() - .password("test") .deleted(true) .build(); - assertThat(user.authenticate("test")).isFalse(); - assertThat(user.authenticate("xxx")).isFalse(); + user.changePassword("test", passwordEncoder); + + assertThat(user.authenticate("test", passwordEncoder)).isFalse(); + assertThat(user.authenticate("xxx", passwordEncoder)).isFalse(); } + } From 7e94f4ba296b180fa5c1ae37cdd32d36fe5a4d54 Mon Sep 17 00:00:00 2001 From: tmxhsk99 Date: Fri, 18 Aug 2023 06:11:05 +0900 Subject: [PATCH 13/30] =?UTF-8?q?feat=20:=20=EC=9C=A0=EC=A0=80=20=ED=8C=A8?= =?UTF-8?q?=EC=8A=A4=EC=9B=8C=EB=93=9C=20=EC=95=94=ED=98=B8=ED=99=94=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 평문의 패스워드를 암호화하도록 기능 추가 --- .../java/com/codesoom/assignment/App.java | 7 +++++++ .../application/AuthenticationService.java | 8 +++++--- .../assignment/application/UserService.java | 11 ++++++++-- .../assignment/config/SecurityJavaConfig.java | 5 +++++ .../com/codesoom/assignment/domain/User.java | 20 +++++++++++++------ 5 files changed, 40 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/com/codesoom/assignment/App.java b/app/src/main/java/com/codesoom/assignment/App.java index 44ca515b..9da15fd5 100644 --- a/app/src/main/java/com/codesoom/assignment/App.java +++ b/app/src/main/java/com/codesoom/assignment/App.java @@ -5,6 +5,8 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; @SpringBootApplication public class App { @@ -16,4 +18,9 @@ public static void main(String[] args) { public Mapper dozerMapper() { return DozerBeanMapperBuilder.buildDefault(); } + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } } diff --git a/app/src/main/java/com/codesoom/assignment/application/AuthenticationService.java b/app/src/main/java/com/codesoom/assignment/application/AuthenticationService.java index 3fba45f8..37d53afe 100644 --- a/app/src/main/java/com/codesoom/assignment/application/AuthenticationService.java +++ b/app/src/main/java/com/codesoom/assignment/application/AuthenticationService.java @@ -4,24 +4,26 @@ import com.codesoom.assignment.domain.UserRepository; import com.codesoom.assignment.errors.LoginFailException; import com.codesoom.assignment.utils.JwtUtil; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; @Service public class AuthenticationService { private final UserRepository userRepository; private final JwtUtil jwtUtil; + private PasswordEncoder passwordEncoder; - public AuthenticationService(UserRepository userRepository, - JwtUtil jwtUtil) { + public AuthenticationService(UserRepository userRepository, JwtUtil jwtUtil, PasswordEncoder passwordEncoder) { this.userRepository = userRepository; this.jwtUtil = jwtUtil; + this.passwordEncoder = passwordEncoder; } public String login(String email, String password) { User user = userRepository.findByEmail(email) .orElseThrow(() -> new LoginFailException(email)); - if (!user.authenticate(password)) { + if (!user.authenticate(password, passwordEncoder)) { throw new LoginFailException(email); } diff --git a/app/src/main/java/com/codesoom/assignment/application/UserService.java b/app/src/main/java/com/codesoom/assignment/application/UserService.java index 99eb0260..7de7c1fe 100644 --- a/app/src/main/java/com/codesoom/assignment/application/UserService.java +++ b/app/src/main/java/com/codesoom/assignment/application/UserService.java @@ -7,6 +7,7 @@ import com.codesoom.assignment.errors.UserEmailDuplicationException; import com.codesoom.assignment.errors.UserNotFoundException; import com.github.dozermapper.core.Mapper; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import javax.transaction.Transactional; @@ -17,9 +18,12 @@ public class UserService { private final Mapper mapper; private final UserRepository userRepository; - public UserService(Mapper dozerMapper, UserRepository userRepository) { - this.mapper = dozerMapper; + private final PasswordEncoder passwordEncoder; + + public UserService(Mapper mapper, UserRepository userRepository, PasswordEncoder passwordEncoder) { + this.mapper = mapper; this.userRepository = userRepository; + this.passwordEncoder = passwordEncoder; } public User registerUser(UserRegistrationData registrationData) { @@ -29,6 +33,9 @@ public User registerUser(UserRegistrationData registrationData) { } User user = mapper.map(registrationData, User.class); + + user.changePassword(registrationData.getPassword(),passwordEncoder); + return userRepository.save(user); } diff --git a/app/src/main/java/com/codesoom/assignment/config/SecurityJavaConfig.java b/app/src/main/java/com/codesoom/assignment/config/SecurityJavaConfig.java index 2744d8e0..915b89fc 100644 --- a/app/src/main/java/com/codesoom/assignment/config/SecurityJavaConfig.java +++ b/app/src/main/java/com/codesoom/assignment/config/SecurityJavaConfig.java @@ -3,12 +3,15 @@ import com.codesoom.assignment.filters.AuthenticationErrorFilter; import com.codesoom.assignment.filters.JwtAuthenticationFilter; import com.codesoom.assignment.utils.JwtUtil; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpStatus; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.authentication.HttpStatusEntryPoint; import javax.servlet.Filter; @@ -83,4 +86,6 @@ private boolean matchesPostUserRequest(HttpServletRequest req) { private boolean matchesDeleteUserRequest(HttpServletRequest req) { return req.getMethod().equals("DELETE") && req.getRequestURI().matches("^/users/[0-9]+$"); } + + } diff --git a/app/src/main/java/com/codesoom/assignment/domain/User.java b/app/src/main/java/com/codesoom/assignment/domain/User.java index c2299bcf..7b7dbb6c 100644 --- a/app/src/main/java/com/codesoom/assignment/domain/User.java +++ b/app/src/main/java/com/codesoom/assignment/domain/User.java @@ -4,6 +4,8 @@ import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; import javax.persistence.Entity; import javax.persistence.GeneratedValue; @@ -19,25 +21,31 @@ public class User { @GeneratedValue private Long id; - private String email; + @Builder.Default + private String email = ""; - private String name; + @Builder.Default + private String name = ""; - private String password; + @Builder.Default + private String password = ""; @Builder.Default private boolean deleted = false; public void changeWith(User source) { name = source.name; - password = source.password; + } + + public void changePassword(String password, PasswordEncoder passwordEncoder) { + this.password = passwordEncoder.encode(password); } public void destroy() { deleted = true; } - public boolean authenticate(String password) { - return !deleted && password.equals(this.password); + public boolean authenticate(String password, PasswordEncoder passwordEncoder) { + return !deleted && passwordEncoder.matches(password, this.password); } } From 58215a59fcd1e6a71498b90495dd2a8394107152 Mon Sep 17 00:00:00 2001 From: juhyeon Date: Fri, 18 Aug 2023 09:13:15 +0900 Subject: [PATCH 14/30] =?UTF-8?q?build=20:=20aop=20=EA=B4=80=EB=A0=A8=20?= =?UTF-8?q?=EC=9D=98=EC=A1=B4=EC=84=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/build.gradle b/app/build.gradle index 959766ee..ffce1920 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -78,6 +78,10 @@ dependencies { } // Spring Security implementation 'org.springframework.boot:spring-boot-starter-security' + + // Spring AOP + implementation 'org.springframework:spring-aop' + } application { From 3a548926015df643b4091a248a6c42c1025335cd Mon Sep 17 00:00:00 2001 From: juhyeon Date: Fri, 18 Aug 2023 09:13:57 +0900 Subject: [PATCH 15/30] =?UTF-8?q?test=20:=20=EB=8B=A4=EB=A5=B8=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=EC=9E=90=20=ED=86=A0=ED=81=B0=20=ED=94=BD=EC=8A=A4?= =?UTF-8?q?=EC=B3=90=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/test/java/com/codesoom/assignment/utils/TestHelper.java | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/test/java/com/codesoom/assignment/utils/TestHelper.java b/app/src/test/java/com/codesoom/assignment/utils/TestHelper.java index 8df1bd4e..35d3700a 100644 --- a/app/src/test/java/com/codesoom/assignment/utils/TestHelper.java +++ b/app/src/test/java/com/codesoom/assignment/utils/TestHelper.java @@ -12,6 +12,7 @@ public class TestHelper { public static final String VALID_TOKEN = "eyJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOjF9.neCsyNLzy3lQ4o2yliotWT06FwSGZagaHpKdAkjnGGw"; + public static final String OTHER_USER_VALID_TOKEN = "eyJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOjJ9.TEM6MULsZeqkBbUKziCR4Dg_8kymmZkyxsCXlfNJ3g0"; public static final String INVALID_TOKEN = VALID_TOKEN + "INVALID"; public static final String AUTH_NAME = "AUTH_NAME"; public static final String AUTH_EMAIL = "auth@foo.com"; From 9c491121d4fabfdc15b0001d888a6be38030c581 Mon Sep 17 00:00:00 2001 From: juhyeon Date: Fri, 18 Aug 2023 09:15:27 +0900 Subject: [PATCH 16/30] =?UTF-8?q?fix=20:=20=EC=A3=BC=EC=84=9D=20=EC=A7=81?= =?UTF-8?q?=EA=B4=80=EC=A0=81=EC=9C=BC=EB=A1=9C=20=EC=88=98=EC=A0=95,=20?= =?UTF-8?q?=EA=B2=BD=EB=A1=9C=20=EC=A0=91=EA=B7=BC=EA=B6=8C=ED=95=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../assignment/config/SecurityJavaConfig.java | 43 ++++++++++++++++--- 1 file changed, 36 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/com/codesoom/assignment/config/SecurityJavaConfig.java b/app/src/main/java/com/codesoom/assignment/config/SecurityJavaConfig.java index 915b89fc..6583080a 100644 --- a/app/src/main/java/com/codesoom/assignment/config/SecurityJavaConfig.java +++ b/app/src/main/java/com/codesoom/assignment/config/SecurityJavaConfig.java @@ -28,22 +28,26 @@ public SecurityJavaConfig(JwtUtil jwtUtil) { @Override protected void configure(HttpSecurity http) throws Exception { + Filter authenticationFilter = new JwtAuthenticationFilter(authenticationManager(), jwtUtil); Filter authenticationErrorFilter = new AuthenticationErrorFilter(); - configureAuthorizations(http); - http .csrf().disable() .addFilter(authenticationFilter) - .addFilterBefore(authenticationErrorFilter, - JwtAuthenticationFilter.class) + .addFilterBefore(authenticationErrorFilter, JwtAuthenticationFilter.class) .sessionManagement() - .sessionCreationPolicy(SessionCreationPolicy.STATELESS) + .sessionCreationPolicy(SessionCreationPolicy.STATELESS) + .and(); + + configureAuthorizations(http); + + http + .authorizeRequests() + .anyRequest().permitAll() .and() .exceptionHandling() - .authenticationEntryPoint( - new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED)); + .authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED)); } private void configureAuthorizations(HttpSecurity http) throws Exception { @@ -63,26 +67,51 @@ private void configureAuthorizations(HttpSecurity http) throws Exception { .requestMatchers(this::matchesDeleteUserRequest).authenticated(); } + /** + * 요청이 상품 생성 요청인 경우 true를 아닌경우 false를 반환합니다. + * @param req + * @return + */ private boolean matchesPostProductRequest(HttpServletRequest req) { return req.getMethod().equals("POST") && (req.getRequestURI().matches("^/products$") || req.getRequestURI().matches("^/products/[0-9]+$")); } + /** + * 요청이 상품 수정 요청인 경우 true를 아닌경우 false를 반환합니다. + * @param req + * @return + */ private boolean matchesPatchProductRequest(HttpServletRequest req) { return req.getMethod().equals("PATCH") && req.getRequestURI().matches("^/products/[0-9]+$"); } + /** + * 요청이 상품 삭제 요청인 경우 true를 아닌경우 false를 반환합니다. + * @param req + * @return + */ private boolean matchesDeleteProductRequest(HttpServletRequest req) { return req.getMethod().equals("DELETE") && req.getRequestURI().matches("^/products/[0-9]+$"); } + /** + * 요청이 유저 생성 요청인 경우 true를 아닌경우 false를 반환합니다. + * @param req + * @return + */ private boolean matchesPostUserRequest(HttpServletRequest req) { return req.getMethod().equals("POST") && req.getRequestURI().matches("^/users/[0-9]+$"); } + /** + * 요청이 유저 삭제 요청인 경우 true를 아닌경우 false를 반환합니다. + * @param req + * @return + */ private boolean matchesDeleteUserRequest(HttpServletRequest req) { return req.getMethod().equals("DELETE") && req.getRequestURI().matches("^/users/[0-9]+$"); } From cbdcc486e5a83180c6a5b24b815b0b0661aca730 Mon Sep 17 00:00:00 2001 From: juhyeon Date: Fri, 18 Aug 2023 09:15:48 +0900 Subject: [PATCH 17/30] =?UTF-8?q?chore=20:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20=EC=A3=BC=EC=84=9D=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/codesoom/assignment/security/UserAuthentication.java | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/java/com/codesoom/assignment/security/UserAuthentication.java b/app/src/main/java/com/codesoom/assignment/security/UserAuthentication.java index 67efb3da..f4f2666a 100644 --- a/app/src/main/java/com/codesoom/assignment/security/UserAuthentication.java +++ b/app/src/main/java/com/codesoom/assignment/security/UserAuthentication.java @@ -36,7 +36,6 @@ public Object getPrincipal() { private static List authorities() { List authorities = new ArrayList<>(); - // todo : userId에 따른 다른 권한 부여 => e.g ADMIN, USER authorities.add((GrantedAuthority) () -> "USER"); return authorities; } From 1beee7a84667a940917a6a7ab0f14c1815e0c096 Mon Sep 17 00:00:00 2001 From: juhyeon Date: Fri, 18 Aug 2023 09:16:41 +0900 Subject: [PATCH 18/30] =?UTF-8?q?test=20:=20=EC=83=81=ED=92=88=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EA=B6=8C=ED=95=9C=20=EC=B2=B4=ED=81=AC=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 해당 given 이 제대로 동작을 안하여 현재 수정 진행중 --- .../controllers/ProductControllerTest.java | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/app/src/test/java/com/codesoom/assignment/controllers/ProductControllerTest.java b/app/src/test/java/com/codesoom/assignment/controllers/ProductControllerTest.java index 19bceabb..6a464aec 100644 --- a/app/src/test/java/com/codesoom/assignment/controllers/ProductControllerTest.java +++ b/app/src/test/java/com/codesoom/assignment/controllers/ProductControllerTest.java @@ -275,6 +275,31 @@ void It_returns_404_error() throws Exception { } } + @Nested + @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) + class 상품을_수정할_권한이_없는_경우 { + @BeforeEach + void setUp() { + Mockito.reset(jwtUtil); + Mockito.reset(productService); + given(jwtUtil.decode(OTHER_USER_VALID_TOKEN)).will(invocation -> { + String accessToken = invocation.getArgument(0); + Claims claims = new JwtUtil("12345678901234567890123456789010").decode(accessToken); + return claims; + }); + } + @Test + @DisplayName("에러메시지를 반환한다") + void It_returns_403_error() throws Exception { + mockMvc.perform(patch("/products/1") + .header("Authorization", "Bearer " + OTHER_USER_VALID_TOKEN) + .accept(MediaType.APPLICATION_JSON_UTF8) + .contentType(MediaType.APPLICATION_JSON_UTF8) + .content(objectMapper.writeValueAsString(UPDATE_PRODUCT_DATA))) + .andExpect(status().isForbidden()) + .andDo(print()); + } + } } @Nested @@ -349,7 +374,6 @@ void It_returns_404_error() throws Exception { .andDo(print()); } } - } @Nested From 887bfb2e9fed05d26fb1ade2ccaf3b4bf8dee061 Mon Sep 17 00:00:00 2001 From: juhyeon Date: Fri, 18 Aug 2023 09:17:23 +0900 Subject: [PATCH 19/30] =?UTF-8?q?feat=20:=20=EB=B3=B8=EC=9D=B8=EA=B6=8C?= =?UTF-8?q?=ED=95=9C=20=EA=B4=80=EB=A0=A8=20AOP=20=EB=B0=8F=20=EC=96=B4?= =?UTF-8?q?=EB=85=B8=ED=85=8C=EC=9D=B4=EC=85=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../assignment/aop/OwnerCheckAspect.java | 31 +++++++++++++++++++ .../assignment/aop/annotation/CheckOwner.java | 11 +++++++ .../controllers/ProductController.java | 7 +++-- 3 files changed, 46 insertions(+), 3 deletions(-) create mode 100644 app/src/main/java/com/codesoom/assignment/aop/OwnerCheckAspect.java create mode 100644 app/src/main/java/com/codesoom/assignment/aop/annotation/CheckOwner.java diff --git a/app/src/main/java/com/codesoom/assignment/aop/OwnerCheckAspect.java b/app/src/main/java/com/codesoom/assignment/aop/OwnerCheckAspect.java new file mode 100644 index 00000000..d10ead72 --- /dev/null +++ b/app/src/main/java/com/codesoom/assignment/aop/OwnerCheckAspect.java @@ -0,0 +1,31 @@ +package com.codesoom.assignment.aop; + +import com.codesoom.assignment.errors.InvalidTokenException; +import com.codesoom.assignment.errors.UserNoPermission; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Component; + +@Aspect +@Component +public class OwnerCheckAspect { + @Before("@annotation(com.codesoom.assignment.aop.annotation.CheckOwner) && args(id)") + public void checkOwner(Long id) throws InvalidTokenException, UserNoPermission { + System.out.println("checkOwner Beforer 전처리 수행"); + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + + if (authentication == null) { + throw new InvalidTokenException("AccessToken is null"); + } + + Long loginUserId = (Long) authentication.getPrincipal(); + + if(loginUserId != id){ + throw new UserNoPermission("You do not have permission."); + } + + } +} diff --git a/app/src/main/java/com/codesoom/assignment/aop/annotation/CheckOwner.java b/app/src/main/java/com/codesoom/assignment/aop/annotation/CheckOwner.java new file mode 100644 index 00000000..55aaaf0e --- /dev/null +++ b/app/src/main/java/com/codesoom/assignment/aop/annotation/CheckOwner.java @@ -0,0 +1,11 @@ +package com.codesoom.assignment.aop.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface CheckOwner { +} diff --git a/app/src/main/java/com/codesoom/assignment/controllers/ProductController.java b/app/src/main/java/com/codesoom/assignment/controllers/ProductController.java index 9557bf60..450f99f0 100644 --- a/app/src/main/java/com/codesoom/assignment/controllers/ProductController.java +++ b/app/src/main/java/com/codesoom/assignment/controllers/ProductController.java @@ -4,6 +4,7 @@ package com.codesoom.assignment.controllers; +import com.codesoom.assignment.aop.annotation.CheckOwner; import com.codesoom.assignment.application.ProductService; import com.codesoom.assignment.domain.Product; import com.codesoom.assignment.dto.ProductData; @@ -39,15 +40,15 @@ public Product detail(@PathVariable Long id) { @ResponseStatus(HttpStatus.CREATED) @PreAuthorize("isAuthenticated() and hasAuthority('USER')") public Product create( - @RequestBody @Valid ProductData productData, - Authentication authentication + @RequestBody @Valid ProductData productData ) { return productService.createProduct(productData); } @PatchMapping("{id}") + @PreAuthorize("isAuthenticated() and hasAuthority('USER')") + @CheckOwner public Product update( - Authentication authentication, @PathVariable Long id, @RequestBody @Valid ProductData productData ) { From 9c3aa2296829dd7d537f0f248ee8f6b6f0529aea Mon Sep 17 00:00:00 2001 From: juhyeon Date: Fri, 18 Aug 2023 09:18:02 +0900 Subject: [PATCH 20/30] =?UTF-8?q?feat=20:=20=EC=9C=A0=EC=A0=80=20=EA=B6=8C?= =?UTF-8?q?=ED=95=9C=20=EC=97=86=EC=9D=8C=20=EC=97=90=EB=9F=AC=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../assignment/controllers/ControllerErrorAdvice.java | 7 +++++++ .../com/codesoom/assignment/errors/UserNoPermission.java | 7 +++++++ 2 files changed, 14 insertions(+) create mode 100644 app/src/main/java/com/codesoom/assignment/errors/UserNoPermission.java diff --git a/app/src/main/java/com/codesoom/assignment/controllers/ControllerErrorAdvice.java b/app/src/main/java/com/codesoom/assignment/controllers/ControllerErrorAdvice.java index 053fda86..17bc8b73 100644 --- a/app/src/main/java/com/codesoom/assignment/controllers/ControllerErrorAdvice.java +++ b/app/src/main/java/com/codesoom/assignment/controllers/ControllerErrorAdvice.java @@ -49,6 +49,13 @@ public ErrorResponse handleConstraintValidateError( return new ErrorResponse(messageTemplate); } + @ResponseBody + @ResponseStatus(HttpStatus.FORBIDDEN) + @ExceptionHandler(UserNoPermission.class) + public ErrorResponse handleUserNoPermission(UserNoPermission exception) { + return new ErrorResponse(exception.getMessage()); + } + private String getViolatedMessage(ConstraintViolationException exception) { String messageTemplate = null; Set> violations = exception.getConstraintViolations(); diff --git a/app/src/main/java/com/codesoom/assignment/errors/UserNoPermission.java b/app/src/main/java/com/codesoom/assignment/errors/UserNoPermission.java new file mode 100644 index 00000000..58fd05f1 --- /dev/null +++ b/app/src/main/java/com/codesoom/assignment/errors/UserNoPermission.java @@ -0,0 +1,7 @@ +package com.codesoom.assignment.errors; + +public class UserNoPermission extends RuntimeException{ + public UserNoPermission(String message) { + super("No user permissions : " + message); + } +} From 90903bb50307cecb1e4f2e054f849a8410f4439f Mon Sep 17 00:00:00 2001 From: tmxhsk99 Date: Sat, 19 Aug 2023 09:01:00 +0900 Subject: [PATCH 21/30] =?UTF-8?q?test=20:=20secret=20=EA=B0=92=EC=9D=B4=20?= =?UTF-8?q?=EC=84=9C=EB=A1=9C=20=EB=8B=A4=EB=A5=B4=EA=B2=8C=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=ED=95=98=EB=8D=98=20=EA=B2=83=EC=9D=84=20=ED=86=B5?= =?UTF-8?q?=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/codesoom/assignment/utils/JwtUtilTest.java | 9 ++------- .../java/com/codesoom/assignment/utils/TestHelper.java | 3 ++- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/app/src/test/java/com/codesoom/assignment/utils/JwtUtilTest.java b/app/src/test/java/com/codesoom/assignment/utils/JwtUtilTest.java index 107cce06..4b74c3c1 100644 --- a/app/src/test/java/com/codesoom/assignment/utils/JwtUtilTest.java +++ b/app/src/test/java/com/codesoom/assignment/utils/JwtUtilTest.java @@ -5,17 +5,12 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import static com.codesoom.assignment.utils.TestHelper.*; +import static com.codesoom.assignment.utils.TestHelper.OTHER_USER_VALID_TOKEN; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; class JwtUtilTest { - private static final String SECRET = "12345678901234567890123456789012"; - - private static final String VALID_TOKEN = "eyJhbGciOiJIUzI1NiJ9." + - "eyJ1c2VySWQiOjF9.ZZ3CUl0jxeLGvQ1Js5nG2Ty5qGTlqai5ubDMXZOdaDk"; - private static final String INVALID_TOKEN = "eyJhbGciOiJIUzI1NiJ9." + - "eyJ1c2VySWQiOjF9.ZZ3CUl0jxeLGvQ1Js5nG2Ty5qGTlqai5ubDMXZOdaD0"; - private JwtUtil jwtUtil; @BeforeEach diff --git a/app/src/test/java/com/codesoom/assignment/utils/TestHelper.java b/app/src/test/java/com/codesoom/assignment/utils/TestHelper.java index 35d3700a..17c6e5b3 100644 --- a/app/src/test/java/com/codesoom/assignment/utils/TestHelper.java +++ b/app/src/test/java/com/codesoom/assignment/utils/TestHelper.java @@ -12,8 +12,9 @@ public class TestHelper { public static final String VALID_TOKEN = "eyJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOjF9.neCsyNLzy3lQ4o2yliotWT06FwSGZagaHpKdAkjnGGw"; - public static final String OTHER_USER_VALID_TOKEN = "eyJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOjJ9.TEM6MULsZeqkBbUKziCR4Dg_8kymmZkyxsCXlfNJ3g0"; + public static final String OTHER_USER_VALID_TOKEN = "eyJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOjJ9.i-iHszAs6H2JFTdm3vOVuN18tb_w6n2FqEYIRtr6gaU"; public static final String INVALID_TOKEN = VALID_TOKEN + "INVALID"; + public static final String SECRET = "12345678901234567890123456789010"; public static final String AUTH_NAME = "AUTH_NAME"; public static final String AUTH_EMAIL = "auth@foo.com"; public static final String INVALID_EMAIL = AUTH_EMAIL + "INVALID"; From 3576adf0db2a58e7dc18e7ca11273aa5fba757c6 Mon Sep 17 00:00:00 2001 From: tmxhsk99 Date: Sat, 19 Aug 2023 11:41:32 +0900 Subject: [PATCH 22/30] =?UTF-8?q?test=20:=20=EC=83=81=ED=92=88=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EC=82=AD=EC=A0=9C=20=EA=B6=8C=ED=95=9C=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controllers/ProductAopTest.java | 88 +++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 app/src/test/java/com/codesoom/assignment/controllers/ProductAopTest.java diff --git a/app/src/test/java/com/codesoom/assignment/controllers/ProductAopTest.java b/app/src/test/java/com/codesoom/assignment/controllers/ProductAopTest.java new file mode 100644 index 00000000..c39a6ca9 --- /dev/null +++ b/app/src/test/java/com/codesoom/assignment/controllers/ProductAopTest.java @@ -0,0 +1,88 @@ +package com.codesoom.assignment.controllers; + +import com.codesoom.assignment.domain.ProductRepository; +import com.codesoom.assignment.domain.UserRepository; +import com.codesoom.assignment.utils.JwtUtil; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.jsonwebtoken.Claims; +import org.junit.jupiter.api.*; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.web.servlet.MockMvc; + +import static com.codesoom.assignment.utils.TestHelper.*; +import static org.mockito.BDDMockito.given; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@AutoConfigureMockMvc +@SpringBootTest +@ActiveProfiles("test") +@DisplayName("ProductController 클래스") +public class ProductAopTest { + @Autowired + MockMvc mockMvc; + + @Autowired + UserRepository userRepository; + + @Autowired + ProductRepository productRepository; + + @Autowired + ObjectMapper objectMapper; + + @BeforeEach + void setUp() { + productRepository.save(TEST_PRODUCT); + } + + @Nested + @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) + class update_메서드는 { + + + @Nested + @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) + class 상품을_수정할_권한이_없는_경우 { + @Test + @DisplayName("에러메시지를 반환한다") + void It_returns_403_error() throws Exception { + mockMvc.perform(patch("/products/1") + .header("Authorization", "Bearer " + OTHER_USER_VALID_TOKEN) + .accept(MediaType.APPLICATION_JSON_UTF8) + .contentType(MediaType.APPLICATION_JSON_UTF8) + .content(objectMapper.writeValueAsString(UPDATE_PRODUCT_DATA))) + .andExpect(status().isForbidden()) + .andDo(print()); + } + } + } + + @Nested + @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) + class delete_메서드는 { + + + @Nested + @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) + class 상품을_삭제할_권한이_없는_경우 { + @Test + @DisplayName("에러메시지를 반환한다") + void It_returns_403_error() throws Exception { + mockMvc.perform(delete("/products/1") + .header("Authorization", "Bearer " + OTHER_USER_VALID_TOKEN) + .accept(MediaType.APPLICATION_JSON_UTF8) + .contentType(MediaType.APPLICATION_JSON_UTF8)) + .andExpect(status().isForbidden()) + .andDo(print()); + } + } + } + +} From 1c516dd423c98bb871ebaa3a5b243b7fb443d0e4 Mon Sep 17 00:00:00 2001 From: tmxhsk99 Date: Sat, 19 Aug 2023 11:42:18 +0900 Subject: [PATCH 23/30] =?UTF-8?q?feat=20:=20=EC=83=81=ED=92=88=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=EC=97=90=20=EC=B6=94=EA=B0=80=ED=95=9C=20=EC=9C=A0?= =?UTF-8?q?=EC=A0=80=EC=95=84=EC=9D=B4=EB=94=94=20=EC=86=8D=EC=84=B1=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/codesoom/assignment/domain/Product.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/src/main/java/com/codesoom/assignment/domain/Product.java b/app/src/main/java/com/codesoom/assignment/domain/Product.java index 2a6c959a..e1272273 100644 --- a/app/src/main/java/com/codesoom/assignment/domain/Product.java +++ b/app/src/main/java/com/codesoom/assignment/domain/Product.java @@ -43,10 +43,16 @@ public class Product { private String imageUrl; + private Long createUserId; + public void changeWith(Product source) { this.name = source.name; this.maker = source.maker; this.price = source.price; this.imageUrl = source.imageUrl; } + + public void setCreateUserId(Long createUserId) { + this.createUserId = createUserId; + } } From d1a71ee7fb97f5ad95718a9a6fc6ae40c39d3843 Mon Sep 17 00:00:00 2001 From: tmxhsk99 Date: Sat, 19 Aug 2023 11:44:24 +0900 Subject: [PATCH 24/30] =?UTF-8?q?fix=20:=20=EC=83=81=ED=92=88=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=EC=9D=98=20=EB=93=B1=EB=A1=9D=EC=9E=90=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=EB=A5=BC=20=EA=B0=80=EC=A0=B8=EC=99=80=20=EB=B9=84?= =?UTF-8?q?=EA=B5=90=ED=95=98=EB=8F=84=EB=A1=9D=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=88=98=EC=A0=95,=20=EC=83=81=ED=92=88=EC=95=84=EC=9D=B4?= =?UTF-8?q?=EB=94=94=20=EB=A7=A4=EA=B0=9C=EB=B3=80=EC=88=98=EC=8B=9D=20,?= =?UTF-8?q?=20=EC=97=AC=EB=9F=AC=EA=B0=9C=20=EB=A7=A4=EA=B0=9C=EB=B3=80?= =?UTF-8?q?=EC=88=98=EC=9E=88=EC=96=B4=EB=8F=84=20=EC=A0=81=EC=9A=A9?= =?UTF-8?q?=EB=90=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../assignment/aop/OwnerCheckAspect.java | 33 +++++++++++++++---- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/com/codesoom/assignment/aop/OwnerCheckAspect.java b/app/src/main/java/com/codesoom/assignment/aop/OwnerCheckAspect.java index d10ead72..17788985 100644 --- a/app/src/main/java/com/codesoom/assignment/aop/OwnerCheckAspect.java +++ b/app/src/main/java/com/codesoom/assignment/aop/OwnerCheckAspect.java @@ -1,6 +1,9 @@ package com.codesoom.assignment.aop; +import com.codesoom.assignment.application.ProductService; +import com.codesoom.assignment.domain.Product; import com.codesoom.assignment.errors.InvalidTokenException; +import com.codesoom.assignment.errors.ProductNotFoundException; import com.codesoom.assignment.errors.UserNoPermission; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; @@ -12,20 +15,36 @@ @Aspect @Component public class OwnerCheckAspect { - @Before("@annotation(com.codesoom.assignment.aop.annotation.CheckOwner) && args(id)") - public void checkOwner(Long id) throws InvalidTokenException, UserNoPermission { - System.out.println("checkOwner Beforer 전처리 수행"); + private final ProductService productService; + + public OwnerCheckAspect(ProductService productService) { + this.productService = productService; + } + + @Before("@annotation(com.codesoom.assignment.aop.annotation.CheckOwner) && args(id,..)") + public void checkOwner(Long id) throws InvalidTokenException, UserNoPermission, ProductNotFoundException { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - if (authentication == null) { - throw new InvalidTokenException("AccessToken is null"); - } + authenticationValidate(authentication); Long loginUserId = (Long) authentication.getPrincipal(); - if(loginUserId != id){ + Product product = productService.getProduct(id); + + Long createdUserId = product.getCreateUserId(); + + if (loginUserId != createdUserId) { throw new UserNoPermission("You do not have permission."); } + } + private void authenticationValidate(Authentication authentication) { + if (authentication == null) { + throw new InvalidTokenException("AccessToken is Invalid."); + } + if (authentication.getPrincipal().equals("anonymousUser")) { + throw new AccessDeniedException("You do not have permission."); + } } } + From b6eb6ebcd8d86472ca94753fa1a676d47354f2be Mon Sep 17 00:00:00 2001 From: tmxhsk99 Date: Sat, 19 Aug 2023 11:45:56 +0900 Subject: [PATCH 25/30] =?UTF-8?q?feat=20:=20=EC=83=81=ED=92=88=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C,=20=EC=83=81=ED=92=88=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?=EC=8B=9C=20=EA=B6=8C=ED=95=9C=20=EA=B4=80=EB=A0=A8=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../assignment/controllers/ProductController.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/codesoom/assignment/controllers/ProductController.java b/app/src/main/java/com/codesoom/assignment/controllers/ProductController.java index 450f99f0..7b7ece37 100644 --- a/app/src/main/java/com/codesoom/assignment/controllers/ProductController.java +++ b/app/src/main/java/com/codesoom/assignment/controllers/ProductController.java @@ -40,9 +40,10 @@ public Product detail(@PathVariable Long id) { @ResponseStatus(HttpStatus.CREATED) @PreAuthorize("isAuthenticated() and hasAuthority('USER')") public Product create( - @RequestBody @Valid ProductData productData + @RequestBody @Valid ProductData productData, + Authentication authentication ) { - return productService.createProduct(productData); + return productService.createProduct(productData, (Long)authentication.getPrincipal()); } @PatchMapping("{id}") @@ -56,11 +57,13 @@ public Product update( } @DeleteMapping("{id}") + @PreAuthorize("isAuthenticated() and hasAuthority('USER')") @ResponseStatus(HttpStatus.NO_CONTENT) + @CheckOwner public void destroy( - Authentication authentication, @PathVariable Long id ) { productService.deleteProduct(id); } + } From e1d9b4d38cc30ca512c5a99d45b729f5052f422d Mon Sep 17 00:00:00 2001 From: tmxhsk99 Date: Sat, 19 Aug 2023 11:47:05 +0900 Subject: [PATCH 26/30] =?UTF-8?q?chore=20:=20=EC=83=81=ED=92=88=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=EC=8B=9C=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=9D=B8?= =?UTF-8?q?=ED=84=B0=ED=8E=98=EC=9D=B4=EC=8A=A4=20=EB=B3=80=ED=99=94?= =?UTF-8?q?=EC=97=90=20=EB=94=B0=EB=A5=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controllers/ProductControllerTest.java | 27 +------------------ 1 file changed, 1 insertion(+), 26 deletions(-) diff --git a/app/src/test/java/com/codesoom/assignment/controllers/ProductControllerTest.java b/app/src/test/java/com/codesoom/assignment/controllers/ProductControllerTest.java index 6a464aec..9220d1c7 100644 --- a/app/src/test/java/com/codesoom/assignment/controllers/ProductControllerTest.java +++ b/app/src/test/java/com/codesoom/assignment/controllers/ProductControllerTest.java @@ -132,7 +132,7 @@ void setUp() { class 상품_요청이_정상적인_경우 { @BeforeEach void setUp() { - given(productService.createProduct(any(ProductData.class))) + given(productService.createProduct(any(ProductData.class),any(Long.class))) .willReturn(TEST_PRODUCT); } @@ -275,31 +275,6 @@ void It_returns_404_error() throws Exception { } } - @Nested - @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) - class 상품을_수정할_권한이_없는_경우 { - @BeforeEach - void setUp() { - Mockito.reset(jwtUtil); - Mockito.reset(productService); - given(jwtUtil.decode(OTHER_USER_VALID_TOKEN)).will(invocation -> { - String accessToken = invocation.getArgument(0); - Claims claims = new JwtUtil("12345678901234567890123456789010").decode(accessToken); - return claims; - }); - } - @Test - @DisplayName("에러메시지를 반환한다") - void It_returns_403_error() throws Exception { - mockMvc.perform(patch("/products/1") - .header("Authorization", "Bearer " + OTHER_USER_VALID_TOKEN) - .accept(MediaType.APPLICATION_JSON_UTF8) - .contentType(MediaType.APPLICATION_JSON_UTF8) - .content(objectMapper.writeValueAsString(UPDATE_PRODUCT_DATA))) - .andExpect(status().isForbidden()) - .andDo(print()); - } - } } @Nested From bf63a4141ba9c68fba35430b5c394fd852a76d4c Mon Sep 17 00:00:00 2001 From: tmxhsk99 Date: Sat, 19 Aug 2023 11:47:44 +0900 Subject: [PATCH 27/30] =?UTF-8?q?feat=20:=20=EC=83=81=ED=92=88=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=EC=8B=9C=20=EC=83=9D=EC=84=B1=EC=9E=90=20=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=EB=8F=84=20=EC=B6=94=EA=B0=80=ED=95=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/codesoom/assignment/application/ProductService.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/codesoom/assignment/application/ProductService.java b/app/src/main/java/com/codesoom/assignment/application/ProductService.java index 921337bd..194f3e8e 100644 --- a/app/src/main/java/com/codesoom/assignment/application/ProductService.java +++ b/app/src/main/java/com/codesoom/assignment/application/ProductService.java @@ -32,8 +32,9 @@ public Product getProduct(Long id) { return findProduct(id); } - public Product createProduct(ProductData productData) { + public Product createProduct(ProductData productData, Long createUserId) { Product product = mapper.map(productData, Product.class); + product.setCreateUserId(createUserId); return productRepository.save(product); } From 29f2d122ffe0b658c212ff1d25a41c3c6d8bfb69 Mon Sep 17 00:00:00 2001 From: tmxhsk99 Date: Sat, 19 Aug 2023 11:48:13 +0900 Subject: [PATCH 28/30] =?UTF-8?q?chore=20:=20=EC=83=81=ED=92=88=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=EC=8B=9C=20=EC=9D=B8=ED=84=B0=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=8A=A4=20=EB=B3=80=EA=B2=BD=EC=97=90=20=EB=94=B0=EB=A5=B8=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/codesoom/assignment/application/ProductServiceTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/test/java/com/codesoom/assignment/application/ProductServiceTest.java b/app/src/test/java/com/codesoom/assignment/application/ProductServiceTest.java index f8325526..7a19384a 100644 --- a/app/src/test/java/com/codesoom/assignment/application/ProductServiceTest.java +++ b/app/src/test/java/com/codesoom/assignment/application/ProductServiceTest.java @@ -99,7 +99,7 @@ void createProduct() { .price(5000) .build(); - Product product = productService.createProduct(productData); + Product product = productService.createProduct(productData ,1L); verify(productRepository).save(any(Product.class)); From 17b2f05659f7a47f7d447739bdb965ace08e20b1 Mon Sep 17 00:00:00 2001 From: tmxhsk99 Date: Sat, 19 Aug 2023 11:48:17 +0900 Subject: [PATCH 29/30] =?UTF-8?q?chore=20:=20=EC=83=81=ED=92=88=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=EC=8B=9C=20=EC=9D=B8=ED=84=B0=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=8A=A4=20=EB=B3=80=EA=B2=BD=EC=97=90=20=EB=94=B0=EB=A5=B8=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/test/java/com/codesoom/assignment/utils/TestHelper.java | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/test/java/com/codesoom/assignment/utils/TestHelper.java b/app/src/test/java/com/codesoom/assignment/utils/TestHelper.java index 17c6e5b3..00072f11 100644 --- a/app/src/test/java/com/codesoom/assignment/utils/TestHelper.java +++ b/app/src/test/java/com/codesoom/assignment/utils/TestHelper.java @@ -58,6 +58,7 @@ public class TestHelper { .name(TEST_PRODUCT_NAME) .maker(TEST_PRODUCT_MAKER) .price(TEST_PRODUCT_PRICE) + .createUserId(1L) .build(); public static final ProductData TEST_PRODUCT_DATA = ProductData.builder() From 5cb56c282d66ef3d7461f0c999c2dfa72cf13397 Mon Sep 17 00:00:00 2001 From: tmxhsk99 Date: Sat, 19 Aug 2023 11:48:40 +0900 Subject: [PATCH 30/30] =?UTF-8?q?test=20:=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=A0=84=EC=9A=A9=20=EC=84=A4=EC=A0=95=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/test/resources/application-test.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 app/src/test/resources/application-test.yml diff --git a/app/src/test/resources/application-test.yml b/app/src/test/resources/application-test.yml new file mode 100644 index 00000000..21d9b9fa --- /dev/null +++ b/app/src/test/resources/application-test.yml @@ -0,0 +1,16 @@ +spring: + datasource: + driver-class-name: org.h2.Driver + url: jdbc:h2:mem:testdb + username: sa + password: + jpa: + database-platform: org.hibernate.dialect.H2Dialect + hibernate: + ddl-auto: create + h2: + console: + enabled: true + +jwt: + secret: "12345678901234567890123456789010"