diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md new file mode 100644 index 0000000..8e3f6ae --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report.md @@ -0,0 +1,30 @@ +--- +name: Bug Report +about: Bug Report μ„€λͺ… +title: "[BUG]" +labels: "bug" +assignees: '' + +--- + +## πŸ€” 버그 λ‚΄μš© +버그 λ‚΄μš© 및 버그 λ°œμƒ 상황을 μ•Œλ €μ£Όμ„Έμš”! +ex) μ–΄λ–€ apiλ₯Ό ν˜ΈμΆœν•˜μ˜€λ”λ‹ˆ 였λ₯˜κ°€ μƒκ²Όμ–΄μš”. +
+ +## 🚩 버그 발견 μœ„μΉ˜ +버그 발견 μ˜ˆμƒ νŒ¨ν‚€μ§€ μœ„μΉ˜λ₯Ό μ•Œλ €μ£Όμ„Έμš”! +ex) + +
+ +## ⚠ μ—λŸ¬ 캑쳐 +μ£Όμš” μ—λŸ¬ stack traceλ₯Ό μΊ‘μ³ν•΄μ£Όμ„Έμš”! + +
+ + +## πŸ™‚ etc +μΆ”κ°€μ μœΌλ‘œ ν•˜κ³  싢은 말! (Optional) + +
diff --git a/.github/ISSUE_TEMPLATE/feature-request.md b/.github/ISSUE_TEMPLATE/feature-request.md new file mode 100644 index 0000000..9c1d33d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature-request.md @@ -0,0 +1,24 @@ +--- +name: Feature Request +about: κΈ°λŠ₯ 개발 μ œμ•ˆ +title: "[FEAT]" +labels: "enhancement" +assignees: '' + +--- + +## ✨ μ œμ•ˆ κΈ°λŠ₯ +κΈ°λŠ₯에 λŒ€ν•΄ κ°„λž΅ν•˜κ²Œ μ„€λͺ…ν•΄μ£Όμ„Έμš”! + +## πŸŽ‡ μ œμ•ˆ λ°°κ²½ +이 κΈ°λŠ₯을 μΆ”κ°€ν•˜κ²Œ λ˜λŠ” 배경에 λŒ€ν•΄ μ μ–΄μ£Όμ„Έμš”! +ex) 쀑고 κ±°λž˜μ— ν•„μš”ν•œ μ±„νŒ… κΈ°λŠ₯이 ν•„μš”ν•΄μ„œ λ„μž…ν•œλ‹€! + +
+ +## πŸ“’ κΈ°λŠ₯ μ„€λͺ… +κ°œλ°œν•  κΈ°λŠ₯에 λŒ€ν•΄ μ„€λͺ…ν•΄μ£Όμ„Έμš”. + +
+ +### πŸ“• 래퍼런슀 diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..77abbd6 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,22 @@ +--- +name: Pull Request +about: pr μ„€λͺ… +title: "[PR]" +labels: "" +assignees: '' + +--- + + +## μΆ”κ°€ν•œ κΈ°λŠ₯ μ„€λͺ… + +
+ + +## check list +- [ ] issue numberλ₯Ό 브랜치 μ•žμ— μΆ”κ°€ ν•˜μ˜€λŠ”κ°€?
+- [ ] κ·Έμ™Έμ˜ [pr κ·œμΉ™](https://github.com/h-jjang/bauction/wiki/%5BProject-Rules%5D#pr-%EA%B7%9C%EC%B9%99)을 잘 μ§€μΌ°λŠ”κ°€? +- [ ] λͺ¨λ“  λ‹¨μœ„ ν…ŒμŠ€νŠΈλ₯Ό 돌렀보고 기쑴에 μž‘λ™ν•˜λ˜ ν…ŒμŠ€νŠΈμ— 영ν–₯이 μ—†λŠ” 것을 ν™•μΈν–ˆλŠ”κ°€? +μ„€λͺ… +- [ ] naver code style formatting(단좕킀)을 ν–ˆλŠ”κ°€? +- [ ] [μš°ν…Œμ½” pr κ·œμΉ™](https://github.com/woowacourse/woowacourse-docs/blob/master/cleancode/pr_checklist.md)을 μ€€μˆ˜ν•˜μ˜€λŠ”κ°€? \ No newline at end of file diff --git a/backend/.gitignore b/backend/.gitignore index 4e98f71..d698e1a 100644 --- a/backend/.gitignore +++ b/backend/.gitignore @@ -65,3 +65,4 @@ hs_err_pid* /src/main/resources/application-auth.yml /src/main/resources/application-db.yml /src/main/resources/application-email.yml +/src/test/resources/application.yml diff --git a/backend/build.gradle b/backend/build.gradle index de017ef..87db889 100644 --- a/backend/build.gradle +++ b/backend/build.gradle @@ -1,9 +1,10 @@ plugins { id 'org.springframework.boot' version '2.6.4' id 'io.spring.dependency-management' version '1.0.11.RELEASE' - id 'org.asciidoctor.convert' version '1.5.8' + id "org.asciidoctor.jvm.convert" version "3.3.2" id 'java' id 'application' + id 'jacoco' } group = 'com.hjjang' @@ -11,9 +12,11 @@ version = '0.0.1-SNAPSHOT' sourceCompatibility = '11' configurations { + asciidoctorExtensions // dependencies μ—μ„œ μ μš©ν•œ 것 μΆ”κ°€ compileOnly { extendsFrom annotationProcessor } + } repositories { @@ -25,47 +28,68 @@ ext { } dependencies { + + // λͺ¨λ‹ˆν„°λ§ κ΄€λ ¨ μ˜μ‘΄μ„± implementation 'org.springframework.boot:spring-boot-starter-actuator' + runtimeOnly 'io.micrometer:micrometer-registry-prometheus' + implementation 'org.springframework.boot:spring-boot-starter-batch' implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-data-redis' implementation 'org.springframework.boot:spring-boot-starter-mail' - implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' - implementation 'org.springframework.boot:spring-boot-starter-security' + implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-websocket' implementation 'org.flywaydb:flyway-core' implementation 'org.springframework.kafka:spring-kafka' implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE' + + // 둬볡 κ΄€λ ¨ μ˜μ‘΄μ„± compileOnly 'org.projectlombok:lombok' + annotationProcessor 'org.projectlombok:lombok' + developmentOnly 'org.springframework.boot:spring-boot-devtools' - runtimeOnly 'io.micrometer:micrometer-registry-prometheus' - runtimeOnly 'mysql:mysql-connector-java' + runtimeOnly 'mysql:mysql-connector-java' annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor' - annotationProcessor 'org.projectlombok:lombok' + testAnnotationProcessor 'org.springframework.boot:spring-boot-configuration-processor' + //swagger-ui + implementation 'org.springdoc:springdoc-openapi-ui:1.6.8' + //jwt κ΄€λ ¨ μ˜μ‘΄μ„± + implementation 'io.jsonwebtoken:jjwt-api:0.11.5' + runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5' + runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5' + + // oauth κ΄€λ ¨ μ˜μ‘΄μ„± + implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' + implementation 'org.springframework.boot:spring-boot-starter-security' - testImplementation 'com.h2database:h2' - testImplementation 'org.springframework.boot:spring-boot-starter-test' + implementation 'com.google.guava:guava:31.1-jre' + + // testλ₯Ό μœ„ν•œ μ˜μ‘΄μ„± testImplementation 'org.springframework.batch:spring-batch-test' testImplementation 'org.springframework.kafka:spring-kafka-test' testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc' + asciidoctorExtensions 'org.springframework.restdocs:spring-restdocs-asciidoctor' testImplementation 'org.springframework.security:spring-security-test' + testImplementation('org.springframework.boot:spring-boot-starter-test') { + exclude group: 'org.junit.vintage', module: 'junit-vintage-engine' //junit 4 ν•˜μœ„ ν˜Έν™˜μ„ μœ„ν•œ 라이브러리 제거 + } + testCompileOnly 'org.projectlombok:lombok' + testAnnotationProcessor 'org.projectlombok:lombok' + runtimeOnly 'com.h2database:h2' - implementation 'org.springdoc:springdoc-openapi-ui:1.6.6' - //jwt κ΄€λ ¨ μ˜μ‘΄μ„± - implementation 'io.jsonwebtoken:jjwt-api:0.11.2' - runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.2' - runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.2' - implementation 'org.springframework.boot:spring-boot-starter-log4j2' - +// λ‘œκΉ… κ΄€λ ¨ μ˜μ‘΄μ„± + implementation "org.springframework.boot:spring-boot-starter-log4j2" + modules { + module("org.springframework.boot:spring-boot-starter-logging") { + replacedBy("org.springframework.boot:spring-boot-starter-log4j2", "Use Log4j2 instead of Logback") + } + } + implementation 'org.apache.logging.log4j:log4j-web' } -configurations { - all*.exclude group: 'org.springframework.boot', module: 'spring-boot-starter-logging' - all*.exclude group: 'org.springframework.boot', module: 'logback-classic' -} tasks.named('compileJava') { inputs.files(tasks.named('processResources')) @@ -73,12 +97,18 @@ tasks.named('compileJava') { bootRun { mainClassName = 'com.hjjang.backend.BackendApplication' + } +// μž‘μ„±λœ λ¬Έμ„œλ₯Ό μ„œλ²„μ—μ„œ μ ‘κ·Όν•  수 μžˆλ„λ‘ 이동 bootJar { mainClassName = 'com.hjjang.backend.BackendApplication' -} + dependsOn asciidoctor + from ("${asciidoctor.outputDir}/html5") { + into 'static/docs' + } +} tasks.named('test') { outputs.dir snippetsDir useJUnitPlatform() @@ -88,3 +118,34 @@ tasks.named('asciidoctor') { inputs.dir snippetsDir dependsOn test } + +jacocoTestReport { + reports { + html.enabled true + xml.enabled false + csv.enabled true + } + finalizedBy 'jacocoTestCoverageVerification' +} + +jacocoTestCoverageVerification { + violationRules { + rule { + enabled = true + element = 'CLASS' + + limit { + counter = 'METHOD' + value = 'COVEREDRATIO' + minimum = 0.00 + } + + limit { + counter = 'INSTRUCTION' + value = 'COVEREDRATIO' + minimum = 0.00 + } + excludes = ["*Dto*", "*.dto.*", "*.SvProjectApplication*", "*.config.*", "*.exceptions.*", "*.utils.*"] + } + } +} \ No newline at end of file diff --git a/backend/lombok.config b/backend/lombok.config new file mode 100644 index 0000000..a23edb4 --- /dev/null +++ b/backend/lombok.config @@ -0,0 +1,2 @@ +config.stopBubbling = true +lombok.addLombokGeneratedAnnotation = true \ No newline at end of file diff --git a/backend/src/main/java/com/hjjang/backend/domain/post/service/PostServiceImpl.java b/backend/src/main/java/com/hjjang/backend/domain/post/service/PostServiceImpl.java index caa6911..1ce00d9 100644 --- a/backend/src/main/java/com/hjjang/backend/domain/post/service/PostServiceImpl.java +++ b/backend/src/main/java/com/hjjang/backend/domain/post/service/PostServiceImpl.java @@ -33,7 +33,7 @@ public Post findOneById(Long id) { public Post updateOneById(Long id, PostRequestDto postRequestDto, User user) { Post foundPost = findOneById(id); if (user == foundPost.getUser()) return save(foundPost.update(postRequestDto)); - else throw new UserNotMatchException("μ‚¬μš©μž 정보가 μΌμΉ˜ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.", ErrorCode.NO_AUTHORITY); + throw new UserNotMatchException("μ‚¬μš©μž 정보가 μΌμΉ˜ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.", ErrorCode.NO_AUTHORITY); } public void deleteOneById(Long id) { diff --git a/backend/src/main/java/com/hjjang/backend/domain/user/controller/UserAuthController.java b/backend/src/main/java/com/hjjang/backend/domain/user/controller/UserAuthController.java deleted file mode 100644 index 1cc3f30..0000000 --- a/backend/src/main/java/com/hjjang/backend/domain/user/controller/UserAuthController.java +++ /dev/null @@ -1,81 +0,0 @@ -package com.hjjang.backend.domain.user.controller; - -import com.hjjang.backend.domain.user.entity.RoleType; -import com.hjjang.backend.domain.user.entity.UserRefreshToken; -import com.hjjang.backend.domain.user.repository.UserRefreshTokenRepository; -import com.hjjang.backend.domain.user.service.UserAuthService; -import com.hjjang.backend.global.config.properties.AuthProperties; -import com.hjjang.backend.global.config.security.token.AuthToken; -import com.hjjang.backend.global.config.security.token.AuthTokenProvider; -import com.hjjang.backend.global.dto.ApiResponse; -import com.hjjang.backend.global.util.CookieUtil; -import com.hjjang.backend.global.util.HeaderUtil; -import io.jsonwebtoken.Claims; -import lombok.RequiredArgsConstructor; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.util.Date; - -@RestController -@RequestMapping("/api/v1/auth") -@RequiredArgsConstructor -public class UserAuthController { - - private final AuthProperties authProperties; - private final AuthTokenProvider tokenProvider; - private final UserRefreshTokenRepository userRefreshTokenRepository; - private final UserAuthService userAuthService; - - private static final long THREE_DAYS_MSEC = 259200000; - private static final String REFRESH_TOKEN = "refresh_token"; - - @GetMapping("/refresh") - public ApiResponse refreshToken(HttpServletRequest request, HttpServletResponse response) { - // access token 확인 - String accessToken = HeaderUtil.getAccessToken(request); - AuthToken authToken = tokenProvider.convertAuthToken(accessToken); - if (!authToken.validate()) { - return ApiResponse.invalidAccessToken(); - } - - // 만료된 access token 인지 확인 - Claims claims = authToken.getExpiredTokenClaims(); - if (claims == null) { - return ApiResponse.notExpiredTokenYet(); - } - String providerId = claims.getSubject(); - RoleType roleType = RoleType.of(claims.get("role", String.class)); - - // refresh token - String refreshToken = CookieUtil.getCookie(request, REFRESH_TOKEN) - .map(Cookie::getValue) - .orElse((null)); - AuthToken authRefreshToken = tokenProvider.convertAuthToken(refreshToken); - - if (authRefreshToken.validate()) { - return ApiResponse.invalidRefreshToken(); - } - - // providerId, refresh token 으둜 DB 확인 - UserRefreshToken userRefreshToken = userRefreshTokenRepository.findByProviderIdAndRefreshToken(providerId, - refreshToken); - if (userRefreshToken == null) { - return ApiResponse.invalidRefreshToken(); - } - - Date now = new Date(); - AuthToken newAccessToken = tokenProvider.createAuthToken( - providerId, roleType.getCode(), - new Date(now.getTime() + authProperties.getTokenProperties().getTokenExpireDate()) - ); - userAuthService.reissueRefreshTokenIfValidTimeleft3days(request, response, userRefreshToken, authRefreshToken, - now); - - return ApiResponse.success("token", newAccessToken.getToken()); - } -} diff --git a/backend/src/main/java/com/hjjang/backend/domain/user/controller/UserController.java b/backend/src/main/java/com/hjjang/backend/domain/user/controller/UserController.java index 3c3d1c0..37c8d71 100644 --- a/backend/src/main/java/com/hjjang/backend/domain/user/controller/UserController.java +++ b/backend/src/main/java/com/hjjang/backend/domain/user/controller/UserController.java @@ -1,14 +1,15 @@ package com.hjjang.backend.domain.user.controller; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - import com.hjjang.backend.domain.user.dto.UserProfileDTO; import com.hjjang.backend.domain.user.service.UserProfileService; -import com.hjjang.backend.global.dto.ApiResponse; - +import com.hjjang.backend.global.response.code.SuccessCode; +import com.hjjang.backend.global.response.response.SuccessResponse; +import com.hjjang.backend.global.util.UserUtil; import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/api/v1/users") @@ -18,8 +19,10 @@ public class UserController { private final UserProfileService userProfileService; @GetMapping("/profile") - public ApiResponse getProfile() { - UserProfileDTO userProfile = userProfileService.getUserProfile(); - return ApiResponse.success("userProfile", userProfile); + public ResponseEntity getProfile() { + String userId = UserUtil.getLoginUserIdByToken(); + UserProfileDTO userProfile = userProfileService.getUserProfile(userId); + return ResponseEntity.ok(SuccessResponse.of(SuccessCode.USER_PROFILE_SUCCESS, userProfile)); } + } diff --git a/backend/src/main/java/com/hjjang/backend/domain/user/dto/LoginRequest.java b/backend/src/main/java/com/hjjang/backend/domain/user/dto/LoginRequest.java deleted file mode 100644 index 85b3357..0000000 --- a/backend/src/main/java/com/hjjang/backend/domain/user/dto/LoginRequest.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.hjjang.backend.domain.user.dto; - -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; - -@Getter -@Setter -@NoArgsConstructor -@AllArgsConstructor -public class LoginRequest { - private String id; - private String password; -} diff --git a/backend/src/main/java/com/hjjang/backend/domain/user/entity/AnonymousEmptyUser.java b/backend/src/main/java/com/hjjang/backend/domain/user/entity/AnonymousEmptyUser.java new file mode 100644 index 0000000..a8d39e5 --- /dev/null +++ b/backend/src/main/java/com/hjjang/backend/domain/user/entity/AnonymousEmptyUser.java @@ -0,0 +1,8 @@ +package com.hjjang.backend.domain.user.entity; + +public class AnonymousEmptyUser extends User { + public AnonymousEmptyUser() { + super(null, null, null, null, null, null, null, null, null, false, false); + } + +} diff --git a/backend/src/main/java/com/hjjang/backend/domain/user/entity/User.java b/backend/src/main/java/com/hjjang/backend/domain/user/entity/User.java index 8662ca3..57f9530 100644 --- a/backend/src/main/java/com/hjjang/backend/domain/user/entity/User.java +++ b/backend/src/main/java/com/hjjang/backend/domain/user/entity/User.java @@ -1,15 +1,12 @@ package com.hjjang.backend.domain.user.entity; +import com.hjjang.backend.global.domain.BaseTimeEntity; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -import org.hibernate.annotations.CreationTimestamp; -import org.springframework.data.annotation.CreatedDate; -import org.springframework.data.jpa.domain.support.AuditingEntityListener; import javax.persistence.*; -import java.time.LocalDateTime; import static javax.persistence.EnumType.STRING; import static javax.persistence.GenerationType.IDENTITY; @@ -17,11 +14,10 @@ @Getter @AllArgsConstructor -@EntityListeners(AuditingEntityListener.class) @Entity @NoArgsConstructor(access = PROTECTED) @Table(name = "user") -public class User { +public class User extends BaseTimeEntity { @Id @GeneratedValue(strategy = IDENTITY) @@ -40,13 +36,9 @@ public class User { @Column(name = "manner_temperature", nullable = false) private Long mannerTemperature; - @Column(name = "image_url", nullable = true, length = 50) + @Column(name = "image_url", length = 50) private String imageUrl; - @CreationTimestamp - @Column(name = "created_at", nullable = false) - private LocalDateTime createdAt; - @Enumerated(STRING) @Column(name = "is_push_agree", nullable = false, length = 10) private Agreement isPushAgree; @@ -54,12 +46,20 @@ public class User { @Column(name = "univ_id") private Long univId; - @Column(name = "role",length = 20) + @Column(name = "role", length = 20) @Enumerated(STRING) private RoleType role; + @Column(name = "is_email_verification") + private Boolean isEmailVerification; + + @Column(name = "is_blocked") + private Boolean isBlocked; + @Builder - public User(String providerId, String nickName, String email, Long mannerTemperature, String imageUrl, Agreement isPushAgree, Long univId, RoleType role) { + public User(String providerId, String nickName, String email, Long mannerTemperature, + String imageUrl, Agreement isPushAgree, Long univId, RoleType role, Boolean isEmailVerification, + Boolean isBlocked) { this.providerId = providerId; this.nickName = nickName; this.email = email; @@ -68,5 +68,7 @@ public User(String providerId, String nickName, String email, Long mannerTempera this.isPushAgree = isPushAgree; this.univId = univId; this.role = role; + this.isEmailVerification = isEmailVerification; + this.isBlocked = isBlocked; } } diff --git a/backend/src/main/java/com/hjjang/backend/domain/user/service/UserAuthService.java b/backend/src/main/java/com/hjjang/backend/domain/user/service/UserAuthService.java deleted file mode 100644 index 29ec82c..0000000 --- a/backend/src/main/java/com/hjjang/backend/domain/user/service/UserAuthService.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.hjjang.backend.domain.user.service; - -import static com.hjjang.backend.global.config.security.repository.OAuth2AuthorizationRequestBasedOnCookieRepository.*; - -import java.util.Date; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.stereotype.Service; - -import com.hjjang.backend.domain.user.entity.UserRefreshToken; -import com.hjjang.backend.domain.user.repository.UserRefreshTokenRepository; -import com.hjjang.backend.global.config.properties.AuthProperties; -import com.hjjang.backend.global.config.security.token.AuthToken; -import com.hjjang.backend.global.config.security.token.AuthTokenProvider; -import com.hjjang.backend.global.util.CookieUtil; - -import lombok.RequiredArgsConstructor; - -@RequiredArgsConstructor -@Service -public class UserAuthService { - - private final AuthProperties authProperties; - private final AuthTokenProvider tokenProvider; - private final AuthenticationManager authenticationManager; - private final UserRefreshTokenRepository userRefreshTokenRepository; - private final static long THREE_DAYS_MSEC = 259200000; - - public void reissueRefreshTokenIfValidTimeleft3days(HttpServletRequest request, HttpServletResponse response, - UserRefreshToken userRefreshToken, AuthToken authRefreshToken, Date now) { - long validTime = authRefreshToken.getTokenClaims().getExpiration().getTime() - now.getTime(); - - // refresh 토큰 기간이 3일 μ΄ν•˜λ‘œ 남은 경우, refresh 토큰 κ°±μ‹  - if (validTime <= THREE_DAYS_MSEC) { - // refresh 토큰 μ„€μ • - long refreshTokenExpiry = authProperties.getTokenProperties().getRefreshTokenExpiry(); - - authRefreshToken = tokenProvider.createAuthToken( - authProperties.getTokenProperties().getTokenSecretKey(), - new Date(now.getTime() + refreshTokenExpiry) - ); - - // DB에 refresh 토큰 μ—…λ°μ΄νŠΈ - userRefreshToken.reissueRefreshToken(authRefreshToken.getToken()); - - int cookieMaxAge = (int)refreshTokenExpiry / 60; - CookieUtil.deleteCookie(request, response, REFRESH_TOKEN); - CookieUtil.addCookie(response, REFRESH_TOKEN, authRefreshToken.getToken(), cookieMaxAge); - } - } - -} diff --git a/backend/src/main/java/com/hjjang/backend/domain/user/service/UserProfileService.java b/backend/src/main/java/com/hjjang/backend/domain/user/service/UserProfileService.java index 0a809fe..92021bc 100644 --- a/backend/src/main/java/com/hjjang/backend/domain/user/service/UserProfileService.java +++ b/backend/src/main/java/com/hjjang/backend/domain/user/service/UserProfileService.java @@ -1,23 +1,19 @@ package com.hjjang.backend.domain.user.service; -import javax.persistence.EntityNotFoundException; - -import org.springframework.stereotype.Service; - import com.hjjang.backend.domain.user.dto.UserProfileDTO; import com.hjjang.backend.domain.user.entity.User; import com.hjjang.backend.domain.user.repository.UserRepository; -import com.hjjang.backend.global.util.UserUtil; - import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import javax.persistence.EntityNotFoundException; @Service @RequiredArgsConstructor public class UserProfileService { private final UserRepository userRepository; - public UserProfileDTO getUserProfile() { - String userId = UserUtil.getLoginUserIdByToken(); + public UserProfileDTO getUserProfile(String userId) { //todo add exception User user = userRepository.findUserByProviderId(userId).orElseThrow(EntityNotFoundException::new); diff --git a/backend/src/main/java/com/hjjang/backend/global/config/JpaAuditingConfig.java b/backend/src/main/java/com/hjjang/backend/global/config/JpaAuditingConfig.java deleted file mode 100644 index f7916b1..0000000 --- a/backend/src/main/java/com/hjjang/backend/global/config/JpaAuditingConfig.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.hjjang.backend.global.config; - -import org.springframework.context.annotation.Configuration; -import org.springframework.data.jpa.repository.config.EnableJpaAuditing; - -@Configuration -@EnableJpaAuditing -public class JpaAuditingConfig { -} \ No newline at end of file diff --git a/backend/src/main/java/com/hjjang/backend/global/config/security/SecurityConfig.java b/backend/src/main/java/com/hjjang/backend/global/config/security/SecurityConfig.java index 63860b0..7f6fa4b 100644 --- a/backend/src/main/java/com/hjjang/backend/global/config/security/SecurityConfig.java +++ b/backend/src/main/java/com/hjjang/backend/global/config/security/SecurityConfig.java @@ -1,7 +1,16 @@ package com.hjjang.backend.global.config.security; -import java.util.Arrays; - +import com.hjjang.backend.domain.user.repository.UserRefreshTokenRepository; +import com.hjjang.backend.global.config.security.properties.AuthProperties; +import com.hjjang.backend.global.security.exception.RestAuthenticationEntryPoint; +import com.hjjang.backend.global.security.filter.TokenAuthenticationFilter; +import com.hjjang.backend.global.security.handler.OAuth2AuthenticationFailureHandler; +import com.hjjang.backend.global.security.handler.OAuth2AuthenticationSuccessHandler; +import com.hjjang.backend.global.security.handler.TokenAccessDeniedHandler; +import com.hjjang.backend.global.security.repository.OAuth2AuthorizationRequestBasedOnCookieRepository; +import com.hjjang.backend.global.security.service.CustomOAuth2UserService; +import com.hjjang.backend.global.security.token.AuthTokenProvider; +import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; @@ -17,18 +26,9 @@ import org.springframework.web.cors.CorsUtils; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; -import com.hjjang.backend.domain.user.repository.UserRefreshTokenRepository; -import com.hjjang.backend.global.config.properties.AuthProperties; -import com.hjjang.backend.global.config.security.exception.RestAuthenticationEntryPoint; -import com.hjjang.backend.global.config.security.filter.TokenAuthenticationFilter; -import com.hjjang.backend.global.config.security.handler.OAuth2AuthenticationFailureHandler; -import com.hjjang.backend.global.config.security.handler.OAuth2AuthenticationSuccessHandler; -import com.hjjang.backend.global.config.security.handler.TokenAccessDeniedHandler; -import com.hjjang.backend.global.config.security.repository.OAuth2AuthorizationRequestBasedOnCookieRepository; -import com.hjjang.backend.global.config.security.service.CustomOAuth2UserService; -import com.hjjang.backend.global.config.security.token.AuthTokenProvider; +import java.util.Arrays; -import lombok.RequiredArgsConstructor; +import static com.hjjang.backend.domain.user.entity.RoleType.USER; @Configuration @EnableWebSecurity @@ -48,7 +48,7 @@ public void configure(WebSecurity web) throws Exception { // swagger web.ignoring().antMatchers( "/v2/api-docs", "/configuration/ui", "/swagger-resources", - "/configuration/security", "/swagger-ui.html", "/webjars/**", "/swagger/**"); + "/configuration/security", "/swagger-ui.html", "/webjars/**", "/swagger/**", "/h2-console/**"); } @@ -75,10 +75,11 @@ protected void configure(HttpSecurity http) throws Exception { .accessDeniedHandler(tokenAccessDeniedHandler) // 둜그인 κ±°λΆ€ μ˜ˆμ™Έ .and() .authorizeRequests() - .antMatchers("/login", "/accounts", "/swagger-resources/**", "/swagger-ui/**").permitAll() + .antMatchers("/login", "/accounts", "/swagger-resources/**", "/swagger-ui/**", "/h2-console/**").permitAll() .antMatchers("/oauth2/authorization/**", "**/oauth2/code/*").permitAll() .requestMatchers(CorsUtils::isPreFlightRequest).permitAll() //corsλ₯Ό 검증 ν•˜λŠ” option ν•¨μˆ˜μ˜ 경우 λ³„λ„μ˜ filter 없이 ν—ˆμš© // .antMatchers("/api/**").hasAnyAuthority(RoleType.USER.getCode()) + .antMatchers("/api/v1/users/**").hasAnyAuthority(USER.getCode()) .anyRequest().permitAll(); http @@ -128,7 +129,7 @@ public UrlBasedCorsConfigurationSource corsConfigurationSource() { @Override @Bean(BeanIds.AUTHENTICATION_MANAGER) protected AuthenticationManager authenticationManager() throws Exception { - return super.authenticationManager(); + return super.authenticationManagerBean(); } /* diff --git a/backend/src/main/java/com/hjjang/backend/global/config/properties/AuthProperties.java b/backend/src/main/java/com/hjjang/backend/global/config/security/properties/AuthProperties.java similarity index 96% rename from backend/src/main/java/com/hjjang/backend/global/config/properties/AuthProperties.java rename to backend/src/main/java/com/hjjang/backend/global/config/security/properties/AuthProperties.java index 8a7afa5..5129c32 100644 --- a/backend/src/main/java/com/hjjang/backend/global/config/properties/AuthProperties.java +++ b/backend/src/main/java/com/hjjang/backend/global/config/security/properties/AuthProperties.java @@ -1,13 +1,12 @@ -package com.hjjang.backend.global.config.properties; - -import java.util.ArrayList; -import java.util.List; +package com.hjjang.backend.global.config.security.properties; +import lombok.Data; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Configuration; -import lombok.Data; +import java.util.ArrayList; +import java.util.List; @Data @Configuration diff --git a/backend/src/main/java/com/hjjang/backend/global/domain/BaseTimeEntity.java b/backend/src/main/java/com/hjjang/backend/global/domain/BaseTimeEntity.java new file mode 100644 index 0000000..fddf0d6 --- /dev/null +++ b/backend/src/main/java/com/hjjang/backend/global/domain/BaseTimeEntity.java @@ -0,0 +1,21 @@ +package com.hjjang.backend.global.domain; + +import lombok.Getter; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import javax.persistence.EntityListeners; +import javax.persistence.MappedSuperclass; +import java.time.LocalDateTime; + +@Getter +@EntityListeners(value = {AuditingEntityListener.class}) +@MappedSuperclass +public abstract class BaseTimeEntity { + @CreatedDate + private LocalDateTime createdDate; + + @LastModifiedDate + private LocalDateTime updatedDate; +} \ No newline at end of file diff --git a/backend/src/main/java/com/hjjang/backend/global/execption/handler/GlobalExceptionHandler.java b/backend/src/main/java/com/hjjang/backend/global/execption/handler/GlobalExceptionHandler.java index a6f4a42..73db56a 100644 --- a/backend/src/main/java/com/hjjang/backend/global/execption/handler/GlobalExceptionHandler.java +++ b/backend/src/main/java/com/hjjang/backend/global/execption/handler/GlobalExceptionHandler.java @@ -1,21 +1,15 @@ package com.hjjang.backend.global.execption.handler; -import com.hjjang.backend.global.response.response.ErrorResponse; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; -import static com.hjjang.backend.global.response.code.ErrorCode.INTERNAL_SERVER_ERROR; - @RestControllerAdvice public class GlobalExceptionHandler { // 500 μ—λŸ¬ - @ExceptionHandler - protected ResponseEntity handleException(Exception e) { - final ErrorResponse response = ErrorResponse.of(INTERNAL_SERVER_ERROR); - return new ResponseEntity<>(response, HttpStatus.INTERNAL_SERVER_ERROR); - } +// @ExceptionHandler +// protected ResponseEntity handleException(Exception e) { +// final ErrorResponse response = ErrorResponse.of(INTERNAL_SERVER_ERROR); +// return new ResponseEntity<>(response, HttpStatus.INTERNAL_SERVER_ERROR); +// } } diff --git a/backend/src/main/java/com/hjjang/backend/global/response/code/ErrorCode.java b/backend/src/main/java/com/hjjang/backend/global/response/code/ErrorCode.java index 233d6d7..d8cf3df 100644 --- a/backend/src/main/java/com/hjjang/backend/global/response/code/ErrorCode.java +++ b/backend/src/main/java/com/hjjang/backend/global/response/code/ErrorCode.java @@ -1,7 +1,5 @@ package com.hjjang.backend.global.response.code; -import org.springframework.http.HttpStatus; - import lombok.AllArgsConstructor; import lombok.Getter; @@ -10,16 +8,15 @@ public enum ErrorCode { //Global - INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR.value(), "G001", "λ‚΄λΆ€ μ„œλ²„ 였λ₯˜"), - NOT_ALLOWED_METHOD(HttpStatus.METHOD_NOT_ALLOWED.value(), "G002", "ν—ˆμš© λ˜μ§€ μ•Šμ€ HTTP method"), - INVALID_INPUT_VALUE(HttpStatus.BAD_REQUEST.value(), "G003", "검증 λ˜μ§€ μ•Šμ€ μž…λ ₯"), + INTERNAL_SERVER_ERROR("G001", "λ‚΄λΆ€ μ„œλ²„ 였λ₯˜"), + NOT_ALLOWED_METHOD("G002", "ν—ˆμš© λ˜μ§€ μ•Šμ€ HTTP method"), + INVALID_INPUT_VALUE("G003", "검증 λ˜μ§€ μ•Šμ€ μž…λ ₯"), //User - MEMBER_NOT_FOUND(HttpStatus.BAD_REQUEST.value(), "U001", "쑴재 ν•˜μ§€ μ•ŠλŠ” μ‚¬μš©μž"), - NO_AUTHORITY(HttpStatus.FORBIDDEN.value(), "U002", "κΆŒν•œμ΄ μ—†μŒ"),; + MEMBER_NOT_FOUND("U001", "쑴재 ν•˜μ§€ μ•ŠλŠ” μ‚¬μš©μž"), + NO_AUTHORITY("U002", "κΆŒν•œμ΄ μ—†μŒ"),; - private final int httpStatus; private final String code; private final String message; } diff --git a/backend/src/main/java/com/hjjang/backend/global/response/code/SuccessCode.java b/backend/src/main/java/com/hjjang/backend/global/response/code/SuccessCode.java index f045aae..506a47c 100644 --- a/backend/src/main/java/com/hjjang/backend/global/response/code/SuccessCode.java +++ b/backend/src/main/java/com/hjjang/backend/global/response/code/SuccessCode.java @@ -8,9 +8,8 @@ public enum SuccessCode { //User - USERPROFILE_SUCCESS(200, "U001", "ν”„λ‘œν•„ 쑰회 μ™„λ£Œ."),; + USER_PROFILE_SUCCESS( "U001", "ν”„λ‘œν•„ 쑰회 μ™„λ£Œ."),; - private final int status; private final String code; private final String message; } diff --git a/backend/src/main/java/com/hjjang/backend/global/response/response/ErrorResponse.java b/backend/src/main/java/com/hjjang/backend/global/response/response/ErrorResponse.java index bc53a6d..e577079 100644 --- a/backend/src/main/java/com/hjjang/backend/global/response/response/ErrorResponse.java +++ b/backend/src/main/java/com/hjjang/backend/global/response/response/ErrorResponse.java @@ -20,14 +20,12 @@ public class ErrorResponse { private ErrorResponse(final ErrorCode code, final List errors) { this.message = code.getMessage(); - this.status = code.getHttpStatus(); this.errors = errors; this.code = code.getCode(); } private ErrorResponse(final ErrorCode code) { this.message = code.getMessage(); - this.status = code.getHttpStatus(); this.code = code.getCode(); this.errors = new ArrayList<>(); } diff --git a/backend/src/main/java/com/hjjang/backend/global/response/response/SuccessResponse.java b/backend/src/main/java/com/hjjang/backend/global/response/response/SuccessResponse.java index 9f53bc5..9e571a6 100644 --- a/backend/src/main/java/com/hjjang/backend/global/response/response/SuccessResponse.java +++ b/backend/src/main/java/com/hjjang/backend/global/response/response/SuccessResponse.java @@ -7,8 +7,6 @@ @Getter public class SuccessResponse { - @Schema(description = "status code") - private final int status; @Schema(description = "Business code") private final String code; @Schema(description = "response message") @@ -25,7 +23,6 @@ public static SuccessResponse of(SuccessCode successCode) { } public SuccessResponse(SuccessCode successCode, Object data) { - this.status = successCode.getStatus(); this.code = successCode.getCode(); this.message = successCode.getMessage(); this.data = data; diff --git a/backend/src/main/java/com/hjjang/backend/global/config/security/AuthProviderConfig.java b/backend/src/main/java/com/hjjang/backend/global/security/AuthProviderConfig.java similarity index 69% rename from backend/src/main/java/com/hjjang/backend/global/config/security/AuthProviderConfig.java rename to backend/src/main/java/com/hjjang/backend/global/security/AuthProviderConfig.java index 73af0d7..0b8f23c 100644 --- a/backend/src/main/java/com/hjjang/backend/global/config/security/AuthProviderConfig.java +++ b/backend/src/main/java/com/hjjang/backend/global/security/AuthProviderConfig.java @@ -1,7 +1,7 @@ -package com.hjjang.backend.global.config.security; +package com.hjjang.backend.global.security; -import com.hjjang.backend.global.config.properties.AuthProperties; -import com.hjjang.backend.global.config.security.token.AuthTokenProvider; +import com.hjjang.backend.global.config.security.properties.AuthProperties; +import com.hjjang.backend.global.security.token.AuthTokenProvider; import lombok.RequiredArgsConstructor; diff --git a/backend/src/main/java/com/hjjang/backend/global/config/security/exception/RestAuthenticationEntryPoint.java b/backend/src/main/java/com/hjjang/backend/global/security/exception/RestAuthenticationEntryPoint.java similarity index 93% rename from backend/src/main/java/com/hjjang/backend/global/config/security/exception/RestAuthenticationEntryPoint.java rename to backend/src/main/java/com/hjjang/backend/global/security/exception/RestAuthenticationEntryPoint.java index 61db8bc..28b47c1 100644 --- a/backend/src/main/java/com/hjjang/backend/global/config/security/exception/RestAuthenticationEntryPoint.java +++ b/backend/src/main/java/com/hjjang/backend/global/security/exception/RestAuthenticationEntryPoint.java @@ -1,15 +1,13 @@ -package com.hjjang.backend.global.config.security.exception; +package com.hjjang.backend.global.security.exception; -import java.io.IOException; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.AuthenticationEntryPoint; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; - -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.web.AuthenticationEntryPoint; - -import lombok.extern.slf4j.Slf4j; +import java.io.IOException; @Slf4j public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint { diff --git a/backend/src/main/java/com/hjjang/backend/global/config/security/exception/TokenValidFailedException.java b/backend/src/main/java/com/hjjang/backend/global/security/exception/TokenValidFailedException.java similarity index 80% rename from backend/src/main/java/com/hjjang/backend/global/config/security/exception/TokenValidFailedException.java rename to backend/src/main/java/com/hjjang/backend/global/security/exception/TokenValidFailedException.java index 4b44d26..fc36540 100644 --- a/backend/src/main/java/com/hjjang/backend/global/config/security/exception/TokenValidFailedException.java +++ b/backend/src/main/java/com/hjjang/backend/global/security/exception/TokenValidFailedException.java @@ -1,4 +1,4 @@ -package com.hjjang.backend.global.config.security.exception; +package com.hjjang.backend.global.security.exception; public class TokenValidFailedException extends RuntimeException { diff --git a/backend/src/main/java/com/hjjang/backend/global/config/security/filter/TokenAuthenticationFilter.java b/backend/src/main/java/com/hjjang/backend/global/security/filter/TokenAuthenticationFilter.java similarity index 86% rename from backend/src/main/java/com/hjjang/backend/global/config/security/filter/TokenAuthenticationFilter.java rename to backend/src/main/java/com/hjjang/backend/global/security/filter/TokenAuthenticationFilter.java index dae1dea..1cedb98 100644 --- a/backend/src/main/java/com/hjjang/backend/global/config/security/filter/TokenAuthenticationFilter.java +++ b/backend/src/main/java/com/hjjang/backend/global/security/filter/TokenAuthenticationFilter.java @@ -1,22 +1,19 @@ -package com.hjjang.backend.global.config.security.filter; +package com.hjjang.backend.global.security.filter; -import java.io.IOException; +import com.hjjang.backend.global.security.token.AuthToken; +import com.hjjang.backend.global.security.token.AuthTokenProvider; +import com.hjjang.backend.global.util.HeaderUtil; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.web.filter.OncePerRequestFilter; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; - -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.web.filter.OncePerRequestFilter; - -import com.hjjang.backend.global.config.security.token.AuthToken; -import com.hjjang.backend.global.config.security.token.AuthTokenProvider; -import com.hjjang.backend.global.util.HeaderUtil; - -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; +import java.io.IOException; @Slf4j @RequiredArgsConstructor diff --git a/backend/src/main/java/com/hjjang/backend/global/config/security/handler/OAuth2AuthenticationFailureHandler.java b/backend/src/main/java/com/hjjang/backend/global/security/handler/OAuth2AuthenticationFailureHandler.java similarity index 78% rename from backend/src/main/java/com/hjjang/backend/global/config/security/handler/OAuth2AuthenticationFailureHandler.java rename to backend/src/main/java/com/hjjang/backend/global/security/handler/OAuth2AuthenticationFailureHandler.java index 43f0aae..93dbb72 100644 --- a/backend/src/main/java/com/hjjang/backend/global/config/security/handler/OAuth2AuthenticationFailureHandler.java +++ b/backend/src/main/java/com/hjjang/backend/global/security/handler/OAuth2AuthenticationFailureHandler.java @@ -1,23 +1,18 @@ -package com.hjjang.backend.global.config.security.handler; - -import static com.hjjang.backend.global.config.security.repository.OAuth2AuthorizationRequestBasedOnCookieRepository.*; - -import java.io.IOException; - -import javax.servlet.ServletException; -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +package com.hjjang.backend.global.security.handler; +import com.hjjang.backend.global.security.repository.OAuth2AuthorizationRequestBasedOnCookieRepository; +import com.hjjang.backend.global.util.CookieUtil; +import lombok.RequiredArgsConstructor; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; import org.springframework.stereotype.Component; import org.springframework.web.util.UriComponentsBuilder; -import com.hjjang.backend.global.config.security.repository.OAuth2AuthorizationRequestBasedOnCookieRepository; -import com.hjjang.backend.global.util.CookieUtil; - -import lombok.RequiredArgsConstructor; +import javax.servlet.ServletException; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; @Component @RequiredArgsConstructor @@ -28,7 +23,7 @@ public class OAuth2AuthenticationFailureHandler extends SimpleUrlAuthenticationF @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { - String targetUrl = CookieUtil.getCookie(request, REDIRECT_URI_PARAM_COOKIE_NAME) + String targetUrl = CookieUtil.getCookie(request, OAuth2AuthorizationRequestBasedOnCookieRepository.REDIRECT_URI_PARAM_COOKIE_NAME) .map(Cookie::getValue) .orElse(("/")); diff --git a/backend/src/main/java/com/hjjang/backend/global/config/security/handler/OAuth2AuthenticationSuccessHandler.java b/backend/src/main/java/com/hjjang/backend/global/security/handler/OAuth2AuthenticationSuccessHandler.java similarity index 88% rename from backend/src/main/java/com/hjjang/backend/global/config/security/handler/OAuth2AuthenticationSuccessHandler.java rename to backend/src/main/java/com/hjjang/backend/global/security/handler/OAuth2AuthenticationSuccessHandler.java index 2ae9bdf..3e821ae 100644 --- a/backend/src/main/java/com/hjjang/backend/global/config/security/handler/OAuth2AuthenticationSuccessHandler.java +++ b/backend/src/main/java/com/hjjang/backend/global/security/handler/OAuth2AuthenticationSuccessHandler.java @@ -1,15 +1,15 @@ -package com.hjjang.backend.global.config.security.handler; +package com.hjjang.backend.global.security.handler; import com.hjjang.backend.domain.user.entity.ProviderType; import com.hjjang.backend.domain.user.entity.RoleType; import com.hjjang.backend.domain.user.entity.UserRefreshToken; import com.hjjang.backend.domain.user.repository.UserRefreshTokenRepository; -import com.hjjang.backend.global.config.properties.AuthProperties; -import com.hjjang.backend.global.config.security.parser.ParsingUserContext; -import com.hjjang.backend.global.config.security.parser.ParsingUserContextFactory; -import com.hjjang.backend.global.config.security.repository.OAuth2AuthorizationRequestBasedOnCookieRepository; -import com.hjjang.backend.global.config.security.token.AuthToken; -import com.hjjang.backend.global.config.security.token.AuthTokenProvider; +import com.hjjang.backend.global.config.security.properties.AuthProperties; +import com.hjjang.backend.global.security.parser.ParsingUserContext; +import com.hjjang.backend.global.security.parser.ParsingUserContextFactory; +import com.hjjang.backend.global.security.repository.OAuth2AuthorizationRequestBasedOnCookieRepository; +import com.hjjang.backend.global.security.token.AuthToken; +import com.hjjang.backend.global.security.token.AuthTokenProvider; import com.hjjang.backend.global.util.CookieUtil; import lombok.RequiredArgsConstructor; import org.springframework.security.core.Authentication; @@ -30,8 +30,8 @@ import java.util.Date; import java.util.Optional; -import static com.hjjang.backend.global.config.security.repository.OAuth2AuthorizationRequestBasedOnCookieRepository.REDIRECT_URI_PARAM_COOKIE_NAME; -import static com.hjjang.backend.global.config.security.repository.OAuth2AuthorizationRequestBasedOnCookieRepository.REFRESH_TOKEN; +import static com.hjjang.backend.global.security.repository.OAuth2AuthorizationRequestBasedOnCookieRepository.REDIRECT_URI_PARAM_COOKIE_NAME; +import static com.hjjang.backend.global.security.repository.OAuth2AuthorizationRequestBasedOnCookieRepository.REFRESH_TOKEN; @Component @RequiredArgsConstructor diff --git a/backend/src/main/java/com/hjjang/backend/global/config/security/handler/TokenAccessDeniedHandler.java b/backend/src/main/java/com/hjjang/backend/global/security/handler/TokenAccessDeniedHandler.java similarity index 94% rename from backend/src/main/java/com/hjjang/backend/global/config/security/handler/TokenAccessDeniedHandler.java rename to backend/src/main/java/com/hjjang/backend/global/security/handler/TokenAccessDeniedHandler.java index f974a06..5374e7c 100644 --- a/backend/src/main/java/com/hjjang/backend/global/config/security/handler/TokenAccessDeniedHandler.java +++ b/backend/src/main/java/com/hjjang/backend/global/security/handler/TokenAccessDeniedHandler.java @@ -1,4 +1,4 @@ -package com.hjjang.backend.global.config.security.handler; +package com.hjjang.backend.global.security.handler; import java.io.IOException; import javax.servlet.http.HttpServletRequest; diff --git a/backend/src/main/java/com/hjjang/backend/global/config/security/parser/KakaoParsingParsingUserContext.java b/backend/src/main/java/com/hjjang/backend/global/security/parser/KakaoParsingParsingUserContext.java similarity index 89% rename from backend/src/main/java/com/hjjang/backend/global/config/security/parser/KakaoParsingParsingUserContext.java rename to backend/src/main/java/com/hjjang/backend/global/security/parser/KakaoParsingParsingUserContext.java index 292e710..583bace 100644 --- a/backend/src/main/java/com/hjjang/backend/global/config/security/parser/KakaoParsingParsingUserContext.java +++ b/backend/src/main/java/com/hjjang/backend/global/security/parser/KakaoParsingParsingUserContext.java @@ -1,6 +1,6 @@ -package com.hjjang.backend.global.config.security.parser; +package com.hjjang.backend.global.security.parser; -import static com.hjjang.backend.global.config.security.parser.KakaoParsingParsingUserContext.KakaoInfoProperties.*; +import static com.hjjang.backend.global.security.parser.KakaoParsingParsingUserContext.KakaoInfoProperties.*; import java.util.Map; diff --git a/backend/src/main/java/com/hjjang/backend/global/config/security/parser/ParsingUserContext.java b/backend/src/main/java/com/hjjang/backend/global/security/parser/ParsingUserContext.java similarity index 88% rename from backend/src/main/java/com/hjjang/backend/global/config/security/parser/ParsingUserContext.java rename to backend/src/main/java/com/hjjang/backend/global/security/parser/ParsingUserContext.java index e3392a6..f3198a3 100644 --- a/backend/src/main/java/com/hjjang/backend/global/config/security/parser/ParsingUserContext.java +++ b/backend/src/main/java/com/hjjang/backend/global/security/parser/ParsingUserContext.java @@ -1,4 +1,4 @@ -package com.hjjang.backend.global.config.security.parser; +package com.hjjang.backend.global.security.parser; import java.util.Map; diff --git a/backend/src/main/java/com/hjjang/backend/global/config/security/parser/ParsingUserContextFactory.java b/backend/src/main/java/com/hjjang/backend/global/security/parser/ParsingUserContextFactory.java similarity index 88% rename from backend/src/main/java/com/hjjang/backend/global/config/security/parser/ParsingUserContextFactory.java rename to backend/src/main/java/com/hjjang/backend/global/security/parser/ParsingUserContextFactory.java index 660a8dc..dac3639 100644 --- a/backend/src/main/java/com/hjjang/backend/global/config/security/parser/ParsingUserContextFactory.java +++ b/backend/src/main/java/com/hjjang/backend/global/security/parser/ParsingUserContextFactory.java @@ -1,9 +1,9 @@ -package com.hjjang.backend.global.config.security.parser; - -import java.util.Map; +package com.hjjang.backend.global.security.parser; import com.hjjang.backend.domain.user.entity.ProviderType; +import java.util.Map; + public class ParsingUserContextFactory { public static ParsingUserContext getParsingParsingUserContext(ProviderType providerType, Map attributes) { diff --git a/backend/src/main/java/com/hjjang/backend/global/config/security/principal/UserPrincipal.java b/backend/src/main/java/com/hjjang/backend/global/security/principal/UserPrincipal.java similarity index 97% rename from backend/src/main/java/com/hjjang/backend/global/config/security/principal/UserPrincipal.java rename to backend/src/main/java/com/hjjang/backend/global/security/principal/UserPrincipal.java index cbae845..b34f7dc 100644 --- a/backend/src/main/java/com/hjjang/backend/global/config/security/principal/UserPrincipal.java +++ b/backend/src/main/java/com/hjjang/backend/global/security/principal/UserPrincipal.java @@ -1,4 +1,4 @@ -package com.hjjang.backend.global.config.security.principal; +package com.hjjang.backend.global.security.principal; import com.hjjang.backend.domain.user.entity.RoleType; import com.hjjang.backend.domain.user.entity.User; diff --git a/backend/src/main/java/com/hjjang/backend/global/config/security/repository/OAuth2AuthorizationRequestBasedOnCookieRepository.java b/backend/src/main/java/com/hjjang/backend/global/security/repository/OAuth2AuthorizationRequestBasedOnCookieRepository.java similarity index 97% rename from backend/src/main/java/com/hjjang/backend/global/config/security/repository/OAuth2AuthorizationRequestBasedOnCookieRepository.java rename to backend/src/main/java/com/hjjang/backend/global/security/repository/OAuth2AuthorizationRequestBasedOnCookieRepository.java index dc4e802..b08fc15 100644 --- a/backend/src/main/java/com/hjjang/backend/global/config/security/repository/OAuth2AuthorizationRequestBasedOnCookieRepository.java +++ b/backend/src/main/java/com/hjjang/backend/global/security/repository/OAuth2AuthorizationRequestBasedOnCookieRepository.java @@ -1,4 +1,4 @@ -package com.hjjang.backend.global.config.security.repository; +package com.hjjang.backend.global.security.repository; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; diff --git a/backend/src/main/java/com/hjjang/backend/global/config/security/service/CustomOAuth2UserService.java b/backend/src/main/java/com/hjjang/backend/global/security/service/CustomOAuth2UserService.java similarity index 88% rename from backend/src/main/java/com/hjjang/backend/global/config/security/service/CustomOAuth2UserService.java rename to backend/src/main/java/com/hjjang/backend/global/security/service/CustomOAuth2UserService.java index 1224372..ec921f5 100644 --- a/backend/src/main/java/com/hjjang/backend/global/config/security/service/CustomOAuth2UserService.java +++ b/backend/src/main/java/com/hjjang/backend/global/security/service/CustomOAuth2UserService.java @@ -1,12 +1,12 @@ -package com.hjjang.backend.global.config.security.service; +package com.hjjang.backend.global.security.service; import com.hjjang.backend.domain.user.entity.Agreement; import com.hjjang.backend.domain.user.entity.RoleType; import com.hjjang.backend.domain.user.entity.User; import com.hjjang.backend.domain.user.repository.UserRepository; -import com.hjjang.backend.global.config.security.parser.KakaoParsingParsingUserContext; -import com.hjjang.backend.global.config.security.parser.ParsingUserContext; -import com.hjjang.backend.global.config.security.principal.UserPrincipal; +import com.hjjang.backend.global.security.parser.KakaoParsingParsingUserContext; +import com.hjjang.backend.global.security.parser.ParsingUserContext; +import com.hjjang.backend.global.security.principal.UserPrincipal; import lombok.RequiredArgsConstructor; import org.springframework.security.authentication.InternalAuthenticationServiceException; import org.springframework.security.core.AuthenticationException; @@ -62,6 +62,8 @@ private User createUser(ParsingUserContext userInfo) { .mannerTemperature((long)36.5) .imageUrl(userInfo.getImageUrl()) .role(RoleType.USER) + .isEmailVerification(false) + .isBlocked(false) .build(); return userRepository.saveAndFlush(user); } diff --git a/backend/src/main/java/com/hjjang/backend/global/config/security/token/AuthToken.java b/backend/src/main/java/com/hjjang/backend/global/security/token/AuthToken.java similarity index 66% rename from backend/src/main/java/com/hjjang/backend/global/config/security/token/AuthToken.java rename to backend/src/main/java/com/hjjang/backend/global/security/token/AuthToken.java index 1a333e2..a212b26 100644 --- a/backend/src/main/java/com/hjjang/backend/global/config/security/token/AuthToken.java +++ b/backend/src/main/java/com/hjjang/backend/global/security/token/AuthToken.java @@ -1,18 +1,13 @@ -package com.hjjang.backend.global.config.security.token; +package com.hjjang.backend.global.security.token; -import java.security.Key; -import java.util.Date; - -import io.jsonwebtoken.Claims; -import io.jsonwebtoken.ExpiredJwtException; -import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.MalformedJwtException; -import io.jsonwebtoken.SignatureAlgorithm; -import io.jsonwebtoken.UnsupportedJwtException; +import io.jsonwebtoken.*; import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import java.security.Key; +import java.util.Date; + @Slf4j @RequiredArgsConstructor public class AuthToken { @@ -35,19 +30,19 @@ public class AuthToken { private String createAuthToken(String id, Date expiry) { return Jwts.builder() - .setSubject(id) - .signWith(key, SignatureAlgorithm.HS256) - .setExpiration(expiry) - .compact(); + .setSubject(id) + .signWith(key, SignatureAlgorithm.HS256) + .setExpiration(expiry) + .compact(); } private String createAuthToken(String id, String role, Date expiry) { return Jwts.builder() - .setSubject(id) - .claim(AUTHORITIES_KEY, role) - .signWith(key, SignatureAlgorithm.HS256) - .setExpiration(expiry) - .compact(); + .setSubject(id) + .claim(AUTHORITIES_KEY, role) + .signWith(key, SignatureAlgorithm.HS256) + .setExpiration(expiry) + .compact(); } public boolean validate() { @@ -57,10 +52,10 @@ public boolean validate() { public Claims getTokenClaims() { try { return Jwts.parserBuilder() - .setSigningKey(key) - .build() - .parseClaimsJws(token) - .getBody(); + .setSigningKey(key) + .build() + .parseClaimsJws(token) + .getBody(); } catch (SecurityException e) { log.info("Invalid JWT signature."); } catch (MalformedJwtException e) { @@ -78,10 +73,10 @@ public Claims getTokenClaims() { public Claims getExpiredTokenClaims() { try { Jwts.parserBuilder() - .setSigningKey(key) - .build() - .parseClaimsJws(token) - .getBody(); + .setSigningKey(key) + .build() + .parseClaimsJws(token) + .getBody(); } catch (ExpiredJwtException e) { log.info("Expired JWT token."); return e.getClaims(); diff --git a/backend/src/main/java/com/hjjang/backend/global/config/security/token/AuthTokenProvider.java b/backend/src/main/java/com/hjjang/backend/global/security/token/AuthTokenProvider.java similarity index 92% rename from backend/src/main/java/com/hjjang/backend/global/config/security/token/AuthTokenProvider.java rename to backend/src/main/java/com/hjjang/backend/global/security/token/AuthTokenProvider.java index 84d4f0e..912c135 100644 --- a/backend/src/main/java/com/hjjang/backend/global/config/security/token/AuthTokenProvider.java +++ b/backend/src/main/java/com/hjjang/backend/global/security/token/AuthTokenProvider.java @@ -1,22 +1,20 @@ -package com.hjjang.backend.global.config.security.token; - -import java.security.Key; -import java.util.Arrays; -import java.util.Collection; -import java.util.Date; -import java.util.stream.Collectors; +package com.hjjang.backend.global.security.token; +import com.hjjang.backend.global.security.exception.TokenValidFailedException; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.security.Keys; +import lombok.extern.slf4j.Slf4j; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.User; -import com.hjjang.backend.global.config.security.exception.TokenValidFailedException; - -import io.jsonwebtoken.Claims; -import io.jsonwebtoken.security.Keys; -import lombok.extern.slf4j.Slf4j; +import java.security.Key; +import java.util.Arrays; +import java.util.Collection; +import java.util.Date; +import java.util.stream.Collectors; @Slf4j public class AuthTokenProvider { diff --git a/backend/src/main/resources/log4j2.xml b/backend/src/main/resources/log4j2.xml index 85b2612..d490d33 100644 --- a/backend/src/main/resources/log4j2.xml +++ b/backend/src/main/resources/log4j2.xml @@ -1,5 +1,5 @@ - + Default-Setting @@ -11,35 +11,21 @@ - - - - - - - - + - + - + - - - - - - - \ No newline at end of file diff --git a/backend/src/main/resources/static.docs/api.adoc b/backend/src/main/resources/static.docs/api.adoc new file mode 100644 index 0000000..adb71df --- /dev/null +++ b/backend/src/main/resources/static.docs/api.adoc @@ -0,0 +1,46 @@ +ifndef::snippets[] +:snippets: ./build/generated-snippets +endif::[] +:doctype: book +:icons: font +:source-highlighter: highlightjs +:toc: left +:toclevels: 2 +:sectlinks: +:docinfo: shared-head + += REST API Document + +[[introduction]] +== Introduction + +✨BAuction (Book + Auction)✨ + +쀑고책 μ‹€μ‹œκ°„ 경맀 μ„œλΉ„μŠ€ + +[[common]] +== Common + +=== Domain + +|=== +| ν™˜κ²½ | Domain +| 둜컬 μ„œλ²„ +| `http://localhost:3000` +|=== + +=== Exception + +|=== +| μƒνƒœ μ½”λ“œ | μ„€λͺ… + +| 400 +| `잘λͺ»λœ 데이터` + +| 401 +| `κΆŒν•œ μ—†μŒ` +|=== +== User API +operation::v1/users/profile[snippets='http-request,request-fields,http-response,response-fields'] +|=== +|=== diff --git a/backend/src/test/java/com/hjjang/backend/domain/post/controller/PostControllerTest.java b/backend/src/test/java/com/hjjang/backend/domain/post/controller/PostControllerTest.java index 5ed3ef6..e753e20 100644 --- a/backend/src/test/java/com/hjjang/backend/domain/post/controller/PostControllerTest.java +++ b/backend/src/test/java/com/hjjang/backend/domain/post/controller/PostControllerTest.java @@ -1,9 +1,7 @@ package com.hjjang.backend.domain.post.controller; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.hjjang.backend.domain.post.domain.entity.Post; -import com.hjjang.backend.domain.post.domain.entity.PostDefaultValue; import com.hjjang.backend.domain.post.dto.PostMapper; import com.hjjang.backend.domain.post.dto.PostRequestDto; import com.hjjang.backend.domain.post.dto.PostResponseDto; @@ -11,26 +9,16 @@ import com.hjjang.backend.domain.user.entity.Agreement; import com.hjjang.backend.domain.user.entity.RoleType; import com.hjjang.backend.domain.user.entity.User; -import com.hjjang.backend.domain.user.repository.UserRefreshTokenRepository; -import com.hjjang.backend.global.config.properties.AuthProperties; -import com.hjjang.backend.global.config.security.handler.TokenAccessDeniedHandler; -import com.hjjang.backend.global.config.security.service.CustomOAuth2UserService; -import com.hjjang.backend.global.config.security.token.AuthTokenProvider; import com.hjjang.backend.global.util.UserUtil; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.http.MediaType; -import org.springframework.restdocs.RestDocumentationExtension; import org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.RequestBuilder; -import org.springframework.test.web.servlet.result.MockMvcResultMatchers; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.filter.CharacterEncodingFilter; @@ -43,7 +31,8 @@ import static com.hjjang.backend.domain.post.domain.entity.PostDefaultValue.*; import static org.mockito.Mockito.*; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @ExtendWith(SpringExtension.class) @WebMvcTest(controllers = PostController.class) @@ -121,8 +110,8 @@ void setUp(WebApplicationContext context) { .itemPrice(givenItemPrice) .build(); - User expectSavedUser = new User(givenId, providerId, nickName, email, - mannerTemperature, imagePath, LocalDateTime.now(), pushAgree, univId, role); +// User expectSavedUser = new User(givenId, providerId, nickName, email, +// mannerTemperature, imagePath, LocalDateTime.now(), pushAgree, univId, role); Post expectSavedPost = new Post(givenId, givenUser, givenTitle, givenContent, givenItemPrice, DEFAULT_VIEWS, DEFAULT_INTEREST_NUMBER, DEFAULT_CHAT_NUMBER, @@ -168,7 +157,6 @@ void setUp(WebApplicationContext context) { .thenReturn(expectUpdatedPost); doNothing().when(postService).deleteOneById(anyLong()); - when(userUtil.getLoginUserByToken()).thenReturn(expectSavedUser); } @Test diff --git a/backend/src/test/java/com/hjjang/backend/domain/user/controller/UserControllerTest.java b/backend/src/test/java/com/hjjang/backend/domain/user/controller/UserControllerTest.java new file mode 100644 index 0000000..f8f4a26 --- /dev/null +++ b/backend/src/test/java/com/hjjang/backend/domain/user/controller/UserControllerTest.java @@ -0,0 +1,114 @@ +package com.hjjang.backend.domain.user.controller; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.hjjang.backend.domain.user.controller.docs.UserRestDocument; +import com.hjjang.backend.domain.user.dto.UserProfileDTO; +import com.hjjang.backend.domain.user.service.UserProfileService; +import com.hjjang.backend.global.security.CustomSecurityExtension; +import com.hjjang.backend.global.security.WithMockCustomUser; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.restdocs.RestDocumentationContextProvider; +import org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.filter.CharacterEncodingFilter; + +import java.nio.charset.StandardCharsets; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; +import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.documentationConfiguration; +import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + + +@WebMvcTest(controllers = UserController.class) +class UserControllerTest extends CustomSecurityExtension { + + @MockBean + private UserProfileService userProfileService; + + private ObjectMapper objectMapper; + + private MockMvc mockMvc; + + @BeforeEach + void setUp(WebApplicationContext webApplicationContext, + RestDocumentationContextProvider restDocumentationContextProvider) { + //security κ΄€λ ¨ user repository mock + super.userInfoSetUp(); + objectMapper = new ObjectMapper(); + + mockMvc = MockMvcBuilders + .webAppContextSetup(webApplicationContext) + .addFilter(new CharacterEncodingFilter(StandardCharsets.UTF_8.name(), true)) + .apply(documentationConfiguration(restDocumentationContextProvider)) + .apply(springSecurity()) + .build(); + + } + + @Test + @WithMockCustomUser + @DisplayName("μœ μ € ν”„λ‘œν•„ 정보 쑰회 성곡") + void getUserProfileSuccess() throws Exception { + UserProfileDTO userProfileDTO = UserProfileDTO.builder() + .userEmail("tester@tukorea.ac.kr") + .userImageUrl("이미지 urlμž…λ‹ˆλ‹€") + .userNickname("λ‚΄ λ‹‰λ„€μž„μ€ ν…ŒμŠ€νƒ€") + .userMannerTemperature(36L) + .userUnivName("ν•œκ΅­κ³΅ν•™λŒ€") + .build(); + when(userProfileService.getUserProfile(any())).thenReturn(userProfileDTO); + + mockMvc.perform(RestDocumentationRequestBuilders.get("/api/v1/users/profile") + .header("Authorization", "Bearer USER_TOKEN") + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andDo(print()) + .andDo(UserRestDocument.getProfile()); + } + + // μ•„λž˜ μ˜ˆμ™Έ ν…ŒμŠ€νŠΈλ“€μ€ 정상적인 ν…ŒμŠ€νŠΈκ°€ μ•„λ‹ˆλ―€λ‘œ μ‹€νŒ¨λ˜λŠ” 게 맞음 + @Test + @DisplayName("μœ μ € ν”„λ‘œν•„ 정보 쑰회 토큰이 μ—†μ–΄μ„œ 인증 μ‹€νŒ¨ 401 μ—λŸ¬") + void getUserProfileNoToken() throws Exception { + + when(userProfileService.getUserProfile(any())).thenThrow(new IllegalArgumentException("μœ νš¨ν•˜μ§€ μ•Šμ€ ν† ν°μž…λ‹ˆλ‹€.")); + + mockMvc.perform(RestDocumentationRequestBuilders.get("/api/v1/users/profile") + .header("Authorization", "Bearer USER_TOKEN") + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isUnauthorized()) + .andDo(print()) + .andDo(UserRestDocument.getProfile()); + } + + @Test + @DisplayName("μœ μ € ν”„λ‘œν•„ 정보 쑰회 μœ νš¨ν•˜μ§€ μ•Šμ€ κΆŒν•œ 403 μ—λŸ¬") + void getUserProfileNoAuthorization() throws Exception { + UserProfileDTO userProfileDTO = UserProfileDTO.builder() + .userEmail("tester@tukorea.ac.kr") + .userImageUrl("이미지 urlμž…λ‹ˆλ‹€") + .userNickname("λ‚΄ λ‹‰λ„€μž„μ€ ν…ŒμŠ€νƒ€") + .userMannerTemperature(36L) + .userUnivName("ν•œκ΅­κ³΅ν•™λŒ€") + .build(); + when(userProfileService.getUserProfile(any())).thenReturn(userProfileDTO); + + mockMvc.perform(RestDocumentationRequestBuilders.get("/api/v1/users/profile") + .header("Authorization", "Bearer USER_TOKEN") + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isForbidden()) + .andDo(print()) + .andDo(UserRestDocument.getProfile()); + } + +} \ No newline at end of file diff --git a/backend/src/test/java/com/hjjang/backend/domain/user/controller/docs/UserRestDocument.java b/backend/src/test/java/com/hjjang/backend/domain/user/controller/docs/UserRestDocument.java new file mode 100644 index 0000000..37087c3 --- /dev/null +++ b/backend/src/test/java/com/hjjang/backend/domain/user/controller/docs/UserRestDocument.java @@ -0,0 +1,31 @@ +package com.hjjang.backend.domain.user.controller.docs; + +import org.springframework.restdocs.mockmvc.RestDocumentationResultHandler; +import org.springframework.restdocs.operation.preprocess.Preprocessors; +import org.springframework.restdocs.payload.JsonFieldType; + +import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName; +import static org.springframework.restdocs.headers.HeaderDocumentation.requestHeaders; +import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; +import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; + +public class UserRestDocument { + public static RestDocumentationResultHandler getProfile() { + return document("v1/users/profile", + Preprocessors.preprocessRequest(Preprocessors.prettyPrint()), + Preprocessors.preprocessResponse(Preprocessors.prettyPrint()), + requestHeaders( + headerWithName("Authorization").description("Bearer 토큰")), + responseFields( + fieldWithPath("code").type(JsonFieldType.STRING).description("Business code"), + fieldWithPath("message").type(JsonFieldType.STRING).description("response message"), + fieldWithPath("data.userNickname").type(JsonFieldType.STRING).description("user λ‹‰λ„€μž„"), + fieldWithPath("data.userImageUrl").type(JsonFieldType.STRING).description("user ν”„λ‘œν•„ 사진"), + fieldWithPath("data.userEmail").type(JsonFieldType.STRING).description("user 이메일"), + fieldWithPath("data.userMannerTemperature").type(JsonFieldType.NUMBER).description("user λ§€λ„ˆ μ˜¨λ„"), + fieldWithPath("data.userUnivName").type(JsonFieldType.STRING).description("user λŒ€ν•™ 이름") + ) + ); + } +} diff --git a/backend/src/test/java/com/hjjang/backend/domain/user/repository/UserRepositoryTest.java b/backend/src/test/java/com/hjjang/backend/domain/user/repository/UserRepositoryTest.java new file mode 100644 index 0000000..4ce9f07 --- /dev/null +++ b/backend/src/test/java/com/hjjang/backend/domain/user/repository/UserRepositoryTest.java @@ -0,0 +1,66 @@ +package com.hjjang.backend.domain.user.repository; + +import com.hjjang.backend.domain.user.entity.Agreement; +import com.hjjang.backend.domain.user.entity.RoleType; +import com.hjjang.backend.domain.user.entity.User; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; + +import javax.persistence.EntityExistsException; + +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertEquals; + +@DataJpaTest +class UserRepositoryTest { + + @Autowired + private UserRepository userRepository; + + private User givenUser; + private User newUser; + + @BeforeEach + void setUp() { + givenUser = User.builder() + .email("tester@tukorea.ac.kr") + .imageUrl("이미지 url") + .isPushAgree(Agreement.AGREE) + .mannerTemperature(36L) + .nickName("kevinkim") + .providerId("123412512") + .role(RoleType.USER) + .univId(null) + .isEmailVerification(false) + .build(); + + newUser = userRepository.save(givenUser); + } + + @DisplayName("userId둜 μ‚¬μš©μž 쑰회") + @Test + void findUserById_test() { + System.out.println(newUser.getProviderId()); + User foundUser = userRepository.findUserById(newUser.getId()).orElseThrow(EntityExistsException::new); + + assertAll( + () -> assertEquals(foundUser, newUser) + ); + } + + @DisplayName("providerId둜 μ‚¬μš©μž 쑰회") + @Test + void findUserByProviderId_test() { + + User foundUser = userRepository.findUserByProviderId(newUser.getProviderId()) + .orElseThrow(EntityExistsException::new); + + assertAll( + () -> assertEquals(foundUser, newUser) + ); + } + +} diff --git a/backend/src/test/java/com/hjjang/backend/domain/user/service/UserProfileServiceTest.java b/backend/src/test/java/com/hjjang/backend/domain/user/service/UserProfileServiceTest.java new file mode 100644 index 0000000..960b6aa --- /dev/null +++ b/backend/src/test/java/com/hjjang/backend/domain/user/service/UserProfileServiceTest.java @@ -0,0 +1,72 @@ +package com.hjjang.backend.domain.user.service; + +import com.hjjang.backend.domain.user.dto.UserProfileDTO; +import com.hjjang.backend.domain.user.entity.Agreement; +import com.hjjang.backend.domain.user.entity.RoleType; +import com.hjjang.backend.domain.user.entity.User; +import com.hjjang.backend.domain.user.repository.UserRepository; +import com.hjjang.backend.global.security.WithMockCustomUser; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class UserProfileServiceTest { + + @InjectMocks + private UserProfileService userProfileService; + + @Mock + private UserRepository userRepository; + + @Test + @WithMockCustomUser + @DisplayName("μœ μ € 쑰회 api test") + void getUserProfileTest() { + // given + User givenUser = User.builder() + .email("tester@tukorea.ac.kr") + .imageUrl("이미지 url") + .isPushAgree(Agreement.AGREE) + .mannerTemperature(36L) + .nickName("kevinkim") + .providerId("123412512") + .role(RoleType.USER) + .univId(null) + .isEmailVerification(true) + .build(); + + UserProfileDTO exceptUserProfileDTO = UserProfileDTO.builder() + .userEmail("tester@tukorea.ac.kr") + .userImageUrl("이미지 url") + .userMannerTemperature(36L) + .userNickname("kevinkim") + .userUnivName("μ‚°κΈ°λŒ€") + .build(); + + // when + when(userRepository.findUserByProviderId(any())).thenReturn(Optional.of(givenUser)); + UserProfileDTO actualUserProfileDTO = userProfileService.getUserProfile(givenUser.getProviderId()); + + // then + assertAll( + () -> assertEquals(exceptUserProfileDTO.getUserEmail(), actualUserProfileDTO.getUserEmail()), + () -> assertEquals(exceptUserProfileDTO.getUserNickname(), actualUserProfileDTO.getUserNickname()), + () -> assertEquals(exceptUserProfileDTO.getUserImageUrl(), actualUserProfileDTO.getUserImageUrl()), + () -> assertEquals(exceptUserProfileDTO.getUserMannerTemperature(), actualUserProfileDTO.getUserMannerTemperature()), + () -> assertEquals(exceptUserProfileDTO.getUserUnivName(), actualUserProfileDTO.getUserUnivName()) + ); + + } + +} \ No newline at end of file diff --git a/backend/src/test/java/com/hjjang/backend/global/security/CustomSecurityExtension.java b/backend/src/test/java/com/hjjang/backend/global/security/CustomSecurityExtension.java new file mode 100644 index 0000000..b9fd1be --- /dev/null +++ b/backend/src/test/java/com/hjjang/backend/global/security/CustomSecurityExtension.java @@ -0,0 +1,47 @@ +package com.hjjang.backend.global.security; + +import com.hjjang.backend.domain.user.entity.Agreement; +import com.hjjang.backend.domain.user.entity.RoleType; +import com.hjjang.backend.domain.user.entity.User; +import com.hjjang.backend.domain.user.repository.UserRefreshTokenRepository; +import com.hjjang.backend.domain.user.repository.UserRepository; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.restdocs.RestDocumentationExtension; + +import java.util.Optional; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +@ComponentScan(basePackages = "com.hjjang.backend.global.config.security") +@ComponentScan(basePackages = "com.hjjang.backend.global.security") +@ExtendWith(RestDocumentationExtension.class) +public class CustomSecurityExtension { + + @MockBean + protected UserRepository userRepository; + + @MockBean + protected UserRefreshTokenRepository userRefreshTokenRepository; + + // 인증된 user setup + public void userInfoSetUp() { + User givenUser = User.builder() + .email("kevinkim@email.com") + .imageUrl("μ΄λ―Έμ§€μž…λ‹ˆλ‹€μ•„μ•„μ•„") + .isPushAgree(Agreement.AGREE) + .mannerTemperature(36L) + .nickName("김겨여여연") + .providerId("kakao123456") + .role(RoleType.USER) + .univId(1L) + .isEmailVerification(true) + .build(); + when(userRepository.findUserByProviderId(any())).thenReturn(Optional.of(givenUser)); + } + + // todo μΈμ¦λ˜μ§€ μ•Šμ€ user setup + +} diff --git a/backend/src/test/java/com/hjjang/backend/global/security/WithMockCustomUser.java b/backend/src/test/java/com/hjjang/backend/global/security/WithMockCustomUser.java new file mode 100644 index 0000000..3e193ad --- /dev/null +++ b/backend/src/test/java/com/hjjang/backend/global/security/WithMockCustomUser.java @@ -0,0 +1,31 @@ +package com.hjjang.backend.global.security; + +import com.hjjang.backend.domain.user.entity.Agreement; +import com.hjjang.backend.domain.user.entity.RoleType; +import org.springframework.security.test.context.support.WithSecurityContext; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +@WithSecurityContext(factory = WithMockUserSecurityContextFactory.class) +public @interface WithMockCustomUser { + + long id() default 1L; + + String email() default "kevinkim@email.com"; + + String imageUrl() default "μ΄λ―Έμ§€μž…λ‹ˆλ‹€μ•„μ•„μ•„"; + + String nickName() default "김겨여여연"; + + RoleType roles() default RoleType.USER; + + String providerId() default "kakao123456"; + + long mannerTemperature() default 36L; + + Agreement isPushAgree() default Agreement.AGREE; + + long univId() default 1L; +} \ No newline at end of file diff --git a/backend/src/test/java/com/hjjang/backend/global/security/WithMockUserSecurityContextFactory.java b/backend/src/test/java/com/hjjang/backend/global/security/WithMockUserSecurityContextFactory.java new file mode 100644 index 0000000..514e6bd --- /dev/null +++ b/backend/src/test/java/com/hjjang/backend/global/security/WithMockUserSecurityContextFactory.java @@ -0,0 +1,45 @@ +package com.hjjang.backend.global.security; + +import com.hjjang.backend.domain.user.entity.RoleType; +import com.hjjang.backend.global.config.security.properties.AuthProperties; +import lombok.RequiredArgsConstructor; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.test.context.support.WithSecurityContextFactory; + +import java.util.Collections; +import java.util.Date; + + +@RequiredArgsConstructor +public class WithMockUserSecurityContextFactory implements WithSecurityContextFactory { + + private final AuthProperties authProperties; + + @Override + public SecurityContext createSecurityContext(WithMockCustomUser customUser) { + SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); + + Date now = new Date(); + + // κ°€μ§œ user 정보 μ„€μ • + User principal = new User( + customUser.providerId(), + "", + Collections.singletonList(new SimpleGrantedAuthority(RoleType.USER.getCode())) + ); + + // κ°€μ§œ security 인증 user μ €μž₯ + Authentication authentication = new UsernamePasswordAuthenticationToken( + principal, + "", + principal.getAuthorities() + ); + securityContext.setAuthentication(authentication); + return securityContext; + } +} \ No newline at end of file diff --git a/backend/src/test/resources/application.yml b/backend/src/test/resources/application.yml deleted file mode 100644 index 884acd5..0000000 --- a/backend/src/test/resources/application.yml +++ /dev/null @@ -1,16 +0,0 @@ -spring: - datasource: - url: jdbc:h2:mem:test_db;MODE=MySQL;DB_CLOSE_DELAY=-1 - driverClassName: org.h2.Driver - username: sa - password: - h2: - console: - enabled: true - jpa: - database-platform: org.hibernate.dialect.H2Dialect - hibernate.ddl-auto: create-drop - properties: - hibernate: - format_sql: true - generate-ddl: true \ No newline at end of file diff --git a/backend/src/test/resources/log4j2-test.xml b/backend/src/test/resources/log4j2-test.xml new file mode 100644 index 0000000..bc07f2f --- /dev/null +++ b/backend/src/test/resources/log4j2-test.xml @@ -0,0 +1,31 @@ + + + + + Default-Setting + %style{%d{yyyy/MM/dd HH:mm:ss,SSS}}{cyan} %highlight{[%-5p]}{FATAL=bg_red, ERROR=red, INFO=green, DEBUG=blue} [%C] %style{[%t]}{yellow}- %m%n - + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index ddab888..779076c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -23,7 +23,7 @@ services: backend: container_name: bauction-backend build: ./backend - restart: on-failure +# restart: on-failure depends_on: - mysqldb ports: