diff --git a/src/docs/asciidoc/admin.adoc b/src/docs/asciidoc/admin.adoc new file mode 100644 index 00000000..07e7ef82 --- /dev/null +++ b/src/docs/asciidoc/admin.adoc @@ -0,0 +1,42 @@ +:toc: left +:source-highlighter: highlightjs +:sectlinks: +:toclevels: 2 +:sectlinks: +:sectnums: + +== Admin + +=== 회원 가입(POST api/admins/auth/sign-up) + +==== 요청 +include::{snippets}/admin-auth-controller-test/관리자_회원가입/http-request.adoc[] +include::{snippets}/admin-auth-controller-test/관리자_회원가입/request-fields.adoc[] + +==== 응답 +include::{snippets}/admin-auth-controller-test/관리자_회원가입/http-response.adoc[] +include::{snippets}/admin-auth-controller-test/관리자_회원가입/response-headers.adoc[] +include::{snippets}/admin-auth-controller-test/관리자_회원가입/response-fields.adoc[] + + +=== 로그인(POST api/admins/auth/login) + +==== 요청 +include::{snippets}/admin-auth-controller-test/관리자_로그인/http-request.adoc[] +include::{snippets}/admin-auth-controller-test/관리자_로그인/request-fields.adoc[] + +==== 응답 +include::{snippets}/admin-auth-controller-test/관리자_로그인/http-response.adoc[] +include::{snippets}/admin-auth-controller-test/관리자_로그인/response-headers.adoc[] +include::{snippets}/admin-auth-controller-test/관리자_로그인/response-fields.adoc[] + + +=== 엑세스 토큰 재발행(POST api/admins/auth/access-token-regeneration) + +==== 요청 +include::{snippets}/admin-auth-controller-test/관리자_액세스_토큰_재발행/http-request.adoc[] +include::{snippets}/admin-auth-controller-test/관리자_액세스_토큰_재발행/request-headers.adoc[] + +==== 응답 +include::{snippets}/admin-auth-controller-test/관리자_액세스_토큰_재발행/http-response.adoc[] +include::{snippets}/admin-auth-controller-test/관리자_액세스_토큰_재발행/response-fields.adoc[] diff --git a/src/docs/asciidoc/auth.adoc b/src/docs/asciidoc/auth.adoc deleted file mode 100644 index 6835b9ac..00000000 --- a/src/docs/asciidoc/auth.adoc +++ /dev/null @@ -1,25 +0,0 @@ -:toc: left -:source-highlighter: highlightjs -:sectlinks: -:toclevels: 2 -:sectlinks: -:sectnums: - -== Auth - -=== 로그인 - -==== 요청 (POST api/auth/login) - -include::{snippets}/auth-controller-web-mvc-test/do_signup/http-request.adoc[] -include::{snippets}/auth-controller-web-mvc-test/do_signup/request-fields.adoc[] - - -==== 응답 -include::{snippets}/auth-controller-web-mvc-test/do_signup/http-response.adoc[] -include::{snippets}/auth-controller-web-mvc-test/do_signup/request-fields.adoc[] - - - - - diff --git a/src/docs/asciidoc/index.adoc b/src/docs/asciidoc/index.adoc index 0b4143a2..4f2c51d8 100644 --- a/src/docs/asciidoc/index.adoc +++ b/src/docs/asciidoc/index.adoc @@ -5,8 +5,8 @@ :sectlinks: == atwoz Server API -* link:auth.html[회원 API] * link:member.html[회원 정보 API] +* link:admin.html[관리자 정보 API] * link:mission.html[미션 API] * link:membermissions.html[회원 미션 API] * link:survey.html[연애고사 API] diff --git a/src/docs/asciidoc/member.adoc b/src/docs/asciidoc/member.adoc index 18984df6..b0bab758 100644 --- a/src/docs/asciidoc/member.adoc +++ b/src/docs/asciidoc/member.adoc @@ -7,11 +7,23 @@ == Member +=== 로그인 (POST api/members/auth/login) + +==== 요청 +include::{snippets}/member-auth-controller-web-mvc-test/유저_로그인/http-request.adoc[] +include::{snippets}/member-auth-controller-web-mvc-test/유저_로그인/request-fields.adoc[] + +==== 응답 +include::{snippets}/member-auth-controller-web-mvc-test/유저_로그인/http-response.adoc[] +include::{snippets}/member-auth-controller-web-mvc-test/유저_로그인/request-fields.adoc[] + + === 닉네임 중복 확인 (GET /api/members/nickname/existence) ==== 요청 -include::{snippets}/member-controller-web-mvc-test/닉네임_중복_확인/request-headers.adoc[] include::{snippets}/member-controller-web-mvc-test/닉네임_중복_확인/http-request.adoc[] +include::{snippets}/member-controller-web-mvc-test/닉네임_중복_확인/request-headers.adoc[] +include::{snippets}/member-controller-web-mvc-test/닉네임_중복_확인/path-parameters.adoc[] ==== 응답 include::{snippets}/member-controller-web-mvc-test/닉네임_중복_확인/http-response.adoc[] diff --git a/src/main/java/com/atwoz/admin/application/auth/AdminAuthService.java b/src/main/java/com/atwoz/admin/application/auth/AdminAuthService.java new file mode 100644 index 00000000..cc9ba182 --- /dev/null +++ b/src/main/java/com/atwoz/admin/application/auth/AdminAuthService.java @@ -0,0 +1,69 @@ +package com.atwoz.admin.application.auth; + +import com.atwoz.admin.application.auth.dto.AdminAccessTokenResponse; +import com.atwoz.admin.application.auth.dto.AdminLoginRequest; +import com.atwoz.admin.application.auth.dto.AdminProfileSignUpRequest; +import com.atwoz.admin.application.auth.dto.AdminSignUpRequest; +import com.atwoz.admin.application.auth.dto.AdminTokenResponse; +import com.atwoz.admin.domain.admin.Admin; +import com.atwoz.admin.domain.admin.AdminRepository; +import com.atwoz.admin.domain.admin.AdminTokenProvider; +import com.atwoz.admin.exception.exceptions.AdminNotFoundException; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@RequiredArgsConstructor +@Service +@Transactional +public class AdminAuthService { + + private static final String ID = "id"; + + private final AdminRepository adminRepository; + private final AdminTokenProvider adminTokenProvider; + + public AdminTokenResponse signUp(final AdminSignUpRequest adminSignUpRequest) { + AdminProfileSignUpRequest adminProfileSignUpRequest = adminSignUpRequest.adminProfileSignUpRequest(); + Admin admin = Admin.createWith( + adminSignUpRequest.email(), + adminSignUpRequest.password(), + adminSignUpRequest.confirmPassword(), + adminProfileSignUpRequest.name(), + adminProfileSignUpRequest.phoneNumber() + ); + Admin savedAdmin = adminRepository.save(admin); + + return createAdminTokenResponse(savedAdmin.getId()); + } + + private AdminTokenResponse createAdminTokenResponse(final Long id) { + return new AdminTokenResponse( + adminTokenProvider.createAccessToken(id), + adminTokenProvider.createRefreshToken(id) + ); + } + + public AdminTokenResponse login(final AdminLoginRequest adminLoginRequest) { + Admin foundAdmin = findAdminByEmail(adminLoginRequest.email()); + foundAdmin.validatePassword(adminLoginRequest.password()); + + return createAdminTokenResponse(foundAdmin.getId()); + } + + public AdminAccessTokenResponse reGenerateAccessToken(final String refreshToken) { + Admin foundAdmin = findAdminById(adminTokenProvider.extract(refreshToken, ID, Long.class)); + + return new AdminAccessTokenResponse(adminTokenProvider.createAccessToken(foundAdmin.getId())); + } + + private Admin findAdminByEmail(final String email) { + return adminRepository.findAdminByEmail(email) + .orElseThrow(AdminNotFoundException::new); + } + + private Admin findAdminById(final Long id) { + return adminRepository.findAdminById(id) + .orElseThrow(AdminNotFoundException::new); + } +} diff --git a/src/main/java/com/atwoz/admin/application/auth/dto/AdminAccessTokenResponse.java b/src/main/java/com/atwoz/admin/application/auth/dto/AdminAccessTokenResponse.java new file mode 100644 index 00000000..c0c7bbea --- /dev/null +++ b/src/main/java/com/atwoz/admin/application/auth/dto/AdminAccessTokenResponse.java @@ -0,0 +1,6 @@ +package com.atwoz.admin.application.auth.dto; + +public record AdminAccessTokenResponse( + String accessToken +) { +} diff --git a/src/main/java/com/atwoz/admin/application/auth/dto/AdminLoginRequest.java b/src/main/java/com/atwoz/admin/application/auth/dto/AdminLoginRequest.java new file mode 100644 index 00000000..1ec13c2e --- /dev/null +++ b/src/main/java/com/atwoz/admin/application/auth/dto/AdminLoginRequest.java @@ -0,0 +1,12 @@ +package com.atwoz.admin.application.auth.dto; + +import jakarta.validation.constraints.NotBlank; + +public record AdminLoginRequest( + @NotBlank(message = "이메일을 입력해주세요") + String email, + + @NotBlank(message = "비밀번호를 입력해주세요") + String password +) { +} diff --git a/src/main/java/com/atwoz/admin/application/auth/dto/AdminProfileSignUpRequest.java b/src/main/java/com/atwoz/admin/application/auth/dto/AdminProfileSignUpRequest.java new file mode 100644 index 00000000..77eab4c8 --- /dev/null +++ b/src/main/java/com/atwoz/admin/application/auth/dto/AdminProfileSignUpRequest.java @@ -0,0 +1,12 @@ +package com.atwoz.admin.application.auth.dto; + +import jakarta.validation.constraints.NotBlank; + +public record AdminProfileSignUpRequest( + @NotBlank(message = "이름을 입력해주세요.") + String name, + + @NotBlank(message = "전화번호를 입력해주세요.") + String phoneNumber +) { +} diff --git a/src/main/java/com/atwoz/admin/application/auth/dto/AdminSignUpRequest.java b/src/main/java/com/atwoz/admin/application/auth/dto/AdminSignUpRequest.java new file mode 100644 index 00000000..ba1336fc --- /dev/null +++ b/src/main/java/com/atwoz/admin/application/auth/dto/AdminSignUpRequest.java @@ -0,0 +1,21 @@ +package com.atwoz.admin.application.auth.dto; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; + +public record AdminSignUpRequest( + @NotBlank(message = "이메일을 입력해주세요") + String email, + + @NotBlank(message = "비밀번호를 입력해주세요") + String password, + + @NotBlank(message = "비밀번호 확인을 위해 다시한번 입력해주세요") + String confirmPassword, + + @Valid + @NotNull(message = "관리자 프로필 정보를 입력해주세요") + AdminProfileSignUpRequest adminProfileSignUpRequest +) { +} diff --git a/src/main/java/com/atwoz/admin/application/auth/dto/AdminTokenResponse.java b/src/main/java/com/atwoz/admin/application/auth/dto/AdminTokenResponse.java new file mode 100644 index 00000000..0b2a69cb --- /dev/null +++ b/src/main/java/com/atwoz/admin/application/auth/dto/AdminTokenResponse.java @@ -0,0 +1,7 @@ +package com.atwoz.admin.application.auth.dto; + +public record AdminTokenResponse( + String accessToken, + String refreshToken +) { +} diff --git a/src/main/java/com/atwoz/admin/config/AdminAuthConfig.java b/src/main/java/com/atwoz/admin/config/AdminAuthConfig.java new file mode 100644 index 00000000..97388421 --- /dev/null +++ b/src/main/java/com/atwoz/admin/config/AdminAuthConfig.java @@ -0,0 +1,40 @@ +package com.atwoz.admin.config; + +import com.atwoz.admin.ui.auth.interceptor.AdminLoginValidCheckerInterceptor; +import com.atwoz.admin.ui.auth.support.resolver.AdminAuthArgumentResolver; +import com.atwoz.admin.ui.auth.support.resolver.AdminRefreshTokenExtractionArgumentResolver; +import com.atwoz.global.config.interceptor.PathMatcherInterceptor; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.servlet.HandlerInterceptor; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +import static com.atwoz.global.config.interceptor.support.HttpMethod.OPTIONS; + +@RequiredArgsConstructor +@Configuration +public class AdminAuthConfig implements WebMvcConfigurer { + + private final AdminAuthArgumentResolver adminAuthArgumentResolver; + private final AdminRefreshTokenExtractionArgumentResolver adminRefreshTokenExtractionArgumentResolver; + private final AdminLoginValidCheckerInterceptor adminLoginValidCheckerInterceptor; + + @Override + public void addInterceptors(final InterceptorRegistry registry) { + registry.addInterceptor(adminLoginValidCheckerInterceptor()); + } + + private HandlerInterceptor adminLoginValidCheckerInterceptor() { + return new PathMatcherInterceptor(adminLoginValidCheckerInterceptor) + .excludePathPattern("/**", OPTIONS); + } + + @Override + public void addArgumentResolvers(final List resolvers) { + resolvers.add(adminAuthArgumentResolver); + resolvers.add(adminRefreshTokenExtractionArgumentResolver); + } +} diff --git a/src/main/java/com/atwoz/admin/domain/admin/Admin.java b/src/main/java/com/atwoz/admin/domain/admin/Admin.java new file mode 100644 index 00000000..8be1adac --- /dev/null +++ b/src/main/java/com/atwoz/admin/domain/admin/Admin.java @@ -0,0 +1,87 @@ +package com.atwoz.admin.domain.admin; + +import com.atwoz.admin.domain.admin.vo.AdminStatus; +import com.atwoz.admin.domain.admin.vo.Authority; +import com.atwoz.admin.domain.admin.vo.Department; +import com.atwoz.admin.exception.exceptions.InvalidPasswordException; +import com.atwoz.admin.exception.exceptions.PasswordMismatchException; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Builder +@EqualsAndHashCode(of = "id", callSuper = false) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Entity +public class Admin { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(unique = true, nullable = false) + private String email; + + @Column(nullable = false) + private String password; + + @Column(nullable = false) + private String name; + + @Column(nullable = false) + private String phoneNumber; + + @Enumerated(value = EnumType.STRING) + @Column(nullable = false) + private Authority authority; + + @Enumerated(value = EnumType.STRING) + @Column(nullable = false) + private Department department; + + @Enumerated(value = EnumType.STRING) + @Column(nullable = false) + private AdminStatus adminStatus; + + public static Admin createWith(final String email, + final String password, + final String confirmPassword, + final String name, + final String phoneNumber) { + validatePasswordsMatch(password, confirmPassword); + return Admin.builder() + .email(email) + .password(password) + .name(name) + .phoneNumber(phoneNumber) + .authority(Authority.ADMIN) + .department(Department.OPERATION) + .adminStatus(AdminStatus.PENDING) + .build(); + } + + private static void validatePasswordsMatch(final String password, + final String confirmPassword) { + if (!password.equals(confirmPassword)) { + throw new PasswordMismatchException(); + } + } + + public void validatePassword(final String password) { + if (!password.equals(this.password)) { + throw new InvalidPasswordException(); + } + } +} diff --git a/src/main/java/com/atwoz/admin/domain/admin/AdminRepository.java b/src/main/java/com/atwoz/admin/domain/admin/AdminRepository.java new file mode 100644 index 00000000..8cc4830d --- /dev/null +++ b/src/main/java/com/atwoz/admin/domain/admin/AdminRepository.java @@ -0,0 +1,12 @@ +package com.atwoz.admin.domain.admin; + +import java.util.Optional; + +public interface AdminRepository { + + Admin save(Admin admin); + + Optional findAdminById(Long id); + + Optional findAdminByEmail(String email); +} diff --git a/src/main/java/com/atwoz/admin/domain/admin/AdminTokenProvider.java b/src/main/java/com/atwoz/admin/domain/admin/AdminTokenProvider.java new file mode 100644 index 00000000..427d63b5 --- /dev/null +++ b/src/main/java/com/atwoz/admin/domain/admin/AdminTokenProvider.java @@ -0,0 +1,10 @@ +package com.atwoz.admin.domain.admin; + +public interface AdminTokenProvider { + + String createAccessToken(Long id); + + String createRefreshToken(Long id); + + T extract(String token, String claimName, Class classType); +} diff --git a/src/main/java/com/atwoz/admin/domain/admin/vo/AdminStatus.java b/src/main/java/com/atwoz/admin/domain/admin/vo/AdminStatus.java new file mode 100644 index 00000000..af420f09 --- /dev/null +++ b/src/main/java/com/atwoz/admin/domain/admin/vo/AdminStatus.java @@ -0,0 +1,8 @@ +package com.atwoz.admin.domain.admin.vo; + +public enum AdminStatus { + + AVAILABLE, + UNAVAILABLE, + PENDING +} diff --git a/src/main/java/com/atwoz/admin/domain/admin/vo/Authority.java b/src/main/java/com/atwoz/admin/domain/admin/vo/Authority.java new file mode 100644 index 00000000..db3871e5 --- /dev/null +++ b/src/main/java/com/atwoz/admin/domain/admin/vo/Authority.java @@ -0,0 +1,6 @@ +package com.atwoz.admin.domain.admin.vo; + +public enum Authority { + MASTER, + ADMIN +} diff --git a/src/main/java/com/atwoz/admin/domain/admin/vo/Department.java b/src/main/java/com/atwoz/admin/domain/admin/vo/Department.java new file mode 100644 index 00000000..d113f1f2 --- /dev/null +++ b/src/main/java/com/atwoz/admin/domain/admin/vo/Department.java @@ -0,0 +1,5 @@ +package com.atwoz.admin.domain.admin.vo; + +public enum Department { + OPERATION +} diff --git a/src/main/java/com/atwoz/admin/exception/AdminExceptionHandler.java b/src/main/java/com/atwoz/admin/exception/AdminExceptionHandler.java new file mode 100644 index 00000000..29f8bd93 --- /dev/null +++ b/src/main/java/com/atwoz/admin/exception/AdminExceptionHandler.java @@ -0,0 +1,61 @@ +package com.atwoz.admin.exception; + +import com.atwoz.admin.exception.exceptions.AdminLoginInvalidException; +import com.atwoz.admin.exception.exceptions.AdminNotFoundException; +import com.atwoz.admin.exception.exceptions.InvalidPasswordException; +import com.atwoz.admin.exception.exceptions.PasswordMismatchException; +import com.atwoz.admin.exception.exceptions.UnauthorizedAccessToAdminException; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +@RestControllerAdvice +public class AdminExceptionHandler { + + @ExceptionHandler(PasswordMismatchException.class) + public ResponseEntity handlePasswordMismatchException(final PasswordMismatchException e) { + return getBadRequest(e); + } + + @ExceptionHandler(AdminNotFoundException.class) + public ResponseEntity handleAdminNotFoundException(final AdminNotFoundException e) { + return getNotFoundResponse(e); + } + + @ExceptionHandler(InvalidPasswordException.class) + public ResponseEntity handlePasswordInvalidException(final InvalidPasswordException e) { + return getBadRequest(e); + } + + @ExceptionHandler(UnauthorizedAccessToAdminException.class) + public ResponseEntity handleUnauthorizedAccessToAdminException(final UnauthorizedAccessToAdminException e) { + return getUnauthorized(e); + } + + @ExceptionHandler(AdminLoginInvalidException.class) + public ResponseEntity handleAdminLoginInvalidException(final AdminLoginInvalidException e) { + return getUnauthorized(e); + } + + private ResponseEntity getNotFoundResponse(final Exception e) { + return ResponseEntity.status(HttpStatus.NOT_FOUND) + .body(e.getMessage()); + } + + private ResponseEntity getUnauthorized(final Exception e) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED) + .body(e.getMessage()); + } + + private ResponseEntity getConflicted(final Exception e) { + return ResponseEntity.status(HttpStatus.CONFLICT) + .body(e.getMessage()); + } + + private ResponseEntity getBadRequest(final Exception e) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST) + .body(e.getMessage()); + } +} + diff --git a/src/main/java/com/atwoz/admin/exception/exceptions/AdminLoginInvalidException.java b/src/main/java/com/atwoz/admin/exception/exceptions/AdminLoginInvalidException.java new file mode 100644 index 00000000..031ab33f --- /dev/null +++ b/src/main/java/com/atwoz/admin/exception/exceptions/AdminLoginInvalidException.java @@ -0,0 +1,8 @@ +package com.atwoz.admin.exception.exceptions; + +public class AdminLoginInvalidException extends RuntimeException { + + public AdminLoginInvalidException() { + super("관리자 로그인이 유효하지 않습니다"); + } +} diff --git a/src/main/java/com/atwoz/admin/exception/exceptions/AdminNotFoundException.java b/src/main/java/com/atwoz/admin/exception/exceptions/AdminNotFoundException.java new file mode 100644 index 00000000..c0a27b90 --- /dev/null +++ b/src/main/java/com/atwoz/admin/exception/exceptions/AdminNotFoundException.java @@ -0,0 +1,8 @@ +package com.atwoz.admin.exception.exceptions; + +public class AdminNotFoundException extends RuntimeException { + + public AdminNotFoundException() { + super("관리자를 찾을 수 없습니다."); + } +} diff --git a/src/main/java/com/atwoz/admin/exception/exceptions/InvalidPasswordException.java b/src/main/java/com/atwoz/admin/exception/exceptions/InvalidPasswordException.java new file mode 100644 index 00000000..c9549806 --- /dev/null +++ b/src/main/java/com/atwoz/admin/exception/exceptions/InvalidPasswordException.java @@ -0,0 +1,8 @@ +package com.atwoz.admin.exception.exceptions; + +public class InvalidPasswordException extends RuntimeException { + + public InvalidPasswordException() { + super("잘못된 이메일/비밀번호 입력입니다. 다시 입력해주세요"); + } +} diff --git a/src/main/java/com/atwoz/admin/exception/exceptions/PasswordMismatchException.java b/src/main/java/com/atwoz/admin/exception/exceptions/PasswordMismatchException.java new file mode 100644 index 00000000..fa51bc6f --- /dev/null +++ b/src/main/java/com/atwoz/admin/exception/exceptions/PasswordMismatchException.java @@ -0,0 +1,8 @@ +package com.atwoz.admin.exception.exceptions; + +public class PasswordMismatchException extends RuntimeException { + + public PasswordMismatchException() { + super("비밀번호가 일치하지 않습니다."); + } +} diff --git a/src/main/java/com/atwoz/admin/exception/exceptions/UnauthorizedAccessToAdminException.java b/src/main/java/com/atwoz/admin/exception/exceptions/UnauthorizedAccessToAdminException.java new file mode 100644 index 00000000..fe196384 --- /dev/null +++ b/src/main/java/com/atwoz/admin/exception/exceptions/UnauthorizedAccessToAdminException.java @@ -0,0 +1,8 @@ +package com.atwoz.admin.exception.exceptions; + +public class UnauthorizedAccessToAdminException extends RuntimeException { + + public UnauthorizedAccessToAdminException() { + super("권한이 없습니다."); + } +} diff --git a/src/main/java/com/atwoz/admin/infrastructure/admin/AdminJpaRepository.java b/src/main/java/com/atwoz/admin/infrastructure/admin/AdminJpaRepository.java new file mode 100644 index 00000000..d305fcac --- /dev/null +++ b/src/main/java/com/atwoz/admin/infrastructure/admin/AdminJpaRepository.java @@ -0,0 +1,10 @@ +package com.atwoz.admin.infrastructure.admin; + +import com.atwoz.admin.domain.admin.Admin; +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface AdminJpaRepository extends JpaRepository { + + Optional findAdminByEmail(String email); +} diff --git a/src/main/java/com/atwoz/admin/infrastructure/admin/AdminRepositoryImpl.java b/src/main/java/com/atwoz/admin/infrastructure/admin/AdminRepositoryImpl.java new file mode 100644 index 00000000..62b717b1 --- /dev/null +++ b/src/main/java/com/atwoz/admin/infrastructure/admin/AdminRepositoryImpl.java @@ -0,0 +1,29 @@ +package com.atwoz.admin.infrastructure.admin; + +import com.atwoz.admin.domain.admin.Admin; +import com.atwoz.admin.domain.admin.AdminRepository; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + +@RequiredArgsConstructor +@Repository +public class AdminRepositoryImpl implements AdminRepository { + + private final AdminJpaRepository adminJpaRepository; + + @Override + public Admin save(final Admin admin) { + return adminJpaRepository.save(admin); + } + + @Override + public Optional findAdminById(final Long id) { + return adminJpaRepository.findById(id); + } + + @Override + public Optional findAdminByEmail(final String email) { + return adminJpaRepository.findAdminByEmail(email); + } +} diff --git a/src/main/java/com/atwoz/admin/infrastructure/auth/AdminJwtTokenProvider.java b/src/main/java/com/atwoz/admin/infrastructure/auth/AdminJwtTokenProvider.java new file mode 100644 index 00000000..0ee81c0c --- /dev/null +++ b/src/main/java/com/atwoz/admin/infrastructure/auth/AdminJwtTokenProvider.java @@ -0,0 +1,117 @@ +package com.atwoz.admin.infrastructure.auth; + +import com.atwoz.admin.domain.admin.AdminTokenProvider; +import com.atwoz.member.exception.exceptions.auth.ExpiredTokenException; +import com.atwoz.member.exception.exceptions.auth.SignatureInvalidException; +import com.atwoz.member.exception.exceptions.auth.TokenFormInvalidException; +import com.atwoz.member.exception.exceptions.auth.TokenInvalidException; +import com.atwoz.member.exception.exceptions.auth.UnsupportedTokenException; +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.security.Keys; +import jakarta.annotation.PostConstruct; +import java.security.Key; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.Date; +import lombok.NoArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +@NoArgsConstructor +@Component +public class AdminJwtTokenProvider implements AdminTokenProvider { + + private static final String ID = "id"; + private static final String TOKEN_TYPE = "token type"; + private static final String REFRESH_TOKEN = "refresh token"; + private static final String ACCESS_TOKEN = "access token"; + private static final String ROLE = "role"; + private static final String ADMIN = "admin"; + + @Value("${jwt.secret}") + private String secret; + + @Value("${jwt.access-token-expiration-period}") + private int accessTokenExpirationPeriod; + + @Value("${jwt.refresh-token-expiration-period}") + private int refreshTokenExpirationPeriod; + + private Key key; + + @PostConstruct + private void init() { + key = Keys.hmacShaKeyFor(secret.getBytes()); + } + + @Override + public String createAccessToken(final Long id) { + Claims claims = Jwts.claims(); + claims.put(ID, id); + claims.put(TOKEN_TYPE, ACCESS_TOKEN); + claims.put(ROLE, ADMIN); + + return createToken(claims, accessTokenExpirationPeriod); + } + + @Override + public String createRefreshToken(final Long id) { + Claims claims = Jwts.claims(); + claims.put(ID, id); + claims.put(TOKEN_TYPE, REFRESH_TOKEN); + claims.put(ROLE, ADMIN); + + return createToken(claims, refreshTokenExpirationPeriod); + } + + private String createToken(final Claims claims, final int expirationPeriod) { + return Jwts.builder() + .setClaims(claims) + .setIssuedAt(issuedAt()) + .setExpiration(expiredAt(expirationPeriod)) + .signWith(key, SignatureAlgorithm.HS256) + .compact(); + } + + private Date issuedAt() { + LocalDateTime now = LocalDateTime.now(); + + return Date.from(now.atZone(ZoneId.systemDefault()) + .toInstant()); + } + + private Date expiredAt(final int expirationPeriod) { + LocalDateTime now = LocalDateTime.now(); + + return Date.from(now.plusDays(expirationPeriod) + .atZone(ZoneId.systemDefault()) + .toInstant()); + } + + @Override + public T extract(final String token, final String claimName, final Class classType) { + try { + return Jwts.parserBuilder() + .setSigningKey(secret.getBytes()) + .build() + .parseClaimsJws(token) + .getBody() + .get(claimName, classType); + } catch (SecurityException e) { + throw new SignatureInvalidException(); + } catch (MalformedJwtException e) { + throw new TokenFormInvalidException(); + } catch (ExpiredJwtException e) { + throw new ExpiredTokenException(); + } catch (UnsupportedJwtException e) { + throw new UnsupportedTokenException(); + } catch (IllegalArgumentException e) { + throw new TokenInvalidException(); + } + } +} diff --git a/src/main/java/com/atwoz/admin/ui/auth/AdminAuthController.java b/src/main/java/com/atwoz/admin/ui/auth/AdminAuthController.java new file mode 100644 index 00000000..8f162f8a --- /dev/null +++ b/src/main/java/com/atwoz/admin/ui/auth/AdminAuthController.java @@ -0,0 +1,72 @@ +package com.atwoz.admin.ui.auth; + +import com.atwoz.admin.application.auth.AdminAuthService; +import com.atwoz.admin.application.auth.dto.AdminAccessTokenResponse; +import com.atwoz.admin.application.auth.dto.AdminLoginRequest; +import com.atwoz.admin.application.auth.dto.AdminSignUpRequest; +import com.atwoz.admin.application.auth.dto.AdminTokenResponse; +import com.atwoz.admin.ui.auth.support.AdminRefreshToken; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpHeaders; +import org.springframework.http.ResponseCookie; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RequiredArgsConstructor +@RestController +@RequestMapping("/api/admins/auth") +public class AdminAuthController { + + private static final String COOKIE_NAME = "refreshToken"; + private static final String ANY_WAY = "/"; + + @Value("${cookie.max-age}") + private int maxAge; + + private final AdminAuthService adminAuthService; + + @PostMapping("/sign-up") + public ResponseEntity singUp( + @RequestBody @Valid final AdminSignUpRequest adminSignUpRequest) { + AdminTokenResponse adminTokenResponse = adminAuthService.signUp(adminSignUpRequest); + + return ResponseEntity.ok() + .headers(createCookieHeaders(adminTokenResponse.refreshToken())) + .body(new AdminAccessTokenResponse(adminTokenResponse.accessToken())); + } + + @PostMapping("/login") + public ResponseEntity login( + @RequestBody @Valid final AdminLoginRequest adminLoginRequest) { + AdminTokenResponse adminTokenResponse = adminAuthService.login(adminLoginRequest); + + return ResponseEntity.ok() + .headers(createCookieHeaders(adminTokenResponse.refreshToken())) + .body(new AdminAccessTokenResponse(adminTokenResponse.accessToken())); + } + + @PostMapping("/access-token-regeneration") + public ResponseEntity reGenerateAccessToken( + @AdminRefreshToken final String refreshToken) { + return ResponseEntity.ok(adminAuthService.reGenerateAccessToken(refreshToken)); + } + + + private HttpHeaders createCookieHeaders(final String refreshToken) { + ResponseCookie cookie = ResponseCookie.from(COOKIE_NAME, refreshToken) + .httpOnly(true) + .secure(true) + .path(ANY_WAY) + .maxAge(maxAge) + .build(); + HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.add(HttpHeaders.COOKIE, cookie.toString()); + + return httpHeaders; + } +} diff --git a/src/main/java/com/atwoz/admin/ui/auth/interceptor/AdminLoginValidCheckerInterceptor.java b/src/main/java/com/atwoz/admin/ui/auth/interceptor/AdminLoginValidCheckerInterceptor.java new file mode 100644 index 00000000..d8785050 --- /dev/null +++ b/src/main/java/com/atwoz/admin/ui/auth/interceptor/AdminLoginValidCheckerInterceptor.java @@ -0,0 +1,42 @@ +package com.atwoz.admin.ui.auth.interceptor; + +import com.atwoz.admin.domain.admin.AdminTokenProvider; +import com.atwoz.admin.exception.exceptions.AdminLoginInvalidException; +import com.atwoz.admin.exception.exceptions.UnauthorizedAccessToAdminException; +import com.atwoz.admin.ui.auth.support.AdminAuthenticationContext; +import com.atwoz.admin.ui.auth.support.AdminAuthenticationExtractor; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.HandlerInterceptor; + +@RequiredArgsConstructor +@Component +public class AdminLoginValidCheckerInterceptor implements HandlerInterceptor { + + private static final String ADMIN_ID = "id"; + private static final String ROLE = "role"; + private static final String ADMIN = "admin"; + + private final AdminAuthenticationContext adminAuthenticationContext; + private final AdminAuthenticationExtractor adminAuthenticationExtractor; + private final AdminTokenProvider adminTokenProvider; + + @Override + public boolean preHandle(final HttpServletRequest request, + final HttpServletResponse response, + final Object handler) throws Exception { + String token = adminAuthenticationExtractor.extractFromRequest(request) + .orElseThrow(AdminLoginInvalidException::new); + String extractedRole = adminTokenProvider.extract(token, ROLE, String.class); + if (!extractedRole.equals(ADMIN)) { + throw new UnauthorizedAccessToAdminException(); + } + + Long extractedId = adminTokenProvider.extract(token, ADMIN_ID, Long.class); + adminAuthenticationContext.setAuthentication(extractedId); + + return true; + } +} diff --git a/src/main/java/com/atwoz/admin/ui/auth/support/AdminAuthenticationContext.java b/src/main/java/com/atwoz/admin/ui/auth/support/AdminAuthenticationContext.java new file mode 100644 index 00000000..efd4f4b5 --- /dev/null +++ b/src/main/java/com/atwoz/admin/ui/auth/support/AdminAuthenticationContext.java @@ -0,0 +1,25 @@ +package com.atwoz.admin.ui.auth.support; + +import com.atwoz.admin.exception.exceptions.AdminLoginInvalidException; +import java.util.Objects; +import org.springframework.stereotype.Component; +import org.springframework.web.context.annotation.RequestScope; + +@RequestScope +@Component +public class AdminAuthenticationContext { + + private Long memberId; + + public void setAuthentication(final Long memberId) { + this.memberId = memberId; + } + + public Long getPrincipal() { + if (Objects.isNull(this.memberId)) { + throw new AdminLoginInvalidException(); + } + + return memberId; + } +} diff --git a/src/main/java/com/atwoz/admin/ui/auth/support/AdminAuthenticationExtractor.java b/src/main/java/com/atwoz/admin/ui/auth/support/AdminAuthenticationExtractor.java new file mode 100644 index 00000000..3a7d77ac --- /dev/null +++ b/src/main/java/com/atwoz/admin/ui/auth/support/AdminAuthenticationExtractor.java @@ -0,0 +1,36 @@ +package com.atwoz.admin.ui.auth.support; + +import jakarta.servlet.http.HttpServletRequest; +import java.util.Optional; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +@Component +public class AdminAuthenticationExtractor { + + private static final String AUTHORIZATION_HEADER = "Authorization"; + private static final String BEARER = "Bearer"; + private static final String HEADER_SPLIT_DELIMITER = " "; + private static final int TOKEN_TYPE_INDEX = 0; + private static final int TOKEN_VALUE_INDEX = 1; + private static final int VALID_HEADER_SPLIT_LENGTH = 2; + + public Optional extractFromRequest(final HttpServletRequest request) { + String header = request.getHeader(AUTHORIZATION_HEADER); + + if (!StringUtils.hasText(header)) { + return Optional.empty(); + } + + return extractFromHeader(header.split(HEADER_SPLIT_DELIMITER)); + } + + private Optional extractFromHeader(final String[] headerParts) { + if (headerParts.length == VALID_HEADER_SPLIT_LENGTH && + headerParts[TOKEN_TYPE_INDEX].equals(BEARER)) { + return Optional.ofNullable(headerParts[TOKEN_VALUE_INDEX]); + } + + return Optional.empty(); + } +} diff --git a/src/main/java/com/atwoz/admin/ui/auth/support/AdminRefreshToken.java b/src/main/java/com/atwoz/admin/ui/auth/support/AdminRefreshToken.java new file mode 100644 index 00000000..f10a0fdd --- /dev/null +++ b/src/main/java/com/atwoz/admin/ui/auth/support/AdminRefreshToken.java @@ -0,0 +1,11 @@ +package com.atwoz.admin.ui.auth.support; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.RUNTIME) +public @interface AdminRefreshToken { +} diff --git a/src/main/java/com/atwoz/admin/ui/auth/support/AuthAdmin.java b/src/main/java/com/atwoz/admin/ui/auth/support/AuthAdmin.java new file mode 100644 index 00000000..6334786c --- /dev/null +++ b/src/main/java/com/atwoz/admin/ui/auth/support/AuthAdmin.java @@ -0,0 +1,11 @@ +package com.atwoz.admin.ui.auth.support; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.RUNTIME) +public @interface AuthAdmin { +} diff --git a/src/main/java/com/atwoz/admin/ui/auth/support/resolver/AdminAuthArgumentResolver.java b/src/main/java/com/atwoz/admin/ui/auth/support/resolver/AdminAuthArgumentResolver.java new file mode 100644 index 00000000..4d2a53bc --- /dev/null +++ b/src/main/java/com/atwoz/admin/ui/auth/support/resolver/AdminAuthArgumentResolver.java @@ -0,0 +1,32 @@ +package com.atwoz.admin.ui.auth.support.resolver; + +import com.atwoz.admin.ui.auth.support.AdminAuthenticationContext; +import com.atwoz.admin.ui.auth.support.AuthAdmin; +import lombok.RequiredArgsConstructor; +import org.springframework.core.MethodParameter; +import org.springframework.stereotype.Component; +import org.springframework.web.bind.support.WebDataBinderFactory; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.method.support.ModelAndViewContainer; + +@RequiredArgsConstructor +@Component +public class AdminAuthArgumentResolver implements HandlerMethodArgumentResolver { + + private final AdminAuthenticationContext adminAuthenticationContext; + + @Override + public boolean supportsParameter(final MethodParameter parameter) { + return parameter.hasParameterAnnotation(AuthAdmin.class) && + parameter.getParameterType().equals(Long.class); + } + + @Override + public Object resolveArgument(final MethodParameter parameter, + final ModelAndViewContainer mavContainer, + final NativeWebRequest webRequest, + final WebDataBinderFactory binderFactory) throws Exception { + return adminAuthenticationContext.getPrincipal(); + } +} diff --git a/src/main/java/com/atwoz/admin/ui/auth/support/resolver/AdminRefreshTokenExtractionArgumentResolver.java b/src/main/java/com/atwoz/admin/ui/auth/support/resolver/AdminRefreshTokenExtractionArgumentResolver.java new file mode 100644 index 00000000..e137c71e --- /dev/null +++ b/src/main/java/com/atwoz/admin/ui/auth/support/resolver/AdminRefreshTokenExtractionArgumentResolver.java @@ -0,0 +1,61 @@ +package com.atwoz.admin.ui.auth.support.resolver; + +import com.atwoz.admin.domain.admin.AdminTokenProvider; +import com.atwoz.admin.exception.exceptions.AdminLoginInvalidException; +import com.atwoz.admin.exception.exceptions.UnauthorizedAccessToAdminException; +import com.atwoz.admin.ui.auth.support.AdminRefreshToken; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import java.util.Arrays; +import java.util.Objects; +import lombok.RequiredArgsConstructor; +import org.springframework.core.MethodParameter; +import org.springframework.stereotype.Component; +import org.springframework.web.bind.support.WebDataBinderFactory; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.method.support.ModelAndViewContainer; + +@RequiredArgsConstructor +@Component +public class AdminRefreshTokenExtractionArgumentResolver implements HandlerMethodArgumentResolver { + + private static final String REFRESH_TOKEN = "refreshToken"; + private static final String ROLE = "role"; + private static final String ADMIN = "admin"; + + private final AdminTokenProvider adminTokenProvider; + + @Override + public boolean supportsParameter(final MethodParameter parameter) { + return parameter.hasParameterAnnotation(AdminRefreshToken.class) && + parameter.getParameterType().equals(String.class); + } + + @Override + public Object resolveArgument(final MethodParameter parameter, + final ModelAndViewContainer mavContainer, + final NativeWebRequest webRequest, + final WebDataBinderFactory binderFactory) throws Exception { + HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class); + if (request == null) { + throw new AdminLoginInvalidException(); + } + + String refreshToken = findRefreshToken(request.getCookies()); + String role = adminTokenProvider.extract(refreshToken, ROLE, String.class); + if (!Objects.equals(role, ADMIN)) { + throw new UnauthorizedAccessToAdminException(); + } + + return refreshToken; + } + + private String findRefreshToken(final Cookie[] cookies) { + return Arrays.stream(cookies) + .filter(cookie -> REFRESH_TOKEN.equals(cookie.getName())) + .findAny() + .orElseThrow(AdminLoginInvalidException::new) + .getValue(); + } +} diff --git a/src/main/java/com/atwoz/member/ui/auth/interceptor/PathMatcherInterceptor.java b/src/main/java/com/atwoz/global/config/interceptor/PathMatcherInterceptor.java similarity index 91% rename from src/main/java/com/atwoz/member/ui/auth/interceptor/PathMatcherInterceptor.java rename to src/main/java/com/atwoz/global/config/interceptor/PathMatcherInterceptor.java index 51e8cd30..00e1dcb5 100644 --- a/src/main/java/com/atwoz/member/ui/auth/interceptor/PathMatcherInterceptor.java +++ b/src/main/java/com/atwoz/global/config/interceptor/PathMatcherInterceptor.java @@ -1,5 +1,7 @@ -package com.atwoz.member.ui.auth.interceptor; +package com.atwoz.global.config.interceptor; +import com.atwoz.global.config.interceptor.support.HttpMethod; +import com.atwoz.global.config.interceptor.support.PathContainer; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.lang.Nullable; diff --git a/src/main/java/com/atwoz/member/ui/auth/interceptor/HttpMethod.java b/src/main/java/com/atwoz/global/config/interceptor/support/HttpMethod.java similarity index 84% rename from src/main/java/com/atwoz/member/ui/auth/interceptor/HttpMethod.java rename to src/main/java/com/atwoz/global/config/interceptor/support/HttpMethod.java index d61ba25e..267b9e2a 100644 --- a/src/main/java/com/atwoz/member/ui/auth/interceptor/HttpMethod.java +++ b/src/main/java/com/atwoz/global/config/interceptor/support/HttpMethod.java @@ -1,4 +1,4 @@ -package com.atwoz.member.ui.auth.interceptor; +package com.atwoz.global.config.interceptor.support; public enum HttpMethod { diff --git a/src/main/java/com/atwoz/member/ui/auth/interceptor/PathContainer.java b/src/main/java/com/atwoz/global/config/interceptor/support/PathContainer.java similarity index 96% rename from src/main/java/com/atwoz/member/ui/auth/interceptor/PathContainer.java rename to src/main/java/com/atwoz/global/config/interceptor/support/PathContainer.java index 53bffbf0..f955fa96 100644 --- a/src/main/java/com/atwoz/member/ui/auth/interceptor/PathContainer.java +++ b/src/main/java/com/atwoz/global/config/interceptor/support/PathContainer.java @@ -1,10 +1,9 @@ -package com.atwoz.member.ui.auth.interceptor; - -import org.springframework.util.AntPathMatcher; -import org.springframework.util.PathMatcher; +package com.atwoz.global.config.interceptor.support; import java.util.ArrayList; import java.util.List; +import org.springframework.util.AntPathMatcher; +import org.springframework.util.PathMatcher; public class PathContainer { diff --git a/src/main/java/com/atwoz/member/ui/auth/interceptor/PathRequest.java b/src/main/java/com/atwoz/global/config/interceptor/support/PathRequest.java similarity index 91% rename from src/main/java/com/atwoz/member/ui/auth/interceptor/PathRequest.java rename to src/main/java/com/atwoz/global/config/interceptor/support/PathRequest.java index 44301134..2c6ed0b9 100644 --- a/src/main/java/com/atwoz/member/ui/auth/interceptor/PathRequest.java +++ b/src/main/java/com/atwoz/global/config/interceptor/support/PathRequest.java @@ -1,4 +1,4 @@ -package com.atwoz.member.ui.auth.interceptor; +package com.atwoz.global.config.interceptor.support; import org.springframework.util.PathMatcher; diff --git a/src/main/java/com/atwoz/member/application/auth/AuthService.java b/src/main/java/com/atwoz/member/application/auth/MemberAuthService.java similarity index 57% rename from src/main/java/com/atwoz/member/application/auth/AuthService.java rename to src/main/java/com/atwoz/member/application/auth/MemberAuthService.java index 4183b5a4..f6383453 100644 --- a/src/main/java/com/atwoz/member/application/auth/AuthService.java +++ b/src/main/java/com/atwoz/member/application/auth/MemberAuthService.java @@ -1,9 +1,9 @@ package com.atwoz.member.application.auth; -import com.atwoz.global.event.Events; import com.atwoz.member.application.auth.dto.LoginRequest; -import com.atwoz.member.application.auth.event.ValidatedLoginEvent; -import com.atwoz.member.domain.auth.TokenProvider; +import com.atwoz.member.domain.auth.MemberTokenProvider; +import com.atwoz.member.domain.member.Member; +import com.atwoz.member.domain.member.MemberRepository; import com.atwoz.member.infrastructure.auth.dto.MemberInfoResponse; import com.atwoz.member.infrastructure.auth.dto.OAuthProviderRequest; import lombok.RequiredArgsConstructor; @@ -12,23 +12,23 @@ @RequiredArgsConstructor @Service -public class AuthService { +public class MemberAuthService { private static final String DEFAULT_PHONE_NUMBER = "01011111111"; - private final TokenProvider tokenProvider; + private final MemberTokenProvider memberTokenProvider; private final OAuthRequester oAuthRequester; + private final MemberRepository memberRepository; + /** + * OAuth 인증방식과 PASS 인증 방식에 차이가 존재해서 회의 후 메서드 변경을 진행할 예정 + */ @Transactional public String login(final LoginRequest request, final OAuthProviderRequest provider) { String accessToken = oAuthRequester.getAccessToken(request.code(), provider); MemberInfoResponse memberInfoResponse = oAuthRequester.getMemberInfo(accessToken, provider); - - /** - * OAuth 인증방식과 PASS 인증 방식에 차이가 존재해서 회의 후 메서드 변경을 진행할 예정 - */ - Events.raise(new ValidatedLoginEvent(DEFAULT_PHONE_NUMBER)); - - return tokenProvider.createTokenWithPhoneNumber(DEFAULT_PHONE_NUMBER); + Member createdMember = Member.createWithOAuth(DEFAULT_PHONE_NUMBER); + memberRepository.save(createdMember); + return memberTokenProvider.createAccessToken(createdMember.getId()); } } diff --git a/src/main/java/com/atwoz/member/config/AuthConfig.java b/src/main/java/com/atwoz/member/config/AuthConfig.java deleted file mode 100644 index 43b0912e..00000000 --- a/src/main/java/com/atwoz/member/config/AuthConfig.java +++ /dev/null @@ -1,69 +0,0 @@ -package com.atwoz.member.config; - -import com.atwoz.member.ui.auth.interceptor.LoginValidCheckerInterceptor; -import com.atwoz.member.ui.auth.interceptor.ParseMemberIdFromTokenInterceptor; -import com.atwoz.member.ui.auth.interceptor.PathMatcherInterceptor; -import com.atwoz.member.ui.auth.interceptor.TokenRegenerateInterceptor; -import com.atwoz.member.ui.auth.support.resolver.AuthArgumentResolver; -import java.util.List; -import lombok.RequiredArgsConstructor; -import org.springframework.context.annotation.Configuration; -import org.springframework.web.method.support.HandlerMethodArgumentResolver; -import org.springframework.web.servlet.HandlerInterceptor; -import org.springframework.web.servlet.config.annotation.InterceptorRegistry; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; - -import static com.atwoz.member.ui.auth.interceptor.HttpMethod.DELETE; -import static com.atwoz.member.ui.auth.interceptor.HttpMethod.GET; -import static com.atwoz.member.ui.auth.interceptor.HttpMethod.OPTIONS; -import static com.atwoz.member.ui.auth.interceptor.HttpMethod.PATCH; -import static com.atwoz.member.ui.auth.interceptor.HttpMethod.POST; - -@RequiredArgsConstructor -@Configuration -public class AuthConfig implements WebMvcConfigurer { - - private final AuthArgumentResolver authArgumentResolver; - private final ParseMemberIdFromTokenInterceptor parseMemberIdFromTokenInterceptor; - private final LoginValidCheckerInterceptor loginValidCheckerInterceptor; - private final TokenRegenerateInterceptor tokenRegenerateInterceptor; - - @Override - public void addInterceptors(final InterceptorRegistry registry) { - registry.addInterceptor(parseMemberIdFromTokenInterceptor()); - registry.addInterceptor(loginValidCheckerInterceptor()); - registry.addInterceptor(tokenRegenerateInterceptor()); - } - - private HandlerInterceptor parseMemberIdFromTokenInterceptor() { - return new PathMatcherInterceptor(parseMemberIdFromTokenInterceptor) - .excludePathPattern("/**", OPTIONS) - .addPathPatterns("/api/info/**", GET, POST, PATCH) - .addPathPatterns("/api/surveys/**", GET, POST) - .addPathPatterns("/api/members/me/missions/**", GET, POST, PATCH) - .addPathPatterns("/api/members/me/surveys/**", GET, POST); - } - - /** - * @AuthMember를 통해서 인증이 필요한 경우에 해당 메서드에 URI를 추가해주면 된다. 추가를 해야지 인증,인가 가능 - */ - private HandlerInterceptor loginValidCheckerInterceptor() { - return new PathMatcherInterceptor(loginValidCheckerInterceptor) - .excludePathPattern("/**", OPTIONS) - .excludePathPattern("/api/missions/**", GET, POST, PATCH, DELETE) - .excludePathPattern("/api/surveys/**", GET, POST) - .addPathPatterns("/api/members/**", GET, POST, PATCH, DELETE) - .addPathPatterns("/api/reports/**", POST); - } - - private HandlerInterceptor tokenRegenerateInterceptor() { - return new PathMatcherInterceptor(tokenRegenerateInterceptor) - .excludePathPattern("/**", OPTIONS) - .addPathPatterns("/api/auth/login", POST); - } - - @Override - public void addArgumentResolvers(final List resolvers) { - resolvers.add(authArgumentResolver); - } -} diff --git a/src/main/java/com/atwoz/member/config/MemberAuthConfig.java b/src/main/java/com/atwoz/member/config/MemberAuthConfig.java new file mode 100644 index 00000000..8dac265f --- /dev/null +++ b/src/main/java/com/atwoz/member/config/MemberAuthConfig.java @@ -0,0 +1,51 @@ +package com.atwoz.member.config; + +import com.atwoz.global.config.interceptor.PathMatcherInterceptor; +import com.atwoz.member.ui.auth.interceptor.MemberLoginValidCheckerInterceptor; +import com.atwoz.member.ui.auth.support.resolver.MemberAuthArgumentResolver; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.servlet.HandlerInterceptor; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +import static com.atwoz.global.config.interceptor.support.HttpMethod.DELETE; +import static com.atwoz.global.config.interceptor.support.HttpMethod.GET; +import static com.atwoz.global.config.interceptor.support.HttpMethod.OPTIONS; +import static com.atwoz.global.config.interceptor.support.HttpMethod.PATCH; +import static com.atwoz.global.config.interceptor.support.HttpMethod.POST; + +@RequiredArgsConstructor +@Configuration +public class MemberAuthConfig implements WebMvcConfigurer { + + private final MemberAuthArgumentResolver memberAuthArgumentResolver; + private final MemberLoginValidCheckerInterceptor memberLoginValidCheckerInterceptor; + + @Override + public void addInterceptors(final InterceptorRegistry registry) { + registry.addInterceptor(loginValidCheckerInterceptor()); + } + + /** + * @AuthMember를 통해서 인증이 필요한 경우에 해당 메서드에 URI를 추가해주면 된다. 추가를 해야지 인증,인가 가능 + */ + private HandlerInterceptor loginValidCheckerInterceptor() { + return new PathMatcherInterceptor(memberLoginValidCheckerInterceptor) + .excludePathPattern("/**", OPTIONS) + .excludePathPattern("/api/missions/**", GET, POST, PATCH, DELETE) + .excludePathPattern("/api/surveys/**", GET, POST) + .addPathPatterns("/api/members/**", GET, POST, PATCH, DELETE) + .addPathPatterns("/api/reports/**", POST) + .addPathPatterns("/api/surveys/**", GET, POST) + .addPathPatterns("/api/members/me/missions/**", GET, POST, PATCH) + .addPathPatterns("/api/members/me/surveys/**", GET, POST); + } + + @Override + public void addArgumentResolvers(final List resolvers) { + resolvers.add(memberAuthArgumentResolver); + } +} diff --git a/src/main/java/com/atwoz/member/domain/auth/MemberTokenProvider.java b/src/main/java/com/atwoz/member/domain/auth/MemberTokenProvider.java new file mode 100644 index 00000000..01f63460 --- /dev/null +++ b/src/main/java/com/atwoz/member/domain/auth/MemberTokenProvider.java @@ -0,0 +1,10 @@ +package com.atwoz.member.domain.auth; + +public interface MemberTokenProvider { + + String createAccessToken(Long id); + + String createRefreshToken(Long id); + + T extract(String token, String claimName, Class classType); +} diff --git a/src/main/java/com/atwoz/member/domain/auth/TokenProvider.java b/src/main/java/com/atwoz/member/domain/auth/TokenProvider.java deleted file mode 100644 index e5e27192..00000000 --- a/src/main/java/com/atwoz/member/domain/auth/TokenProvider.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.atwoz.member.domain.auth; - -public interface TokenProvider { - - String createTokenWithId(Long id); - - String createTokenWithPhoneNumber(String phoneNumber); - - T extract(String token, String claimName, Class classType); -} diff --git a/src/main/java/com/atwoz/member/exception/MemberExceptionHandler.java b/src/main/java/com/atwoz/member/exception/MemberExceptionHandler.java index 76e4ffb9..cd13c9e0 100644 --- a/src/main/java/com/atwoz/member/exception/MemberExceptionHandler.java +++ b/src/main/java/com/atwoz/member/exception/MemberExceptionHandler.java @@ -3,7 +3,7 @@ import com.atwoz.member.exception.exceptions.auth.ExpiredTokenException; import com.atwoz.member.exception.exceptions.auth.InvalidJsonKeyException; import com.atwoz.member.exception.exceptions.auth.JsonDataInvalidException; -import com.atwoz.member.exception.exceptions.auth.LoginInvalidException; +import com.atwoz.member.exception.exceptions.auth.MemberLoginInvalidException; import com.atwoz.member.exception.exceptions.auth.OAuthPlatformNotFountException; import com.atwoz.member.exception.exceptions.auth.SignatureInvalidException; import com.atwoz.member.exception.exceptions.auth.TokenFormInvalidException; @@ -85,8 +85,8 @@ public ResponseEntity handleTokenInvalidException(final TokenInvalidExce return getUnauthorized(e); } - @ExceptionHandler(LoginInvalidException.class) - public ResponseEntity handleLoginInvalidException(final LoginInvalidException e) { + @ExceptionHandler(MemberLoginInvalidException.class) + public ResponseEntity handleLoginInvalidException(final MemberLoginInvalidException e) { return getUnauthorized(e); } diff --git a/src/main/java/com/atwoz/member/exception/exceptions/auth/LoginInvalidException.java b/src/main/java/com/atwoz/member/exception/exceptions/auth/MemberLoginInvalidException.java similarity index 52% rename from src/main/java/com/atwoz/member/exception/exceptions/auth/LoginInvalidException.java rename to src/main/java/com/atwoz/member/exception/exceptions/auth/MemberLoginInvalidException.java index 1716d862..e217bcc0 100644 --- a/src/main/java/com/atwoz/member/exception/exceptions/auth/LoginInvalidException.java +++ b/src/main/java/com/atwoz/member/exception/exceptions/auth/MemberLoginInvalidException.java @@ -1,8 +1,8 @@ package com.atwoz.member.exception.exceptions.auth; -public class LoginInvalidException extends RuntimeException { +public class MemberLoginInvalidException extends RuntimeException { - public LoginInvalidException() { + public MemberLoginInvalidException() { super("로그인 정보를 찾을 수 없습니다."); } } diff --git a/src/main/java/com/atwoz/member/infrastructure/auth/JwtTokenProvider.java b/src/main/java/com/atwoz/member/infrastructure/auth/MemberJwtTokenProvider.java similarity index 66% rename from src/main/java/com/atwoz/member/infrastructure/auth/JwtTokenProvider.java rename to src/main/java/com/atwoz/member/infrastructure/auth/MemberJwtTokenProvider.java index 073c7df2..50f44b75 100644 --- a/src/main/java/com/atwoz/member/infrastructure/auth/JwtTokenProvider.java +++ b/src/main/java/com/atwoz/member/infrastructure/auth/MemberJwtTokenProvider.java @@ -1,6 +1,6 @@ package com.atwoz.member.infrastructure.auth; -import com.atwoz.member.domain.auth.TokenProvider; +import com.atwoz.member.domain.auth.MemberTokenProvider; import com.atwoz.member.exception.exceptions.auth.ExpiredTokenException; import com.atwoz.member.exception.exceptions.auth.SignatureInvalidException; import com.atwoz.member.exception.exceptions.auth.TokenFormInvalidException; @@ -24,13 +24,23 @@ @NoArgsConstructor @Component -public class JwtTokenProvider implements TokenProvider { +public class MemberJwtTokenProvider implements MemberTokenProvider { + + private static final String ID = "id"; + private static final String TOKEN_TYPE = "token type"; + private static final String REFRESH_TOKEN = "refresh token"; + private static final String ACCESS_TOKEN = "access token"; + private static final String ROLE = "role"; + private static final String MEMBER = "member"; @Value("${jwt.secret}") private String secret; - @Value("${jwt.expiration-period}") - private int expirationPeriod; + @Value("${jwt.access-token-expiration-period}") + private int accessTokenExpirationPeriod; + + @Value("${jwt.refresh-token-expiration-period}") + private int refreshTokenExpirationPeriod; private Key key; @@ -40,24 +50,28 @@ private void init() { } @Override - public String createTokenWithId(final Long id) { + public String createAccessToken(final Long id) { Claims claims = Jwts.claims(); - claims.put("id", id); - return createToken(claims); + claims.put(ID, id); + claims.put(TOKEN_TYPE, ACCESS_TOKEN); + claims.put(ROLE, MEMBER); + return createToken(claims, accessTokenExpirationPeriod); } @Override - public String createTokenWithPhoneNumber(final String phoneNumber) { + public String createRefreshToken(final Long id) { Claims claims = Jwts.claims(); - claims.put("phoneNumber", phoneNumber); - return createToken(claims); + claims.put(ID, id); + claims.put(TOKEN_TYPE, REFRESH_TOKEN); + claims.put(ROLE, MEMBER); + return createToken(claims, refreshTokenExpirationPeriod); } - private String createToken(final Claims claims) { + private String createToken(final Claims claims, final int expirationPeriod) { return Jwts.builder() .setClaims(claims) .setIssuedAt(issuedAt()) - .setExpiration(expiredAt()) + .setExpiration(expiredAt(expirationPeriod)) .signWith(key, SignatureAlgorithm.HS256) .compact(); } @@ -69,10 +83,10 @@ private Date issuedAt() { .toInstant()); } - private Date expiredAt() { + private Date expiredAt(final int expirationPeriod) { LocalDateTime now = LocalDateTime.now(); - return Date.from(now.plusHours(expirationPeriod) + return Date.from(now.plusDays(expirationPeriod) .atZone(ZoneId.systemDefault()) .toInstant()); } diff --git a/src/main/java/com/atwoz/member/ui/auth/AuthController.java b/src/main/java/com/atwoz/member/ui/auth/MemberAuthController.java similarity index 74% rename from src/main/java/com/atwoz/member/ui/auth/AuthController.java rename to src/main/java/com/atwoz/member/ui/auth/MemberAuthController.java index 00868d7a..79790959 100644 --- a/src/main/java/com/atwoz/member/ui/auth/AuthController.java +++ b/src/main/java/com/atwoz/member/ui/auth/MemberAuthController.java @@ -1,10 +1,10 @@ package com.atwoz.member.ui.auth; -import com.atwoz.member.application.auth.AuthService; +import com.atwoz.member.application.auth.MemberAuthService; import com.atwoz.member.application.auth.dto.LoginRequest; import com.atwoz.member.infrastructure.auth.dto.OAuthProviderRequest; import com.atwoz.member.ui.auth.dto.TokenResponse; -import com.atwoz.member.ui.auth.support.auth.OAuthAuthority; +import com.atwoz.member.ui.auth.support.OAuthAuthority; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; @@ -14,16 +14,16 @@ import org.springframework.web.bind.annotation.RestController; @RequiredArgsConstructor -@RequestMapping("/api/auth") +@RequestMapping("/api/members/auth") @RestController -public class AuthController { +public class MemberAuthController { - private final AuthService authService; + private final MemberAuthService memberAuthService; @PostMapping("/login") public ResponseEntity login(@RequestBody @Valid final LoginRequest request, @OAuthAuthority final OAuthProviderRequest provider) { - String token = authService.login(request, provider); + String token = memberAuthService.login(request, provider); return ResponseEntity.ok(new TokenResponse(token)); } } diff --git a/src/main/java/com/atwoz/member/ui/auth/interceptor/LoginValidCheckerInterceptor.java b/src/main/java/com/atwoz/member/ui/auth/interceptor/LoginValidCheckerInterceptor.java deleted file mode 100644 index 23681fca..00000000 --- a/src/main/java/com/atwoz/member/ui/auth/interceptor/LoginValidCheckerInterceptor.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.atwoz.member.ui.auth.interceptor; - -import com.atwoz.member.domain.auth.TokenProvider; -import com.atwoz.member.exception.exceptions.auth.LoginInvalidException; -import com.atwoz.member.ui.auth.support.auth.AuthenticationContext; -import com.atwoz.member.ui.auth.support.auth.AuthenticationExtractor; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Component; -import org.springframework.web.servlet.HandlerInterceptor; - -@RequiredArgsConstructor -@Component -public class LoginValidCheckerInterceptor implements HandlerInterceptor { - - private static final String MEMBER_ID = "id"; - - private final TokenProvider tokenProvider; - private final AuthenticationContext authenticationContext; - private final AuthenticationExtractor authenticationExtractor; - - @Override - public boolean preHandle(final HttpServletRequest request, - final HttpServletResponse response, - final Object handler) throws Exception { - String token = authenticationExtractor.extractFromRequest(request) - .orElseThrow(LoginInvalidException::new); - - Long extractedMemberId = tokenProvider.extract(token, MEMBER_ID, Long.class); - authenticationContext.setAuthentication(extractedMemberId); - - return true; - } -} diff --git a/src/main/java/com/atwoz/member/ui/auth/interceptor/MemberLoginValidCheckerInterceptor.java b/src/main/java/com/atwoz/member/ui/auth/interceptor/MemberLoginValidCheckerInterceptor.java new file mode 100644 index 00000000..9475d5e2 --- /dev/null +++ b/src/main/java/com/atwoz/member/ui/auth/interceptor/MemberLoginValidCheckerInterceptor.java @@ -0,0 +1,34 @@ +package com.atwoz.member.ui.auth.interceptor; + +import com.atwoz.member.domain.auth.MemberTokenProvider; +import com.atwoz.member.exception.exceptions.auth.MemberLoginInvalidException; +import com.atwoz.member.ui.auth.support.MemberAuthenticationContext; +import com.atwoz.member.ui.auth.support.MemberAuthenticationExtractor; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.HandlerInterceptor; + +@RequiredArgsConstructor +@Component +public class MemberLoginValidCheckerInterceptor implements HandlerInterceptor { + + private static final String MEMBER_ID = "id"; + + private final MemberAuthenticationContext memberAuthenticationContext; + private final MemberAuthenticationExtractor memberAuthenticationExtractor; + private final MemberTokenProvider memberTokenProvider; + + @Override + public boolean preHandle(final HttpServletRequest request, + final HttpServletResponse response, + final Object handler) throws Exception { + String token = memberAuthenticationExtractor.extractFromRequest(request) + .orElseThrow(MemberLoginInvalidException::new); + Long extractedId = memberTokenProvider.extract(token, MEMBER_ID, Long.class); + memberAuthenticationContext.setAuthentication(extractedId); + + return true; + } +} diff --git a/src/main/java/com/atwoz/member/ui/auth/interceptor/ParseMemberIdFromTokenInterceptor.java b/src/main/java/com/atwoz/member/ui/auth/interceptor/ParseMemberIdFromTokenInterceptor.java deleted file mode 100644 index eec586cf..00000000 --- a/src/main/java/com/atwoz/member/ui/auth/interceptor/ParseMemberIdFromTokenInterceptor.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.atwoz.member.ui.auth.interceptor; - -import com.atwoz.member.ui.auth.support.auth.AuthenticationContext; -import com.atwoz.member.ui.auth.support.auth.AuthenticationExtractor; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Component; -import org.springframework.web.servlet.HandlerInterceptor; - -@RequiredArgsConstructor -@Component -public class ParseMemberIdFromTokenInterceptor implements HandlerInterceptor { - - private final LoginValidCheckerInterceptor loginValidCheckerInterceptor; - private final AuthenticationContext authenticationContext; - private final AuthenticationExtractor authenticationExtractor; - - @Override - public boolean preHandle(final HttpServletRequest request, - final HttpServletResponse response, - final Object handler) throws Exception { - if (authenticationExtractor.extractFromRequest(request).isEmpty()) { - authenticationContext.setAnonymous(); - return true; - } - - return loginValidCheckerInterceptor.preHandle(request, response, handler); - } -} diff --git a/src/main/java/com/atwoz/member/ui/auth/interceptor/TokenRegenerateInterceptor.java b/src/main/java/com/atwoz/member/ui/auth/interceptor/TokenRegenerateInterceptor.java deleted file mode 100644 index e5d37a50..00000000 --- a/src/main/java/com/atwoz/member/ui/auth/interceptor/TokenRegenerateInterceptor.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.atwoz.member.ui.auth.interceptor; - -import com.atwoz.member.domain.auth.JsonMapper; -import com.atwoz.member.domain.auth.TokenProvider; -import com.atwoz.member.domain.member.Member; -import com.atwoz.member.domain.member.MemberRepository; -import com.atwoz.member.exception.exceptions.member.MemberNotFoundException; -import com.atwoz.member.ui.auth.dto.TokenResponse; -import com.atwoz.member.ui.auth.support.auth.AuthenticationExtractor; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import lombok.RequiredArgsConstructor; -import org.springframework.lang.Nullable; -import org.springframework.stereotype.Component; -import org.springframework.web.servlet.HandlerInterceptor; -import org.springframework.web.servlet.ModelAndView; - -@RequiredArgsConstructor -@Component -public class TokenRegenerateInterceptor implements HandlerInterceptor { - - private static final String PHONE_NUMBER = "phoneNumber"; - - private final TokenProvider tokenProvider; - private final MemberRepository memberRepository; - private final JsonMapper jsonMapper; - private final AuthenticationExtractor authenticationExtractor; - - @Override - public void postHandle(final HttpServletRequest request, - final HttpServletResponse response, - final Object handler, - final @Nullable ModelAndView modelAndView) throws Exception { - String token = authenticationExtractor.extractFromResponse(response); - String extractedPhoneNumber = tokenProvider.extract(token, PHONE_NUMBER, String.class); - - if (extractedPhoneNumber != null) { - response.resetBuffer(); - Member foundMember = findMemberByPhoneNumber(extractedPhoneNumber); - String createdToken = tokenProvider.createTokenWithId(foundMember.getId()); - byte[] convertedTokenResponse = jsonMapper.convertObjectToJsonByteArray(new TokenResponse(createdToken)); - response.getOutputStream().write(convertedTokenResponse); - } - } - - private Member findMemberByPhoneNumber(final String phoneNumber) { - return memberRepository.findByPhoneNumber(phoneNumber) - .orElseThrow(MemberNotFoundException::new); - } -} diff --git a/src/main/java/com/atwoz/member/ui/auth/support/auth/AuthMember.java b/src/main/java/com/atwoz/member/ui/auth/support/AuthMember.java similarity index 84% rename from src/main/java/com/atwoz/member/ui/auth/support/auth/AuthMember.java rename to src/main/java/com/atwoz/member/ui/auth/support/AuthMember.java index 7d29f516..4a1f9058 100644 --- a/src/main/java/com/atwoz/member/ui/auth/support/auth/AuthMember.java +++ b/src/main/java/com/atwoz/member/ui/auth/support/AuthMember.java @@ -1,4 +1,4 @@ -package com.atwoz.member.ui.auth.support.auth; +package com.atwoz.member.ui.auth.support; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; diff --git a/src/main/java/com/atwoz/member/ui/auth/support/MemberAuthenticationContext.java b/src/main/java/com/atwoz/member/ui/auth/support/MemberAuthenticationContext.java new file mode 100644 index 00000000..3ab06f7c --- /dev/null +++ b/src/main/java/com/atwoz/member/ui/auth/support/MemberAuthenticationContext.java @@ -0,0 +1,25 @@ +package com.atwoz.member.ui.auth.support; + +import com.atwoz.member.exception.exceptions.auth.MemberLoginInvalidException; +import java.util.Objects; +import org.springframework.stereotype.Component; +import org.springframework.web.context.annotation.RequestScope; + +@RequestScope +@Component +public class MemberAuthenticationContext { + + private Long memberId; + + public void setAuthentication(final Long memberId) { + this.memberId = memberId; + } + + public Long getPrincipal() { + if (Objects.isNull(this.memberId)) { + throw new MemberLoginInvalidException(); + } + + return memberId; + } +} diff --git a/src/main/java/com/atwoz/member/ui/auth/support/auth/AuthenticationExtractor.java b/src/main/java/com/atwoz/member/ui/auth/support/MemberAuthenticationExtractor.java similarity index 56% rename from src/main/java/com/atwoz/member/ui/auth/support/auth/AuthenticationExtractor.java rename to src/main/java/com/atwoz/member/ui/auth/support/MemberAuthenticationExtractor.java index 51a6f8e1..d6da4b8a 100644 --- a/src/main/java/com/atwoz/member/ui/auth/support/auth/AuthenticationExtractor.java +++ b/src/main/java/com/atwoz/member/ui/auth/support/MemberAuthenticationExtractor.java @@ -1,28 +1,22 @@ -package com.atwoz.member.ui.auth.support.auth; +package com.atwoz.member.ui.auth.support; -import com.atwoz.member.domain.auth.JsonMapper; import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import java.util.Optional; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; -import org.springframework.web.util.ContentCachingResponseWrapper; @RequiredArgsConstructor @Component -public class AuthenticationExtractor { +public class MemberAuthenticationExtractor { private static final String AUTHORIZATION_HEADER = "Authorization"; private static final String BEARER = "Bearer"; private static final String HEADER_SPLIT_DELIMITER = " "; - private static final String TOKEN = "token"; private static final int TOKEN_TYPE_INDEX = 0; private static final int TOKEN_VALUE_INDEX = 1; private static final int VALID_HEADER_SPLIT_LENGTH = 2; - private final JsonMapper jsonMapper; - public Optional extractFromRequest(final HttpServletRequest request) { String header = request.getHeader(AUTHORIZATION_HEADER); @@ -33,7 +27,7 @@ public Optional extractFromRequest(final HttpServletRequest request) { return extractFromHeader(header.split(HEADER_SPLIT_DELIMITER)); } - public Optional extractFromHeader(final String[] headerParts) { + private Optional extractFromHeader(final String[] headerParts) { if (headerParts.length == VALID_HEADER_SPLIT_LENGTH && headerParts[TOKEN_TYPE_INDEX].equals(BEARER)) { return Optional.ofNullable(headerParts[TOKEN_VALUE_INDEX]); @@ -41,16 +35,5 @@ public Optional extractFromHeader(final String[] headerParts) { return Optional.empty(); } - - public String extractFromResponse(final HttpServletResponse response) { - ContentCachingResponseWrapper responseWrapper = (ContentCachingResponseWrapper) response; - String responseBody = getResponseBody(responseWrapper); - - return jsonMapper.getValueByKey(responseBody, TOKEN); - } - - private String getResponseBody(final ContentCachingResponseWrapper responseWrapper) { - return new String(responseWrapper.getContentAsByteArray()); - } } diff --git a/src/main/java/com/atwoz/member/ui/auth/support/auth/OAuthAuthority.java b/src/main/java/com/atwoz/member/ui/auth/support/OAuthAuthority.java similarity index 85% rename from src/main/java/com/atwoz/member/ui/auth/support/auth/OAuthAuthority.java rename to src/main/java/com/atwoz/member/ui/auth/support/OAuthAuthority.java index 5f2a41b2..10c4f1e8 100644 --- a/src/main/java/com/atwoz/member/ui/auth/support/auth/OAuthAuthority.java +++ b/src/main/java/com/atwoz/member/ui/auth/support/OAuthAuthority.java @@ -1,4 +1,4 @@ -package com.atwoz.member.ui.auth.support.auth; +package com.atwoz.member.ui.auth.support; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; diff --git a/src/main/java/com/atwoz/member/ui/auth/support/auth/OAuthPlatform.java b/src/main/java/com/atwoz/member/ui/auth/support/OAuthPlatform.java similarity index 92% rename from src/main/java/com/atwoz/member/ui/auth/support/auth/OAuthPlatform.java rename to src/main/java/com/atwoz/member/ui/auth/support/OAuthPlatform.java index 2a64d1fb..27ae49cc 100644 --- a/src/main/java/com/atwoz/member/ui/auth/support/auth/OAuthPlatform.java +++ b/src/main/java/com/atwoz/member/ui/auth/support/OAuthPlatform.java @@ -1,4 +1,4 @@ -package com.atwoz.member.ui.auth.support.auth; +package com.atwoz.member.ui.auth.support; import com.atwoz.member.exception.exceptions.auth.OAuthPlatformNotFountException; import java.util.Arrays; diff --git a/src/main/java/com/atwoz/member/ui/auth/support/auth/OAuthProperties.java b/src/main/java/com/atwoz/member/ui/auth/support/OAuthProperties.java similarity index 92% rename from src/main/java/com/atwoz/member/ui/auth/support/auth/OAuthProperties.java rename to src/main/java/com/atwoz/member/ui/auth/support/OAuthProperties.java index 3439f194..cd53303a 100644 --- a/src/main/java/com/atwoz/member/ui/auth/support/auth/OAuthProperties.java +++ b/src/main/java/com/atwoz/member/ui/auth/support/OAuthProperties.java @@ -1,4 +1,4 @@ -package com.atwoz.member.ui.auth.support.auth; +package com.atwoz.member.ui.auth.support; import com.atwoz.member.infrastructure.auth.dto.OAuthProviderRequest; import java.util.Map; diff --git a/src/main/java/com/atwoz/member/ui/auth/support/auth/AuthenticationContext.java b/src/main/java/com/atwoz/member/ui/auth/support/auth/AuthenticationContext.java deleted file mode 100644 index 1cfb9c50..00000000 --- a/src/main/java/com/atwoz/member/ui/auth/support/auth/AuthenticationContext.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.atwoz.member.ui.auth.support.auth; - -import com.atwoz.member.exception.exceptions.auth.LoginInvalidException; -import java.util.Objects; -import org.springframework.stereotype.Component; -import org.springframework.web.context.annotation.RequestScope; - -@RequestScope -@Component -public class AuthenticationContext { - - private static final Long ANONYMOUS_MEMBER = -1L; - - private Long memberId; - - public void setAuthentication(Long memberId) { - this.memberId = memberId; - } - - public Long getPrincipal() { - if (Objects.isNull(this.memberId)) { - throw new LoginInvalidException(); - } - - return memberId; - } - - public void setAnonymous() { - this.memberId = ANONYMOUS_MEMBER; - } -} diff --git a/src/main/java/com/atwoz/member/ui/auth/support/resolver/AuthArgumentResolver.java b/src/main/java/com/atwoz/member/ui/auth/support/resolver/MemberAuthArgumentResolver.java similarity index 65% rename from src/main/java/com/atwoz/member/ui/auth/support/resolver/AuthArgumentResolver.java rename to src/main/java/com/atwoz/member/ui/auth/support/resolver/MemberAuthArgumentResolver.java index 930d6283..01cf7fce 100644 --- a/src/main/java/com/atwoz/member/ui/auth/support/resolver/AuthArgumentResolver.java +++ b/src/main/java/com/atwoz/member/ui/auth/support/resolver/MemberAuthArgumentResolver.java @@ -1,8 +1,7 @@ package com.atwoz.member.ui.auth.support.resolver; -import com.atwoz.member.exception.exceptions.auth.LoginInvalidException; -import com.atwoz.member.ui.auth.support.auth.AuthMember; -import com.atwoz.member.ui.auth.support.auth.AuthenticationContext; +import com.atwoz.member.ui.auth.support.AuthMember; +import com.atwoz.member.ui.auth.support.MemberAuthenticationContext; import lombok.RequiredArgsConstructor; import org.springframework.core.MethodParameter; import org.springframework.stereotype.Component; @@ -13,11 +12,9 @@ @RequiredArgsConstructor @Component -public class AuthArgumentResolver implements HandlerMethodArgumentResolver { +public class MemberAuthArgumentResolver implements HandlerMethodArgumentResolver { - private static final int ANONYMOUS = -1; - - private final AuthenticationContext authenticationContext; + private final MemberAuthenticationContext memberAuthenticationContext; @Override public boolean supportsParameter(final MethodParameter parameter) { @@ -30,12 +27,7 @@ public Object resolveArgument(final MethodParameter parameter, final ModelAndViewContainer mavContainer, final NativeWebRequest webRequest, final WebDataBinderFactory binderFactory) throws Exception { - Long memberId = authenticationContext.getPrincipal(); - - if (memberId == ANONYMOUS) { - throw new LoginInvalidException(); - } - return memberId; + return memberAuthenticationContext.getPrincipal(); } } diff --git a/src/main/java/com/atwoz/member/ui/auth/support/resolver/OAuthArgumentResolver.java b/src/main/java/com/atwoz/member/ui/auth/support/resolver/OAuthArgumentResolver.java index 4a8fe4d4..998460e1 100644 --- a/src/main/java/com/atwoz/member/ui/auth/support/resolver/OAuthArgumentResolver.java +++ b/src/main/java/com/atwoz/member/ui/auth/support/resolver/OAuthArgumentResolver.java @@ -1,8 +1,8 @@ package com.atwoz.member.ui.auth.support.resolver; import com.atwoz.member.domain.auth.JsonMapper; -import com.atwoz.member.ui.auth.support.auth.OAuthAuthority; -import com.atwoz.member.ui.auth.support.auth.OAuthProperties; +import com.atwoz.member.ui.auth.support.OAuthAuthority; +import com.atwoz.member.ui.auth.support.OAuthProperties; import java.util.Objects; import lombok.RequiredArgsConstructor; import org.springframework.core.MethodParameter; diff --git a/src/main/java/com/atwoz/member/ui/member/MemberController.java b/src/main/java/com/atwoz/member/ui/member/MemberController.java index d9263f6f..40d12ada 100644 --- a/src/main/java/com/atwoz/member/ui/member/MemberController.java +++ b/src/main/java/com/atwoz/member/ui/member/MemberController.java @@ -5,7 +5,7 @@ import com.atwoz.member.application.member.dto.MemberInitializeRequest; import com.atwoz.member.application.member.dto.MemberUpdateRequest; import com.atwoz.member.infrastructure.member.dto.MemberResponse; -import com.atwoz.member.ui.auth.support.auth.AuthMember; +import com.atwoz.member.ui.auth.support.AuthMember; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; diff --git a/src/main/java/com/atwoz/memberlike/ui/MemberLikeController.java b/src/main/java/com/atwoz/memberlike/ui/MemberLikeController.java index 0537173f..c5415c41 100644 --- a/src/main/java/com/atwoz/memberlike/ui/MemberLikeController.java +++ b/src/main/java/com/atwoz/memberlike/ui/MemberLikeController.java @@ -1,6 +1,6 @@ package com.atwoz.memberlike.ui; -import com.atwoz.member.ui.auth.support.auth.AuthMember; +import com.atwoz.member.ui.auth.support.AuthMember; import com.atwoz.memberlike.application.MemberLikeQueryService; import com.atwoz.memberlike.application.MemberLikeService; import com.atwoz.memberlike.application.dto.MemberLikeCreateRequest; diff --git a/src/main/java/com/atwoz/mission/ui/membermission/MemberMissionsController.java b/src/main/java/com/atwoz/mission/ui/membermission/MemberMissionsController.java index 1bd7dce2..d5946377 100644 --- a/src/main/java/com/atwoz/mission/ui/membermission/MemberMissionsController.java +++ b/src/main/java/com/atwoz/mission/ui/membermission/MemberMissionsController.java @@ -1,6 +1,6 @@ package com.atwoz.mission.ui.membermission; -import com.atwoz.member.ui.auth.support.auth.AuthMember; +import com.atwoz.member.ui.auth.support.AuthMember; import com.atwoz.mission.application.membermission.MemberMissionsQueryService; import com.atwoz.mission.intrastructure.membermission.dto.MemberMissionPagingResponse; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/com/atwoz/report/ui/ReportController.java b/src/main/java/com/atwoz/report/ui/ReportController.java index f3afd230..27cde16e 100644 --- a/src/main/java/com/atwoz/report/ui/ReportController.java +++ b/src/main/java/com/atwoz/report/ui/ReportController.java @@ -1,6 +1,6 @@ package com.atwoz.report.ui; -import com.atwoz.member.ui.auth.support.auth.AuthMember; +import com.atwoz.member.ui.auth.support.AuthMember; import com.atwoz.report.application.ReportService; import com.atwoz.report.application.dto.ReportCreateRequest; import jakarta.validation.Valid; diff --git a/src/main/java/com/atwoz/survey/ui/membersurvey/MemberSurveysController.java b/src/main/java/com/atwoz/survey/ui/membersurvey/MemberSurveysController.java index 46ec5e9a..7aa15f71 100644 --- a/src/main/java/com/atwoz/survey/ui/membersurvey/MemberSurveysController.java +++ b/src/main/java/com/atwoz/survey/ui/membersurvey/MemberSurveysController.java @@ -1,12 +1,13 @@ package com.atwoz.survey.ui.membersurvey; -import com.atwoz.member.ui.auth.support.auth.AuthMember; +import com.atwoz.member.ui.auth.support.AuthMember; import com.atwoz.survey.application.membersurvey.MemberSurveysQueryService; import com.atwoz.survey.application.membersurvey.MemberSurveysService; import com.atwoz.survey.application.membersurvey.dto.SurveySubmitRequest; import com.atwoz.survey.infrastructure.membersurvey.dto.MemberSurveyResponse; import com.atwoz.survey.ui.membersurvey.dto.MatchMemberSearchResponse; import jakarta.validation.Valid; +import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -17,8 +18,6 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import java.util.List; - @RequiredArgsConstructor @RequestMapping("/api/members/me/surveys") @RestController diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index c80ea683..74125ae3 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -21,9 +21,13 @@ jasypt: bean: jasyptEncryptor password: ${ENCRYPT_KEY} +cookie: + max-age: ENC(LZMhucgSTnZxWG8aPhgclw==) + jwt: secret: ENC(Tnm4CPIFdSiqmH67nWcDfRgaKmpSr4EvRJYZAYLiwOXazIY9aZnLBMlxQnKC2C0j10l6zkOEbRMKIa2W4u7DtIRjCC7QEzCSCdtm1NCEntKRybirDvUH8g==) - expiration-period: ENC(wtkJPr944E3bs6eMwjj4lQ==) + access-token-expiration-period: ENC(46U7S/gH1lH6kpaOwhVaow==) + refresh-token-expiration-period: ENC(kb9YVXMB9HqUuxoxcnMHXw==) mail: host: ENC(2PviuayFL6dKe91WydIBx81bpSBoI3FU) diff --git a/src/test/java/com/atwoz/admin/application/auth/AdminAuthServiceTest.java b/src/test/java/com/atwoz/admin/application/auth/AdminAuthServiceTest.java new file mode 100644 index 00000000..eff1aafb --- /dev/null +++ b/src/test/java/com/atwoz/admin/application/auth/AdminAuthServiceTest.java @@ -0,0 +1,133 @@ +package com.atwoz.admin.application.auth; + +import com.atwoz.admin.application.auth.dto.AdminAccessTokenResponse; +import com.atwoz.admin.application.auth.dto.AdminLoginRequest; +import com.atwoz.admin.application.auth.dto.AdminSignUpRequest; +import com.atwoz.admin.application.auth.dto.AdminTokenResponse; +import com.atwoz.admin.domain.admin.AdminRepository; +import com.atwoz.admin.domain.admin.AdminTokenProvider; +import com.atwoz.admin.exception.exceptions.AdminNotFoundException; +import com.atwoz.admin.exception.exceptions.InvalidPasswordException; +import com.atwoz.admin.infrastructure.admin.AdminFakeRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import static com.atwoz.admin.fixture.AdminFixture.관리자_생성; +import static com.atwoz.admin.fixture.AdminRequestFixture.관리자_로그인_요청; +import static com.atwoz.admin.fixture.AdminRequestFixture.관리자_회원_가입_요청; +import static com.atwoz.admin.fixture.AdminTokenResponseFixture.관리자_액세스_토큰_생성_응답; +import static com.atwoz.admin.fixture.AdminTokenResponseFixture.관리자_토큰_생성_응답; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.SoftAssertions.assertSoftly; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +@DisplayNameGeneration(ReplaceUnderscores.class) +@SuppressWarnings("NonAsciiCharacters") +@ExtendWith(MockitoExtension.class) +class AdminAuthServiceTest { + + private static final String EMAIL = "email@email.com"; + private static final String PASSWORD = "password"; + private static final String ID = "id"; + + @Mock + private AdminTokenProvider adminTokenProvider; + private AdminAuthService adminAuthService; + private AdminRepository adminRepository; + + @BeforeEach + void setup() { + adminRepository = new AdminFakeRepository(); + adminAuthService = new AdminAuthService(adminRepository, adminTokenProvider); + } + + @Test + void 회원_가입을_진행하면_토큰을_반환한다() { + // given + AdminSignUpRequest adminSignUpRequest = 관리자_회원_가입_요청(); + AdminTokenResponse adminTokenResponse = 관리자_토큰_생성_응답(); + when(adminTokenProvider.createAccessToken(any())).thenReturn(adminTokenResponse.accessToken()); + when(adminTokenProvider.createRefreshToken(any())).thenReturn(adminTokenResponse.refreshToken()); + + // when + AdminTokenResponse response = adminAuthService.signUp(adminSignUpRequest); + + // then + assertSoftly(softly -> { + softly.assertThat(response.accessToken()).isEqualTo(adminTokenResponse.accessToken()); + softly.assertThat(response.refreshToken()).isEqualTo(adminTokenResponse.refreshToken()); + }); + } + + @Nested + class 로그인 { + + @Test + void 로그인_입력이_올바르면_토큰을_반환한다() { + // given + adminRepository.save(관리자_생성()); + AdminLoginRequest adminLoginRequest = 관리자_로그인_요청(); + AdminTokenResponse adminTokenResponse = 관리자_토큰_생성_응답(); + when(adminTokenProvider.createAccessToken(any())).thenReturn(adminTokenResponse.accessToken()); + when(adminTokenProvider.createRefreshToken(any())).thenReturn(adminTokenResponse.refreshToken()); + + // when + AdminTokenResponse response = adminAuthService.login(adminLoginRequest); + + // then + assertSoftly(softly -> { + softly.assertThat(response.accessToken()).isEqualTo(adminTokenResponse.accessToken()); + softly.assertThat(response.refreshToken()).isEqualTo(adminTokenResponse.refreshToken()); + }); + } + + @Test + void 입력한_이메일이_올바르지_않으면_예외가_발생한다() { + // given + adminRepository.save(관리자_생성()); + String invalidEmail = "invalidEmail"; + AdminLoginRequest adminLoginRequest = 관리자_로그인_요청(invalidEmail, PASSWORD); + + // when & then + assertThatThrownBy(() -> adminAuthService.login(adminLoginRequest)) + .isInstanceOf(AdminNotFoundException.class); + } + + @Test + void 입력한_비밀번호가_올바르지_않으면_예외가_발생한다() { + // given + adminRepository.save(관리자_생성()); + String invalidPassword = "invalidPassword"; + AdminLoginRequest adminLoginRequest = 관리자_로그인_요청(EMAIL, invalidPassword); + + // when & then + assertThatThrownBy(() -> adminAuthService.login(adminLoginRequest)) + .isInstanceOf(InvalidPasswordException.class); + } + } + + @Test + void 리프레쉬_토큰으로_액세스_토큰을_재생성한다() { + // given + adminRepository.save(관리자_생성()); + Long expectedId = 1L; + String refreshToken = "refreshToken"; + AdminAccessTokenResponse adminAccessTokenResponse = 관리자_액세스_토큰_생성_응답(); + when(adminTokenProvider.extract(refreshToken, ID, Long.class)).thenReturn(expectedId); + when(adminTokenProvider.createAccessToken(any())).thenReturn(adminAccessTokenResponse.accessToken()); + + // when + AdminAccessTokenResponse response = adminAuthService.reGenerateAccessToken(refreshToken); + + // then + assertThat(response.accessToken()).isEqualTo(adminAccessTokenResponse.accessToken()); + } +} diff --git a/src/test/java/com/atwoz/admin/domain/admin/AdminTest.java b/src/test/java/com/atwoz/admin/domain/admin/AdminTest.java new file mode 100644 index 00000000..c6f88ae1 --- /dev/null +++ b/src/test/java/com/atwoz/admin/domain/admin/AdminTest.java @@ -0,0 +1,80 @@ +package com.atwoz.admin.domain.admin; + +import com.atwoz.admin.domain.admin.vo.AdminStatus; +import com.atwoz.admin.domain.admin.vo.Authority; +import com.atwoz.admin.domain.admin.vo.Department; +import com.atwoz.admin.exception.exceptions.InvalidPasswordException; +import com.atwoz.admin.exception.exceptions.PasswordMismatchException; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import static com.atwoz.admin.fixture.AdminFixture.관리자_생성; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.SoftAssertions.assertSoftly; + +@DisplayNameGeneration(ReplaceUnderscores.class) +@SuppressWarnings("NonAsciiCharacters") +class AdminTest { + + private static final String EMAIL = "email@email.com"; + private static final String PASSWORD = "password"; + private static final String CONFIRM_PASSWORD = "password"; + private static final String NAME = "name"; + private static final String PHONE_NUMBER = "010-1234-5678"; + + @Nested + class 관리자_생성 { + + @Test + void 입력이_올바르면_관리자를_생성한다() { + // when + Admin admin = Admin.createWith( + EMAIL, + PASSWORD, + CONFIRM_PASSWORD, + NAME, + PHONE_NUMBER + ); + + // then + assertSoftly(softly -> { + softly.assertThat(admin.getEmail()).isEqualTo(EMAIL); + softly.assertThat(admin.getPassword()).isEqualTo(PASSWORD); + softly.assertThat(admin.getName()).isEqualTo(NAME); + softly.assertThat(admin.getPhoneNumber()).isEqualTo(PHONE_NUMBER); + softly.assertThat(admin.getAuthority()).isEqualTo(Authority.ADMIN); + softly.assertThat(admin.getDepartment()).isEqualTo(Department.OPERATION); + softly.assertThat(admin.getAdminStatus()).isEqualTo(AdminStatus.PENDING); + }); + + } + + @Test + void 회원_가입시_입력한_비밀번호와_확인_비밀번호가_다르면_예외가_발생한다() { + // given + String invalidConfirmPassword = "invalidConfirmPassword"; + + // when & then + assertThatThrownBy(() -> Admin.createWith( + EMAIL, + PASSWORD, + invalidConfirmPassword, + NAME, + PHONE_NUMBER + )).isInstanceOf(PasswordMismatchException.class); + } + } + + @Test + void 로그인시_입력한_비밀번호가_관리자의_비밀번호와_다르면_예외가_발생한다() { + // given + Admin admin = 관리자_생성(); + String invalidPassword = "invalidPassword"; + + // when & then + assertThatThrownBy(() -> admin.validatePassword(invalidPassword)) + .isInstanceOf(InvalidPasswordException.class); + } +} diff --git a/src/test/java/com/atwoz/admin/fixture/AdminFixture.java b/src/test/java/com/atwoz/admin/fixture/AdminFixture.java new file mode 100644 index 00000000..818f1c28 --- /dev/null +++ b/src/test/java/com/atwoz/admin/fixture/AdminFixture.java @@ -0,0 +1,35 @@ +package com.atwoz.admin.fixture; + +import com.atwoz.admin.domain.admin.Admin; + +public class AdminFixture { + + private static final String EMAIL = "email@email.com"; + private static final String PASSWORD = "password"; + private static final String NAME = "name"; + private static final String PHONE_NUMBER = "010-1234-5678"; + + public static Admin 관리자_생성() { + return Admin.createWith( + EMAIL, + PASSWORD, + PASSWORD, + NAME, + PHONE_NUMBER + ); + } + + public static Admin 관리자_생성(final String email, + final String password, + final String confirmPassword, + final String name, + final String phoneNumber) { + return Admin.createWith( + email, + password, + confirmPassword, + name, + phoneNumber + ); + } +} diff --git a/src/test/java/com/atwoz/admin/fixture/AdminRequestFixture.java b/src/test/java/com/atwoz/admin/fixture/AdminRequestFixture.java new file mode 100644 index 00000000..509098e0 --- /dev/null +++ b/src/test/java/com/atwoz/admin/fixture/AdminRequestFixture.java @@ -0,0 +1,40 @@ +package com.atwoz.admin.fixture; + +import com.atwoz.admin.application.auth.dto.AdminLoginRequest; +import com.atwoz.admin.application.auth.dto.AdminProfileSignUpRequest; +import com.atwoz.admin.application.auth.dto.AdminSignUpRequest; + +public class AdminRequestFixture { + + private static final String EMAIL = "email@email.com"; + private static final String PASSWORD = "password"; + private static final String NAME = "name"; + private static final String PHONE_NUMBER = "010-1234-5678"; + + public static AdminSignUpRequest 관리자_회원_가입_요청() { + return new AdminSignUpRequest( + EMAIL, + PASSWORD, + PASSWORD, + new AdminProfileSignUpRequest( + NAME, + PHONE_NUMBER + ) + ); + } + + public static AdminLoginRequest 관리자_로그인_요청() { + return new AdminLoginRequest( + EMAIL, + PASSWORD + ); + } + + public static AdminLoginRequest 관리자_로그인_요청(final String email, + final String password) { + return new AdminLoginRequest( + email, + password + ); + } +} diff --git a/src/test/java/com/atwoz/admin/fixture/AdminTokenResponseFixture.java b/src/test/java/com/atwoz/admin/fixture/AdminTokenResponseFixture.java new file mode 100644 index 00000000..c9076a9e --- /dev/null +++ b/src/test/java/com/atwoz/admin/fixture/AdminTokenResponseFixture.java @@ -0,0 +1,24 @@ +package com.atwoz.admin.fixture; + +import com.atwoz.admin.application.auth.dto.AdminAccessTokenResponse; +import com.atwoz.admin.application.auth.dto.AdminTokenResponse; + +@SuppressWarnings("NonAsciiCharacters") +public class AdminTokenResponseFixture { + + private static final String ACCESS_TOKEN = "accessToken"; + private static final String REFRESH_TOKEN = "refreshToken"; + + public static AdminTokenResponse 관리자_토큰_생성_응답() { + return new AdminTokenResponse( + ACCESS_TOKEN, + REFRESH_TOKEN + ); + } + + public static AdminAccessTokenResponse 관리자_액세스_토큰_생성_응답() { + return new AdminAccessTokenResponse( + ACCESS_TOKEN + ); + } +} diff --git a/src/test/java/com/atwoz/admin/infrastructure/admin/AdminFakeRepository.java b/src/test/java/com/atwoz/admin/infrastructure/admin/AdminFakeRepository.java new file mode 100644 index 00000000..ab3c9001 --- /dev/null +++ b/src/test/java/com/atwoz/admin/infrastructure/admin/AdminFakeRepository.java @@ -0,0 +1,40 @@ +package com.atwoz.admin.infrastructure.admin; + +import com.atwoz.admin.domain.admin.Admin; +import com.atwoz.admin.domain.admin.AdminRepository; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +@SuppressWarnings("NonAsciiCharacters") +public class AdminFakeRepository implements AdminRepository { + + private final Map map = new HashMap<>(); + private Long id = 1L; + + @Override + public Admin save(final Admin admin) { + Admin savedAdmin = Admin.createWith( + admin.getEmail(), + admin.getPassword(), + admin.getPassword(), + admin.getName(), + admin.getPhoneNumber() + ); + + map.put(id++, savedAdmin); + return savedAdmin; + } + + @Override + public Optional findAdminById(final Long id) { + return Optional.ofNullable(map.get(id)); + } + + @Override + public Optional findAdminByEmail(final String email) { + return map.values().stream() + .filter(admin -> email.equals(admin.getEmail())) + .findAny(); + } +} diff --git a/src/test/java/com/atwoz/admin/infrastructure/admin/AdminJpaRepositoryTest.java b/src/test/java/com/atwoz/admin/infrastructure/admin/AdminJpaRepositoryTest.java new file mode 100644 index 00000000..ce5814e9 --- /dev/null +++ b/src/test/java/com/atwoz/admin/infrastructure/admin/AdminJpaRepositoryTest.java @@ -0,0 +1,65 @@ +package com.atwoz.admin.infrastructure.admin; + +import com.atwoz.admin.domain.admin.Admin; +import java.util.Optional; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; + +import static com.atwoz.admin.fixture.AdminFixture.관리자_생성; +import static org.assertj.core.api.SoftAssertions.assertSoftly; + +@DisplayNameGeneration(ReplaceUnderscores.class) +@SuppressWarnings("NonAsciiCharacters") +@DataJpaTest +class AdminJpaRepositoryTest { + + @Autowired + private AdminJpaRepository adminJpaRepository; + + private Admin admin; + + @BeforeEach + void setup() { + admin = 관리자_생성(); + adminJpaRepository.save(admin); + } + + @Nested + class 회원_조회 { + + @Test + void 아이디로_관리자를_찾는다() { + // given + Long adminId = admin.getId(); + + // when + Optional foundAdmin = adminJpaRepository.findById(adminId); + + // then + assertSoftly(softly -> { + softly.assertThat(foundAdmin).isPresent(); + softly.assertThat(foundAdmin.get()).usingRecursiveComparison().isEqualTo(admin); + }); + } + + @Test + void 이메일로_관리자를_찾는다() { + // given + String adminEmail = admin.getEmail(); + + // when + Optional foundAdmin = adminJpaRepository.findAdminByEmail(adminEmail); + + // then + assertSoftly(softly -> { + softly.assertThat(foundAdmin).isPresent(); + softly.assertThat(foundAdmin.get()).usingRecursiveComparison().isEqualTo(admin); + }); + } + } +} diff --git a/src/test/java/com/atwoz/admin/ui/auth/AdminAuthControllerTest.java b/src/test/java/com/atwoz/admin/ui/auth/AdminAuthControllerTest.java new file mode 100644 index 00000000..84ea99d4 --- /dev/null +++ b/src/test/java/com/atwoz/admin/ui/auth/AdminAuthControllerTest.java @@ -0,0 +1,137 @@ +package com.atwoz.admin.ui.auth; + +import com.atwoz.admin.application.auth.AdminAuthService; +import com.atwoz.admin.application.auth.dto.AdminAccessTokenResponse; +import com.atwoz.admin.application.auth.dto.AdminLoginRequest; +import com.atwoz.admin.application.auth.dto.AdminSignUpRequest; +import com.atwoz.admin.application.auth.dto.AdminTokenResponse; +import com.atwoz.helper.MockBeanInjection; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; + +import static com.atwoz.admin.fixture.AdminRequestFixture.관리자_로그인_요청; +import static com.atwoz.admin.fixture.AdminRequestFixture.관리자_회원_가입_요청; +import static com.atwoz.admin.fixture.AdminTokenResponseFixture.관리자_액세스_토큰_생성_응답; +import static com.atwoz.admin.fixture.AdminTokenResponseFixture.관리자_토큰_생성_응답; +import static com.atwoz.helper.RestDocsHelper.customDocument; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; +import static org.springframework.http.HttpHeaders.AUTHORIZATION; +import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName; +import static org.springframework.restdocs.headers.HeaderDocumentation.requestHeaders; +import static org.springframework.restdocs.headers.HeaderDocumentation.responseHeaders; +import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields; +import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@DisplayNameGeneration(ReplaceUnderscores.class) +@SuppressWarnings("NonAsciiCharacters") +@AutoConfigureRestDocs +@WebMvcTest(AdminAuthController.class) +class AdminAuthControllerTest extends MockBeanInjection { + + private static final String BEARER_TOKEN = "Bearer token"; + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + @Autowired + private AdminAuthService adminAuthService; + + @Test + void 회원_가입을_진행한다() throws Exception { + // given + AdminSignUpRequest adminSignUpRequest = 관리자_회원_가입_요청(); + AdminTokenResponse adminTokenResponse = 관리자_토큰_생성_응답(); + when(adminAuthService.signUp(adminSignUpRequest)).thenReturn(adminTokenResponse); + + // when & then + mockMvc.perform(post("/api/admins/auth/sign-up") + .content(objectMapper.writeValueAsString(adminSignUpRequest)) + .contentType(MediaType.APPLICATION_JSON) + ).andExpect(status().isOk()) + .andDo(customDocument("관리자_회원가입", + requestFields( + fieldWithPath("email").description("이메일"), + fieldWithPath("password").description("비밀번호"), + fieldWithPath("confirmPassword").description("비밀번호 확인"), + fieldWithPath("adminProfileSignUpRequest").description("관리자 프로필 정보"), + fieldWithPath("adminProfileSignUpRequest.name").description("이름"), + fieldWithPath("adminProfileSignUpRequest.phoneNumber").description("전화번호") + ), + responseHeaders( + headerWithName("Cookie").description("발급된 리프레쉬 토큰") + ), + responseFields( + fieldWithPath("accessToken").description("발급된 액세스 토큰") + ) + ) + ); + } + + @Test + void 로그인을_진행한다() throws Exception { + // given + AdminLoginRequest adminLoginRequest = 관리자_로그인_요청(); + AdminTokenResponse adminTokenResponse = 관리자_토큰_생성_응답(); + when(adminAuthService.login(adminLoginRequest)).thenReturn(adminTokenResponse); + + // when & then + mockMvc.perform(post("/api/admins/auth/login") + .content(objectMapper.writeValueAsString(adminLoginRequest)) + .contentType(MediaType.APPLICATION_JSON) + ).andExpect(status().isOk()) + .andDo(print()) + .andDo(customDocument("관리자_로그인", + requestFields( + fieldWithPath("email").description("이메일"), + fieldWithPath("password").description("비밀번호") + ), + responseHeaders( + headerWithName("Cookie").description("발급된 리프레쉬 토큰") + ), + responseFields( + fieldWithPath("accessToken").description("발급된 액세스 토큰") + ) + ) + ); + } + + @Test + void 액세스_토큰을_재발행한다() throws Exception { + // given + String refreshToken = "refreshToken"; + AdminAccessTokenResponse adminAccessTokenResponse = 관리자_액세스_토큰_생성_응답(); + when(adminAuthService.reGenerateAccessToken(any())).thenReturn(adminAccessTokenResponse); + + // when & then + mockMvc.perform(post("/api/admins/auth/access-token-regeneration") + .header(AUTHORIZATION, BEARER_TOKEN) + .header(HttpHeaders.COOKIE, refreshToken) + ).andExpect(status().isOk()) + .andDo(print()) + .andDo(customDocument("관리자_액세스_토큰_재발행", + requestHeaders( + headerWithName(AUTHORIZATION).description("유저 토큰 정보"), + headerWithName("Cookie").description("이전에 발급받은 리프레쉬 토큰") + ), + responseFields( + fieldWithPath("accessToken").description("지금 발급된 액세스 토큰") + ) + ) + ); + } +} diff --git a/src/test/java/com/atwoz/admin/ui/auth/interceptor/AdminLoginValidCheckerInterceptorTest.java b/src/test/java/com/atwoz/admin/ui/auth/interceptor/AdminLoginValidCheckerInterceptorTest.java new file mode 100644 index 00000000..03ce531b --- /dev/null +++ b/src/test/java/com/atwoz/admin/ui/auth/interceptor/AdminLoginValidCheckerInterceptorTest.java @@ -0,0 +1,41 @@ +package com.atwoz.admin.ui.auth.interceptor; + +import com.atwoz.admin.domain.admin.AdminTokenProvider; +import com.atwoz.admin.exception.exceptions.AdminLoginInvalidException; +import com.atwoz.admin.ui.auth.support.AdminAuthenticationContext; +import com.atwoz.admin.ui.auth.support.AdminAuthenticationExtractor; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@DisplayNameGeneration(ReplaceUnderscores.class) +@SuppressWarnings("NonAsciiCharacters") +class AdminLoginValidCheckerInterceptorTest { + + private final HttpServletRequest req = mock(HttpServletRequest.class); + private final HttpServletResponse res = mock(HttpServletResponse.class); + private final AdminAuthenticationContext adminAuthenticationContext = mock(AdminAuthenticationContext.class); + private final AdminAuthenticationExtractor adminAuthenticationExtractor = mock(AdminAuthenticationExtractor.class); + private final AdminTokenProvider adminTokenProvider = mock(AdminTokenProvider.class); + + @Test + void token이_없다면_예외를_발생한다() { + // given + AdminLoginValidCheckerInterceptor adminLoginValidCheckerInterceptor = new AdminLoginValidCheckerInterceptor( + adminAuthenticationContext, + adminAuthenticationExtractor, + adminTokenProvider + ); + when(req.getHeader("any")).thenReturn(null); + + // when & then + assertThatThrownBy(() -> adminLoginValidCheckerInterceptor.preHandle(req, res, new Object())) + .isInstanceOf(AdminLoginInvalidException.class); + } +} diff --git a/src/test/java/com/atwoz/admin/ui/auth/support/AdminAuthenticationContextTest.java b/src/test/java/com/atwoz/admin/ui/auth/support/AdminAuthenticationContextTest.java new file mode 100644 index 00000000..f9f3374d --- /dev/null +++ b/src/test/java/com/atwoz/admin/ui/auth/support/AdminAuthenticationContextTest.java @@ -0,0 +1,44 @@ +package com.atwoz.admin.ui.auth.support; + +import com.atwoz.admin.exception.exceptions.AdminLoginInvalidException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +@DisplayNameGeneration(ReplaceUnderscores.class) +@SuppressWarnings("NonAsciiCharacters") +class AdminAuthenticationContextTest { + + private AdminAuthenticationContext adminAuthenticationContext; + + @BeforeEach + void setup() { + adminAuthenticationContext = new AdminAuthenticationContext(); + } + + @Test + void admin_id를_반환한다() { + // given + adminAuthenticationContext.setAuthentication(1L); + + // when + Long result = adminAuthenticationContext.getPrincipal(); + + // then + assertThat(result).isEqualTo(1L); + } + + @Test + void admin_id가_없다면_예외를_발생한다() { + // given + adminAuthenticationContext.setAuthentication(null); + + // when & then + assertThatThrownBy(() -> adminAuthenticationContext.getPrincipal()) + .isInstanceOf(AdminLoginInvalidException.class); + } +} diff --git a/src/test/java/com/atwoz/admin/ui/auth/support/AdminAuthenticationExtractorTest.java b/src/test/java/com/atwoz/admin/ui/auth/support/AdminAuthenticationExtractorTest.java new file mode 100644 index 00000000..ff6871ad --- /dev/null +++ b/src/test/java/com/atwoz/admin/ui/auth/support/AdminAuthenticationExtractorTest.java @@ -0,0 +1,62 @@ +package com.atwoz.admin.ui.auth.support; + +import jakarta.servlet.http.HttpServletRequest; +import java.util.Optional; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.SoftAssertions.assertSoftly; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@DisplayNameGeneration(ReplaceUnderscores.class) +@SuppressWarnings("NonAsciiCharacters") +class AdminAuthenticationExtractorTest { + + private static final String AUTHORIZATION_HEADER = "Authorization"; + private static final String BEARER = "Bearer"; + + private final HttpServletRequest request = mock(HttpServletRequest.class); + private AdminAuthenticationExtractor adminAuthenticationExtractor; + + @BeforeEach + void setup() { + adminAuthenticationExtractor = new AdminAuthenticationExtractor(); + } + + @Nested + class 요청에서_토큰_추출 { + + @Test + void 토큰이_정상적으로_조회된다() { + // given + String expectedResponseToken = BEARER + " tokenSignature"; + when(request.getHeader(AUTHORIZATION_HEADER)).thenReturn(expectedResponseToken); + + // when + Optional result = adminAuthenticationExtractor.extractFromRequest(request); + + // then + assertSoftly(softly -> { + softly.assertThat(result).isPresent(); + softly.assertThat(result).isEqualTo(Optional.of("tokenSignature")); + }); + } + + @Test + void 토큰_헤더가_없다면_빈_값이_반환된다() { + // given + when(request.getHeader(AUTHORIZATION_HEADER)).thenReturn("InvalidType token"); + + // when + Optional result = adminAuthenticationExtractor.extractFromRequest(request); + + // then + assertThat(result).isEmpty(); + } + } +} diff --git a/src/test/java/com/atwoz/member/ui/auth/interceptor/HttpMethodTest.java b/src/test/java/com/atwoz/global/HttpMethodTest.java similarity index 87% rename from src/test/java/com/atwoz/member/ui/auth/interceptor/HttpMethodTest.java rename to src/test/java/com/atwoz/global/HttpMethodTest.java index 524f43f6..3e3d1e28 100644 --- a/src/test/java/com/atwoz/member/ui/auth/interceptor/HttpMethodTest.java +++ b/src/test/java/com/atwoz/global/HttpMethodTest.java @@ -1,5 +1,6 @@ -package com.atwoz.member.ui.auth.interceptor; +package com.atwoz.global; +import com.atwoz.global.config.interceptor.support.HttpMethod; import org.junit.jupiter.api.DisplayNameGeneration; import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.Test; diff --git a/src/test/java/com/atwoz/member/ui/auth/interceptor/PathContainerTest.java b/src/test/java/com/atwoz/global/PathContainerTest.java similarity index 91% rename from src/test/java/com/atwoz/member/ui/auth/interceptor/PathContainerTest.java rename to src/test/java/com/atwoz/global/PathContainerTest.java index 3776ec64..5c83cb7e 100644 --- a/src/test/java/com/atwoz/member/ui/auth/interceptor/PathContainerTest.java +++ b/src/test/java/com/atwoz/global/PathContainerTest.java @@ -1,5 +1,7 @@ -package com.atwoz.member.ui.auth.interceptor; +package com.atwoz.global; +import com.atwoz.global.config.interceptor.support.HttpMethod; +import com.atwoz.global.config.interceptor.support.PathContainer; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayNameGeneration; import org.junit.jupiter.api.DisplayNameGenerator; diff --git a/src/test/java/com/atwoz/member/ui/auth/interceptor/PathRequestTest.java b/src/test/java/com/atwoz/global/PathRequestTest.java similarity index 83% rename from src/test/java/com/atwoz/member/ui/auth/interceptor/PathRequestTest.java rename to src/test/java/com/atwoz/global/PathRequestTest.java index 7c914b53..51deff64 100644 --- a/src/test/java/com/atwoz/member/ui/auth/interceptor/PathRequestTest.java +++ b/src/test/java/com/atwoz/global/PathRequestTest.java @@ -1,5 +1,7 @@ -package com.atwoz.member.ui.auth.interceptor; +package com.atwoz.global; +import com.atwoz.global.config.interceptor.support.HttpMethod; +import com.atwoz.global.config.interceptor.support.PathRequest; import org.junit.jupiter.api.DisplayNameGeneration; import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.Test; diff --git a/src/test/java/com/atwoz/helper/MockBeanInjection.java b/src/test/java/com/atwoz/helper/MockBeanInjection.java index b3e89b09..9eb8413f 100644 --- a/src/test/java/com/atwoz/helper/MockBeanInjection.java +++ b/src/test/java/com/atwoz/helper/MockBeanInjection.java @@ -1,15 +1,23 @@ package com.atwoz.helper; -import com.atwoz.member.application.auth.AuthService; +import com.atwoz.admin.application.auth.AdminAuthService; +import com.atwoz.admin.domain.admin.AdminTokenProvider; +import com.atwoz.admin.ui.auth.interceptor.AdminLoginValidCheckerInterceptor; +import com.atwoz.admin.ui.auth.support.AdminAuthenticationContext; +import com.atwoz.admin.ui.auth.support.AdminAuthenticationExtractor; +import com.atwoz.admin.ui.auth.support.resolver.AdminAuthArgumentResolver; +import com.atwoz.admin.ui.auth.support.resolver.AdminRefreshTokenExtractionArgumentResolver; +import com.atwoz.member.application.auth.MemberAuthService; import com.atwoz.member.application.member.MemberQueryService; import com.atwoz.member.application.member.MemberService; -import com.atwoz.member.domain.auth.TokenProvider; -import com.atwoz.member.ui.auth.interceptor.LoginValidCheckerInterceptor; -import com.atwoz.member.ui.auth.interceptor.ParseMemberIdFromTokenInterceptor; -import com.atwoz.member.ui.auth.interceptor.TokenRegenerateInterceptor; -import com.atwoz.member.ui.auth.support.auth.AuthenticationContext; -import com.atwoz.member.ui.auth.support.auth.OAuthProperties; -import com.atwoz.member.ui.auth.support.resolver.AuthArgumentResolver; +import com.atwoz.member.domain.auth.JsonMapper; +import com.atwoz.member.domain.auth.MemberTokenProvider; +import com.atwoz.member.domain.member.MemberRepository; +import com.atwoz.member.ui.auth.interceptor.MemberLoginValidCheckerInterceptor; +import com.atwoz.member.ui.auth.support.MemberAuthenticationContext; +import com.atwoz.member.ui.auth.support.MemberAuthenticationExtractor; +import com.atwoz.member.ui.auth.support.OAuthProperties; +import com.atwoz.member.ui.auth.support.resolver.MemberAuthArgumentResolver; import com.atwoz.member.ui.auth.support.resolver.OAuthArgumentResolver; import com.atwoz.memberlike.application.MemberLikeQueryService; import com.atwoz.memberlike.application.MemberLikeService; @@ -27,33 +35,67 @@ @MockBean(JpaMetamodelMappingContext.class) public class MockBeanInjection { + // Member @MockBean - protected TokenProvider tokenProvider; + protected OAuthArgumentResolver oAuthArgumentResolver; + + @MockBean + protected MemberAuthArgumentResolver memberAuthArgumentResolver; + + @MockBean + protected MemberLoginValidCheckerInterceptor memberLoginValidCheckerInterceptor; @MockBean - protected AuthenticationContext authenticationContext; + protected MemberAuthenticationContext memberAuthenticationContext; @MockBean - protected AuthService authService; + protected MemberAuthenticationExtractor authenticationExtractor; @MockBean protected OAuthProperties oAuthProperties; @MockBean - protected OAuthArgumentResolver oAuthArgumentResolver; + protected MemberService memberService; + + @MockBean + protected MemberQueryService memberQueryService; + + @MockBean + protected MemberAuthService memberAuthService; + + @MockBean + protected MemberTokenProvider memberTokenProvider; + + @MockBean + protected MemberRepository memberRepository; @MockBean - protected AuthArgumentResolver authArgumentResolver; + protected JsonMapper jsonMapper; + // Admin @MockBean - protected ParseMemberIdFromTokenInterceptor parseMemberIdFromTokenInterceptor; + protected AdminAuthArgumentResolver adminAuthArgumentResolver; @MockBean - protected LoginValidCheckerInterceptor loginValidCheckerInterceptor; + protected AdminRefreshTokenExtractionArgumentResolver adminRefreshTokenExtractionArgumentResolver; @MockBean - protected TokenRegenerateInterceptor tokenRegenerateInterceptor; + protected AdminLoginValidCheckerInterceptor adminLoginValidCheckerInterceptor; + @MockBean + protected AdminAuthenticationContext adminAuthenticationContext; + + @MockBean + protected AdminAuthenticationExtractor adminAuthenticationExtractor; + + @MockBean + protected AdminAuthService adminAuthService; + + @MockBean + protected AdminTokenProvider adminTokenProvider; + + + // Mission @MockBean protected MissionService missionService; @@ -66,9 +108,7 @@ public class MockBeanInjection { @MockBean protected MemberMissionsQueryService memberMissionsQueryService; - @MockBean - protected MemberService memberService; - + // Survey @MockBean protected SurveyService surveyService; @@ -78,9 +118,7 @@ public class MockBeanInjection { @MockBean protected MemberSurveysQueryService memberSurveysQueryService; - @MockBean - protected MemberQueryService memberQueryService; - + // Report @MockBean protected ReportService reportService; diff --git a/src/test/java/com/atwoz/member/application/auth/AuthServiceTest.java b/src/test/java/com/atwoz/member/application/auth/MemberAuthServiceTest.java similarity index 61% rename from src/test/java/com/atwoz/member/application/auth/AuthServiceTest.java rename to src/test/java/com/atwoz/member/application/auth/MemberAuthServiceTest.java index f98d4965..2c7bf9ec 100644 --- a/src/test/java/com/atwoz/member/application/auth/AuthServiceTest.java +++ b/src/test/java/com/atwoz/member/application/auth/MemberAuthServiceTest.java @@ -1,12 +1,12 @@ package com.atwoz.member.application.auth; import com.atwoz.member.application.auth.dto.LoginRequest; -import com.atwoz.member.domain.auth.TokenProvider; +import com.atwoz.member.domain.auth.MemberTokenProvider; import com.atwoz.member.infrastructure.auth.OAuthFakeRequester; import com.atwoz.member.infrastructure.auth.dto.OAuthProviderRequest; +import com.atwoz.member.infrastructure.member.MemberFakeRepository; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayNameGeneration; -import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; @@ -14,21 +14,22 @@ import static com.atwoz.member.fixture.OAuthProviderFixture.인증_기관_생성; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.anyString; +import static org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.when; -@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +@DisplayNameGeneration(ReplaceUnderscores.class) @SuppressWarnings("NonAsciiCharacters") @ExtendWith(MockitoExtension.class) -class AuthServiceTest { +class MemberAuthServiceTest { @Mock - private TokenProvider tokenProvider; - private AuthService authService; + private MemberTokenProvider memberTokenProvider; + private MemberAuthService memberAuthService; @BeforeEach void setup() { - authService = new AuthService(tokenProvider, new OAuthFakeRequester()); + memberAuthService = new MemberAuthService(memberTokenProvider, new OAuthFakeRequester(), new MemberFakeRepository()); } @Test @@ -37,10 +38,10 @@ void setup() { LoginRequest loginRequest = new LoginRequest("kakao", "code"); OAuthProviderRequest oAuthProviderRequest = 인증_기관_생성(); String expectedToken = "token"; - when(tokenProvider.createTokenWithPhoneNumber(anyString())).thenReturn(expectedToken); + when(memberTokenProvider.createAccessToken(any())).thenReturn(expectedToken); // when - String token = authService.login(loginRequest, oAuthProviderRequest); + String token = memberAuthService.login(loginRequest, oAuthProviderRequest); // then assertThat(token).isEqualTo(expectedToken); diff --git a/src/test/java/com/atwoz/member/ui/auth/AuthControllerWebMvcTest.java b/src/test/java/com/atwoz/member/ui/auth/MemberAuthControllerWebMvcTest.java similarity index 89% rename from src/test/java/com/atwoz/member/ui/auth/AuthControllerWebMvcTest.java rename to src/test/java/com/atwoz/member/ui/auth/MemberAuthControllerWebMvcTest.java index d607ab70..84ee1e17 100644 --- a/src/test/java/com/atwoz/member/ui/auth/AuthControllerWebMvcTest.java +++ b/src/test/java/com/atwoz/member/ui/auth/MemberAuthControllerWebMvcTest.java @@ -26,8 +26,8 @@ @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) @SuppressWarnings("NonAsciiCharacters") @AutoConfigureRestDocs -@WebMvcTest(AuthController.class) -class AuthControllerWebMvcTest extends MockBeanInjection { +@WebMvcTest(MemberAuthController.class) +class MemberAuthControllerWebMvcTest extends MockBeanInjection { @Autowired private MockMvc mockMvc; @@ -41,15 +41,15 @@ class AuthControllerWebMvcTest extends MockBeanInjection { OAuthProviderRequest oAuthProviderRequest = 인증_기관_생성(); LoginRequest loginRequest = new LoginRequest("kakao", "code"); String expectedToken = "token"; - when(authService.login(loginRequest, oAuthProviderRequest)).thenReturn(expectedToken); + when(memberAuthService.login(loginRequest, oAuthProviderRequest)).thenReturn(expectedToken); // when & then - mockMvc.perform(post("/api/auth/login") + mockMvc.perform(post("/api/members/auth/login") .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(loginRequest)) ).andExpect(status().isOk()) .andDo(print()) - .andDo(customDocument("do_signup", + .andDo(customDocument("유저_로그인", requestFields( fieldWithPath("provider").description("인증기관"), fieldWithPath("code").description("인증코드") diff --git a/src/test/java/com/atwoz/member/ui/auth/interceptor/LoginValidCheckerInterceptorTest.java b/src/test/java/com/atwoz/member/ui/auth/interceptor/LoginValidCheckerInterceptorTest.java deleted file mode 100644 index 9a5c2774..00000000 --- a/src/test/java/com/atwoz/member/ui/auth/interceptor/LoginValidCheckerInterceptorTest.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.atwoz.member.ui.auth.interceptor; - -import com.atwoz.member.domain.auth.TokenProvider; -import com.atwoz.member.exception.exceptions.auth.LoginInvalidException; -import com.atwoz.member.ui.auth.support.auth.AuthenticationContext; -import com.atwoz.member.ui.auth.support.auth.AuthenticationExtractor; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.junit.jupiter.api.DisplayNameGeneration; -import org.junit.jupiter.api.DisplayNameGenerator; -import org.junit.jupiter.api.Test; - -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) -@SuppressWarnings("NonAsciiCharacters") -class LoginValidCheckerInterceptorTest { - - private final HttpServletRequest req = mock(HttpServletRequest.class); - private final HttpServletResponse res = mock(HttpServletResponse.class); - private final TokenProvider tokenProvider = mock(TokenProvider.class); - private final AuthenticationContext authenticationContext = mock(AuthenticationContext.class); - private final AuthenticationExtractor authenticationExtractor = mock(AuthenticationExtractor.class); - - @Test - void token이_없다면_예외를_발생한다() { - // given - LoginValidCheckerInterceptor loginValidCheckerInterceptor = new LoginValidCheckerInterceptor( - tokenProvider, - authenticationContext, - authenticationExtractor - ); - - when(req.getHeader("any")).thenReturn(null); - - // when & then - assertThatThrownBy(() -> loginValidCheckerInterceptor.preHandle(req, res, new Object())) - .isInstanceOf(LoginInvalidException.class); - } -} diff --git a/src/test/java/com/atwoz/member/ui/auth/interceptor/MemberLoginValidCheckerInterceptorTest.java b/src/test/java/com/atwoz/member/ui/auth/interceptor/MemberLoginValidCheckerInterceptorTest.java new file mode 100644 index 00000000..0925fae8 --- /dev/null +++ b/src/test/java/com/atwoz/member/ui/auth/interceptor/MemberLoginValidCheckerInterceptorTest.java @@ -0,0 +1,41 @@ +package com.atwoz.member.ui.auth.interceptor; + +import com.atwoz.member.domain.auth.MemberTokenProvider; +import com.atwoz.member.exception.exceptions.auth.MemberLoginInvalidException; +import com.atwoz.member.ui.auth.support.MemberAuthenticationContext; +import com.atwoz.member.ui.auth.support.MemberAuthenticationExtractor; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +@SuppressWarnings("NonAsciiCharacters") +class MemberLoginValidCheckerInterceptorTest { + + private final HttpServletRequest req = mock(HttpServletRequest.class); + private final HttpServletResponse res = mock(HttpServletResponse.class); + private final MemberAuthenticationContext memberAuthenticationContext = mock(MemberAuthenticationContext.class); + private final MemberAuthenticationExtractor memberAuthenticationExtractor = mock(MemberAuthenticationExtractor.class); + private final MemberTokenProvider memberTokenProvider = mock(MemberTokenProvider.class); + + @Test + void token이_없다면_예외를_발생한다() { + // given + MemberLoginValidCheckerInterceptor memberLoginValidCheckerInterceptor = new MemberLoginValidCheckerInterceptor( + memberAuthenticationContext, + memberAuthenticationExtractor, + memberTokenProvider + ); + when(req.getHeader("any")).thenReturn(null); + + // when & then + assertThatThrownBy(() -> memberLoginValidCheckerInterceptor.preHandle(req, res, new Object())) + .isInstanceOf(MemberLoginInvalidException.class); + } +} diff --git a/src/test/java/com/atwoz/member/ui/auth/interceptor/TokenRegenerateInterceptorTest.java b/src/test/java/com/atwoz/member/ui/auth/interceptor/TokenRegenerateInterceptorTest.java deleted file mode 100644 index d0de0a61..00000000 --- a/src/test/java/com/atwoz/member/ui/auth/interceptor/TokenRegenerateInterceptorTest.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.atwoz.member.ui.auth.interceptor; - -import com.atwoz.member.domain.auth.JsonMapper; -import com.atwoz.member.domain.auth.TokenProvider; -import com.atwoz.member.domain.member.MemberRepository; -import com.atwoz.member.exception.exceptions.auth.InvalidJsonKeyException; -import com.atwoz.member.infrastructure.member.MemberFakeRepository; -import com.atwoz.member.ui.auth.support.auth.AuthenticationExtractor; -import jakarta.servlet.http.HttpServletRequest; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayNameGeneration; -import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores; -import org.junit.jupiter.api.Test; -import org.springframework.web.servlet.ModelAndView; -import org.springframework.web.util.ContentCachingResponseWrapper; - -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -@DisplayNameGeneration(ReplaceUnderscores.class) -@SuppressWarnings("NonAsciiCharacters") -class TokenRegenerateInterceptorTest { - - private final HttpServletRequest request = mock(HttpServletRequest.class); - private final ContentCachingResponseWrapper response = mock(ContentCachingResponseWrapper.class); - private final TokenProvider tokenProvider = mock(TokenProvider.class); - private final JsonMapper jsonMapper = mock(JsonMapper.class); - private final MemberRepository memberRepository = new MemberFakeRepository(); - private final AuthenticationExtractor authenticationExtractor = mock(AuthenticationExtractor.class); - - private TokenRegenerateInterceptor tokenRegenerateInterceptor; - - @BeforeEach - void setup() { - tokenRegenerateInterceptor = new TokenRegenerateInterceptor( - tokenProvider, - memberRepository, - jsonMapper, - authenticationExtractor - ); - } - - @Test - void token이_없으면_예외가_발생한다() { - // given - when(authenticationExtractor.extractFromResponse(response)).thenThrow(InvalidJsonKeyException.class); - - // when & then - assertThatThrownBy( - () -> tokenRegenerateInterceptor.postHandle(request, response, new Object(), new ModelAndView())) - .isInstanceOf(InvalidJsonKeyException.class); - } -} diff --git a/src/test/java/com/atwoz/member/ui/auth/support/MemberAuthenticationContextTest.java b/src/test/java/com/atwoz/member/ui/auth/support/MemberAuthenticationContextTest.java new file mode 100644 index 00000000..b2b9b611 --- /dev/null +++ b/src/test/java/com/atwoz/member/ui/auth/support/MemberAuthenticationContextTest.java @@ -0,0 +1,45 @@ +package com.atwoz.member.ui.auth.support; + +import com.atwoz.member.exception.exceptions.auth.MemberLoginInvalidException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +@SuppressWarnings("NonAsciiCharacters") +class MemberAuthenticationContextTest { + + private MemberAuthenticationContext memberAuthenticationContext; + + @BeforeEach + void setup() { + memberAuthenticationContext = new MemberAuthenticationContext(); + } + + @Test + void member_id를_반환한다() { + // given + memberAuthenticationContext.setAuthentication(1L); + + // when + Long result = memberAuthenticationContext.getPrincipal(); + + // then + assertThat(result).isEqualTo(1L); + } + + @Test + void member_id가_없다면_예외를_발생한다() { + // given + memberAuthenticationContext.setAuthentication(null); + + // when & then + assertThatThrownBy(() -> memberAuthenticationContext.getPrincipal()) + .isInstanceOf(MemberLoginInvalidException.class); + } +} + diff --git a/src/test/java/com/atwoz/member/ui/auth/support/MemberAuthenticationExtractorTest.java b/src/test/java/com/atwoz/member/ui/auth/support/MemberAuthenticationExtractorTest.java new file mode 100644 index 00000000..9b43d47a --- /dev/null +++ b/src/test/java/com/atwoz/member/ui/auth/support/MemberAuthenticationExtractorTest.java @@ -0,0 +1,62 @@ +package com.atwoz.member.ui.auth.support; + +import jakarta.servlet.http.HttpServletRequest; +import java.util.Optional; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.SoftAssertions.assertSoftly; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +@SuppressWarnings("NonAsciiCharacters") +class MemberAuthenticationExtractorTest { + + private static final String AUTHORIZATION_HEADER = "Authorization"; + private static final String BEARER = "Bearer"; + + private final HttpServletRequest request = mock(HttpServletRequest.class); + private MemberAuthenticationExtractor memberAuthenticationExtractor; + + @BeforeEach + void setup() { + memberAuthenticationExtractor = new MemberAuthenticationExtractor(); + } + + @Nested + class 요청에서_토큰_추출 { + + @Test + void 토큰이_정상적으로_조회된다() { + // given + String expectedResponseToken = BEARER + " tokenSignature"; + when(request.getHeader(AUTHORIZATION_HEADER)).thenReturn(expectedResponseToken); + + // when + Optional result = memberAuthenticationExtractor.extractFromRequest(request); + + // then + assertSoftly(softly -> { + softly.assertThat(result).isPresent(); + softly.assertThat(result).isEqualTo(Optional.of("tokenSignature")); + }); + } + + @Test + void 토큰_헤더가_없다면_빈_값이_반환된다() { + // given + when(request.getHeader(AUTHORIZATION_HEADER)).thenReturn("InvalidType token"); + + // when + Optional result = memberAuthenticationExtractor.extractFromRequest(request); + + // then + assertThat(result).isEmpty(); + } + } +} diff --git a/src/test/java/com/atwoz/member/ui/auth/support/auth/OAuthPlatformTest.java b/src/test/java/com/atwoz/member/ui/auth/support/OAuthPlatformTest.java similarity index 96% rename from src/test/java/com/atwoz/member/ui/auth/support/auth/OAuthPlatformTest.java rename to src/test/java/com/atwoz/member/ui/auth/support/OAuthPlatformTest.java index a1ffd6d1..ea165058 100644 --- a/src/test/java/com/atwoz/member/ui/auth/support/auth/OAuthPlatformTest.java +++ b/src/test/java/com/atwoz/member/ui/auth/support/OAuthPlatformTest.java @@ -1,4 +1,4 @@ -package com.atwoz.member.ui.auth.support.auth; +package com.atwoz.member.ui.auth.support; import com.atwoz.member.exception.exceptions.auth.OAuthPlatformNotFountException; import org.junit.jupiter.api.DisplayName; diff --git a/src/test/java/com/atwoz/member/ui/auth/support/auth/OAuthPropertiesTest.java b/src/test/java/com/atwoz/member/ui/auth/support/OAuthPropertiesTest.java similarity index 95% rename from src/test/java/com/atwoz/member/ui/auth/support/auth/OAuthPropertiesTest.java rename to src/test/java/com/atwoz/member/ui/auth/support/OAuthPropertiesTest.java index e9bd2b8a..5047b77c 100644 --- a/src/test/java/com/atwoz/member/ui/auth/support/auth/OAuthPropertiesTest.java +++ b/src/test/java/com/atwoz/member/ui/auth/support/OAuthPropertiesTest.java @@ -1,4 +1,4 @@ -package com.atwoz.member.ui.auth.support.auth; +package com.atwoz.member.ui.auth.support; import com.atwoz.helper.IntegrationHelper; import com.atwoz.member.infrastructure.auth.dto.OAuthProviderRequest; diff --git a/src/test/java/com/atwoz/member/ui/auth/support/auth/AuthenticationContextTest.java b/src/test/java/com/atwoz/member/ui/auth/support/auth/AuthenticationContextTest.java deleted file mode 100644 index 5f5cab3d..00000000 --- a/src/test/java/com/atwoz/member/ui/auth/support/auth/AuthenticationContextTest.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.atwoz.member.ui.auth.support.auth; - -import com.atwoz.member.exception.exceptions.auth.LoginInvalidException; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayNameGeneration; -import org.junit.jupiter.api.DisplayNameGenerator; -import org.junit.jupiter.api.Test; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) -@SuppressWarnings("NonAsciiCharacters") -class AuthenticationContextTest { - - private AuthenticationContext authenticationContext; - - @BeforeEach - void setup() { - authenticationContext = new AuthenticationContext(); - } - - @Test - void member_id를_반환한다() { - // given - authenticationContext.setAuthentication(1L); - - // when - Long result = authenticationContext.getPrincipal(); - - // then - assertThat(result).isEqualTo(1L); - } - - @Test - void member_id가_없다면_예외를_발생한다() { - // given - authenticationContext.setAuthentication(null); - - // when & then - assertThatThrownBy(() -> authenticationContext.getPrincipal()) - .isInstanceOf(LoginInvalidException.class); - } - - @Test - void 미확인_유저로_바꾼다() { - // given - authenticationContext.setAnonymous(); - - // when - Long result = authenticationContext.getPrincipal(); - - // then - assertThat(result).isEqualTo(-1L); - } -} diff --git a/src/test/java/com/atwoz/member/ui/auth/support/auth/AuthenticationExtractorTest.java b/src/test/java/com/atwoz/member/ui/auth/support/auth/AuthenticationExtractorTest.java deleted file mode 100644 index 9606cf94..00000000 --- a/src/test/java/com/atwoz/member/ui/auth/support/auth/AuthenticationExtractorTest.java +++ /dev/null @@ -1,100 +0,0 @@ -package com.atwoz.member.ui.auth.support.auth; - -import com.atwoz.member.domain.auth.JsonMapper; -import com.atwoz.member.exception.exceptions.auth.InvalidJsonKeyException; -import com.fasterxml.jackson.core.JsonProcessingException; -import jakarta.servlet.http.HttpServletRequest; -import java.util.Optional; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayNameGeneration; -import org.junit.jupiter.api.DisplayNameGenerator; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.springframework.web.util.ContentCachingResponseWrapper; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.assertj.core.api.SoftAssertions.assertSoftly; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) -@SuppressWarnings("NonAsciiCharacters") -class AuthenticationExtractorTest { - - private static final String AUTHORIZATION_HEADER = "Authorization"; - private static final String BEARER = "Bearer"; - - private final HttpServletRequest request = mock(HttpServletRequest.class); - private final ContentCachingResponseWrapper response = mock(ContentCachingResponseWrapper.class); - private final JsonMapper jsonMapper = mock(JsonMapper.class); - private AuthenticationExtractor authenticationExtractor; - - @BeforeEach - void setup() { - authenticationExtractor = new AuthenticationExtractor(jsonMapper); - } - - - @Nested - class 요청에서_토큰_추출 { - - @Test - void 토큰이_정상적으로_조회된다() { - // given - String expectedResponseToken = BEARER + " tokenSignature"; - when(request.getHeader(AUTHORIZATION_HEADER)).thenReturn(expectedResponseToken); - - // when - Optional result = authenticationExtractor.extractFromRequest(request); - - // then - assertSoftly(softly -> { - softly.assertThat(result).isPresent(); - softly.assertThat(result).isEqualTo(Optional.of("tokenSignature")); - }); - } - - @Test - void 토큰_헤더가_없다면_빈_값이_반환된다() { - // given - when(request.getHeader(AUTHORIZATION_HEADER)).thenReturn("InvalidType token"); - - // when - Optional result = authenticationExtractor.extractFromRequest(request); - - // then - assertThat(result).isEmpty(); - } - } - - @Nested - class 응답에서_토큰_추출 { - - @Test - void 토큰이_정상적으로_조회된다() throws JsonProcessingException { - // given - String tokenJson = "{\"token\":\"validToken\"}"; - when(response.getContentAsByteArray()).thenReturn(tokenJson.getBytes()); - when(jsonMapper.getValueByKey(tokenJson, "token")).thenReturn("validToken"); - - // when - String extractedToken = authenticationExtractor.extractFromResponse(response); - - // then - assertThat(extractedToken).isEqualTo("validToken"); - } - - @Test - void 응답_body에_토큰의_정보가_없으면_예외가_발생한다() { - // given - String invalidToken = "invalidToken"; - when(response.getContentAsByteArray()).thenReturn(invalidToken.getBytes()); - when(jsonMapper.getValueByKey(invalidToken, "token")).thenThrow(InvalidJsonKeyException.class); - - // when & then - assertThatThrownBy(() -> authenticationExtractor.extractFromResponse(response)) - .isInstanceOf(InvalidJsonKeyException.class); - } - } -} diff --git a/src/test/java/com/atwoz/member/ui/member/MemberControllerAcceptanceFixture.java b/src/test/java/com/atwoz/member/ui/member/MemberControllerAcceptanceFixture.java index 0b8f0af2..4af22e8e 100644 --- a/src/test/java/com/atwoz/member/ui/member/MemberControllerAcceptanceFixture.java +++ b/src/test/java/com/atwoz/member/ui/member/MemberControllerAcceptanceFixture.java @@ -3,7 +3,7 @@ import com.atwoz.helper.IntegrationHelper; import com.atwoz.member.application.member.dto.MemberInitializeRequest; import com.atwoz.member.application.member.dto.MemberUpdateRequest; -import com.atwoz.member.domain.auth.TokenProvider; +import com.atwoz.member.domain.auth.MemberTokenProvider; import com.atwoz.member.domain.member.Member; import com.atwoz.member.domain.member.MemberRepository; import com.atwoz.member.fixture.MemberRequestFixture; @@ -31,14 +31,14 @@ public class MemberControllerAcceptanceFixture extends IntegrationHelper { protected MemberRepository memberRepository; @Autowired - protected TokenProvider tokenProvider; + protected MemberTokenProvider memberTokenProvider; protected Member 회원_생성() { return memberRepository.save(일반_유저_생성()); } protected String 토큰_생성(final Member member) { - return tokenProvider.createTokenWithId(member.getId()); + return memberTokenProvider.createAccessToken(member.getId()); } protected String 회원_닉네임을_요청한다() { diff --git a/src/test/java/com/atwoz/memberlike/ui/MemberLikeControllerAcceptanceFixture.java b/src/test/java/com/atwoz/memberlike/ui/MemberLikeControllerAcceptanceFixture.java index 6ca7bb56..214c1ee4 100644 --- a/src/test/java/com/atwoz/memberlike/ui/MemberLikeControllerAcceptanceFixture.java +++ b/src/test/java/com/atwoz/memberlike/ui/MemberLikeControllerAcceptanceFixture.java @@ -3,7 +3,7 @@ import com.atwoz.helper.IntegrationHelper; import com.atwoz.member.domain.member.Member; import com.atwoz.member.domain.member.MemberRepository; -import com.atwoz.member.infrastructure.auth.JwtTokenProvider; +import com.atwoz.member.infrastructure.auth.MemberJwtTokenProvider; import com.atwoz.memberlike.application.dto.MemberLikeCreateRequest; import com.atwoz.memberlike.domain.MemberLike; import com.atwoz.memberlike.domain.MemberLikeRepository; @@ -27,7 +27,7 @@ public class MemberLikeControllerAcceptanceFixture extends IntegrationHelper { @Autowired - private JwtTokenProvider jwtTokenProvider; + private MemberJwtTokenProvider jwtTokenProvider; @Autowired private MemberRepository memberRepository; @@ -43,7 +43,7 @@ public class MemberLikeControllerAcceptanceFixture extends IntegrationHelper { } protected String 토큰_생성(final Member member) { - return jwtTokenProvider.createTokenWithId(member.getId()); + return jwtTokenProvider.createAccessToken(member.getId()); } protected void 보낸_호감_목록_생성(final Long memberId) { diff --git a/src/test/java/com/atwoz/mission/ui/membermission/MemberMissionsControllerAcceptanceFixture.java b/src/test/java/com/atwoz/mission/ui/membermission/MemberMissionsControllerAcceptanceFixture.java index f54c15ff..2655c117 100644 --- a/src/test/java/com/atwoz/mission/ui/membermission/MemberMissionsControllerAcceptanceFixture.java +++ b/src/test/java/com/atwoz/mission/ui/membermission/MemberMissionsControllerAcceptanceFixture.java @@ -4,7 +4,7 @@ import com.atwoz.member.domain.member.Member; import com.atwoz.member.domain.member.MemberRepository; import com.atwoz.member.domain.member.profile.physical.vo.Gender; -import com.atwoz.member.infrastructure.auth.JwtTokenProvider; +import com.atwoz.member.infrastructure.auth.MemberJwtTokenProvider; import com.atwoz.mission.domain.membermission.MemberMission; import com.atwoz.mission.domain.membermission.MemberMissions; import com.atwoz.mission.domain.membermission.MemberMissionsRepository; @@ -31,7 +31,7 @@ class MemberMissionsControllerAcceptanceFixture extends IntegrationHelper { @Autowired - private JwtTokenProvider jwtTokenProvider; + private MemberJwtTokenProvider memberJwtTokenProvider; @Autowired private MemberRepository memberRepository; @@ -47,7 +47,7 @@ class MemberMissionsControllerAcceptanceFixture extends IntegrationHelper { } protected String 토큰_생성(final Member member) { - return jwtTokenProvider.createTokenWithId(member.getId()); + return memberJwtTokenProvider.createAccessToken(member.getId()); } protected Mission 데일리_미션_생성() { diff --git a/src/test/java/com/atwoz/report/ui/ReportControllerAcceptanceFixture.java b/src/test/java/com/atwoz/report/ui/ReportControllerAcceptanceFixture.java index aa27bb9e..e49455a3 100644 --- a/src/test/java/com/atwoz/report/ui/ReportControllerAcceptanceFixture.java +++ b/src/test/java/com/atwoz/report/ui/ReportControllerAcceptanceFixture.java @@ -1,7 +1,7 @@ package com.atwoz.report.ui; import com.atwoz.helper.IntegrationHelper; -import com.atwoz.member.domain.auth.TokenProvider; +import com.atwoz.member.domain.auth.MemberTokenProvider; import com.atwoz.member.domain.member.Member; import com.atwoz.member.domain.member.MemberRepository; import com.atwoz.report.application.dto.ReportCreateRequest; @@ -27,14 +27,14 @@ public class ReportControllerAcceptanceFixture extends IntegrationHelper { protected MemberRepository memberRepository; @Autowired - protected TokenProvider tokenProvider; + protected MemberTokenProvider memberTokenProvider; protected Member 회원_생성() { return memberRepository.save(일반_유저_생성()); } protected String 토큰_생성(final Member member) { - return tokenProvider.createTokenWithId(member.getId()); + return memberTokenProvider.createAccessToken(member.getId()); } protected ReportCreateRequest 신고_요청서_요청() { diff --git a/src/test/java/com/atwoz/survey/ui/membersurvey/MemberSurveysControllerAcceptanceFixture.java b/src/test/java/com/atwoz/survey/ui/membersurvey/MemberSurveysControllerAcceptanceFixture.java index de7becb0..d0a2d0ea 100644 --- a/src/test/java/com/atwoz/survey/ui/membersurvey/MemberSurveysControllerAcceptanceFixture.java +++ b/src/test/java/com/atwoz/survey/ui/membersurvey/MemberSurveysControllerAcceptanceFixture.java @@ -3,7 +3,7 @@ import com.atwoz.helper.IntegrationHelper; import com.atwoz.member.domain.member.Member; import com.atwoz.member.domain.member.MemberRepository; -import com.atwoz.member.infrastructure.auth.JwtTokenProvider; +import com.atwoz.member.infrastructure.auth.MemberJwtTokenProvider; import com.atwoz.survey.application.membersurvey.dto.SurveySubmitRequest; import com.atwoz.survey.domain.survey.SurveyRepository; import com.atwoz.survey.infrastructure.membersurvey.dto.MemberSurveyResponse; @@ -11,12 +11,12 @@ import io.restassured.RestAssured; import io.restassured.response.ExtractableResponse; import io.restassured.response.Response; +import java.util.List; import org.junit.jupiter.api.DisplayNameGeneration; import org.junit.jupiter.api.DisplayNameGenerator; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; -import java.util.List; import static com.atwoz.member.fixture.MemberFixture.일반_유저_생성; import static com.atwoz.survey.fixture.SurveyFixture.연애고사_선택_과목_질문_두개씩; import static com.atwoz.survey.fixture.SurveyFixture.연애고사_필수_과목_질문_30개씩; @@ -30,7 +30,7 @@ class MemberSurveysControllerAcceptanceFixture extends IntegrationHelper { @Autowired - private JwtTokenProvider jwtTokenProvider; + private MemberJwtTokenProvider memberJwtTokenProvider; @Autowired private MemberRepository memberRepository; @@ -47,7 +47,7 @@ class MemberSurveysControllerAcceptanceFixture extends IntegrationHelper { } protected String 토큰_생성(final Member member) { - return jwtTokenProvider.createTokenWithId(member.getId()); + return memberJwtTokenProvider.createAccessToken(member.getId()); } protected void 연애고사_필수_과목_질문_두개씩_생성() { diff --git a/src/test/java/com/atwoz/survey/ui/survey/SurveyControllerAcceptanceFixture.java b/src/test/java/com/atwoz/survey/ui/survey/SurveyControllerAcceptanceFixture.java index ef6eeec6..a60fa50a 100644 --- a/src/test/java/com/atwoz/survey/ui/survey/SurveyControllerAcceptanceFixture.java +++ b/src/test/java/com/atwoz/survey/ui/survey/SurveyControllerAcceptanceFixture.java @@ -3,7 +3,7 @@ import com.atwoz.helper.IntegrationHelper; import com.atwoz.member.domain.member.Member; import com.atwoz.member.domain.member.MemberRepository; -import com.atwoz.member.infrastructure.auth.JwtTokenProvider; +import com.atwoz.member.infrastructure.auth.MemberJwtTokenProvider; import com.atwoz.survey.application.survey.dto.SurveyCreateRequest; import com.atwoz.survey.ui.survey.dto.SurveyCreateResponse; import io.restassured.RestAssured; @@ -24,7 +24,7 @@ class SurveyControllerAcceptanceFixture extends IntegrationHelper { @Autowired - private JwtTokenProvider jwtTokenProvider; + private MemberJwtTokenProvider memberJwtTokenProvider; @Autowired private MemberRepository memberRepository; @@ -34,7 +34,7 @@ class SurveyControllerAcceptanceFixture extends IntegrationHelper { } protected String 토큰_생성(final Member member) { - return jwtTokenProvider.createTokenWithId(member.getId()); + return memberJwtTokenProvider.createAccessToken(member.getId()); } protected ExtractableResponse 연애고사_과목_생성_요청(final String url, final String token, final SurveyCreateRequest request) { diff --git a/src/test/resources/application.yml b/src/test/resources/application.yml index d56ed96f..20c85494 100644 --- a/src/test/resources/application.yml +++ b/src/test/resources/application.yml @@ -24,9 +24,13 @@ jasypt: encryptor: password: password +cookie: + max-age: 10000 + jwt: secret: fortestfortestfortestfortestfortestfortestfortestfortestfortestfortestfortestfortestfortestfortestfortest - expiration-period: 10000 + access-token-expiration-period: 10000 + refresh-token-expiration-period: 10000 mail: host: smtp.blabla.com