Skip to content
Open
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
8 changes: 6 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,12 @@ FROM eclipse-temurin:21-jre-jammy

WORKDIR /app

COPY --from=build /app/build/libs/*SNAPSHOT.jar app.jar
RUN curl -L \
https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/latest/download/opentelemetry-javaagent.jar \
-o /app/opentelemetry-javaagent.jar

COPY --from=build /app/build/libs/*.jar app.jar

ENV TZ=Asia/Seoul

ENTRYPOINT ["java", "-jar", "app.jar"]
ENTRYPOINT ["java","-javaagent:/app/opentelemetry-javaagent.jar","-Dotel.service.name=product-service","-Dotel.propagators=tracecontext,baggage,b3,b3multi","-Dotel.traces.exporter=otlp","-Dotel.logs.exporter=none","-Dotel.metrics.exporter=none","-Dotel.exporter.otlp.endpoint=http://otel-collector.istio-system.svc.cluster.local:4317","-Dotel.exporter.otlp.protocol=grpc","-jar", "/app/app.jar"]
12 changes: 6 additions & 6 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -109,19 +109,19 @@ dependencies {
implementation 'org.springframework.cloud:spring-cloud-starter-openfeign'
implementation 'io.github.openfeign:feign-gson:11.0'

// Config-Server
implementation 'org.springframework.cloud:spring-cloud-starter-config'
// // Config-Server
// implementation 'org.springframework.cloud:spring-cloud-starter-config'

// logback
implementation 'net.logstash.logback:logstash-logback-encoder:8.0'

// Actuator
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'io.micrometer:micrometer-core'
implementation 'io.micrometer:micrometer-registry-prometheus'

// Service-Discovery
implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'

// common module
implementation 'com.irum:open-feign-client:0.0.4-SNAPSHOT'
implementation 'com.irum:open-feign-client:1.1.0-SNAPSHOT'
implementation 'com.irum:global-module:0.1.8-SNAPSHOT'

// Liquibase
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
@EnableDiscoveryClient
public class MemberServiceApplication {

public static void main(String[] args) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@

@ConfigurationProperties(prefix = "jwt")
public record JwtProperties(
String accessTokenSecret,
String accessTokenPrivateKey,
String accessTokenPublicKey,
String refreshTokenSecret,
Long accessTokenExpirationTime,
Long refreshTokenExpirationTime,
String issuer) {

public Long accessTokenExpirationMilliTime() {
return accessTokenExpirationTime * 1000;
}
Expand Down
131 changes: 92 additions & 39 deletions src/main/java/com/irum/memberservice/global/security/jwt/JwtUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,17 @@
import com.irum.memberservice.global.infrastructure.properties.JwtProperties;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.JwtParser;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import java.security.Key;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.Date;
import javax.crypto.SecretKey;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;

Expand All @@ -21,6 +27,8 @@
public class JwtUtil {

private final JwtProperties jwtProperties;
private PrivateKey accessTokenPrivateKey;
private PublicKey accessTokenPublicKey;

public AccessTokenDto generateAccessTokenDto(Long memberId, Role authority) {
Date issuedAt = new Date();
Expand Down Expand Up @@ -55,11 +63,11 @@ public String generateRefreshToken(Long memberId) {

public AccessTokenDto parseAccessToken(String accessTokenValue) throws ExpiredJwtException {
try {
Jws<Claims> claims = getClaims(accessTokenValue, getAccessTokenKey());
Claims claims = parseAccessTokenClaims(accessTokenValue);

return AccessTokenDto.of(
Long.parseLong(claims.getBody().getSubject()),
Role.valueOf(claims.getBody().get(TOKEN_ROLE_NAME, String.class)),
Long.parseLong(claims.getSubject()),
Role.valueOf(claims.get(TOKEN_ROLE_NAME, String.class)),
accessTokenValue);
} catch (ExpiredJwtException e) {
throw e;
Expand All @@ -68,25 +76,12 @@ public AccessTokenDto parseAccessToken(String accessTokenValue) throws ExpiredJw
}
}

public String resolveToken(String headerValue) {
if (headerValue != null && headerValue.startsWith("Bearer ")) {
return headerValue.substring(7);
}
return null;
}

public long getRemainingExpirationMillis(String tokenValue) {
Jws<Claims> claims = getClaims(tokenValue, getAccessTokenKey());
Date exp = claims.getBody().getExpiration();
return Math.max(exp.getTime() - System.currentTimeMillis(), 0);
}

public RefreshTokenDto parseRefreshToken(String refreshTokenValue) throws ExpiredJwtException {
try {
Jws<Claims> claims = getClaims(refreshTokenValue, getRefreshTokenKey());
Claims claims = parseRefreshTokenClaims(refreshTokenValue);

return RefreshTokenDto.of(
Long.parseLong(claims.getBody().getSubject()),
Long.parseLong(claims.getSubject()),
refreshTokenValue,
jwtProperties.refreshTokenExpirationTime());
} catch (ExpiredJwtException e) {
Expand All @@ -96,44 +91,102 @@ public RefreshTokenDto parseRefreshToken(String refreshTokenValue) throws Expire
}
}

private Jws<Claims> getClaims(String token, Key key) {
return Jwts.parser()
.requireIssuer(jwtProperties.issuer())
.setSigningKey(key)
.build()
.parseClaimsJws(token);
public long getRemainingExpirationMillis(String tokenValue) {
Claims claims = parseAccessTokenClaims(tokenValue);
Date exp = claims.getExpiration();
return Math.max(exp.getTime() - System.currentTimeMillis(), 0);
}

public long getRefreshTokenExpirationTime() {
return jwtProperties.refreshTokenExpirationTime();
}

private Key getAccessTokenKey() {
return Keys.hmacShaKeyFor(jwtProperties.accessTokenSecret().getBytes());
// Private 헬퍼 메서드들

private Claims parseAccessTokenClaims(String token) {
JwtParser parser =
Jwts.parser()
.verifyWith(getAccessTokenPublicKey())
.requireIssuer(jwtProperties.issuer())
.build();

return parser.parseSignedClaims(token).getPayload();
}

private Claims parseRefreshTokenClaims(String token) {
JwtParser parser =
Jwts.parser()
.verifyWith(getRefreshTokenKey())
.requireIssuer(jwtProperties.issuer())
.build();

return parser.parseSignedClaims(token).getPayload();
}

private PrivateKey getAccessTokenPrivateKey() {
if (accessTokenPrivateKey == null) {
try {
String privateKeyPEM =
jwtProperties
.accessTokenPrivateKey()
.replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "")
.replaceAll("\\s", "");

byte[] decoded = Base64.getDecoder().decode(privateKeyPEM);
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(decoded);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
accessTokenPrivateKey = keyFactory.generatePrivate(keySpec);
} catch (Exception e) {
throw new RuntimeException("Failed to load private key", e);
}
}
return accessTokenPrivateKey;
}

private PublicKey getAccessTokenPublicKey() {
if (accessTokenPublicKey == null) {
try {
String publicKeyPEM =
jwtProperties
.accessTokenPublicKey()
.replace("-----BEGIN PUBLIC KEY-----", "")
.replace("-----END PUBLIC KEY-----", "")
.replaceAll("\\s", "");

byte[] decoded = Base64.getDecoder().decode(publicKeyPEM);
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(decoded);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
accessTokenPublicKey = keyFactory.generatePublic(keySpec);
} catch (Exception e) {
throw new RuntimeException("Failed to load public key", e);
}
}
return accessTokenPublicKey;
}

private Key getRefreshTokenKey() {
private SecretKey getRefreshTokenKey() {
return Keys.hmacShaKeyFor(jwtProperties.refreshTokenSecret().getBytes());
}

private String buildAccessToken(Long memberId, Role authority, Date issuedAt, Date expiredAt) {
return Jwts.builder()
.setIssuer(jwtProperties.issuer())
.setSubject(memberId.toString())
.issuer(jwtProperties.issuer())
.subject(memberId.toString())
.claim(TOKEN_ROLE_NAME, authority.name())
.setIssuedAt(issuedAt)
.setExpiration(expiredAt)
.signWith(getAccessTokenKey())
.issuedAt(issuedAt)
.expiration(expiredAt)
.signWith(getAccessTokenPrivateKey(), Jwts.SIG.RS256)
.compact();
}

private String buildRefreshToken(Long memberId, Date issuedAt, Date expiredAt) {
return Jwts.builder()
.setIssuer(jwtProperties.issuer())
.setSubject(memberId.toString())
.setIssuedAt(issuedAt)
.setExpiration(expiredAt)
.signWith(getRefreshTokenKey())
.issuer(jwtProperties.issuer())
.subject(memberId.toString())
.issuedAt(issuedAt)
.expiration(expiredAt)
.signWith(getRefreshTokenKey(), Jwts.SIG.HS512)
.compact();
}
}
10 changes: 10 additions & 0 deletions src/main/resources/application-actuator.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
spring:
config:
activate:
on-profile: "actuator"

management:
endpoints:
web:
exposure:
include: refresh, health, beans, httpexchanges, busrefresh, info, metrics, prometheus
15 changes: 15 additions & 0 deletions src/main/resources/application-base-url.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
spring:
config:
activate:
on-profile: "base-url"
service:
member:
base-url: ${MEMBER-SERVICE-URL:member-service}:${MEMBER_PORT:8081}
product:
base-url: ${PRODUCT-SERVICE-URL:product-service}:${PRODUCT_PORT:8082}
order:
base-url: ${ORDER-SERVICE-URL:order-service}:${ORDER_PORT:8083}
payment:
base-url: ${PAYMENT-SERVICE-URL:payment-service}:${PAYMENT_PORT:8084}
ai:
base-url: ${AI-SERVICE-URL:ai-service}:${AI_PORT:8085}
26 changes: 26 additions & 0 deletions src/main/resources/application-default-datasource.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
spring:
config:
activate:
on-profile: "default-datasource"

datasource:
url: jdbc:postgresql://${POSTGRESQL_HOST:localhost}:${POSTGRESQL_PORT:5432}/${DB_NAME:come2us}
username: ${POSTGRESQL_USERNAME:developer}
password: ${POSTGRESQL_PASSWORD:1234}
driver-class-name: org.postgresql.Driver
jpa:
show-sql: false
properties:
hibernate:
hibernate.format_sql: true
hibernate.highlight_sql: true
hibernate.session.events.log.LOG_QUERIES_SLOWER_THAN_MS: 200
default_batch_fetch_size: 100
hibernate:
ddl-auto: validate
database-platform: org.hibernate.dialect.PostgreSQLDialect
defer-datasource-initialization: false
sql:
init:
mode: never
encoding: UTF-8
9 changes: 9 additions & 0 deletions src/main/resources/application-default-redis.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
spring:
config:
activate:
on-profile: "default-redis"
data:
redis:
host: ${REDIS_HOST:localhost}
port: ${REDIS_PORT:6379}
password: ${REDIS_PASSWORD:}
8 changes: 8 additions & 0 deletions src/main/resources/application-defualt-db-management.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
spring:
config:
activate:
on-profile: "default-db-management"

liquibase:
enabled: true
change-log: "classpath:db/changelog/db.changelog-master.yaml"
7 changes: 7 additions & 0 deletions src/main/resources/application-docker.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
spring:
config:
activate:
on-profile: "docker"

server:
port: ${MEMBER_PORT:60001}
18 changes: 18 additions & 0 deletions src/main/resources/application-eureka.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
spring:
config:
activate:
on-profile: "eureka"

eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://${EUREKA_HOST:localhost}:${EUREKA_PORT:8761}/eureka
instance:
prefer-ip-address: false
hostname: ${MEMBER_HOSTNAME:member.come2us.local}
ip-address: ${MEMBER_HOSTNAME}
instance-id: ${MEMBER_HOSTNAME}:${MEMBER_PORT}
metadata-map:
port: ${MEMBER_PORT}
7 changes: 7 additions & 0 deletions src/main/resources/application-local.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
spring:
config:
activate:
on-profile: "local"

server:
port: ${MEMBER_PORT:8081}
11 changes: 11 additions & 0 deletions src/main/resources/application-openfeign.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
spring:
config:
activate:
on-profile: "openfeign"

feign:
client:
config:
default:
connectTimeout: 5000
readTimeout: 5000
11 changes: 11 additions & 0 deletions src/main/resources/application-prod-data-redis.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
spring:
config:
activate:
on-profile: "prod-data-redis"
data:
redis:
host: ${DATA_REDIS_HOST}
port: ${DATA_REDIS_PORT}
password: ${DATA_REDIS_PASSWORD}
ssl:
enabled: true
Loading
Loading