diff --git a/.github/workflows/develop_ci_docker.yml b/.github/workflows/develop_ci_docker.yml index ce88d0e..f1ed10d 100644 --- a/.github/workflows/develop_ci_docker.yml +++ b/.github/workflows/develop_ci_docker.yml @@ -9,6 +9,10 @@ jobs: docker-build-and-push: runs-on: ubuntu-latest + env: + GIT_USERNAME: ${{ secrets.GIT_USERNAME }} + GIT_TOKEN: ${{ secrets.GIT_TOKEN }} + steps: - name: Checkout source uses: actions/checkout@v3 diff --git a/.github/workflows/develop_ci_test.yml b/.github/workflows/develop_ci_test.yml index 776faf2..e9a7d7c 100644 --- a/.github/workflows/develop_ci_test.yml +++ b/.github/workflows/develop_ci_test.yml @@ -17,6 +17,10 @@ jobs: if: github.event_name == 'pull_request' && github.base_ref == 'develop' runs-on: ubuntu-latest + env: + GIT_USERNAME: ${{ secrets.GIT_USERNAME }} + GIT_TOKEN: ${{ secrets.GIT_TOKEN }} + steps: - name: Checkout source code uses: actions/checkout@v3 diff --git a/.gitignore b/.gitignore index 04babad..a64ca9d 100644 --- a/.gitignore +++ b/.gitignore @@ -40,4 +40,7 @@ out/ .DS_Store ### .env ### -.env \ No newline at end of file +.env + +### gradle.properties ### +gradle.properties \ No newline at end of file diff --git a/build.gradle b/build.gradle index bbc5b33..dd3d246 100644 --- a/build.gradle +++ b/build.gradle @@ -8,6 +8,17 @@ plugins { group = 'com.doubleo' version = '0.0.1-SNAPSHOT' +repositories { + mavenCentral() + maven { + name = 'GitHubPackages' + url = uri('https://maven.pkg.github.com/TeamDoubleO/tenant-context') + credentials { + username = System.getenv("GIT_USERNAME") ?: project.findProperty("gpr.user") + password = System.getenv("GIT_TOKEN") ?: project.findProperty("gpr.key") + } + } +} java { toolchain { languageVersion = JavaLanguageVersion.of(21) @@ -63,6 +74,9 @@ dependencies { // Redis implementation 'org.springframework.boot:spring-boot-starter-data-redis' + //Tenant-Context + implementation 'com.doubleo:tenant-context:0.0.2' + annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'com.h2database:h2' diff --git a/src/main/java/com/doubleo/adminservice/domain/admin/domain/Admin.java b/src/main/java/com/doubleo/adminservice/domain/admin/domain/Admin.java index 5c0f2be..6b2ced2 100644 --- a/src/main/java/com/doubleo/adminservice/domain/admin/domain/Admin.java +++ b/src/main/java/com/doubleo/adminservice/domain/admin/domain/Admin.java @@ -1,6 +1,6 @@ package com.doubleo.adminservice.domain.admin.domain; -import com.doubleo.adminservice.domain.common.model.BaseTimeEntity; +import com.doubleo.adminservice.domain.common.model.BaseEntity; import jakarta.persistence.*; import lombok.AccessLevel; import lombok.Builder; @@ -10,7 +10,7 @@ @Entity @Getter @NoArgsConstructor -public class Admin extends BaseTimeEntity { +public class Admin extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) diff --git a/src/main/java/com/doubleo/adminservice/domain/admin/service/AdminServiceImpl.java b/src/main/java/com/doubleo/adminservice/domain/admin/service/AdminServiceImpl.java index 45dd8dd..2531228 100644 --- a/src/main/java/com/doubleo/adminservice/domain/admin/service/AdminServiceImpl.java +++ b/src/main/java/com/doubleo/adminservice/domain/admin/service/AdminServiceImpl.java @@ -6,6 +6,7 @@ import com.doubleo.adminservice.domain.admin.repository.AdminRepository; import com.doubleo.adminservice.global.exception.CommonException; import com.doubleo.adminservice.global.exception.errorcode.AdminErrorCode; +import com.doubleo.adminservice.global.util.TenantValidator; import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; @@ -18,6 +19,7 @@ public class AdminServiceImpl implements AdminService { private final AdminRepository adminRepository; private final BCryptPasswordEncoder passwordEncoder; + private final TenantValidator tenantValidator; @Override public AdminInfoResponse getAdminInfo(Long adminId) { @@ -34,9 +36,10 @@ public void updateAdminPassword(Long adminId, AdminPwUpdateRequest request) { // util private Admin findAdmin(Long adminId) { - return adminRepository - .findById(adminId) - .orElseThrow(() -> new CommonException(AdminErrorCode.ADMIN_NOT_FOUND)); + return tenantValidator.validateTenant( + adminRepository + .findById(adminId) + .orElseThrow(() -> new CommonException(AdminErrorCode.ADMIN_NOT_FOUND))); } private void validateAdminPassword(String raw, String encoded) { diff --git a/src/main/java/com/doubleo/adminservice/domain/auth/dto/AccessTokenDto.java b/src/main/java/com/doubleo/adminservice/domain/auth/dto/AccessTokenDto.java index 2ab095e..8fe5174 100644 --- a/src/main/java/com/doubleo/adminservice/domain/auth/dto/AccessTokenDto.java +++ b/src/main/java/com/doubleo/adminservice/domain/auth/dto/AccessTokenDto.java @@ -1,7 +1,7 @@ package com.doubleo.adminservice.domain.auth.dto; -public record AccessTokenDto(Long adminId, String accessTokenValue) { - public static AccessTokenDto of(Long adminId, String accessTokenValue) { - return new AccessTokenDto(adminId, accessTokenValue); +public record AccessTokenDto(Long adminId, String tenantId, String accessTokenValue) { + public static AccessTokenDto of(Long adminId, String tenantId, String accessTokenValue) { + return new AccessTokenDto(adminId, tenantId, accessTokenValue); } } diff --git a/src/main/java/com/doubleo/adminservice/domain/auth/service/AuthServiceImpl.java b/src/main/java/com/doubleo/adminservice/domain/auth/service/AuthServiceImpl.java index bc20c2b..f68a754 100644 --- a/src/main/java/com/doubleo/adminservice/domain/auth/service/AuthServiceImpl.java +++ b/src/main/java/com/doubleo/adminservice/domain/auth/service/AuthServiceImpl.java @@ -7,6 +7,7 @@ import com.doubleo.adminservice.domain.auth.repository.RefreshTokenRepository; import com.doubleo.adminservice.global.exception.CommonException; import com.doubleo.adminservice.global.exception.errorcode.AdminErrorCode; +import com.doubleo.adminservice.global.util.TenantValidator; import lombok.RequiredArgsConstructor; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.stereotype.Service; @@ -19,6 +20,7 @@ public class AuthServiceImpl implements AuthService { private final RefreshTokenRepository refreshTokenRepository; private final JwtTokenService jwtTokenService; private final BCryptPasswordEncoder encoder; + private final TenantValidator tenantValidator; public LoginResponse loginAdmin(LoginRequest request) { Admin admin = validateAdminByEmail(request.username()); @@ -41,13 +43,14 @@ private Admin validateAdminByEmail(String email) { } private void validateAdminById(Long adminId) { - adminRepository - .findById(adminId) - .orElseThrow(() -> new CommonException(AdminErrorCode.ADMIN_NOT_FOUND)); + tenantValidator.validateTenant( + adminRepository + .findById(adminId) + .orElseThrow(() -> new CommonException(AdminErrorCode.ADMIN_NOT_FOUND))); } private LoginResponse getLoginResponse(Admin admin) { - String accessToken = jwtTokenService.createAccessToken(admin.getId()); + String accessToken = jwtTokenService.createAccessToken(admin.getId(), admin.getTenantId()); String refreshToken = jwtTokenService.createRefreshToken(admin.getId()); return LoginResponse.of(accessToken, refreshToken); } diff --git a/src/main/java/com/doubleo/adminservice/domain/auth/service/JwtTokenService.java b/src/main/java/com/doubleo/adminservice/domain/auth/service/JwtTokenService.java index c7f034a..a84056c 100644 --- a/src/main/java/com/doubleo/adminservice/domain/auth/service/JwtTokenService.java +++ b/src/main/java/com/doubleo/adminservice/domain/auth/service/JwtTokenService.java @@ -7,10 +7,10 @@ public interface JwtTokenService { // AccessToken DTO 생성 - AccessTokenDto createAccessTokenDto(Long adminId); + AccessTokenDto createAccessTokenDto(Long adminId, String tenantId); // AccessToken 생성 - String createAccessToken(Long adminId); + String createAccessToken(Long adminId, String tenantId); // RefreshToken 생성 String createRefreshToken(Long adminId); diff --git a/src/main/java/com/doubleo/adminservice/domain/auth/service/JwtTokenServiceImpl.java b/src/main/java/com/doubleo/adminservice/domain/auth/service/JwtTokenServiceImpl.java index a5c4daa..016bd67 100644 --- a/src/main/java/com/doubleo/adminservice/domain/auth/service/JwtTokenServiceImpl.java +++ b/src/main/java/com/doubleo/adminservice/domain/auth/service/JwtTokenServiceImpl.java @@ -22,81 +22,55 @@ public class JwtTokenServiceImpl implements JwtTokenService { private final RefreshTokenRepository refreshTokenRepository; private final BlackListTokenRepository blackListTokenRepository; - public AccessTokenDto createAccessTokenDto(Long adminId) { - return jwtUtil.generateAccessTokenDto(adminId); + public AccessTokenDto createAccessTokenDto(Long adminId, String tenantId) { + return jwtUtil.generateAccessTokenDto(adminId, tenantId); } - public String createAccessToken(Long adminId) { - return jwtUtil.generateAccessToken(adminId); - } - - public String createRefreshToken(Long adminId) { - String token = jwtUtil.generateRefreshToken(adminId); - RefreshToken refreshToken = - RefreshToken.builder() - .adminId(adminId) - .token(token) - .ttl(jwtUtil.getRefreshTokenExpirationTime()) - .build(); - refreshTokenRepository.save(refreshToken); - - return token; - } - - public RefreshTokenDto retrieveRefreshToken(String refreshTokenValue) { - RefreshTokenDto refreshTokenDto = parseRefreshToken(refreshTokenValue); - if (refreshTokenDto == null) { - return null; - } - - Optional refreshToken = getRefreshToken(refreshTokenDto.adminId()); - - if (refreshToken.isPresent() - && refreshTokenDto.refreshTokenValue().equals(refreshToken.get().getToken())) { - return refreshTokenDto; - } - - return null; + public String createAccessToken(Long adminId, String tenantId) { + return jwtUtil.generateAccessToken(adminId, tenantId); } public Optional reissueAccessTokenIfExpired(String accessTokenValue) { try { - // 파싱에 성공하면 아직 유효 ⇒ Optional.empty() - jwtUtil.parseAccessToken(accessTokenValue); - log.info("Access token is still valid, no reissue needed"); + jwtUtil.parseAccessToken(accessTokenValue); // 유효하면 재발급 불필요 return Optional.empty(); } catch (ExpiredJwtException e) { - // 만료된 경우에만 새 토큰 생성 Long adminId = Long.parseLong(e.getClaims().getSubject()); - AccessTokenDto newToken = createAccessTokenDto(adminId); - log.info("Access token expired, issued new one for adminId={}", adminId); + String tenantId = e.getClaims().get("tenantId", String.class); + AccessTokenDto newToken = createAccessTokenDto(adminId, tenantId); return Optional.of(newToken); } } public void putAccessTokenOnBlackList(String accessTokenValue) { - String accessToken = jwtUtil.resolveToken(accessTokenValue); - if (accessToken == null) { - return; - } - + if (accessToken == null) return; long remainingMs = jwtUtil.getRemainingExpirationMillis(accessToken); long ttlSeconds = remainingMs > 0 ? remainingMs / 1000 : 0; - BlackListToken black = BlackListToken.createBlackListToken(accessToken, ttlSeconds); blackListTokenRepository.save(black); } - private RefreshTokenDto parseRefreshToken(String refreshTokenValue) { - try { - return jwtUtil.parseRefreshToken(refreshTokenValue); - } catch (Exception e) { - return null; - } + public String createRefreshToken(Long adminId) { + String token = jwtUtil.generateRefreshToken(adminId); + RefreshToken refreshToken = + RefreshToken.builder() + .adminId(adminId) + .token(token) + .ttl(jwtUtil.getRefreshTokenExpirationTime()) + .build(); + refreshTokenRepository.save(refreshToken); + return token; } - private Optional getRefreshToken(Long adminId) { - return refreshTokenRepository.findById(adminId); + public RefreshTokenDto retrieveRefreshToken(String refreshTokenValue) { + RefreshTokenDto refreshTokenDto = jwtUtil.parseRefreshToken(refreshTokenValue); + if (refreshTokenDto == null) return null; + + return refreshTokenRepository + .findById(refreshTokenDto.adminId()) + .filter(token -> token.getToken().equals(refreshTokenDto.refreshTokenValue())) + .map(t -> refreshTokenDto) + .orElse(null); } } diff --git a/src/main/java/com/doubleo/adminservice/domain/common/model/BaseTimeEntity.java b/src/main/java/com/doubleo/adminservice/domain/common/model/BaseEntity.java similarity index 75% rename from src/main/java/com/doubleo/adminservice/domain/common/model/BaseTimeEntity.java rename to src/main/java/com/doubleo/adminservice/domain/common/model/BaseEntity.java index d696e02..0e8ceca 100644 --- a/src/main/java/com/doubleo/adminservice/domain/common/model/BaseTimeEntity.java +++ b/src/main/java/com/doubleo/adminservice/domain/common/model/BaseEntity.java @@ -12,11 +12,19 @@ @Getter @MappedSuperclass @EntityListeners(AuditingEntityListener.class) -public abstract class BaseTimeEntity { +public abstract class BaseEntity implements Tenant { @CreatedDate @Column(updatable = false) private LocalDateTime createdDt; @LastModifiedDate private LocalDateTime updatedDt; + + @Column(name = "tenant_id", nullable = false) + private String tenantId; + + @Override + public String getTenantId() { + return tenantId; + } } diff --git a/src/main/java/com/doubleo/adminservice/domain/common/model/Tenant.java b/src/main/java/com/doubleo/adminservice/domain/common/model/Tenant.java new file mode 100644 index 0000000..c18a881 --- /dev/null +++ b/src/main/java/com/doubleo/adminservice/domain/common/model/Tenant.java @@ -0,0 +1,5 @@ +package com.doubleo.adminservice.domain.common.model; + +public interface Tenant { + String getTenantId(); +} diff --git a/src/main/java/com/doubleo/adminservice/global/config/tenant/TenantConfig.java b/src/main/java/com/doubleo/adminservice/global/config/tenant/TenantConfig.java new file mode 100644 index 0000000..0035e00 --- /dev/null +++ b/src/main/java/com/doubleo/adminservice/global/config/tenant/TenantConfig.java @@ -0,0 +1,8 @@ +package com.doubleo.adminservice.global.config.tenant; + +import com.doubleo.tenantcontext.annotation.EnableTenantContext; +import org.springframework.context.annotation.Configuration; + +@EnableTenantContext +@Configuration +public class TenantConfig {} diff --git a/src/main/java/com/doubleo/adminservice/global/exception/errorcode/TenantErrorCode.java b/src/main/java/com/doubleo/adminservice/global/exception/errorcode/TenantErrorCode.java new file mode 100644 index 0000000..5afa64f --- /dev/null +++ b/src/main/java/com/doubleo/adminservice/global/exception/errorcode/TenantErrorCode.java @@ -0,0 +1,21 @@ +package com.doubleo.adminservice.global.exception.errorcode; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor +public enum TenantErrorCode implements BaseErrorCode { + TENANT_ID_NOT_FOUND(HttpStatus.UNAUTHORIZED, "요청에 tenant id가 누락되었습니다."), + INVALID_TENANT_ID(HttpStatus.FORBIDDEN, "유효한 tenant id가 아닙니다."), + ; + + private final HttpStatus httpStatus; + private final String message; + + @Override + public String errorClassName() { + return this.name(); + } +} diff --git a/src/main/java/com/doubleo/adminservice/global/util/JwtUtil.java b/src/main/java/com/doubleo/adminservice/global/util/JwtUtil.java index aad7457..c70673f 100644 --- a/src/main/java/com/doubleo/adminservice/global/util/JwtUtil.java +++ b/src/main/java/com/doubleo/adminservice/global/util/JwtUtil.java @@ -19,19 +19,19 @@ public class JwtUtil { private final JwtProperties jwtProperties; - public AccessTokenDto generateAccessTokenDto(Long adminId) { + public AccessTokenDto generateAccessTokenDto(Long adminId, String tenantId) { Date issuedAt = new Date(); Date expiredAt = new Date(issuedAt.getTime() + jwtProperties.accessTokenExpirationMilliTime()); - String tokenValue = buildAccessToken(adminId, issuedAt, expiredAt); - return new AccessTokenDto(adminId, tokenValue); + String tokenValue = buildAccessToken(adminId, tenantId, issuedAt, expiredAt); + return new AccessTokenDto(adminId, tokenValue, tenantId); } - public String generateAccessToken(Long adminId) { + public String generateAccessToken(Long adminId, String tenantId) { Date issuedAt = new Date(); Date expiredAt = new Date(issuedAt.getTime() + jwtProperties.accessTokenExpirationMilliTime()); - return buildAccessToken(adminId, issuedAt, expiredAt); + return buildAccessToken(adminId, tenantId, issuedAt, expiredAt); } // public RefreshTokenDto generateRefreshTokenDto(Long adminId) { @@ -60,9 +60,9 @@ public long getRemainingExpirationMillis(String tokenValue) { public AccessTokenDto parseAccessToken(String accessTokenValue) throws ExpiredJwtException { try { Jws claims = getClaims(accessTokenValue, getAccessTokenKey()); - - return AccessTokenDto.of( - Long.parseLong(claims.getBody().getSubject()), accessTokenValue); + Long adminId = Long.parseLong(claims.getBody().getSubject()); + String tenantId = claims.getBody().get("tenantId", String.class); + return new AccessTokenDto(adminId, accessTokenValue, tenantId); } catch (ExpiredJwtException e) { throw e; } catch (Exception e) { @@ -104,10 +104,11 @@ private Key getRefreshTokenKey() { return Keys.hmacShaKeyFor(jwtProperties.refreshTokenSecret().getBytes()); } - private String buildAccessToken(Long adminId, Date issuedAt, Date expiredAt) { + private String buildAccessToken(Long adminId, String tenantId, Date issuedAt, Date expiredAt) { return Jwts.builder() .setIssuer(jwtProperties.issuer()) .setSubject(adminId.toString()) + .claim("tenantId", tenantId) .setIssuedAt(issuedAt) .setExpiration(expiredAt) .signWith(getAccessTokenKey()) diff --git a/src/main/java/com/doubleo/adminservice/global/util/TenantValidator.java b/src/main/java/com/doubleo/adminservice/global/util/TenantValidator.java new file mode 100644 index 0000000..430b7ec --- /dev/null +++ b/src/main/java/com/doubleo/adminservice/global/util/TenantValidator.java @@ -0,0 +1,28 @@ +package com.doubleo.adminservice.global.util; + +import com.doubleo.adminservice.domain.common.model.Tenant; +import com.doubleo.adminservice.global.exception.CommonException; +import com.doubleo.adminservice.global.exception.errorcode.TenantErrorCode; +import com.doubleo.tenantcontext.TenantContextHolder; +import java.util.Optional; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +public class TenantValidator { + + public String getTenantId() { + return Optional.ofNullable(TenantContextHolder.getTenantId()) + .orElseThrow(() -> new CommonException(TenantErrorCode.TENANT_ID_NOT_FOUND)); + } + + public T validateTenant(T entity) { + String currentTenantId = getTenantId(); + log.info("validateTenant: currentTenantId={}", currentTenantId); + if (!entity.getTenantId().equals(currentTenantId)) { + throw new CommonException(TenantErrorCode.INVALID_TENANT_ID); + } + return entity; + } +} diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql index f6caab1..b24d651 100644 --- a/src/main/resources/data.sql +++ b/src/main/resources/data.sql @@ -1,78 +1,77 @@ -- 서울권 (14개) -INSERT INTO admin (admin_username, admin_password, admin_affiliation, admin_affiliation_id) VALUES - -('admin1', '$2a$10$r8KNcU9cEEuNzBPmCCt8EeNH08JXt8krEWCLU9Yko73jWX7jc3Ehe', '강북삼성병원', 'TSEO01KS'), -('admin2', '$2a$10$r8KNcU9cEEuNzBPmCCt8EeNH08JXt8krEWCLU9Yko73jWX7jc3Ehe', '건국대학교병원', 'TSEO02KK'), -('admin3', '$2a$10$r8KNcU9cEEuNzBPmCCt8EeNH08JXt8krEWCLU9Yko73jWX7jc3Ehe', '경희대학교병원', 'TSEO03KH'), -('admin4', '$2a$10$r8KNcU9cEEuNzBPmCCt8EeNH08JXt8krEWCLU9Yko73jWX7jc3Ehe', '고려대 구로병원', 'TSEO04KU'), -('admin5', '$2a$10$r8KNcU9cEEuNzBPmCCt8EeNH08JXt8krEWCLU9Yko73jWX7jc3Ehe', '삼성서울병원', 'TSEO05SS'), -('admin6', '$2a$10$r8KNcU9cEEuNzBPmCCt8EeNH08JXt8krEWCLU9Yko73jWX7jc3Ehe', '서울대학교병원', 'TSEO06SN'), -('admin7', '$2a$10$r8KNcU9cEEuNzBPmCCt8EeNH08JXt8krEWCLU9Yko73jWX7jc3Ehe', '연세대 신촌세브란스', 'TSEO07YS'), -('admin8', '$2a$10$r8KNcU9cEEuNzBPmCCt8EeNH08JXt8krEWCLU9Yko73jWX7jc3Ehe', '이화여대 목동병원', 'TSEO08EW'), -('admin9', '$2a$10$r8KNcU9cEEuNzBPmCCt8EeNH08JXt8krEWCLU9Yko73jWX7jc3Ehe', '서울아산병원', 'TSEO09AS'), -('admin10', '$2a$10$r8KNcU9cEEuNzBPmCCt8EeNH08JXt8krEWCLU9Yko73jWX7jc3Ehe', '중앙대학교병원', 'TSEO10CU'), -('admin11', '$2a$10$r8KNcU9cEEuNzBPmCCt8EeNH08JXt8krEWCLU9Yko73jWX7jc3Ehe', '고려대 안암병원', 'TSEO11KA'), -('admin12', '$2a$10$r8KNcU9cEEuNzBPmCCt8EeNH08JXt8krEWCLU9Yko73jWX7jc3Ehe', '가톨릭 서울성모', 'TSEO12CS'), -('admin13', '$2a$10$r8KNcU9cEEuNzBPmCCt8EeNH08JXt8krEWCLU9Yko73jWX7jc3Ehe', '연세대 분당세브란스', 'TSEO13YS'), -('admin14', '$2a$10$r8KNcU9cEEuNzBPmCCt8EeNH08JXt8krEWCLU9Yko73jWX7jc3Ehe', '한양대학교병원', 'TSEO14HY'); +INSERT INTO admin (admin_username, admin_password, admin_affiliation, admin_affiliation_id, tenant_id) VALUES + ('admin1', '$2a$10$r8KNcU9cEEuNzBPmCCt8EeNH08JXt8krEWCLU9Yko73jWX7jc3Ehe', '강북삼성병원', 'TSEO01KS', 'SEO25NE01'), + ('admin2', '$2a$10$r8KNcU9cEEuNzBPmCCt8EeNH08JXt8krEWCLU9Yko73jWX7jc3Ehe', '건국대학교병원', 'TSEO02KK', 'SEO25W102'), + ('admin3', '$2a$10$r8KNcU9cEEuNzBPmCCt8EeNH08JXt8krEWCLU9Yko73jWX7jc3Ehe', '경희대학교병원', 'TSEO03KH', 'SEO25ZG03'), + ('admin4', '$2a$10$r8KNcU9cEEuNzBPmCCt8EeNH08JXt8krEWCLU9Yko73jWX7jc3Ehe', '고려대 구로병원', 'TSEO04KU', 'SEO25NY04'), + ('admin5', '$2a$10$r8KNcU9cEEuNzBPmCCt8EeNH08JXt8krEWCLU9Yko73jWX7jc3Ehe', '삼성서울병원', 'TSEO05SS', 'SEO259W05'), + ('admin6', '$2a$10$r8KNcU9cEEuNzBPmCCt8EeNH08JXt8krEWCLU9Yko73jWX7jc3Ehe', '서울대학교병원', 'TSEO06SN', 'SEO25KG06'), + ('admin7', '$2a$10$r8KNcU9cEEuNzBPmCCt8EeNH08JXt8krEWCLU9Yko73jWX7jc3Ehe', '연세대 신촌세브란스', 'TSEO07YS', 'SEO25D107'), + ('admin8', '$2a$10$r8KNcU9cEEuNzBPmCCt8EeNH08JXt8krEWCLU9Yko73jWX7jc3Ehe', '이화여대 목동병원', 'TSEO08EW', 'SEO25JM08'), + ('admin9', '$2a$10$r8KNcU9cEEuNzBPmCCt8EeNH08JXt8krEWCLU9Yko73jWX7jc3Ehe', '서울아산병원', 'TSEO09AS', 'SEO25LH09'), + ('admin10', '$2a$10$r8KNcU9cEEuNzBPmCCt8EeNH08JXt8krEWCLU9Yko73jWX7jc3Ehe', '중앙대학교병원', 'TSEO10CU', 'SEO25HG10'), + ('admin11', '$2a$10$r8KNcU9cEEuNzBPmCCt8EeNH08JXt8krEWCLU9Yko73jWX7jc3Ehe', '고려대 안암병원', 'TSEO11KA', 'SEO25OW11'), + ('admin12', '$2a$10$r8KNcU9cEEuNzBPmCCt8EeNH08JXt8krEWCLU9Yko73jWX7jc3Ehe', '가톨릭 서울성모', 'TSEO12CS', 'SEO251S12'), + ('admin13', '$2a$10$r8KNcU9cEEuNzBPmCCt8EeNH08JXt8krEWCLU9Yko73jWX7jc3Ehe', '연세대 분당세브란스', 'TSEO13YS', 'SEO25UH13'), + ('admin14', '$2a$10$r8KNcU9cEEuNzBPmCCt8EeNH08JXt8krEWCLU9Yko73jWX7jc3Ehe', '한양대학교병원', 'TSEO14HY', 'SEO257T14'); -- 경기 서북권 (4개) -INSERT INTO admin (admin_username, admin_password, admin_affiliation, admin_affiliation_id) VALUES -('admin15', '$2a$10$r8KNcU9cEEuNzBPmCCt8EeNH08JXt8krEWCLU9Yko73jWX7jc3Ehe', '가톨릭 인천성모병원', 'TGYW01CI'), -('admin16', '$2a$10$r8KNcU9cEEuNzBPmCCt8EeNH08JXt8krEWCLU9Yko73jWX7jc3Ehe', '순천향 부천병원', 'TGYW02SC'), -('admin17', '$2a$10$r8KNcU9cEEuNzBPmCCt8EeNH08JXt8krEWCLU9Yko73jWX7jc3Ehe', '길병원', 'TGYW03GL'), -('admin18', '$2a$10$r8KNcU9cEEuNzBPmCCt8EeNH08JXt8krEWCLU9Yko73jWX7jc3Ehe', '인하대병원', 'TGYW04IH'); +INSERT INTO admin (admin_username, admin_password, admin_affiliation, admin_affiliation_id, tenant_id) VALUES + ('admin15', '$2a$10$r8KNcU9cEEuNzBPmCCt8EeNH08JXt8krEWCLU9Yko73jWX7jc3Ehe', '가톨릭 인천성모병원', 'TGYW01CI', 'GYW25LJ15'), + ('admin16', '$2a$10$r8KNcU9cEEuNzBPmCCt8EeNH08JXt8krEWCLU9Yko73jWX7jc3Ehe', '순천향 부천병원', 'TGYW02SC', 'GYW250R16'), + ('admin17', '$2a$10$r8KNcU9cEEuNzBPmCCt8EeNH08JXt8krEWCLU9Yko73jWX7jc3Ehe', '길병원', 'TGYW03GL', 'GYW253L17'), + ('admin18', '$2a$10$r8KNcU9cEEuNzBPmCCt8EeNH08JXt8krEWCLU9Yko73jWX7jc3Ehe', '인하대병원', 'TGYW04IH', 'GYW254F18'); -- 경기 남부권 (4개) -INSERT INTO admin (admin_username, admin_password, admin_affiliation, admin_affiliation_id) VALUES -('admin19', '$2a$10$r8KNcU9cEEuNzBPmCCt8EeNH08JXt8krEWCLU9Yko73jWX7jc3Ehe', '고려대 안산병원', 'TGYS01KA'), -('admin20', '$2a$10$r8KNcU9cEEuNzBPmCCt8EeNH08JXt8krEWCLU9Yko73jWX7jc3Ehe', '분당서울대병원', 'TGYS02SU'), -('admin21', '$2a$10$r8KNcU9cEEuNzBPmCCt8EeNH08JXt8krEWCLU9Yko73jWX7jc3Ehe', '아주대학교병원', 'TGYS03AJ'), -('admin22', '$2a$10$r8KNcU9cEEuNzBPmCCt8EeNH08JXt8krEWCLU9Yko73jWX7jc3Ehe', '한림대 성심병원', 'TGYS04HL'); +INSERT INTO admin (admin_username, admin_password, admin_affiliation, admin_affiliation_id, tenant_id) VALUES + ('admin19', '$2a$10$r8KNcU9cEEuNzBPmCCt8EeNH08JXt8krEWCLU9Yko73jWX7jc3Ehe', '고려대 안산병원', 'TGYS01KA', 'GYS253419'), + ('admin20', '$2a$10$r8KNcU9cEEuNzBPmCCt8EeNH08JXt8krEWCLU9Yko73jWX7jc3Ehe', '분당서울대병원', 'TGYS02SU', 'GYS25OB20'), + ('admin21', '$2a$10$r8KNcU9cEEuNzBPmCCt8EeNH08JXt8krEWCLU9Yko73jWX7jc3Ehe', '아주대학교병원', 'TGYS03AJ', 'GYS25PT21'), + ('admin22', '$2a$10$r8KNcU9cEEuNzBPmCCt8EeNH08JXt8krEWCLU9Yko73jWX7jc3Ehe', '한림대 성심병원', 'TGYS04HL', 'GYS257V22'); -- 강원권 (2개) -INSERT INTO admin (admin_username, admin_password, admin_affiliation, admin_affiliation_id) VALUES -('admin23', '$2a$10$r8KNcU9cEEuNzBPmCCt8EeNH08JXt8krEWCLU9Yko73jWX7jc3Ehe', '강릉아산병원', 'TGWN01GA'), -('admin24', '$2a$10$r8KNcU9cEEuNzBPmCCt8EeNH08JXt8krEWCLU9Yko73jWX7jc3Ehe', '세브란스 원주', 'TGWN02YW'); +INSERT INTO admin (admin_username, admin_password, admin_affiliation, admin_affiliation_id, tenant_id) VALUES + ('admin23', '$2a$10$r8KNcU9cEEuNzBPmCCt8EeNH08JXt8krEWCLU9Yko73jWX7jc3Ehe', '강릉아산병원', 'TGWN01GA', 'GWN25C423'), + ('admin24', '$2a$10$r8KNcU9cEEuNzBPmCCt8EeNH08JXt8krEWCLU9Yko73jWX7jc3Ehe', '세브란스 원주', 'TGWN02YW', 'GWN25HS24'); -- 충북권 (1개) -INSERT INTO admin (admin_username, admin_password, admin_affiliation, admin_affiliation_id) VALUES - ('admin25', '$2a$10$r8KNcU9cEEuNzBPmCCt8EeNH08JXt8krEWCLU9Yko73jWX7jc3Ehe', '충북대학교병원', 'TCBK01CU'); +INSERT INTO admin (admin_username, admin_password, admin_affiliation, admin_affiliation_id, tenant_id) VALUES + ('admin25', '$2a$10$r8KNcU9cEEuNzBPmCCt8EeNH08JXt8krEWCLU9Yko73jWX7jc3Ehe', '충북대학교병원', 'TCBK01CU', 'CBK259X25'); -- 충남권 (3개) -INSERT INTO admin (admin_username, admin_password, admin_affiliation, admin_affiliation_id) VALUES -('admin26', '$2a$10$r8KNcU9cEEuNzBPmCCt8EeNH08JXt8krEWCLU9Yko73jWX7jc3Ehe', '단국대병원', 'TCNM01DK'), -('admin27', '$2a$10$r8KNcU9cEEuNzBPmCCt8EeNH08JXt8krEWCLU9Yko73jWX7jc3Ehe', '순천향 천안병원', 'TCNM02SC'), -('admin28', '$2a$10$r8KNcU9cEEuNzBPmCCt8EeNH08JXt8krEWCLU9Yko73jWX7jc3Ehe', '충남대학교병원', 'TCNM03CN'); +INSERT INTO admin (admin_username, admin_password, admin_affiliation, admin_affiliation_id, tenant_id) VALUES + ('admin26', '$2a$10$r8KNcU9cEEuNzBPmCCt8EeNH08JXt8krEWCLU9Yko73jWX7jc3Ehe', '단국대병원', 'TCNM01DK', 'CNM25FS26'), + ('admin27', '$2a$10$r8KNcU9cEEuNzBPmCCt8EeNH08JXt8krEWCLU9Yko73jWX7jc3Ehe', '순천향 천안병원', 'TCNM02SC', 'CNM25JP27'), + ('admin28', '$2a$10$r8KNcU9cEEuNzBPmCCt8EeNH08JXt8krEWCLU9Yko73jWX7jc3Ehe', '충남대학교병원', 'TCNM03CN', 'CNM25E828'); -- 전북권 (2개) -INSERT INTO admin (admin_username, admin_password, admin_affiliation, admin_affiliation_id) VALUES -('admin29', '$2a$10$r8KNcU9cEEuNzBPmCCt8EeNH08JXt8krEWCLU9Yko73jWX7jc3Ehe', '원광대학교병원', 'TJBK01WK'), -('admin30', '$2a$10$r8KNcU9cEEuNzBPmCCt8EeNH08JXt8krEWCLU9Yko73jWX7jc3Ehe', '전북대학교병원', 'TJBK02JB'); +INSERT INTO admin (admin_username, admin_password, admin_affiliation, admin_affiliation_id, tenant_id) VALUES + ('admin29', '$2a$10$r8KNcU9cEEuNzBPmCCt8EeNH08JXt8krEWCLU9Yko73jWX7jc3Ehe', '원광대학교병원', 'TJBK01WK', 'JBK25FL29'), + ('admin30', '$2a$10$r8KNcU9cEEuNzBPmCCt8EeNH08JXt8krEWCLU9Yko73jWX7jc3Ehe', '전북대학교병원', 'TJBK02JB', 'JBK258T30'); -- 전남권 (3개) -INSERT INTO admin (admin_username, admin_password, admin_affiliation, admin_affiliation_id) VALUES -('admin31', '$2a$10$r8KNcU9cEEuNzBPmCCt8EeNH08JXt8krEWCLU9Yko73jWX7jc3Ehe', '전남대학교병원', 'TJNM01JN'), -('admin32', '$2a$10$r8KNcU9cEEuNzBPmCCt8EeNH08JXt8krEWCLU9Yko73jWX7jc3Ehe', '조선대학교병원', 'TJNM02CS'), -('admin33', '$2a$10$r8KNcU9cEEuNzBPmCCt8EeNH08JXt8krEWCLU9Yko73jWX7jc3Ehe', '화순전남대병원', 'TJNM03HS'); +INSERT INTO admin (admin_username, admin_password, admin_affiliation, admin_affiliation_id, tenant_id) VALUES + ('admin31', '$2a$10$r8KNcU9cEEuNzBPmCCt8EeNH08JXt8krEWCLU9Yko73jWX7jc3Ehe', '전남대학교병원', 'TJNM01JN', 'JNM25LJ31'), + ('admin32', '$2a$10$r8KNcU9cEEuNzBPmCCt8EeNH08JXt8krEWCLU9Yko73jWX7jc3Ehe', '조선대학교병원', 'TJNM02CS', 'JNM25D132'), + ('admin33', '$2a$10$r8KNcU9cEEuNzBPmCCt8EeNH08JXt8krEWCLU9Yko73jWX7jc3Ehe', '화순전남대병원', 'TJNM03HS', 'JNM25M933'); -- 경북권 (5개) -INSERT INTO admin (admin_username, admin_password, admin_affiliation, admin_affiliation_id) VALUES -('admin34', '$2a$10$r8KNcU9cEEuNzBPmCCt8EeNH08JXt8krEWCLU9Yko73jWX7jc3Ehe', '경북대학교병원', 'TGBK01KB'), -('admin35', '$2a$10$r8KNcU9cEEuNzBPmCCt8EeNH08JXt8krEWCLU9Yko73jWX7jc3Ehe', '계명대 동산병원', 'TGBK02KM'), -('admin36', '$2a$10$r8KNcU9cEEuNzBPmCCt8EeNH08JXt8krEWCLU9Yko73jWX7jc3Ehe', '대구가톨릭대병원', 'TGBK03DC'), -('admin37', '$2a$10$r8KNcU9cEEuNzBPmCCt8EeNH08JXt8krEWCLU9Yko73jWX7jc3Ehe', '영남대학교병원', 'TGBK04YN'), -('admin38', '$2a$10$r8KNcU9cEEuNzBPmCCt8EeNH08JXt8krEWCLU9Yko73jWX7jc3Ehe', '칠곡경북대병원', 'TGBK05CB'); +INSERT INTO admin (admin_username, admin_password, admin_affiliation, admin_affiliation_id, tenant_id) VALUES + ('admin34', '$2a$10$r8KNcU9cEEuNzBPmCCt8EeNH08JXt8krEWCLU9Yko73jWX7jc3Ehe', '경북대학교병원', 'TGBK01KB', 'GBK25WH34'), + ('admin35', '$2a$10$r8KNcU9cEEuNzBPmCCt8EeNH08JXt8krEWCLU9Yko73jWX7jc3Ehe', '계명대 동산병원', 'TGBK02KM', 'GBK25Z035'), + ('admin36', '$2a$10$r8KNcU9cEEuNzBPmCCt8EeNH08JXt8krEWCLU9Yko73jWX7jc3Ehe', '대구가톨릭대병원', 'TGBK03DC', 'GBK25NW36'), + ('admin37', '$2a$10$r8KNcU9cEEuNzBPmCCt8EeNH08JXt8krEWCLU9Yko73jWX7jc3Ehe', '영남대학교병원', 'TGBK04YN', 'GBK251J37'), + ('admin38', '$2a$10$r8KNcU9cEEuNzBPmCCt8EeNH08JXt8krEWCLU9Yko73jWX7jc3Ehe', '칠곡경북대병원', 'TGBK05CB', 'GBK25K538'); -- 경남 동부권 (5개) -INSERT INTO admin (admin_username, admin_password, admin_affiliation, admin_affiliation_id) VALUES -('admin39', '$2a$10$r8KNcU9cEEuNzBPmCCt8EeNH08JXt8krEWCLU9Yko73jWX7jc3Ehe', '동아대학교병원', 'TGNE01DA'), -('admin40', '$2a$10$r8KNcU9cEEuNzBPmCCt8EeNH08JXt8krEWCLU9Yko73jWX7jc3Ehe', '부산대학교병원', 'TGNE02PU'), -('admin41', '$2a$10$r8KNcU9cEEuNzBPmCCt8EeNH08JXt8krEWCLU9Yko73jWX7jc3Ehe', '양산부산대병원', 'TGNE03YA'), -('admin42', '$2a$10$r8KNcU9cEEuNzBPmCCt8EeNH08JXt8krEWCLU9Yko73jWX7jc3Ehe', '부산백병원', 'TGNE04IB'), -('admin43', '$2a$10$r8KNcU9cEEuNzBPmCCt8EeNH08JXt8krEWCLU9Yko73jWX7jc3Ehe', '울산대병원', 'TGNE05UI'); +INSERT INTO admin (admin_username, admin_password, admin_affiliation, admin_affiliation_id, tenant_id) VALUES + ('admin39', '$2a$10$r8KNcU9cEEuNzBPmCCt8EeNH08JXt8krEWCLU9Yko73jWX7jc3Ehe', '동아대학교병원', 'TGNE01DA', 'GNE25DM39'), + ('admin40', '$2a$10$r8KNcU9cEEuNzBPmCCt8EeNH08JXt8krEWCLU9Yko73jWX7jc3Ehe', '부산대학교병원', 'TGNE02PU', 'GNE25UW40'), + ('admin41', '$2a$10$r8KNcU9cEEuNzBPmCCt8EeNH08JXt8krEWCLU9Yko73jWX7jc3Ehe', '양산부산대병원', 'TGNE03YA', 'GNE25ZU41'), + ('admin42', '$2a$10$r8KNcU9cEEuNzBPmCCt8EeNH08JXt8krEWCLU9Yko73jWX7jc3Ehe', '부산백병원', 'TGNE04IB', 'GNE25WB42'), + ('admin43', '$2a$10$r8KNcU9cEEuNzBPmCCt8EeNH08JXt8krEWCLU9Yko73jWX7jc3Ehe', '울산대병원', 'TGNE05UI', 'GNE25FR43'); -- 경남 서부권 (2개) -INSERT INTO admin (admin_username, admin_password, admin_affiliation, admin_affiliation_id) VALUES -('admin44', '$2a$10$r8KNcU9cEEuNzBPmCCt8EeNH08JXt8krEWCLU9Yko73jWX7jc3Ehe', '경상대병원', 'TGNW01GS'), -('admin45', '$2a$10$r8KNcU9cEEuNzBPmCCt8EeNH08JXt8krEWCLU9Yko73jWX7jc3Ehe', '성균관대 삼성창원병원','TGNW02SC'); +INSERT INTO admin (admin_username, admin_password, admin_affiliation, admin_affiliation_id, tenant_id) VALUES + ('admin44', '$2a$10$r8KNcU9cEEuNzBPmCCt8EeNH08JXt8krEWCLU9Yko73jWX7jc3Ehe', '경상대병원', 'TGNW01GS', 'GNW25V144'), + ('admin45', '$2a$10$r8KNcU9cEEuNzBPmCCt8EeNH08JXt8krEWCLU9Yko73jWX7jc3Ehe', '성균관대 삼성창원병원','TGNW02SC', 'GNW252X45'); diff --git a/src/test/java/com/doubleo/adminservice/AdminServiceApplicationTests.java b/src/test/java/com/doubleo/adminservice/AdminServiceApplicationTests.java index 43b1f61..2a78ee9 100644 --- a/src/test/java/com/doubleo/adminservice/AdminServiceApplicationTests.java +++ b/src/test/java/com/doubleo/adminservice/AdminServiceApplicationTests.java @@ -5,7 +5,7 @@ import org.springframework.test.context.ActiveProfiles; @SpringBootTest -@ActiveProfiles({"test", "redis", "security"}) +@ActiveProfiles("test") class AdminServiceApplicationTests { @Test diff --git a/src/test/java/com/doubleo/adminservice/service/AdminServiceTest.java b/src/test/java/com/doubleo/adminservice/service/AdminServiceTest.java index 71d87b0..da9919a 100644 --- a/src/test/java/com/doubleo/adminservice/service/AdminServiceTest.java +++ b/src/test/java/com/doubleo/adminservice/service/AdminServiceTest.java @@ -11,6 +11,7 @@ import com.doubleo.adminservice.domain.admin.service.AdminServiceImpl; import com.doubleo.adminservice.global.exception.CommonException; import com.doubleo.adminservice.global.exception.errorcode.AdminErrorCode; +import com.doubleo.adminservice.global.util.TenantValidator; import java.util.Optional; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; @@ -31,6 +32,8 @@ public class AdminServiceTest { @Mock private BCryptPasswordEncoder bCryptPasswordEncoder; + @Mock private TenantValidator tenantValidator; + private final String username = "test@test.com"; private final String password = "password"; private final String affiliation = "서울아산병원"; @@ -51,6 +54,7 @@ class getAdminInfo { void 관리자정보_조회하면_정상적으로_반환된다() { // given given(adminRepository.findById(1L)).willReturn(Optional.of(admin)); + given(tenantValidator.validateTenant(admin)).willReturn(admin); // when AdminInfoResponse response = adminService.getAdminInfo(admin.getId()); @@ -73,6 +77,8 @@ class updateAdminPassword { String encodedNewPassword = "encodedNew"; given(adminRepository.findById(1L)).willReturn(Optional.of(admin)); + given(tenantValidator.validateTenant(admin)).willReturn(admin); + given(bCryptPasswordEncoder.matches(password, "encoded")).willReturn(true); given(bCryptPasswordEncoder.encode(newPassword)).willReturn(encodedNewPassword); @@ -87,6 +93,7 @@ class updateAdminPassword { void 기존_비밀번호_유효하지_않으면_오류_발생한다() { // given given(adminRepository.findById(1L)).willReturn(Optional.of(admin)); + given(tenantValidator.validateTenant(admin)).willReturn(admin); given(bCryptPasswordEncoder.matches("wrongPassword", "encoded")).willReturn(false); // when & then @@ -104,6 +111,7 @@ class updateAdminPassword { void 기존_비밀번호와_신규_비밀번호가_동일하면_오류_발생한다() { // given given(adminRepository.findById(1L)).willReturn(Optional.of(admin)); + given(tenantValidator.validateTenant(admin)).willReturn(admin); given(bCryptPasswordEncoder.matches(password, "encoded")).willReturn(true); // when & then