Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/develop_ci_docker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/develop_ci_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,7 @@ out/
.DS_Store

### .env ###
.env
.env

### gradle.properties ###
gradle.properties
14 changes: 14 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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'
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -10,7 +10,7 @@
@Entity
@Getter
@NoArgsConstructor
public class Admin extends BaseTimeEntity {
public class Admin extends BaseEntity {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -18,6 +19,7 @@ public class AdminServiceImpl implements AdminService {

private final AdminRepository adminRepository;
private final BCryptPasswordEncoder passwordEncoder;
private final TenantValidator<Admin> tenantValidator;

@Override
public AdminInfoResponse getAdminInfo(Long adminId) {
Expand All @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -19,6 +20,7 @@ public class AuthServiceImpl implements AuthService {
private final RefreshTokenRepository refreshTokenRepository;
private final JwtTokenService jwtTokenService;
private final BCryptPasswordEncoder encoder;
private final TenantValidator<Admin> tenantValidator;

public LoginResponse loginAdmin(LoginRequest request) {
Admin admin = validateAdminByEmail(request.username());
Expand All @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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> 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<AccessTokenDto> 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<RefreshToken> 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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.doubleo.adminservice.domain.common.model;

public interface Tenant {
String getTenantId();
}
Original file line number Diff line number Diff line change
@@ -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 {}
Original file line number Diff line number Diff line change
@@ -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();
}
}
19 changes: 10 additions & 9 deletions src/main/java/com/doubleo/adminservice/global/util/JwtUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -60,9 +60,9 @@ public long getRemainingExpirationMillis(String tokenValue) {
public AccessTokenDto parseAccessToken(String accessTokenValue) throws ExpiredJwtException {
try {
Jws<Claims> 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) {
Expand Down Expand Up @@ -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())
Expand Down
Loading