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
3 changes: 3 additions & 0 deletions chat/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ dependencies {

// Redis
implementation("org.springframework.boot:spring-boot-starter-data-redis")

// Slack
implementation("com.slack.api:slack-api-client:1.45.3")
}

tasks.bootJar {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
package org.example.soundlinkchat_java.global.auth;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import jakarta.annotation.PostConstruct;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import io.jsonwebtoken.*;

import javax.crypto.SecretKey;
import java.util.Date;
import java.util.*;
import java.util.concurrent.TimeUnit;

@Slf4j
@Component
@RequiredArgsConstructor
public class JwtProvider {
Expand All @@ -31,18 +31,25 @@ public class JwtProvider {
private long REFRESH_EXPIRATION_TIME;

// 시크릿 키
private final SecretKey SECRET_KEY = Keys.hmacShaKeyFor("ee7d4dcf88086125155386d999b3a2258d5c55671a390e608f49a2db31efc6e0".getBytes());
@Value("${jwt.secret}")
private String SECRET_KEY_STRING;
private SecretKey SECRET_KEY;

@PostConstruct
public void init() {
this.SECRET_KEY = Keys.hmacShaKeyFor(SECRET_KEY_STRING.getBytes());
}

// Access 토큰
public String createAccessToken(long userId) {
Claims claims = Jwts.claims().setSubject(String.valueOf(userId));
Date now = new Date();

return Jwts.builder()
.setClaims(claims)
.setIssuedAt(now)
.setExpiration(new Date(now.getTime()+ACCESS_EXPIRATION_TIME))
.signWith(SECRET_KEY, SignatureAlgorithm.HS256)
.setExpiration(new Date(now.getTime() + ACCESS_EXPIRATION_TIME))
.setHeaderParam("typ", "JWT")
.signWith(SECRET_KEY,SignatureAlgorithm.HS256)
.compact();
}

Expand All @@ -54,6 +61,7 @@ public String createRefreshToken(long userId) {
.setClaims(claims)
.setIssuedAt(now)
.setExpiration(new Date(now.getTime()+REFRESH_EXPIRATION_TIME))
.setHeaderParam("typ", "JWT")
.signWith(SECRET_KEY, SignatureAlgorithm.HS256)
.compact();
try {
Expand All @@ -66,30 +74,23 @@ public String createRefreshToken(long userId) {
}

//토큰 검증(변조, 만료, 올바른 형식)
public boolean validateToken(String token){
public boolean validateToken(String token) {
try {
Jwts.parserBuilder()
.setSigningKey(SECRET_KEY) //서명 검증
.setSigningKey(SECRET_KEY) // 서명 검증
.build()
.parseClaimsJws(token); //토큰 유효한지 확인.
.parseClaimsJws(token); // 토큰 유효한지 확인 (여기서 만료 시간도 체크)

// 토큰이 유효한 경우
return true;
}catch (ExpiredJwtException e) {
log.warn("[ERROR] Token is expired.");
throw e;
} catch (JwtException e) {
log.warn("[ERROR] Token validation failed: {}", e.getMessage());
throw e;
} catch (Exception e) {
System.out.println("[ERROR] Token validation failed: ");
return false;
}
}

public boolean isTokenExpired(String token) {
try {
Jwts.parserBuilder()
.setSigningKey(SECRET_KEY)
.build()
.parseClaimsJws(token); // 만료된 토큰을 처리하려면 ExpiredJwtException이 발생함
return false; // 만료되지 않으면 false
} catch (ExpiredJwtException ex) {
return true; // 만료된 경우 true
} catch (Exception ex) {
return false; // 다른 예외는 false
throw e;
}
}

Expand Down Expand Up @@ -123,5 +124,4 @@ public Long getUserId(String token){
.getBody()
.getSubject());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.repository.configuration.EnableRedisRepositories;
Expand All @@ -21,34 +22,47 @@
public class RedisConfig {
@Value("${spring.data.redis.host}")
private String host;

@Value("${spring.data.redis.port}")
private int port;
private Integer port;

@Value("${spring.data.redis.password}")
private String password;

@Bean
public RedisConnectionFactory redisConnectionFactory(){
return new LettuceConnectionFactory(host, port);
RedisStandaloneConfiguration config = new RedisStandaloneConfiguration(host, port);
config.setPassword(password);

return new LettuceConnectionFactory(config);
}

@Bean
public RedisTemplate<String,Object> redisTemplate(){
RedisTemplate<String,Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory());
//key,value를 email,authCode로 구현. String으로 직렬화
redisTemplate.setKeySerializer(new StringRedisSerializer());
//redisTemplate.setValueSerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
redisTemplate.setKeySerializer(keySerializer());
redisTemplate.setValueSerializer(valueSerializer());

return redisTemplate;
}

@Bean
public CacheManager contentCacheManager(RedisConnectionFactory cf) {
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer())) // Value Serializer 변경
.entryTtl(Duration.ofMinutes(60L)); // 캐시 수명 10분
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(keySerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(valueSerializer()))
.entryTtl(Duration.ofMinutes(30L));

return RedisCacheManager.RedisCacheManagerBuilder.fromConnectionFactory(cf).cacheDefaults(redisCacheConfiguration).build();
}

private GenericJackson2JsonRedisSerializer valueSerializer() {
return new GenericJackson2JsonRedisSerializer();
}

private StringRedisSerializer keySerializer() {
return new StringRedisSerializer();
}
}

3 changes: 3 additions & 0 deletions default/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,9 @@ dependencies {
implementation ("org.apache.kafka:kafka-streams") // Kafka의 스트림 API를 사용할 때 필요
implementation ("org.apache.kafka:kafka-clients") // Kafka 브로커와 직접 통신하는 기본 클라이언트 라이브러리

// Slack
implementation("com.slack.api:slack-api-client:1.45.3")

//QueryDSL 추가
implementation ("com.querydsl:querydsl-apt:5.0.0")
implementation ("com.querydsl:querydsl-jpa:5.0.0:jakarta")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import org.dfbf.soundlink.domain.alert.repository.AlertRepository;
import org.dfbf.soundlink.global.exception.ErrorCode;
import org.dfbf.soundlink.global.exception.ResponseResult;
import org.dfbf.soundlink.global.slack.service.SlackService;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
Expand All @@ -21,6 +22,7 @@
public class AlertService {

private final AlertRepository alertRepository;
private final SlackService slackService;

// 60 * 1000 * 60 = 3,600,000{ms} = 1시간
private static final Long DEFAULT_TIMEOUT = 60L * 1000 * 60;
Expand Down Expand Up @@ -54,6 +56,7 @@ public SseEmitter connectAlarm(Long id, String lastEventId) {
.data("connect completed!!")
);
} catch (IOException e) {
slackService.sendMsg(id, e.getMessage());
log.error("Error sending ping", e);
}

Expand Down Expand Up @@ -98,6 +101,7 @@ public ResponseResult send(Long userId, String alertName, Object data) {

return new ResponseResult(ErrorCode.SUCCESS);
} catch (IOException e) {
slackService.sendMsg(data, e.getMessage());
alertRepository.delete(userId, emitterId);
return new ResponseResult(ErrorCode.BAD_REQUEST_STATUS, e.getMessage());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import org.dfbf.soundlink.domain.user.repository.UserRepository;
import org.dfbf.soundlink.global.exception.ErrorCode;
import org.dfbf.soundlink.global.exception.ResponseResult;
import org.dfbf.soundlink.global.slack.service.SlackService;
import org.springframework.stereotype.Service;

import java.util.List;
Expand All @@ -25,6 +26,7 @@ public class BlockListService {
private final BlockListQueryRepository blockListQueryRepository;
private final UserRepository userRepository;
private final BlockListRepository blockListRepository;
private final SlackService slackService;

@Transactional
public ResponseResult blockUser(Long userId, BlockReq req) {
Expand Down Expand Up @@ -54,16 +56,19 @@ public ResponseResult blockUser(Long userId, BlockReq req) {
ErrorCode.SUCCESS
);
} catch (BlockedUserNotFound e) {
slackService.sendMsg(req, e.getMessage());
return new ResponseResult(
ErrorCode.BLOCKED_USER_NOT_FOUND,
e.getMessage()
);
} catch (BlockingUserNotFound e) {
slackService.sendMsg(req, e.getMessage());
return new ResponseResult(
ErrorCode.BLOCKING_USER_NOT_FOUND,
e.getMessage()
);
} catch (AlreadyBlockedUser e) {
slackService.sendMsg(req, e.getMessage());
return new ResponseResult(
ErrorCode.ALREADY_BLOCKED_USER,
e.getMessage()
Expand All @@ -84,6 +89,7 @@ public ResponseResult unblockUser(Long userId, Long blocklistId) {
ErrorCode.SUCCESS
);
} catch (BlockingUserNotFound e) {
slackService.sendMsg(blocklistId, e.getMessage());
return new ResponseResult(
ErrorCode.BLOCKING_USER_NOT_FOUND,
e.getMessage()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import org.dfbf.soundlink.global.exception.ResponseResult;
import org.dfbf.soundlink.global.feign.chat.DevChatClient;
import org.dfbf.soundlink.global.kafka.KafkaProducer;
import org.dfbf.soundlink.global.slack.service.SlackService;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.stereotype.Service;
Expand All @@ -51,6 +52,7 @@ public class ChatRoomService {
private final DevChatClient devChatClient;
private final KafkaProducer kafkaProducer;
private final UserStatusService userStatusService;
private final SlackService slackService;

private static final String CHAT_REQUEST_KEY = "chatRequest";
private static final String TOPIC = "alert-topic";
Expand Down Expand Up @@ -111,13 +113,17 @@ public ResponseResult saveRequestToRedis(Long requestUserId, Long emotionRecordI

return new ResponseResult(ErrorCode.SUCCESS);
} catch (IllegalArgumentException e) {
slackService.sendMsg(requestUserId, e.getMessage());
log.info(e.getMessage());
return new ResponseResult(ErrorCode.CHAT_REQUEST_SSE_FAILED);
} catch (EmotionRecordNotFoundException e) {
slackService.sendMsg(requestUserId, e.getMessage());
return new ResponseResult(ErrorCode.FAIL_TO_FIND_EMOTION_RECORD, e.getMessage());
} catch (UserNotFoundException e) {
slackService.sendMsg(requestUserId, e.getMessage());
return new ResponseResult(ErrorCode.FAIL_TO_FIND_USER, e.getMessage());
} catch (Exception e) {
slackService.sendMsg(requestUserId, e.getMessage());
return new ResponseResult(ErrorCode.CHAT_REQUEST_FAILED, e.getMessage());
}
}
Expand All @@ -144,11 +150,14 @@ public ResponseResult deleteRequestFromRedis(Long userId, Long emotionRecordId)
}

} catch (EmotionRecordNotFoundException e) {
slackService.sendMsg(userId, e.getMessage());
return new ResponseResult(ErrorCode.FAIL_TO_FIND_EMOTION_RECORD);
} catch (UserNotFoundException e) {
slackService.sendMsg(userId, e.getMessage());
return new ResponseResult(ErrorCode.FAIL_TO_FIND_USER);
} catch (Exception e) {
log.error(e.getMessage());
slackService.sendMsg(userId, e.getMessage());
return new ResponseResult(400, "Failed to delete the chat request.");
}
}
Expand Down Expand Up @@ -180,10 +189,13 @@ public ResponseResult requestRejected(Long responseUserId, ChatRejectDto chatRej
}

} catch (EmotionRecordNotFoundException e) {
slackService.sendMsg(chatRejectDto, e.getMessage());
return new ResponseResult(ErrorCode.FAIL_TO_FIND_EMOTION_RECORD);
} catch (UserNotFoundException e) {
slackService.sendMsg(chatRejectDto, e.getMessage());
return new ResponseResult(ErrorCode.FAIL_TO_FIND_USER);
} catch (Exception e) {
slackService.sendMsg(chatRejectDto, e.getMessage());
log.error(e.getMessage());
return new ResponseResult(400, "Failed to reject the chat request.");
}
Expand Down Expand Up @@ -266,10 +278,13 @@ public ResponseResult createChatRoom(Long userId, Long recordId, String requestN
return new ResponseResult(400, "ChatRequest not found or expired.");
}
} catch (EmotionRecordNotFoundException e) {
slackService.sendMsg(userId, e.getMessage());
return new ResponseResult(ErrorCode.FAIL_TO_FIND_EMOTION_RECORD, e.getMessage());
} catch (NoUserDataException e) {
slackService.sendMsg(userId, e.getMessage());
return new ResponseResult(ErrorCode.FAIL_TO_FIND_USER, e.getMessage());
} catch (Exception e) {
slackService.sendMsg(userId, e.getMessage());
return new ResponseResult(ErrorCode.INTERNAL_SERVER_ERROR, e.getMessage());
}
}
Expand Down Expand Up @@ -298,6 +313,7 @@ public ResponseResult closeChatRoom(@AuthenticationPrincipal Long userId, Long c

return new ResponseResult(ErrorCode.SUCCESS);
} catch (Exception e) {
slackService.sendMsg(userId, e.getMessage());
return new ResponseResult(ErrorCode.INTERNAL_SERVER_ERROR, e.getMessage());
}
}
Expand All @@ -324,6 +340,7 @@ public ResponseResult getChatRoomList(@AuthenticationPrincipal Long userId) {
.toList();
return new ResponseResult(ErrorCode.SUCCESS, chatRoomList);
} catch (Exception e) {
slackService.sendMsg(userId, e.getMessage());
return new ResponseResult(ErrorCode.INTERNAL_SERVER_ERROR, e.getMessage());
}
}
Expand Down Expand Up @@ -351,10 +368,13 @@ public ResponseResult getChatRoomInfo(Long chatRoomId, Long userId) {
);
return new ResponseResult(ErrorCode.SUCCESS, infoDto);
} catch (ChatRoomNotFoundException e) {
slackService.sendMsg(userId, e.getMessage());
return new ResponseResult(ErrorCode.CHATROOM_NOT_FOUND, "채팅방을 찾을 수 없습니다.");
}catch (UnauthorizedAccessException e) {
slackService.sendMsg(userId, e.getMessage());
return new ResponseResult(ErrorCode.CHAT_UNAUTHORIZED,"권한이 없습니다.");
}catch (Exception e) {
slackService.sendMsg(userId, e.getMessage());
return new ResponseResult(ErrorCode.INTERNAL_SERVER_ERROR, "채팅방 세부 정보를 가져오는 데 실패했습니다.");
}
}
Expand Down
Loading
Loading