diff --git a/.github/workflows/backend-ci.yml b/.github/workflows/backend-ci.yml index 93468f56..0e81809b 100644 --- a/.github/workflows/backend-ci.yml +++ b/.github/workflows/backend-ci.yml @@ -33,7 +33,7 @@ jobs: - name: DB를 실행 run: | - docker-compose -f resources/test-develop-environment/docker-compose.yml up -d + docker-compose -f resources/local-develop-environment/docker-compose.yml up -d sleep 20 - name: Gradle 캐싱 diff --git a/BE/BE.md b/BE/BE.md deleted file mode 100644 index e69de29b..00000000 diff --git a/BE/README.md b/BE/README.md index 5c5be3f6..d10e4c8a 100644 --- a/BE/README.md +++ b/BE/README.md @@ -8,23 +8,16 @@ | 운영 환경 구축 | EC2, Docker | | Build | Gradle | | CI/CD | Github Actions | -| Library | Spring Open Feign, OAuth 2.0, Actuator | +| Library | Spring Open Feign, OAuth 2.0, spring actuator | ## 실행방법 -1. git clone +1. git clone [repository url) 2. 환경변수를 설정해줍니다.(env.properties) -3. docker-compose를 통해 db를 실행합니다. -4. spring boot를 실행합니다. - - -## docs -[서브 도메인 적용하기](https://velog.io/@kssumin/%EC%84%9C%EB%B8%8C-%EB%8F%84%EB%A9%94%EC%9D%B8-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0) - -[타임존 설정](https://velog.io/@kssumin/DB%EC%97%90-%EB%93%A4%EC%96%B4%EA%B0%80%EB%8A%94-%EC%8B%9C%EA%B0%84%EC%9D%B4-%EC%9D%B4%EC%83%81%ED%95%98%EB%8B%A4) - -[분기 처리 코드 리팩터링하기](https://velog.io/@kssumin/%EB%B6%84%EA%B8%B0%EC%B2%98%EB%A6%AC-%EC%BD%94%EB%93%9C-%EB%A6%AC%ED%8C%A9%ED%84%B0%EB%A7%81-%ED%95%98%EA%B8%B0) - -[슬랙 API 호출 코드를 추상화하기](https://velog.io/@kssumin/%EC%8A%AC%EB%9E%99-API-%ED%98%B8%EC%B6%9C-%EC%BD%94%EB%93%9C%EB%A5%BC-%EC%B6%94%EC%83%81%ED%99%94%ED%95%98%EA%B8%B0) - -[200 status code만 사용하지 말자](https://velog.io/@kssumin/EEOS-%EC%BA%90%EC%8B%B1) +3. db를 실행합니다. + ```shell + cd resources + cd local-develop-environment + docker-compose up + ``` +5. spring boot를 실행합니다. diff --git a/BE/eeos/build.gradle b/BE/eeos/build.gradle index 545d67f5..ca1e8f17 100644 --- a/BE/eeos/build.gradle +++ b/BE/eeos/build.gradle @@ -74,6 +74,10 @@ dependencies { implementation 'org.flywaydb:flyway-core' implementation 'org.flywaydb:flyway-mysql' + // redis + implementation 'org.springframework.boot:spring-boot-starter-data-redis' + + // openfeign implementation 'org.springframework.cloud:spring-cloud-starter-openfeign' diff --git a/BE/eeos/docker-compose.yml b/BE/eeos/docker-compose.yml index 6ba36c47..7c1a6c2e 100644 --- a/BE/eeos/docker-compose.yml +++ b/BE/eeos/docker-compose.yml @@ -6,7 +6,7 @@ services: environment: - MYSQL_DATABASE=eeos - MYSQL_ROOT_HOST=% - - MYSQL_ROOT_PASSWORD=root + - MYSQL_ROOT_PASSWORD=${DB_PASSWORD} command: [ "--character-set-server=utf8mb4", "--collation-server=utf8mb4_unicode_ci", "--skip-character-set-client-handshake", "--lower_case_table_names=1", "--max_connections=2048", "--wait_timeout=3600" ] ports: - "13306:3306" @@ -25,6 +25,15 @@ services: - eeos-network depends_on: - mysql + redis: + container_name: eeos-redis + image: redis:latest + ports: + - "16379:6379" + environment: + - REDIS_PASSWORD=${DB_PASSWORD} + networks: + - eeos-network backend: build: context: . @@ -35,12 +44,13 @@ services: depends_on: - mysql - flyway + - redis restart: always environment: SPRING_DATASOURCE_URL: jdbc:mysql://eeos-mysql:3306/eeos?useSSL=false&serverTimezone=Asia/Seoul&useLegacyDatetimeCode=false&allowPublicKeyRetrieval=true SPRING_DATASOURCE_USERNAME: root SPRING_DATASOURCE_PASSWORD: root - SPRING_PROFILES_ACTIVE: dev + SPRING_PROFILES_ACTIVE: ${PROFILE} networks: #사용할 네트워크 지정 - eeos-network networks: diff --git a/BE/eeos/resources/local-develop-environment/docker-compose.yml b/BE/eeos/resources/local-develop-environment/docker-compose.yml index b3ca25a1..8214c6f2 100644 --- a/BE/eeos/resources/local-develop-environment/docker-compose.yml +++ b/BE/eeos/resources/local-develop-environment/docker-compose.yml @@ -1,6 +1,6 @@ version: '3.1' services: - overflow-mysql: + eeos-local-mysql: container_name: eeos-mysql8 image: mysql/mysql-server:8.0.27 environment: @@ -12,8 +12,14 @@ services: - "13308:3306" volumes: - ./mysql-init.d/:/docker-entrypoint-initdb.d - - overflow-adminer: # mysql web admin + eeos-local-redis: + container_name: eeos-redis + image: redis:latest + ports: + - "16379:6379" + environment: + - REDIS_PASSWORD=root + eeos-local-adminer: # mysql web admin container_name: eeos-adminer image: adminer:4 ports: @@ -21,4 +27,4 @@ services: environment: - ADMINER_DEFAULT_SERVER=overflow-mysql8 - ADMINER_DESIGN=nette - - ADMINER_PLUGINS=tables-filter tinymce \ No newline at end of file + - ADMINER_PLUGINS=tables-filter tinymce diff --git a/BE/eeos/resources/test-develop-environment/docker-compose.yml b/BE/eeos/resources/test-develop-environment/docker-compose.yml deleted file mode 100644 index 0fa69c8a..00000000 --- a/BE/eeos/resources/test-develop-environment/docker-compose.yml +++ /dev/null @@ -1,14 +0,0 @@ -version: '3.1' -services: - eeos-mysql: - container_name: eeos-mysql8-test - image: mysql/mysql-server:8.0.27 - environment: - - MYSQL_ROOT_PASSWORD=root - - MYSQL_ROOT_HOST=% - - TZ=Asia/Seoul - command: [ "--character-set-server=utf8mb4", "--collation-server=utf8mb4_unicode_ci", "--lower_case_table_names=1", "--max_connections=2048", "--wait_timeout=3600" ] - ports: - - "13309:3306" - volumes: - - ./mysql-init.d/:/docker-entrypoint-initdb.d \ No newline at end of file diff --git a/BE/eeos/src/main/java/com/blackcompany/eeos/auth/application/domain/converter/OauthInfoEntityConverter.java b/BE/eeos/src/main/java/com/blackcompany/eeos/auth/application/domain/converter/OauthInfoEntityConverter.java deleted file mode 100644 index a3224b6d..00000000 --- a/BE/eeos/src/main/java/com/blackcompany/eeos/auth/application/domain/converter/OauthInfoEntityConverter.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.blackcompany.eeos.auth.application.domain.converter; - -import com.blackcompany.eeos.auth.application.domain.OauthMemberModel; -import com.blackcompany.eeos.auth.persistence.OauthInfoEntity; -import com.blackcompany.eeos.common.support.converter.AbstractEntityConverter; -import org.springframework.stereotype.Component; - -@Component -public class OauthInfoEntityConverter - implements AbstractEntityConverter { - @Override - public OauthMemberModel from(final OauthInfoEntity oauthEntity) { - return OauthMemberModel.builder().oauthId(oauthEntity.getOauthId()).build(); - } - - @Override - public OauthInfoEntity toEntity(final OauthMemberModel oauthMemberModel) { - return OauthInfoEntity.builder().oauthId(oauthMemberModel.getOauthId()).build(); - } - - public OauthInfoEntity toEntity(final String oauthId, Long memberId) { - return OauthInfoEntity.builder().oauthId(oauthId).memberId(memberId).build(); - } -} diff --git a/BE/eeos/src/main/java/com/blackcompany/eeos/auth/application/domain/converter/OauthMemberEntityConverter.java b/BE/eeos/src/main/java/com/blackcompany/eeos/auth/application/domain/converter/OauthMemberEntityConverter.java new file mode 100644 index 00000000..5b90a864 --- /dev/null +++ b/BE/eeos/src/main/java/com/blackcompany/eeos/auth/application/domain/converter/OauthMemberEntityConverter.java @@ -0,0 +1,24 @@ +package com.blackcompany.eeos.auth.application.domain.converter; + +import com.blackcompany.eeos.auth.application.domain.OauthMemberModel; +import com.blackcompany.eeos.auth.persistence.OAuthMemberEntity; +import com.blackcompany.eeos.common.support.converter.AbstractEntityConverter; +import org.springframework.stereotype.Component; + +@Component +public class OauthMemberEntityConverter + implements AbstractEntityConverter { + @Override + public OauthMemberModel from(final OAuthMemberEntity entity) { + return OauthMemberModel.builder().oauthId(entity.getOauthId()).build(); + } + + @Override + public OAuthMemberEntity toEntity(final OauthMemberModel model) { + return OAuthMemberEntity.builder().oauthId(model.getOauthId()).build(); + } + + public OAuthMemberEntity toEntity(final String oauthId, Long memberId) { + return OAuthMemberEntity.builder().oauthId(oauthId).memberId(memberId).build(); + } +} diff --git a/BE/eeos/src/main/java/com/blackcompany/eeos/auth/application/domain/token/TokenResolver.java b/BE/eeos/src/main/java/com/blackcompany/eeos/auth/application/domain/token/TokenResolver.java index de601056..f2f7a486 100644 --- a/BE/eeos/src/main/java/com/blackcompany/eeos/auth/application/domain/token/TokenResolver.java +++ b/BE/eeos/src/main/java/com/blackcompany/eeos/auth/application/domain/token/TokenResolver.java @@ -1,9 +1,7 @@ package com.blackcompany.eeos.auth.application.domain.token; -import com.blackcompany.eeos.auth.application.exception.NotFoundCookieException; -import com.blackcompany.eeos.auth.application.exception.NotFoundHeaderTokenException; -import com.blackcompany.eeos.auth.application.exception.RtTokenExpiredException; import com.blackcompany.eeos.auth.application.exception.TokenExpiredException; +import com.blackcompany.eeos.auth.application.exception.TokenParsingException; import io.jsonwebtoken.Claims; import io.jsonwebtoken.ExpiredJwtException; import io.jsonwebtoken.Jwts; @@ -27,61 +25,57 @@ public class TokenResolver { public TokenResolver( @Value("${security.jwt.access.secretKey}") String accessSecretKey, @Value("${security.jwt.refresh.secretKey}") String refreshSecretKey) { - this.accessSecretKey = Keys.hmacShaKeyFor(accessSecretKey.getBytes(StandardCharsets.UTF_8)); - this.refreshSecretKey = Keys.hmacShaKeyFor(refreshSecretKey.getBytes(StandardCharsets.UTF_8)); + this.accessSecretKey = generateSecretKey(accessSecretKey); + this.refreshSecretKey = generateSecretKey(refreshSecretKey); } - public Long getExpiredDateByHeader(final String token) { - Date expiration = getAccessClaims(token).getExpiration(); - - return expiration.getTime(); + public Long getExpiredDateByAccessToken(final String token) { + return getExpirationTime(getAccessClaims(token)); } - public Long getUserInfoByCookie(final String token) { - return getRefreshClaims(token).get(MEMBER_ID_CLAIM_KEY, Long.class); + public Long getExpiredDateByRefreshToken(final String token) { + return getExpirationTime(getRefreshClaims(token)); } - public Long getExpiredDateByCookie(final String token) { - Date expiration = getRefreshClaims(token).getExpiration(); + public Long getUserDataByAccessToken(final String token) { + return getClaimValue(getAccessClaims(token), MEMBER_ID_CLAIM_KEY); + } - return expiration.getTime(); + public Long getUserDataByRefreshToken(final String token) { + return getClaimValue(getRefreshClaims(token), MEMBER_ID_CLAIM_KEY); } - public Long getUserInfoByHeader(final String token) { - return getAccessClaims(token).get(MEMBER_ID_CLAIM_KEY, Long.class); + private SecretKey generateSecretKey(String key) { + return Keys.hmacShaKeyFor(key.getBytes(StandardCharsets.UTF_8)); } private Claims getAccessClaims(final String token) { - try { - return Jwts.parserBuilder() - .setSigningKey(accessSecretKey) - .build() - .parseClaimsJws(token) - .getBody(); - } catch (ExpiredJwtException e) { - throw new TokenExpiredException(); - } catch (SignatureException e) { - log.error("jwt 파싱에 에러가 발생했습니다."); - throw new NotFoundHeaderTokenException(); - } catch (IllegalStateException e) { - throw new NotFoundHeaderTokenException(); - } + return parseClaims(token, accessSecretKey); } private Claims getRefreshClaims(final String token) { + return parseClaims(token, refreshSecretKey); + } + + private Claims parseClaims(final String token, final SecretKey secretKey) { try { - return Jwts.parserBuilder() - .setSigningKey(refreshSecretKey) - .build() - .parseClaimsJws(token) - .getBody(); + return Jwts.parserBuilder().setSigningKey(secretKey).build().parseClaimsJws(token).getBody(); } catch (ExpiredJwtException e) { - throw new RtTokenExpiredException(); + throw new TokenExpiredException(); } catch (SignatureException e) { - log.error("jwt 파싱에 에러가 발생했습니다."); - throw new NotFoundCookieException(); - } catch (IllegalStateException e) { - throw new NotFoundCookieException(); + throw new TokenParsingException(e); + } catch (Exception e) { + log.error("JWT 파싱 중 오류 발생: {}", e.getMessage(), e); + throw new TokenParsingException(e); } } + + private Long getExpirationTime(Claims claims) { + Date expiration = claims.getExpiration(); + return expiration.getTime(); + } + + private Long getClaimValue(Claims claims, String claimKey) { + return claims.get(claimKey, Long.class); + } } diff --git a/BE/eeos/src/main/java/com/blackcompany/eeos/auth/application/event/DeletedMemberEvent.java b/BE/eeos/src/main/java/com/blackcompany/eeos/auth/application/event/DeletedMemberEvent.java new file mode 100644 index 00000000..0f54106b --- /dev/null +++ b/BE/eeos/src/main/java/com/blackcompany/eeos/auth/application/event/DeletedMemberEvent.java @@ -0,0 +1,25 @@ +package com.blackcompany.eeos.auth.application.event; + +import lombok.Getter; + +@Getter +public class DeletedMemberEvent { + private final Long memberId; + private final String token; + + private DeletedMemberEvent(Long memberId, String token) { + this.memberId = memberId; + this.token = token; + } + + /** + * 멤버 탈퇴 이벤트를 발행한다. + * + * @param memberId 회원 탈퇴를 원하는 멤버 id + * @param token 회원 탈퇴 시 사용한 토큰 + * @return + */ + public static DeletedMemberEvent of(Long memberId, String token) { + return new DeletedMemberEvent(memberId, token); + } +} diff --git a/BE/eeos/src/main/java/com/blackcompany/eeos/auth/application/event/DeletedMemberEventListener.java b/BE/eeos/src/main/java/com/blackcompany/eeos/auth/application/event/DeletedMemberEventListener.java new file mode 100644 index 00000000..ac5ceec8 --- /dev/null +++ b/BE/eeos/src/main/java/com/blackcompany/eeos/auth/application/event/DeletedMemberEventListener.java @@ -0,0 +1,42 @@ +package com.blackcompany.eeos.auth.application.event; + +import com.blackcompany.eeos.auth.application.domain.token.TokenResolver; +import com.blackcompany.eeos.auth.persistence.BlackAuthenticationRepository; +import com.blackcompany.eeos.target.persistence.AttendRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.event.TransactionPhase; +import org.springframework.transaction.event.TransactionalEventListener; + +@Component +@RequiredArgsConstructor +@Slf4j +public class DeletedMemberEventListener { + private final AttendRepository attendRepository; + private final BlackAuthenticationRepository blackAuthenticationRepository; + private final TokenResolver tokenResolver; + + @Async + @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) + @Transactional(propagation = Propagation.REQUIRES_NEW) + public void handleDeletedProgram(DeletedMemberEvent event) { + deleteTargetData(event.getMemberId()); + saveUsedToken(event.getToken(), event.getMemberId()); + } + + private void deleteTargetData(Long memberId) { + attendRepository.deleteAllByMemberId(memberId); + } + + private void saveUsedToken(String token, Long memberId) { + blackAuthenticationRepository.save(token, memberId, getExpiredDate(token)); + } + + private Long getExpiredDate(String token) { + return tokenResolver.getExpiredDateByRefreshToken(token); + } +} diff --git a/BE/eeos/src/main/java/com/blackcompany/eeos/auth/application/exception/TokenParsingException.java b/BE/eeos/src/main/java/com/blackcompany/eeos/auth/application/exception/TokenParsingException.java new file mode 100644 index 00000000..dbe91f47 --- /dev/null +++ b/BE/eeos/src/main/java/com/blackcompany/eeos/auth/application/exception/TokenParsingException.java @@ -0,0 +1,20 @@ +package com.blackcompany.eeos.auth.application.exception; + +import com.blackcompany.eeos.common.exception.BusinessException; +import org.springframework.http.HttpStatus; + +/** JWT 파싱 중 발생한 예외 */ +public class TokenParsingException extends BusinessException { + private static final String FAIL_CODE = "4007"; + private String message; + + public TokenParsingException(Exception e) { + super(FAIL_CODE, HttpStatus.BAD_REQUEST); + message = e.getMessage(); + } + + @Override + public String getMessage() { + return String.format("JWT 파싱 중 오류 발생: {}", message); + } +} diff --git a/BE/eeos/src/main/java/com/blackcompany/eeos/auth/application/service/AuthFacadeService.java b/BE/eeos/src/main/java/com/blackcompany/eeos/auth/application/service/AuthFacadeService.java index a11fcff0..bbc93a57 100644 --- a/BE/eeos/src/main/java/com/blackcompany/eeos/auth/application/service/AuthFacadeService.java +++ b/BE/eeos/src/main/java/com/blackcompany/eeos/auth/application/service/AuthFacadeService.java @@ -2,26 +2,23 @@ import com.blackcompany.eeos.auth.application.domain.OauthMemberModel; import com.blackcompany.eeos.auth.application.domain.TokenModel; +import com.blackcompany.eeos.auth.application.support.AuthenticationTokenGenerator; import com.blackcompany.eeos.auth.application.usecase.LoginUsecase; -import com.blackcompany.eeos.auth.persistence.OauthInfoEntity; +import com.blackcompany.eeos.auth.persistence.OAuthMemberEntity; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Propagation; -import org.springframework.transaction.annotation.Transactional; @Service @RequiredArgsConstructor -@Transactional public class AuthFacadeService implements LoginUsecase { private final OauthClientService oauthClientService; private final AuthService authService; - private final CreateTokenService createTokenService; + private final AuthenticationTokenGenerator authenticationTokenGenerator; - @Transactional(propagation = Propagation.REQUIRED) @Override public TokenModel login(String oauthServerType, String authCode, String uri) { OauthMemberModel model = oauthClientService.getOauthMember(oauthServerType, authCode, uri); - OauthInfoEntity entity = authService.login(model); - return createTokenService.execute(entity.getMemberId()); + OAuthMemberEntity entity = authService.authenticate(model); + return authenticationTokenGenerator.execute(entity.getMemberId()); } } diff --git a/BE/eeos/src/main/java/com/blackcompany/eeos/auth/application/service/AuthService.java b/BE/eeos/src/main/java/com/blackcompany/eeos/auth/application/service/AuthService.java index 87fd35a9..3cbc1f79 100644 --- a/BE/eeos/src/main/java/com/blackcompany/eeos/auth/application/service/AuthService.java +++ b/BE/eeos/src/main/java/com/blackcompany/eeos/auth/application/service/AuthService.java @@ -1,44 +1,42 @@ package com.blackcompany.eeos.auth.application.service; import com.blackcompany.eeos.auth.application.domain.OauthMemberModel; -import com.blackcompany.eeos.auth.application.domain.converter.OauthInfoEntityConverter; -import com.blackcompany.eeos.auth.persistence.OauthInfoEntity; -import com.blackcompany.eeos.auth.persistence.OauthInfoRepository; +import com.blackcompany.eeos.auth.application.domain.converter.OauthMemberEntityConverter; +import com.blackcompany.eeos.auth.persistence.OAuthMemberEntity; +import com.blackcompany.eeos.auth.persistence.OAuthMemberRepository; import com.blackcompany.eeos.member.application.model.converter.MemberEntityConverter; import com.blackcompany.eeos.member.persistence.MemberEntity; import com.blackcompany.eeos.member.persistence.MemberRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; @Service -@Transactional(readOnly = true) @RequiredArgsConstructor +@Transactional(readOnly = true) public class AuthService { - - private final OauthInfoRepository oauthInfoRepository; private final MemberRepository memberRepository; + private final OAuthMemberRepository oAuthMemberRepository; private final MemberEntityConverter memberEntityConverter; - private final OauthInfoEntityConverter oauthInfoEntityConverter; + private final OauthMemberEntityConverter oauthMemberEntityConverter; - @Transactional(propagation = Propagation.REQUIRED) - public OauthInfoEntity login(final OauthMemberModel model) { - return oauthInfoRepository + @Transactional + public OAuthMemberEntity authenticate(final OauthMemberModel model) { + return oAuthMemberRepository .findByOauthId(model.getOauthId()) .orElseGet(() -> signUpMember(model)); } - private OauthInfoEntity signUpMember(final OauthMemberModel model) { + private OAuthMemberEntity signUpMember(final OauthMemberModel model) { MemberEntity entity = memberEntityConverter.toEntity(model.getName(), model.getOauthServerType()); MemberEntity savedMember = memberRepository.save(entity); - return createOauthInfoEntity(model.getOauthId(), savedMember.getId()); + return saveOauthInfoEntity(model.getOauthId(), savedMember.getId()); } - private OauthInfoEntity createOauthInfoEntity(final String oauthId, final Long memberId) { - OauthInfoEntity entity = oauthInfoEntityConverter.toEntity(oauthId, memberId); - return oauthInfoRepository.save(entity); + private OAuthMemberEntity saveOauthInfoEntity(final String oauthId, final Long memberId) { + OAuthMemberEntity entity = oauthMemberEntityConverter.toEntity(oauthId, memberId); + return oAuthMemberRepository.save(entity); } } diff --git a/BE/eeos/src/main/java/com/blackcompany/eeos/auth/application/service/CreateTokenService.java b/BE/eeos/src/main/java/com/blackcompany/eeos/auth/application/service/CreateTokenService.java deleted file mode 100644 index 8edddde0..00000000 --- a/BE/eeos/src/main/java/com/blackcompany/eeos/auth/application/service/CreateTokenService.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.blackcompany.eeos.auth.application.service; - -import com.blackcompany.eeos.auth.application.domain.TokenModel; -import com.blackcompany.eeos.auth.application.domain.converter.TokenModelConverter; -import com.blackcompany.eeos.auth.application.domain.token.TokenProvider; -import com.blackcompany.eeos.auth.application.domain.token.TokenResolver; -import com.blackcompany.eeos.auth.persistence.AuthInfoEntity; -import com.blackcompany.eeos.auth.persistence.AuthInfoEntityConverter; -import com.blackcompany.eeos.auth.persistence.AuthInfoRepository; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -@Service -@RequiredArgsConstructor -@Transactional(readOnly = true) -public class CreateTokenService { - private final TokenProvider tokenProvider; - private final TokenModelConverter tokenModelConverter; - private final TokenResolver tokenResolver; - private final AuthInfoRepository authInfoRepository; - private final AuthInfoEntityConverter authInfoEntityConverter; - - @Transactional - public TokenModel execute(final Long memberId) { - String accessToken = tokenProvider.createAccessToken(memberId); - String refreshToken = tokenProvider.createRefreshToken(memberId); - - saveToken(memberId, refreshToken); - - return tokenModelConverter.from( - accessToken, - tokenResolver.getExpiredDateByHeader(accessToken), - refreshToken, - tokenResolver.getExpiredDateByCookie(refreshToken)); - } - - private void saveToken(final Long memberId, final String token) { - AuthInfoEntity authInfoEntity = authInfoEntityConverter.from(memberId, token); - authInfoRepository.save(authInfoEntity); - } -} diff --git a/BE/eeos/src/main/java/com/blackcompany/eeos/auth/application/service/DeactivateMemberService.java b/BE/eeos/src/main/java/com/blackcompany/eeos/auth/application/service/DeactivateMemberService.java new file mode 100644 index 00000000..c91042d2 --- /dev/null +++ b/BE/eeos/src/main/java/com/blackcompany/eeos/auth/application/service/DeactivateMemberService.java @@ -0,0 +1,57 @@ +package com.blackcompany.eeos.auth.application.service; + +import com.blackcompany.eeos.auth.application.domain.token.TokenResolver; +import com.blackcompany.eeos.auth.application.event.DeletedMemberEvent; +import com.blackcompany.eeos.auth.application.usecase.LogOutUsecase; +import com.blackcompany.eeos.auth.application.usecase.WithDrawUsecase; +import com.blackcompany.eeos.auth.persistence.BlackAuthenticationRepository; +import com.blackcompany.eeos.auth.persistence.OAuthMemberRepository; +import com.blackcompany.eeos.member.persistence.MemberRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class DeactivateMemberService implements LogOutUsecase, WithDrawUsecase { + private final BlackAuthenticationRepository blackAuthenticationRepository; + private final TokenResolver tokenResolver; + private final ApplicationEventPublisher eventPublisher; + private final MemberRepository memberRepository; + private final OAuthMemberRepository oAuthMemberRepository; + + @Override + @Transactional + public void logOut(final String token, final Long memberId) { + saveUsedToken(token, memberId); + } + + @Override + @Transactional + public void withDraw(final String token, final Long memberId) { + if (isNotMember(memberId)) { + return; + } + deleteMemberData(memberId); + eventPublisher.publishEvent(DeletedMemberEvent.of(memberId, token)); + } + + private void saveUsedToken(final String token, final Long memberId) { + blackAuthenticationRepository.save(token, memberId, getExpiredToken(token)); + } + + private Long getExpiredToken(final String token) { + return tokenResolver.getExpiredDateByRefreshToken(token); + } + + private boolean isNotMember(Long memberId) { + return memberRepository.findById(memberId).isEmpty(); + } + + private void deleteMemberData(Long memberId) { + memberRepository.deleteById(memberId); + oAuthMemberRepository.findByMemberId(memberId).ifPresent(oAuthMemberRepository::delete); + } +} diff --git a/BE/eeos/src/main/java/com/blackcompany/eeos/auth/application/service/OauthClientService.java b/BE/eeos/src/main/java/com/blackcompany/eeos/auth/application/service/OauthClientService.java index be9654af..993594a0 100644 --- a/BE/eeos/src/main/java/com/blackcompany/eeos/auth/application/service/OauthClientService.java +++ b/BE/eeos/src/main/java/com/blackcompany/eeos/auth/application/service/OauthClientService.java @@ -4,15 +4,12 @@ import com.blackcompany.eeos.auth.application.domain.client.OauthMemberClientComposite; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Propagation; -import org.springframework.transaction.annotation.Transactional; @Service @RequiredArgsConstructor public class OauthClientService { private final OauthMemberClientComposite oauthMemberClientComposite; - @Transactional(propagation = Propagation.REQUIRED) public OauthMemberModel getOauthMember(String oauthServerType, String authCode, String uri) { OauthMemberModel model = oauthMemberClientComposite.fetch(oauthServerType, authCode, uri); model.validateNameFormat(); diff --git a/BE/eeos/src/main/java/com/blackcompany/eeos/auth/application/service/ReissueService.java b/BE/eeos/src/main/java/com/blackcompany/eeos/auth/application/service/ReissueService.java index a75bf1ed..8ec4127a 100644 --- a/BE/eeos/src/main/java/com/blackcompany/eeos/auth/application/service/ReissueService.java +++ b/BE/eeos/src/main/java/com/blackcompany/eeos/auth/application/service/ReissueService.java @@ -3,45 +3,45 @@ import com.blackcompany.eeos.auth.application.domain.TokenModel; import com.blackcompany.eeos.auth.application.domain.token.TokenResolver; import com.blackcompany.eeos.auth.application.exception.InvalidTokenException; +import com.blackcompany.eeos.auth.application.support.AuthenticationTokenGenerator; import com.blackcompany.eeos.auth.application.usecase.ReissueUsecase; -import com.blackcompany.eeos.auth.persistence.AuthInfoEntity; -import com.blackcompany.eeos.auth.persistence.AuthInfoRepository; -import java.util.Optional; +import com.blackcompany.eeos.auth.persistence.BlackAuthenticationRepository; import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service @RequiredArgsConstructor @Transactional(readOnly = true) -@Slf4j public class ReissueService implements ReissueUsecase { - private final CreateTokenService createTokenService; - private final AuthInfoRepository authInfoRepository; + private final AuthenticationTokenGenerator authenticationTokenGenerator; + private final BlackAuthenticationRepository blackAuthenticationRepository; private final TokenResolver tokenResolver; @Transactional @Override public TokenModel execute(final String token) { - Long memberId = tokenResolver.getUserInfoByCookie(token); - validateToken(memberId, token); + Long memberId = tokenResolver.getUserDataByRefreshToken(token); - return createTokenService.execute(memberId); + validateToken(token); + saveUsedToken(token, memberId); + + return authenticationTokenGenerator.execute(memberId); } - private void validateToken(final Long memberId, final String token) { - Optional validToken = - authInfoRepository.findByMemberIdAndToken(memberId, token); - if (validToken.isPresent()) { - validToken.ifPresent(authInfoRepository::delete); - return; + private void validateToken(final String token) { + boolean isExistToken = blackAuthenticationRepository.isExistToken(token); + + if (isExistToken) { + throw new InvalidTokenException(); } - deleteInvalidToken(token); } - private void deleteInvalidToken(final String token) { - authInfoRepository.findByToken(token).ifPresent(authInfoRepository::delete); - throw new InvalidTokenException(); + private void saveUsedToken(final String token, final Long memberId) { + blackAuthenticationRepository.save(token, memberId, getExpiredToken(token)); + } + + private Long getExpiredToken(final String token) { + return tokenResolver.getExpiredDateByRefreshToken(token); } } diff --git a/BE/eeos/src/main/java/com/blackcompany/eeos/auth/application/support/AuthenticationTokenGenerator.java b/BE/eeos/src/main/java/com/blackcompany/eeos/auth/application/support/AuthenticationTokenGenerator.java new file mode 100644 index 00000000..fc1bcf4f --- /dev/null +++ b/BE/eeos/src/main/java/com/blackcompany/eeos/auth/application/support/AuthenticationTokenGenerator.java @@ -0,0 +1,27 @@ +package com.blackcompany.eeos.auth.application.support; + +import com.blackcompany.eeos.auth.application.domain.TokenModel; +import com.blackcompany.eeos.auth.application.domain.converter.TokenModelConverter; +import com.blackcompany.eeos.auth.application.domain.token.TokenProvider; +import com.blackcompany.eeos.auth.application.domain.token.TokenResolver; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class AuthenticationTokenGenerator { + private final TokenProvider tokenProvider; + private final TokenModelConverter tokenModelConverter; + private final TokenResolver tokenResolver; + + public TokenModel execute(final Long memberId) { + String accessToken = tokenProvider.createAccessToken(memberId); + String refreshToken = tokenProvider.createRefreshToken(memberId); + + return tokenModelConverter.from( + accessToken, + tokenResolver.getExpiredDateByAccessToken(accessToken), + refreshToken, + tokenResolver.getExpiredDateByRefreshToken(refreshToken)); + } +} diff --git a/BE/eeos/src/main/java/com/blackcompany/eeos/auth/application/usecase/LogOutUsecase.java b/BE/eeos/src/main/java/com/blackcompany/eeos/auth/application/usecase/LogOutUsecase.java new file mode 100644 index 00000000..9d4773b3 --- /dev/null +++ b/BE/eeos/src/main/java/com/blackcompany/eeos/auth/application/usecase/LogOutUsecase.java @@ -0,0 +1,5 @@ +package com.blackcompany.eeos.auth.application.usecase; + +public interface LogOutUsecase { + void logOut(String token, final Long memberId); +} diff --git a/BE/eeos/src/main/java/com/blackcompany/eeos/auth/application/usecase/WithDrawUsecase.java b/BE/eeos/src/main/java/com/blackcompany/eeos/auth/application/usecase/WithDrawUsecase.java new file mode 100644 index 00000000..92ca3fb6 --- /dev/null +++ b/BE/eeos/src/main/java/com/blackcompany/eeos/auth/application/usecase/WithDrawUsecase.java @@ -0,0 +1,5 @@ +package com.blackcompany.eeos.auth.application.usecase; + +public interface WithDrawUsecase { + void withDraw(String token, Long memberId); +} diff --git a/BE/eeos/src/main/java/com/blackcompany/eeos/auth/persistence/AuthInfoEntity.java b/BE/eeos/src/main/java/com/blackcompany/eeos/auth/persistence/AuthInfoEntity.java deleted file mode 100644 index b519cf47..00000000 --- a/BE/eeos/src/main/java/com/blackcompany/eeos/auth/persistence/AuthInfoEntity.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.blackcompany.eeos.auth.persistence; - -import com.blackcompany.eeos.common.persistence.BaseEntity; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Index; -import javax.persistence.Table; -import lombok.AccessLevel; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.ToString; -import lombok.experimental.SuperBuilder; - -@Getter -@NoArgsConstructor(access = AccessLevel.PROTECTED) -@AllArgsConstructor(access = AccessLevel.PRIVATE) -@ToString -@SuperBuilder(toBuilder = true) -@Entity -@Table( - name = AuthInfoEntity.ENTITY_PREFIX, - indexes = @Index(name = "idx_auth_info_member_id", columnList = "auth_info_member_id")) -public class AuthInfoEntity extends BaseEntity { - - public static final String ENTITY_PREFIX = "auth_info"; - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = ENTITY_PREFIX + "_id", nullable = false) - private Long id; - - @Column(name = ENTITY_PREFIX + "_token", nullable = false) - private String token; - - @Column(name = ENTITY_PREFIX + "_member_id", nullable = false) - private Long memberId; -} diff --git a/BE/eeos/src/main/java/com/blackcompany/eeos/auth/persistence/AuthInfoEntityConverter.java b/BE/eeos/src/main/java/com/blackcompany/eeos/auth/persistence/AuthInfoEntityConverter.java deleted file mode 100644 index 77132522..00000000 --- a/BE/eeos/src/main/java/com/blackcompany/eeos/auth/persistence/AuthInfoEntityConverter.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.blackcompany.eeos.auth.persistence; - -import org.springframework.stereotype.Component; - -@Component -public class AuthInfoEntityConverter { - - public AuthInfoEntity from(Long memberId, String refreshToken) { - return AuthInfoEntity.builder().memberId(memberId).token(refreshToken).build(); - } -} diff --git a/BE/eeos/src/main/java/com/blackcompany/eeos/auth/persistence/AuthInfoRepository.java b/BE/eeos/src/main/java/com/blackcompany/eeos/auth/persistence/AuthInfoRepository.java deleted file mode 100644 index 7efb870d..00000000 --- a/BE/eeos/src/main/java/com/blackcompany/eeos/auth/persistence/AuthInfoRepository.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.blackcompany.eeos.auth.persistence; - -import java.util.Optional; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; - -public interface AuthInfoRepository extends JpaRepository { - @Query("SELECT a FROM AuthInfoEntity a WHERE a.memberId=:memberId AND a.token =:token") - Optional findByMemberIdAndToken( - @Param("memberId") Long memberId, @Param("token") String token); - - @Query("SELECT a FROM AuthInfoEntity a WHERE a.token =:token") - Optional findByToken(@Param("token") String token); -} diff --git a/BE/eeos/src/main/java/com/blackcompany/eeos/auth/persistence/BlackAuthenticationEntity.java b/BE/eeos/src/main/java/com/blackcompany/eeos/auth/persistence/BlackAuthenticationEntity.java new file mode 100644 index 00000000..dc6f64d3 --- /dev/null +++ b/BE/eeos/src/main/java/com/blackcompany/eeos/auth/persistence/BlackAuthenticationEntity.java @@ -0,0 +1,22 @@ +package com.blackcompany.eeos.auth.persistence; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; +import org.springframework.data.annotation.Id; +import org.springframework.data.redis.core.RedisHash; +import org.springframework.data.redis.core.TimeToLive; + +@RedisHash("MemberAuthentication") +@Getter +@Setter +@ToString +@AllArgsConstructor +@NoArgsConstructor +public class BlackAuthenticationEntity { + @Id private String token; + private Long memberId; + @TimeToLive private Long expiration; +} diff --git a/BE/eeos/src/main/java/com/blackcompany/eeos/auth/persistence/BlackAuthenticationRepository.java b/BE/eeos/src/main/java/com/blackcompany/eeos/auth/persistence/BlackAuthenticationRepository.java new file mode 100644 index 00000000..f020d068 --- /dev/null +++ b/BE/eeos/src/main/java/com/blackcompany/eeos/auth/persistence/BlackAuthenticationRepository.java @@ -0,0 +1,21 @@ +package com.blackcompany.eeos.auth.persistence; + +import java.util.concurrent.TimeUnit; +import lombok.RequiredArgsConstructor; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Repository; + +@Repository +@RequiredArgsConstructor +public class BlackAuthenticationRepository { + + private final RedisTemplate redisTemplate; + + public void save(String token, Long memberId, Long expiration) { + redisTemplate.opsForValue().set(token, memberId, expiration, TimeUnit.MILLISECONDS); + } + + public boolean isExistToken(String key) { + return redisTemplate.hasKey(key); + } +} diff --git a/BE/eeos/src/main/java/com/blackcompany/eeos/auth/persistence/OauthInfoEntity.java b/BE/eeos/src/main/java/com/blackcompany/eeos/auth/persistence/OAuthMemberEntity.java similarity index 85% rename from BE/eeos/src/main/java/com/blackcompany/eeos/auth/persistence/OauthInfoEntity.java rename to BE/eeos/src/main/java/com/blackcompany/eeos/auth/persistence/OAuthMemberEntity.java index 0cc4ea38..ce188b9e 100644 --- a/BE/eeos/src/main/java/com/blackcompany/eeos/auth/persistence/OauthInfoEntity.java +++ b/BE/eeos/src/main/java/com/blackcompany/eeos/auth/persistence/OAuthMemberEntity.java @@ -20,9 +20,9 @@ @ToString @SuperBuilder(toBuilder = true) @Entity -@Table(name = OauthInfoEntity.ENTITY_PREFIX) -public class OauthInfoEntity extends BaseEntity { - public static final String ENTITY_PREFIX = "oauth_info"; +@Table(name = OAuthMemberEntity.ENTITY_PREFIX) +public class OAuthMemberEntity extends BaseEntity { + public static final String ENTITY_PREFIX = "oauth_member"; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) diff --git a/BE/eeos/src/main/java/com/blackcompany/eeos/auth/persistence/OAuthMemberRepository.java b/BE/eeos/src/main/java/com/blackcompany/eeos/auth/persistence/OAuthMemberRepository.java new file mode 100644 index 00000000..47f8b5b0 --- /dev/null +++ b/BE/eeos/src/main/java/com/blackcompany/eeos/auth/persistence/OAuthMemberRepository.java @@ -0,0 +1,14 @@ +package com.blackcompany.eeos.auth.persistence; + +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +public interface OAuthMemberRepository extends JpaRepository { + @Query("SELECT o FROM OAuthMemberEntity o WHERE o.oauthId=:oauthId") + Optional findByOauthId(@Param("oauthId") String oauthId); + + @Query("SELECT o FROM OAuthMemberEntity o WHERE o.memberId=:memberId") + Optional findByMemberId(@Param("memberId") Long memberId); +} diff --git a/BE/eeos/src/main/java/com/blackcompany/eeos/auth/persistence/OauthInfoRepository.java b/BE/eeos/src/main/java/com/blackcompany/eeos/auth/persistence/OauthInfoRepository.java deleted file mode 100644 index 33b8d0f6..00000000 --- a/BE/eeos/src/main/java/com/blackcompany/eeos/auth/persistence/OauthInfoRepository.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.blackcompany.eeos.auth.persistence; - -import java.util.Optional; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; - -public interface OauthInfoRepository extends JpaRepository { - @Query("SELECT o FROM OauthInfoEntity o WHERE o.oauthId=:oauthId") - Optional findByOauthId(@Param("oauthId") String oauthId); -} diff --git a/BE/eeos/src/main/java/com/blackcompany/eeos/auth/presentation/controller/AuthController.java b/BE/eeos/src/main/java/com/blackcompany/eeos/auth/presentation/controller/AuthController.java index f49b8e5f..bf2f8910 100644 --- a/BE/eeos/src/main/java/com/blackcompany/eeos/auth/presentation/controller/AuthController.java +++ b/BE/eeos/src/main/java/com/blackcompany/eeos/auth/presentation/controller/AuthController.java @@ -3,18 +3,22 @@ import com.blackcompany.eeos.auth.application.domain.TokenModel; import com.blackcompany.eeos.auth.application.dto.converter.TokenResponseConverter; import com.blackcompany.eeos.auth.application.dto.response.TokenResponse; +import com.blackcompany.eeos.auth.application.usecase.LogOutUsecase; import com.blackcompany.eeos.auth.application.usecase.LoginUsecase; import com.blackcompany.eeos.auth.application.usecase.ReissueUsecase; +import com.blackcompany.eeos.auth.application.usecase.WithDrawUsecase; +import com.blackcompany.eeos.auth.presentation.support.AuthConstants; +import com.blackcompany.eeos.auth.presentation.support.Member; import com.blackcompany.eeos.auth.presentation.support.TokenExtractor; import com.blackcompany.eeos.common.presentation.respnose.ApiResponse; import com.blackcompany.eeos.common.presentation.respnose.ApiResponseBody.SuccessBody; import com.blackcompany.eeos.common.presentation.respnose.ApiResponseGenerator; import com.blackcompany.eeos.common.presentation.respnose.MessageCode; -import com.blackcompany.eeos.common.utils.TimeUtil; +import com.blackcompany.eeos.common.presentation.support.CookieManager; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseCookie; import org.springframework.web.bind.annotation.PathVariable; @@ -29,26 +33,26 @@ public class AuthController { private final LoginUsecase loginUsecase; private final ReissueUsecase reissueUsecase; private final TokenExtractor tokenExtractor; + private final CookieManager cookieManager; private final TokenResponseConverter tokenResponseConverter; - private final String cookieKey; - private final String domain; - private final long validTime; + private final LogOutUsecase logOutUsecase; + private final WithDrawUsecase withDrawUsecase; public AuthController( LoginUsecase loginUsecase, ReissueUsecase reissueUsecase, @Qualifier("cookie") TokenExtractor tokenExtractor, TokenResponseConverter tokenResponseConverter, - @Value("${api.cookie-key}") String cookieKey, - @Value("${api.domain}") String domain, - @Value("${security.jwt.refresh.validTime}") long validTime) { + CookieManager cookieManager, + LogOutUsecase logOutUsecase, + WithDrawUsecase withDrawUsecase) { this.loginUsecase = loginUsecase; this.reissueUsecase = reissueUsecase; this.tokenExtractor = tokenExtractor; this.tokenResponseConverter = tokenResponseConverter; - this.cookieKey = cookieKey; - this.domain = domain; - this.validTime = validTime; + this.cookieManager = cookieManager; + this.logOutUsecase = logOutUsecase; + this.withDrawUsecase = withDrawUsecase; } @PostMapping("/login/{oauthServerType}") @@ -58,7 +62,7 @@ ApiResponse> login( @RequestParam("redirect_uri") String uri, HttpServletResponse httpResponse) { TokenModel tokenModel = loginUsecase.login(oauthServerType, code, uri); - TokenResponse response = toResponse(tokenModel, httpResponse); + TokenResponse response = generateTokenResponse(tokenModel, httpResponse); return ApiResponseGenerator.success(response, HttpStatus.CREATED, MessageCode.CREATE); } @@ -68,30 +72,45 @@ ApiResponse> reissue( HttpServletRequest request, HttpServletResponse httpResponse) { String token = tokenExtractor.extract(request); TokenModel tokenModel = reissueUsecase.execute(token); - TokenResponse response = toResponse(tokenModel, httpResponse); + TokenResponse response = generateTokenResponse(tokenModel, httpResponse); return ApiResponseGenerator.success(response, HttpStatus.CREATED, MessageCode.CREATE); } - private TokenResponse toResponse(TokenModel tokenModel, HttpServletResponse httpResponse) { + @PostMapping("/logout") + ApiResponse> logout( + HttpServletRequest request, HttpServletResponse httpResponse, @Member Long memberId) { + String token = tokenExtractor.extract(request); + logOutUsecase.logOut(token, memberId); + deleteTokenResponse(httpResponse); + + return ApiResponseGenerator.success(HttpStatus.OK, MessageCode.DELETE); + } + + @PostMapping("/withdraw") + ApiResponse> withDraw( + HttpServletRequest request, HttpServletResponse httpResponse, @Member Long memberId) { + String token = tokenExtractor.extract(request); + withDrawUsecase.withDraw(token, memberId); + deleteTokenResponse(httpResponse); + + return ApiResponseGenerator.success(HttpStatus.OK, MessageCode.DELETE); + } + + private TokenResponse generateTokenResponse( + TokenModel tokenModel, HttpServletResponse httpServletResponse) { TokenResponse response = tokenResponseConverter.from(tokenModel.getAccessToken(), tokenModel.getAccessExpiredTime()); - setCookie(httpResponse, tokenModel); + + ResponseCookie cookie = + cookieManager.setCookie(AuthConstants.TOKEN_KEY, tokenModel.getRefreshToken()); + httpServletResponse.addHeader(HttpHeaders.SET_COOKIE, cookie.toString()); return response; } - private void setCookie(HttpServletResponse response, TokenModel tokenModel) { - ResponseCookie cookie = - ResponseCookie.from(cookieKey, tokenModel.getRefreshToken()) - .path("/") - .domain(domain) - .httpOnly(true) - .secure(true) - .sameSite("None") - .maxAge(TimeUtil.convertSecondsFromMillis(validTime)) - .build(); - - response.addHeader("Set-Cookie", cookie.toString()); + private void deleteTokenResponse(HttpServletResponse httpServletResponse) { + ResponseCookie cookie = cookieManager.deleteCookie(AuthConstants.TOKEN_KEY); + httpServletResponse.addHeader(HttpHeaders.SET_COOKIE, cookie.toString()); } } diff --git a/BE/eeos/src/main/java/com/blackcompany/eeos/auth/presentation/interceptor/AuthInterceptor.java b/BE/eeos/src/main/java/com/blackcompany/eeos/auth/presentation/interceptor/AuthInterceptor.java index ff597823..bc923756 100644 --- a/BE/eeos/src/main/java/com/blackcompany/eeos/auth/presentation/interceptor/AuthInterceptor.java +++ b/BE/eeos/src/main/java/com/blackcompany/eeos/auth/presentation/interceptor/AuthInterceptor.java @@ -30,7 +30,7 @@ public boolean preHandle( } String token = tokenExtractor.extract(request); - tokenResolver.getUserInfoByHeader(token); + tokenResolver.getUserDataByAccessToken(token); return true; } diff --git a/BE/eeos/src/main/java/com/blackcompany/eeos/auth/presentation/support/AuthConstants.java b/BE/eeos/src/main/java/com/blackcompany/eeos/auth/presentation/support/AuthConstants.java new file mode 100644 index 00000000..977f954c --- /dev/null +++ b/BE/eeos/src/main/java/com/blackcompany/eeos/auth/presentation/support/AuthConstants.java @@ -0,0 +1,5 @@ +package com.blackcompany.eeos.auth.presentation.support; + +public class AuthConstants { + public static final String TOKEN_KEY = "eeos_token"; +} diff --git a/BE/eeos/src/main/java/com/blackcompany/eeos/auth/presentation/support/AuthCookieManager.java b/BE/eeos/src/main/java/com/blackcompany/eeos/auth/presentation/support/AuthCookieManager.java new file mode 100644 index 00000000..b3024e89 --- /dev/null +++ b/BE/eeos/src/main/java/com/blackcompany/eeos/auth/presentation/support/AuthCookieManager.java @@ -0,0 +1,49 @@ +package com.blackcompany.eeos.auth.presentation.support; + +import com.blackcompany.eeos.common.presentation.support.CookieManager; +import com.blackcompany.eeos.common.utils.TimeUtil; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.ResponseCookie; +import org.springframework.stereotype.Component; + +@Component +public class AuthCookieManager implements CookieManager { + + private static final Boolean HTTP_ONLY = true; + private static final Boolean SECURE = true; + private static final String SAMESITE = "None"; + private static final Long EXPIRATION = 0L; + + @Value("${token.cookie.domain}") + private String domain; + + @Value("${token.cookie.path}") + private String path; + + @Value("${security.jwt.refresh.validTime}") + private Long validTime; + + @Override + public ResponseCookie setCookie(String key, String value) { + return ResponseCookie.from(key, value) + .path(path) + .domain(domain) + .httpOnly(HTTP_ONLY) + .secure(SECURE) + .sameSite(SAMESITE) + .maxAge(TimeUtil.convertSecondsFromMillis(validTime)) + .build(); + } + + @Override + public ResponseCookie deleteCookie(String key) { + return ResponseCookie.from(key, "") + .path(path) + .domain(domain) + .httpOnly(HTTP_ONLY) + .secure(SECURE) + .sameSite(SAMESITE) + .maxAge(TimeUtil.convertSecondsFromMillis(EXPIRATION)) + .build(); + } +} diff --git a/BE/eeos/src/main/java/com/blackcompany/eeos/auth/presentation/support/CookieTokenExtractor.java b/BE/eeos/src/main/java/com/blackcompany/eeos/auth/presentation/support/CookieTokenExtractor.java index cd7baabd..25d86d50 100644 --- a/BE/eeos/src/main/java/com/blackcompany/eeos/auth/presentation/support/CookieTokenExtractor.java +++ b/BE/eeos/src/main/java/com/blackcompany/eeos/auth/presentation/support/CookieTokenExtractor.java @@ -8,14 +8,13 @@ @Component("cookie") public class CookieTokenExtractor implements TokenExtractor { - private static final String cookieKey = "eeos_token"; @Override public String extract(HttpServletRequest request) { Cookie[] cookies = getCookies(request); for (Cookie cookie : cookies) { - if (Objects.equals(cookieKey, cookie.getName())) { + if (Objects.equals(AuthConstants.TOKEN_KEY, cookie.getName())) { return getValue(cookie.getValue()); } } diff --git a/BE/eeos/src/main/java/com/blackcompany/eeos/auth/presentation/support/MemberArgumentResolver.java b/BE/eeos/src/main/java/com/blackcompany/eeos/auth/presentation/support/MemberArgumentResolver.java index bf415322..4509581a 100644 --- a/BE/eeos/src/main/java/com/blackcompany/eeos/auth/presentation/support/MemberArgumentResolver.java +++ b/BE/eeos/src/main/java/com/blackcompany/eeos/auth/presentation/support/MemberArgumentResolver.java @@ -36,6 +36,6 @@ public Object resolveArgument( WebDataBinderFactory binderFactory) { HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class); String token = tokenExtractor.extract(request); - return tokenResolver.getUserInfoByHeader(token); + return tokenResolver.getUserDataByAccessToken(token); } } diff --git a/BE/eeos/src/main/java/com/blackcompany/eeos/common/presentation/support/CookieManager.java b/BE/eeos/src/main/java/com/blackcompany/eeos/common/presentation/support/CookieManager.java new file mode 100644 index 00000000..7f6978fd --- /dev/null +++ b/BE/eeos/src/main/java/com/blackcompany/eeos/common/presentation/support/CookieManager.java @@ -0,0 +1,9 @@ +package com.blackcompany.eeos.common.presentation.support; + +import org.springframework.http.ResponseCookie; + +public interface CookieManager { + ResponseCookie setCookie(String key, String value); + + ResponseCookie deleteCookie(String key); +} diff --git a/BE/eeos/src/main/java/com/blackcompany/eeos/config/RedisConfig.java b/BE/eeos/src/main/java/com/blackcompany/eeos/config/RedisConfig.java new file mode 100644 index 00000000..66c4c658 --- /dev/null +++ b/BE/eeos/src/main/java/com/blackcompany/eeos/config/RedisConfig.java @@ -0,0 +1,31 @@ +package com.blackcompany.eeos.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; + +@Configuration +public class RedisConfig { + @Value("${spring.redis.host}") + private String host; + + @Value("${spring.redis.port}") + private int port; + + @Bean + public RedisConnectionFactory redisConnectionFactory() { + return new LettuceConnectionFactory(host, port); + } + + @Bean + public RedisTemplate redisTemplate() { + RedisTemplate redisTemplate = new RedisTemplate<>(); + redisTemplate.setConnectionFactory(redisConnectionFactory()); + redisTemplate.setEnableTransactionSupport(true); + + return redisTemplate; + } +} diff --git a/BE/eeos/src/main/java/com/blackcompany/eeos/member/persistence/MemberEntity.java b/BE/eeos/src/main/java/com/blackcompany/eeos/member/persistence/MemberEntity.java index 64c86e2d..4582bc2f 100644 --- a/BE/eeos/src/main/java/com/blackcompany/eeos/member/persistence/MemberEntity.java +++ b/BE/eeos/src/main/java/com/blackcompany/eeos/member/persistence/MemberEntity.java @@ -19,8 +19,6 @@ import lombok.NoArgsConstructor; import lombok.ToString; import lombok.experimental.SuperBuilder; -import org.hibernate.annotations.SQLDelete; -import org.hibernate.annotations.Where; @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) @@ -34,8 +32,6 @@ @Index(name = "idx_member_name", columnList = "member_name"), @Index(name = "idx_member_active_status", columnList = "member_active_status") }) -@SQLDelete(sql = "UPDATE member SET is_deleted=true where member_id=?") -@Where(clause = "is_deleted=false") public class MemberEntity extends BaseEntity { public static final String ENTITY_PREFIX = "member"; diff --git a/BE/eeos/src/main/java/com/blackcompany/eeos/program/application/service/ProgramService.java b/BE/eeos/src/main/java/com/blackcompany/eeos/program/application/service/ProgramService.java index 1a4b06bc..108cfc08 100644 --- a/BE/eeos/src/main/java/com/blackcompany/eeos/program/application/service/ProgramService.java +++ b/BE/eeos/src/main/java/com/blackcompany/eeos/program/application/service/ProgramService.java @@ -82,9 +82,9 @@ public QueryProgramResponse getProgram(final Long memberId, final Long programId @Override @Transactional public CommandProgramResponse update( - final Long writerId, final Long programId, final UpdateProgramRequest request) { + final Long memberId, final Long programId, final UpdateProgramRequest request) { ProgramModel model = findProgram(programId); - ProgramModel requestModel = requestConverter.from(writerId, request, programId); + ProgramModel requestModel = requestConverter.from(memberId, request, programId); updateProgram(model, requestModel); updateAttend(request.getMembers(), model); diff --git a/BE/eeos/src/main/java/com/blackcompany/eeos/program/application/service/ProgramValidService.java b/BE/eeos/src/main/java/com/blackcompany/eeos/program/application/service/ProgramValidService.java deleted file mode 100644 index e03398e9..00000000 --- a/BE/eeos/src/main/java/com/blackcompany/eeos/program/application/service/ProgramValidService.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.blackcompany.eeos.program.application.service; - -import com.blackcompany.eeos.program.application.exception.NotFoundProgramException; -import com.blackcompany.eeos.program.persistence.ProgramRepository; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; - -@Service -@RequiredArgsConstructor -public class ProgramValidService { - - private final ProgramRepository programRepository; - - public void validate(Long programId) { - if (!programRepository.existsById(programId)) { - throw new NotFoundProgramException(programId); - } - } -} diff --git a/BE/eeos/src/main/java/com/blackcompany/eeos/target/application/service/AttendService.java b/BE/eeos/src/main/java/com/blackcompany/eeos/target/application/service/AttendService.java index a47545f0..a7758bad 100644 --- a/BE/eeos/src/main/java/com/blackcompany/eeos/target/application/service/AttendService.java +++ b/BE/eeos/src/main/java/com/blackcompany/eeos/target/application/service/AttendService.java @@ -5,7 +5,8 @@ import com.blackcompany.eeos.member.application.model.converter.MemberEntityConverter; import com.blackcompany.eeos.member.application.service.QueryMemberService; import com.blackcompany.eeos.member.persistence.MemberRepository; -import com.blackcompany.eeos.program.application.service.ProgramValidService; +import com.blackcompany.eeos.program.application.exception.NotFoundProgramException; +import com.blackcompany.eeos.program.persistence.ProgramRepository; import com.blackcompany.eeos.target.application.dto.AttendInfoActiveStatusResponse; import com.blackcompany.eeos.target.application.dto.AttendInfoResponse; import com.blackcompany.eeos.target.application.dto.ChangeAttendStatusRequest; @@ -44,7 +45,6 @@ public class AttendService private final AttendRepository attendRepository; private final MemberRepository memberRepository; - private final ProgramValidService programValidService; private final AttendInfoConverter infoConverter; private final AttendEntityConverter attendEntityConverter; private final QueryMemberService queryMemberService; @@ -54,10 +54,11 @@ public class AttendService private final QueryAttendStatusResponseConverter attendStatusResponseConverter; private final AttendInfoActiveStatusConverter attendInfoActiveStatusConverter; private final QueryAttendActiveStatusConverter queryAttendActiveStatusConverter; + private final ProgramRepository programRepository; @Override public List findAttendInfo(final Long programId) { - programValidService.validate(programId); + validateExistsProgram(programId); return memberRepository.findMembersByProgramId(programId).stream() .map(member -> infoConverter.from(member, getAttendStatus(member.getId(), programId))) @@ -66,7 +67,7 @@ public List findAttendInfo(final Long programId) { @Override public QueryAttendStatusResponse findAttendInfo(final Long programId, final String attendStatus) { - programValidService.validate(programId); + validateExistsProgram(programId); List attends = findAttendByAttendStatus(programId, attendStatus); List members = findMembers(attends); @@ -190,4 +191,10 @@ private List findMembersByActiveStatus(final String activeStatus) { .map(memberEntityConverter::from) .collect(Collectors.toList()); } + + private void validateExistsProgram(Long programId) { + if (!programRepository.existsById(programId)) { + throw new NotFoundProgramException(programId); + } + } } diff --git a/BE/eeos/src/main/java/com/blackcompany/eeos/target/persistence/AttendRepository.java b/BE/eeos/src/main/java/com/blackcompany/eeos/target/persistence/AttendRepository.java index b3a83f53..d22f000c 100644 --- a/BE/eeos/src/main/java/com/blackcompany/eeos/target/persistence/AttendRepository.java +++ b/BE/eeos/src/main/java/com/blackcompany/eeos/target/persistence/AttendRepository.java @@ -27,4 +27,6 @@ List findAllByProgramMember( @Query("SELECT a FROM AttendEntity a WHERE a.programId =:programId AND a.isDeleted=false") List findAllByProgramId(@Param("programId") Long programId); + + void deleteAllByMemberId(Long memberId); } diff --git a/BE/eeos/src/main/resources/application-api.yml b/BE/eeos/src/main/resources/application-api.yml index a2c2dbbf..c9498757 100644 --- a/BE/eeos/src/main/resources/application-api.yml +++ b/BE/eeos/src/main/resources/application-api.yml @@ -7,6 +7,7 @@ cors: allow-origin: urls: ${CORS_URL} -api: - domain: ${API_DOMAIN} - cookie-key: ${COOKIE_KEY} \ No newline at end of file +token: + cookie: + domain: ${COOKIE_TOKEN_DOMAIN} + path: ${COOKIE_TOKEN_PATH} \ No newline at end of file diff --git a/BE/eeos/src/main/resources/application-local-redis.yml b/BE/eeos/src/main/resources/application-local-redis.yml new file mode 100644 index 00000000..b8822583 --- /dev/null +++ b/BE/eeos/src/main/resources/application-local-redis.yml @@ -0,0 +1,8 @@ +spring: + config: + activate: + on-profile: local-redis + redis: + host: localhost + port : 16379 + password: root \ No newline at end of file diff --git a/BE/eeos/src/main/resources/application-redis.yml b/BE/eeos/src/main/resources/application-redis.yml new file mode 100644 index 00000000..1f4aa2d1 --- /dev/null +++ b/BE/eeos/src/main/resources/application-redis.yml @@ -0,0 +1,8 @@ +spring: + config: + activate: + on-profile: redis + redis: + host: ${REDIS_HOST} + port : ${REDIS_PORT} + password: ${REDIS_PASSWORD} \ No newline at end of file diff --git a/BE/eeos/src/main/resources/application.yml b/BE/eeos/src/main/resources/application.yml index 51eb056b..7944468c 100644 --- a/BE/eeos/src/main/resources/application.yml +++ b/BE/eeos/src/main/resources/application.yml @@ -7,6 +7,7 @@ spring: - oauth - token - actuator + - local-redis dev: - mysql - api @@ -14,6 +15,7 @@ spring: - token - log - actuator + - redis prod: - mysql - api @@ -21,4 +23,5 @@ spring: - token - log - actuator + - redis active: local \ No newline at end of file diff --git a/BE/eeos/src/test/java/com/blackcompany/eeos/auth/application/service/AuthFacadeServiceTest.java b/BE/eeos/src/test/java/com/blackcompany/eeos/auth/application/service/AuthFacadeServiceTest.java index bcd61d0e..bd66449b 100644 --- a/BE/eeos/src/test/java/com/blackcompany/eeos/auth/application/service/AuthFacadeServiceTest.java +++ b/BE/eeos/src/test/java/com/blackcompany/eeos/auth/application/service/AuthFacadeServiceTest.java @@ -4,8 +4,9 @@ import static org.mockito.Mockito.when; import com.blackcompany.eeos.auth.application.domain.OauthMemberModel; +import com.blackcompany.eeos.auth.application.support.AuthenticationTokenGenerator; import com.blackcompany.eeos.auth.fixture.FakeOauthMember; -import com.blackcompany.eeos.auth.persistence.OauthInfoEntity; +import com.blackcompany.eeos.auth.persistence.OAuthMemberEntity; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -16,10 +17,9 @@ @ExtendWith(MockitoExtension.class) class AuthFacadeServiceTest { - @Mock CreateTokenService createTokenService; - - @Mock AuthService authService; @Mock OauthClientService oauthClientService; + @Mock AuthenticationTokenGenerator authenticationTokenGenerator; + @Mock AuthService authService; @InjectMocks AuthFacadeService authFacadeService; @@ -33,15 +33,15 @@ void response_token() { String uri = "uri"; OauthMemberModel oauthMemberModel = FakeOauthMember.oauthMemberModel(); - OauthInfoEntity oauthInfoEntity = FakeOauthMember.oauthInfoEntity(); + OAuthMemberEntity oAuthMemberEntity = FakeOauthMember.oauthInfoEntity(); when(oauthClientService.getOauthMember(type, authCode, uri)).thenReturn(oauthMemberModel); - when(authService.login(oauthMemberModel)).thenReturn(oauthInfoEntity); + when(authService.authenticate(oauthMemberModel)).thenReturn(oAuthMemberEntity); // when authFacadeService.login(type, authCode, uri); // then - Mockito.verify(createTokenService).execute(memberId); + Mockito.verify(authenticationTokenGenerator).execute(memberId); } } diff --git a/BE/eeos/src/test/java/com/blackcompany/eeos/auth/application/service/AuthServiceTest.java b/BE/eeos/src/test/java/com/blackcompany/eeos/auth/application/service/AuthServiceTest.java index 5d2cc322..0c759869 100644 --- a/BE/eeos/src/test/java/com/blackcompany/eeos/auth/application/service/AuthServiceTest.java +++ b/BE/eeos/src/test/java/com/blackcompany/eeos/auth/application/service/AuthServiceTest.java @@ -5,10 +5,10 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import com.blackcompany.eeos.auth.application.domain.converter.OauthInfoEntityConverter; +import com.blackcompany.eeos.auth.application.domain.converter.OauthMemberEntityConverter; import com.blackcompany.eeos.auth.fixture.FakeOauthMember; -import com.blackcompany.eeos.auth.persistence.OauthInfoEntity; -import com.blackcompany.eeos.auth.persistence.OauthInfoRepository; +import com.blackcompany.eeos.auth.persistence.OAuthMemberEntity; +import com.blackcompany.eeos.auth.persistence.OAuthMemberRepository; import com.blackcompany.eeos.member.application.model.ActiveStatus; import com.blackcompany.eeos.member.application.model.converter.MemberEntityConverter; import com.blackcompany.eeos.member.fixture.MemberFixture; @@ -25,40 +25,40 @@ @ExtendWith(MockitoExtension.class) class AuthServiceTest { - @Mock OauthInfoRepository oauthInfoRepository; - @InjectMocks AuthService authService; - @Mock MemberRepository memberRepository; - @Spy OauthInfoEntityConverter oauthInfoEntityConverter; + @Mock MemberRepository memberRepository; + @Mock OAuthMemberRepository oAuthMemberRepository; @Spy MemberEntityConverter memberEntityConverter; + @Spy OauthMemberEntityConverter oauthMemberEntityConverter; + @InjectMocks AuthService authService; @Test - @DisplayName("새로운 회원인 경우 oauth에서 가져온 회원 정보를 저장한다.") + @DisplayName("신규 회원인 경우 oauth에서 가져온 회원 정보를 저장한다.") void login_existing_user() { // given - when(oauthInfoRepository.findByOauthId(FakeOauthMember.oauthMemberModel().getOauthId())) + when(oAuthMemberRepository.findByOauthId(FakeOauthMember.oauthMemberModel().getOauthId())) .thenReturn(Optional.ofNullable(null)); when(memberRepository.save(Mockito.any())) .thenReturn(MemberFixture.멤버_엔티티(1L, ActiveStatus.AM)); // when - authService.login(FakeOauthMember.oauthMemberModel()); + authService.authenticate(FakeOauthMember.oauthMemberModel()); // then assertAll( () -> verify(memberRepository).save(Mockito.any()), - () -> verify(oauthInfoRepository).save(Mockito.any())); + () -> verify(oAuthMemberRepository).save(Mockito.any())); } @Test @DisplayName("기존 회원인 경우 존재하던 oauth 정보를 가져온다.") void login_new_user() { // given - when(oauthInfoRepository.findByOauthId(FakeOauthMember.oauthMemberModel().getOauthId())) + when(oAuthMemberRepository.findByOauthId(FakeOauthMember.oauthMemberModel().getOauthId())) .thenReturn(Optional.of(FakeOauthMember.oauthInfoEntity())); // when - OauthInfoEntity entity = authService.login(FakeOauthMember.oauthMemberModel()); + OAuthMemberEntity entity = authService.authenticate(FakeOauthMember.oauthMemberModel()); // then assertEquals(entity.getOauthId(), FakeOauthMember.oauthMemberModel().getOauthId()); diff --git a/BE/eeos/src/test/java/com/blackcompany/eeos/auth/application/service/CreateTokenServiceTest.java b/BE/eeos/src/test/java/com/blackcompany/eeos/auth/application/service/CreateTokenServiceTest.java deleted file mode 100644 index 880deb38..00000000 --- a/BE/eeos/src/test/java/com/blackcompany/eeos/auth/application/service/CreateTokenServiceTest.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.blackcompany.eeos.auth.application.service; - -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import com.blackcompany.eeos.auth.application.domain.converter.TokenModelConverter; -import com.blackcompany.eeos.auth.application.domain.token.TokenProvider; -import com.blackcompany.eeos.auth.application.domain.token.TokenResolver; -import com.blackcompany.eeos.auth.persistence.AuthInfoEntity; -import com.blackcompany.eeos.auth.persistence.AuthInfoEntityConverter; -import com.blackcompany.eeos.auth.persistence.AuthInfoRepository; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.Spy; -import org.mockito.junit.jupiter.MockitoExtension; - -@ExtendWith(MockitoExtension.class) -class CreateTokenServiceTest { - @InjectMocks CreateTokenService createTokenService; - @Mock TokenResolver tokenResolver; - @Spy TokenModelConverter tokeModelConverter; - - @Mock TokenProvider tokenProvider; - @Mock AuthInfoRepository authInfoRepository; - @Mock AuthInfoEntityConverter authInfoEntityConverter; - - @Test - @DisplayName("토큰 생성 요청이 들어오면 토큰을 생성한 후 토큰을 저장한다.") - void save_token_when_request_create_token() { - // given - Long userId = 1L; - String accessToken = "mocked_access_token"; - String refreshToken = "mocked_refresh_token"; - - AuthInfoEntity entity = - AuthInfoEntity.builder().id(1L).memberId(userId).token(refreshToken).build(); - - when(tokenProvider.createAccessToken(userId)).thenReturn(accessToken); - when(tokenProvider.createRefreshToken(userId)).thenReturn(refreshToken); - when(authInfoEntityConverter.from(userId, refreshToken)).thenReturn(entity); - - // when - createTokenService.execute(userId); - - // then - verify(authInfoRepository).save(entity); - } -} diff --git a/BE/eeos/src/test/java/com/blackcompany/eeos/auth/application/service/ReissueServiceTest.java b/BE/eeos/src/test/java/com/blackcompany/eeos/auth/application/service/ReissueServiceTest.java index 9e674152..d47bc445 100644 --- a/BE/eeos/src/test/java/com/blackcompany/eeos/auth/application/service/ReissueServiceTest.java +++ b/BE/eeos/src/test/java/com/blackcompany/eeos/auth/application/service/ReissueServiceTest.java @@ -6,10 +6,8 @@ import com.blackcompany.eeos.auth.application.domain.token.TokenResolver; import com.blackcompany.eeos.auth.application.exception.InvalidTokenException; -import com.blackcompany.eeos.auth.fixture.FakeAuthInfo; -import com.blackcompany.eeos.auth.persistence.AuthInfoEntity; -import com.blackcompany.eeos.auth.persistence.AuthInfoRepository; -import java.util.Optional; +import com.blackcompany.eeos.auth.application.support.AuthenticationTokenGenerator; +import com.blackcompany.eeos.auth.persistence.BlackAuthenticationRepository; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -20,51 +18,44 @@ @ExtendWith(MockitoExtension.class) class ReissueServiceTest { - @Mock CreateTokenService createTokenService; - @Mock AuthInfoRepository authInfoRepository; + @Mock AuthenticationTokenGenerator authenticationTokenGenerator; + @Mock BlackAuthenticationRepository blackAuthenticationRepository; @Mock TokenResolver tokenResolver; @InjectMocks ReissueService reissueService; @Test - @DisplayName("전달받은 토큰이 서버가 가지고 있는 유저의 토큰이 아닐 때 유효하지 않은 토큰을 제거하고 예외를 발생시킨다.") + @DisplayName("블랙리스트에 등록된 토큰이라면 예외가 발생한다.") void exception_when_token_invalid() { // given String token = "token"; Long memberId = 1L; - AuthInfoEntity authInfoEntity = FakeAuthInfo.authInfoEntity(); - - when(tokenResolver.getUserInfoByCookie(token)).thenReturn(memberId); - when(authInfoRepository.findByMemberIdAndToken(memberId, token)) - .thenReturn(Optional.ofNullable(null)); - when(authInfoRepository.findByToken(token)).thenReturn(Optional.ofNullable(authInfoEntity)); + when(tokenResolver.getUserDataByRefreshToken(token)).thenReturn(memberId); + when(blackAuthenticationRepository.isExistToken(token)).thenReturn(Boolean.TRUE); // when & then - assertAll( - () -> assertThrows(InvalidTokenException.class, () -> reissueService.execute(token)), - () -> verify(authInfoRepository).delete(authInfoEntity)); + assertThrows(InvalidTokenException.class, () -> reissueService.execute(token)); } @Test - @DisplayName("전달받은 토큰이 유효한 토큰이라면 해당 토큰 정보를 제거하고 새로운 토큰을 생성한다.") - void execute() { + @DisplayName("정상적인 토큰이라면 이전에 사용한 토큰은 블랙리스트로 등록하고 새로운 토큰을 생성한다.") + void token_valid() { // given String token = "token"; Long memberId = 1L; + Long validTime = 1L; - AuthInfoEntity authInfoEntity = FakeAuthInfo.authInfoEntity(); - - when(tokenResolver.getUserInfoByCookie(token)).thenReturn(memberId); - when(authInfoRepository.findByMemberIdAndToken(memberId, token)) - .thenReturn(Optional.ofNullable(authInfoEntity)); + when(tokenResolver.getUserDataByRefreshToken(token)).thenReturn(memberId); + when(blackAuthenticationRepository.isExistToken(token)).thenReturn(Boolean.FALSE); + when(tokenResolver.getExpiredDateByRefreshToken(token)).thenReturn(validTime); // when reissueService.execute(token); // then assertAll( - () -> verify(authInfoRepository).delete(authInfoEntity), - () -> verify(createTokenService).execute(memberId)); + () -> verify(blackAuthenticationRepository).save(token, memberId, validTime), + () -> verify(authenticationTokenGenerator).execute(memberId)); } } diff --git a/BE/eeos/src/test/java/com/blackcompany/eeos/auth/fixture/FakeAuthInfo.java b/BE/eeos/src/test/java/com/blackcompany/eeos/auth/fixture/FakeAuthInfo.java deleted file mode 100644 index b0946001..00000000 --- a/BE/eeos/src/test/java/com/blackcompany/eeos/auth/fixture/FakeAuthInfo.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.blackcompany.eeos.auth.fixture; - -import com.blackcompany.eeos.auth.persistence.AuthInfoEntity; - -public class FakeAuthInfo { - public static AuthInfoEntity authInfoEntity() { - return AuthInfoEntity.builder().memberId(1L).token("token").build(); - } -} diff --git a/BE/eeos/src/test/java/com/blackcompany/eeos/auth/fixture/FakeOauthMember.java b/BE/eeos/src/test/java/com/blackcompany/eeos/auth/fixture/FakeOauthMember.java index c2774137..d889ba05 100644 --- a/BE/eeos/src/test/java/com/blackcompany/eeos/auth/fixture/FakeOauthMember.java +++ b/BE/eeos/src/test/java/com/blackcompany/eeos/auth/fixture/FakeOauthMember.java @@ -2,7 +2,7 @@ import com.blackcompany.eeos.auth.application.domain.OauthMemberModel; import com.blackcompany.eeos.auth.application.domain.OauthServerType; -import com.blackcompany.eeos.auth.persistence.OauthInfoEntity; +import com.blackcompany.eeos.auth.persistence.OAuthMemberEntity; public class FakeOauthMember { public static OauthMemberModel oauthMemberModel() { @@ -13,7 +13,7 @@ public static OauthMemberModel oauthMemberModel() { .build(); } - public static OauthInfoEntity oauthInfoEntity() { - return OauthInfoEntity.builder().oauthId("oauthId").memberId(1L).build(); + public static OAuthMemberEntity oauthInfoEntity() { + return OAuthMemberEntity.builder().oauthId("oauthId").memberId(1L).build(); } } diff --git a/BE/eeos/src/test/java/com/blackcompany/eeos/program/application/service/ProgramValidServiceTest.java b/BE/eeos/src/test/java/com/blackcompany/eeos/program/application/service/ProgramValidServiceTest.java deleted file mode 100644 index f8ab22cf..00000000 --- a/BE/eeos/src/test/java/com/blackcompany/eeos/program/application/service/ProgramValidServiceTest.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.blackcompany.eeos.program.application.service; - -import static org.junit.jupiter.api.Assertions.*; - -import com.blackcompany.eeos.program.application.exception.NotFoundProgramException; -import com.blackcompany.eeos.program.persistence.ProgramRepository; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.mockito.junit.jupiter.MockitoExtension; - -@ExtendWith(MockitoExtension.class) -class ProgramValidServiceTest { - @InjectMocks ProgramValidService programValidService; - - @Mock ProgramRepository programRepository; - - @Test - @DisplayName("프로그램이 존재하지 않으면 예외가 발생한다.") - void validateException() { - // given - Mockito.when(programRepository.existsById(Mockito.anyLong())).thenReturn(Boolean.FALSE); - - // when & then - assertThrows(NotFoundProgramException.class, () -> programValidService.validate(1L)); - } - - @Test - @DisplayName("프로그램이 존재하면 예외가 발생하지 않는다.") - void validateSuccess() { - // given - Mockito.when(programRepository.existsById(Mockito.anyLong())).thenReturn(Boolean.TRUE); - - // when & then - assertDoesNotThrow(() -> programValidService.validate(1L)); - } -} diff --git a/BE/eeos/src/test/java/com/blackcompany/eeos/target/application/service/AttendServiceTest.java b/BE/eeos/src/test/java/com/blackcompany/eeos/target/application/service/AttendServiceTest.java index 809dad2e..97336477 100644 --- a/BE/eeos/src/test/java/com/blackcompany/eeos/target/application/service/AttendServiceTest.java +++ b/BE/eeos/src/test/java/com/blackcompany/eeos/target/application/service/AttendServiceTest.java @@ -1,7 +1,6 @@ package com.blackcompany.eeos.target.application.service; import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.when; import com.blackcompany.eeos.member.application.model.ActiveStatus; @@ -10,7 +9,7 @@ import com.blackcompany.eeos.member.fixture.MemberFixture; import com.blackcompany.eeos.member.persistence.MemberEntity; import com.blackcompany.eeos.member.persistence.MemberRepository; -import com.blackcompany.eeos.program.application.service.ProgramValidService; +import com.blackcompany.eeos.program.persistence.ProgramRepository; import com.blackcompany.eeos.target.application.dto.AttendInfoActiveStatusResponse; import com.blackcompany.eeos.target.application.dto.AttendInfoResponse; import com.blackcompany.eeos.target.application.dto.QueryAttendActiveStatusResponse; @@ -37,10 +36,10 @@ class AttendServiceTest { @Mock private AttendRepository attendRepository; @Mock private MemberRepository memberRepository; - @Mock private ProgramValidService programValidService; @Spy private AttendInfoConverter infoConverter; @Spy private AttendEntityConverter attendEntityConverter; @Mock private QueryMemberService queryMemberService; + @Mock private ProgramRepository programRepository; @Spy private ChangeAttendStatusConverter changeAttendStatusConverter; @Spy private MemberEntityConverter memberEntityConverter; @Spy private AttendInfoConverter attendInfoConverter; @@ -63,7 +62,7 @@ void find_attend_info_by_attend_status() { AttendEntity 바다_참석 = com.blackcompany.eeos.target.fixture.AttendFixture.참석대상자_엔티티(2L, AttendStatus.ATTEND); - doNothing().when(programValidService).validate(programId); + when(programRepository.existsById(programId)).thenReturn(Boolean.TRUE); when(attendRepository.findAllByProgramIdAndStatus(programId, AttendStatus.find(attendStatus))) .thenReturn(List.of(수민_참석, 바다_참석)); when(memberRepository.findMembersByIds(List.of(수민_참석.getMemberId(), 바다_참석.getMemberId()))) diff --git a/BE/eeos/src/test/resources/application-test-api.yml b/BE/eeos/src/test/resources/application-test-api.yml index 7f27f1ea..883b2cfe 100644 --- a/BE/eeos/src/test/resources/application-test-api.yml +++ b/BE/eeos/src/test/resources/application-test-api.yml @@ -2,10 +2,12 @@ spring: config: activate: on-profile: test-api + cors: allow-origin: urls: test -api: - domain: domain - cookie-key: key \ No newline at end of file +token: + cookie: + domain: domain + path: key \ No newline at end of file diff --git a/BE/eeos/src/test/resources/application-test-mysql.yml b/BE/eeos/src/test/resources/application-test-mysql.yml deleted file mode 100644 index 358d01a2..00000000 --- a/BE/eeos/src/test/resources/application-test-mysql.yml +++ /dev/null @@ -1,21 +0,0 @@ -spring: - config: - activate: - on-profile: test-mysql - datasource: - url: jdbc:mysql://localhost:13309/eeos?useSSL=false&serverTimezone=Asia/Seoul&useLegacyDatetimeCode=false&allowPublicKeyRetrieval=true - username: root - password: root - driver-class-name: com.mysql.cj.jdbc.Driver - jpa: - hibernate: - ddl-auto: create - properties: - hibernate: - format_sql: true - flyway: - enabled: false - -logging: - level: - sql: debug \ No newline at end of file diff --git a/BE/eeos/src/test/resources/application.yml b/BE/eeos/src/test/resources/application.yml index 21c8f965..3f1c4812 100644 --- a/BE/eeos/src/test/resources/application.yml +++ b/BE/eeos/src/test/resources/application.yml @@ -2,7 +2,8 @@ spring: profiles: group: test: - - test-mysql + - local-mysql + - local-redis - test-api - test-token - test-oauth