Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
6d4d9f0
[Build] 의존성 추가 (#556)
geniusYoo Jun 19, 2025
441d40f
[Feat] 시크릿 파일 읽어오는 어노테이션 추가 (#556)
geniusYoo Jun 19, 2025
e41a212
[Chore] securityConfig 패키지 이동 (#556)
geniusYoo Jun 19, 2025
f7808ba
[Feat] Filter 단에서 자체 Authentication 객체 사용하도록 변경 (#556)
geniusYoo Jun 19, 2025
dfa92a3
[Chore] 패키지 이동 (#556)
geniusYoo Jun 19, 2025
9e4537a
[Feat] 인증 관련 에러 코드 추가 (#556)
geniusYoo Jun 19, 2025
831f926
[Feat] 커스텀 예외 추가 (#556)
geniusYoo Jun 19, 2025
7e4ebf9
[Chore] 삭제 예정인 API 주석 추가 (#556)
geniusYoo Jun 19, 2025
de7c62b
[Feat] 플랫폼 internal API를 위한 Feign 클라이언트 추가 (#556)
geniusYoo Jun 19, 2025
1af92ed
[Feat] 가이드라인에 명시되어있던 클래스 추가 (#556)
geniusYoo Jun 19, 2025
7ea7a2d
[Feat] 가이드라인에 명시되어있던 클래스 추가 (#556)
geniusYoo Jun 19, 2025
8327410
[Chore] 유저 정보를 사용하지 않는 컨트롤러에서 AuthenticationPrincipal 제거 (#556)
geniusYoo Jun 19, 2025
492e892
[Feat] 플랫폼에서 받아오는 유저 정보 응답 dto (#556)
geniusYoo Jun 19, 2025
da5ef0c
[Feat] 플랫폼 internal API를 위한 클래스 구현 및. 유저 정보 반환 API 작성 (#556)
geniusYoo Jun 19, 2025
084af5e
[Feat] 인증 중앙화로 인한 principal 변경 (#556)
geniusYoo Jun 19, 2025
996d8ce
[Feat] 단일 또는 다중 유저 정보를 받아오는 internal API 요청 및 응답 dto 빌드 (#556)
geniusYoo Jun 19, 2025
7d4f3b7
[#558] feat: 최신 게시물 목록 조회 response dto 생성
hyerinhwang-sailin Jul 15, 2025
48b2f71
[#558] feat(PlaygroundClient): /api/v1/community/posts/latest Feign c…
hyerinhwang-sailin Jul 15, 2025
8d55d48
[#558] feat(PlaygroundAuthService): 토큰 기반으로 최근 게시글 목록 요청 및 반환하는 메서드 구현
hyerinhwang-sailin Jul 15, 2025
3499290
[#558] feat(HomeFacade): 내부 서비스 호출 통해 List<PlaygroundRecentPost> 반환하도…
hyerinhwang-sailin Jul 15, 2025
5ca2b7a
[#558] feat(HomeFacade): 컨트롤러에 엔드포인트 추가, DTO 감싸서 반환
hyerinhwang-sailin Jul 15, 2025
6107ab7
[FEAT] 홈화면 플레이그라운드 최신 게시물 목록 조회 api 구현 - #558 (#559)
hyerinhwang-sailin Jul 15, 2025
9b8a8c1
[#550] feat(PlaygroundClient): url 변경 및 getPlaygroundPopularPosts 추가
hyerinhwang-sailin Jul 16, 2025
adbc2a2
[#550] feat: 인기 게시물 목록 조회 dto 추가
hyerinhwang-sailin Jul 16, 2025
30c9d64
[#550] feat(OperationConfigCategory): PLAYGROUND_POST 추가
hyerinhwang-sailin Jul 16, 2025
e6105cf
[#550] feat(PlaygroundAuthService): getPlaygroundPopularPosts 추가
hyerinhwang-sailin Jul 16, 2025
dd67dc1
[#550] feat(HomeFacade): getPlaygroundPopularPosts 추가, getPlaygroundR…
hyerinhwang-sailin Jul 16, 2025
f97ca5f
[#550] feat(PlaygroundRecentPost): post 생성 30일 초과 시 응답값 변환 로직 추가
hyerinhwang-sailin Jul 16, 2025
6de8908
[#550] feat(HomeController): getPopularPosts 추가
hyerinhwang-sailin Jul 16, 2025
0df64f3
[FEAT] 홈화면 플레이그라운드 게시물 관련 api 구현 - #560 (#561)
hyerinhwang-sailin Jul 16, 2025
8d99f83
[Feat] 인증 중앙화 작업
hyerinhwang-sailin Jul 19, 2025
b1eff18
Merge remote-tracking branch 'origin/dev' into feat/#556
hyerinhwang-sailin Jul 19, 2025
279f52a
[Feat] 인증 중앙화 작업
hyerinhwang-sailin Jul 19, 2025
51b1326
[Feat] 인증 중앙화 작업
hyerinhwang-sailin Jul 22, 2025
1c58a5d
feat: 빌드 에러 해결 및 User pk auto_increment 제거
geniusYoo Jul 22, 2025
4a07fbe
[#556] feat: 인증중앙화 추가작업 by 혜린
hyerinhwang-sailin Jul 24, 2025
ffc65ea
[#556] chore(application.yml): 원래대로 복원
hyerinhwang-sailin Jul 25, 2025
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
6 changes: 5 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,14 @@ dependencies {
// aws-s3
implementation 'software.amazon.awssdk:s3'


// retry
implementation 'org.springframework.retry:spring-retry:2.0.6'

// jwt
implementation 'com.nimbusds:nimbus-jose-jwt:9.37.3'
implementation 'com.github.ben-manes.caffeine:caffeine:3.1.8'
implementation 'org.springframework.boot:spring-boot-starter-webflux'

// test
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
Expand Down
3 changes: 3 additions & 0 deletions src/main/java/org/sopt/app/AppApplication.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package org.sopt.app;

import jakarta.annotation.PostConstruct;
import org.sopt.app.common.external.auth.AuthClientProperty;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.scheduling.annotation.EnableAsync;

Expand All @@ -13,6 +15,7 @@
@EnableAsync
@EnableScheduling
@SpringBootApplication
@EnableConfigurationProperties(AuthClientProperty.class)
public class AppApplication {

public static void main(String[] args) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
package org.sopt.app.application.auth;

import lombok.RequiredArgsConstructor;
import org.sopt.app.common.exception.NotFoundException;
import org.sopt.app.common.response.ErrorCode;
import org.sopt.app.interfaces.postgres.UserRepository;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Service;

@RequiredArgsConstructor
@Service
public class CustomUserDetailService implements UserDetailsService {

private final UserRepository userRepository;

public UserDetails loadUserByUsername(String username) throws NotFoundException {
return userRepository.findUserById(Long.parseLong(username))
.orElseThrow(() -> new NotFoundException(ErrorCode.USER_NOT_FOUND));
}
}
// package org.sopt.app.application.auth;
//
// import lombok.RequiredArgsConstructor;
// import org.sopt.app.common.exception.NotFoundException;
// import org.sopt.app.common.response.ErrorCode;
// import org.sopt.app.interfaces.postgres.UserRepository;
// import org.springframework.security.core.userdetails.UserDetails;
// import org.springframework.security.core.userdetails.UserDetailsService;
// import org.springframework.stereotype.Service;
//
// @RequiredArgsConstructor
// @Service
// public class CustomUserDetailService implements UserDetailsService {
//
// private final UserRepository userRepository;
//
// public UserDetails loadUserByUsername(String username) throws NotFoundException {
// return userRepository.findUserById(Long.parseLong(username))
// .orElseThrow(() -> new NotFoundException(ErrorCode.USER_NOT_FOUND));
// }
// }
222 changes: 111 additions & 111 deletions src/main/java/org/sopt/app/application/auth/JwtTokenService.java
Original file line number Diff line number Diff line change
@@ -1,111 +1,111 @@
package org.sopt.app.application.auth;

import io.jsonwebtoken.*;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import java.nio.charset.StandardCharsets;
import java.security.Key;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Base64;
import java.util.Date;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.val;

import org.sopt.app.application.auth.dto.PlaygroundAuthTokenInfo.AppToken;
import org.sopt.app.common.exception.UnauthorizedException;
import org.sopt.app.common.response.ErrorCode;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;

@Component
@RequiredArgsConstructor
public class JwtTokenService {

private final CustomUserDetailService customUserDetailsService;
@Value("${jwt.secret}")
private String JWT_SECRET;

private String encodeKey(String secretKey) {
return Base64.getEncoder().encodeToString(secretKey.getBytes(StandardCharsets.UTF_8));
}

private Key getSigningKey(String keyString) {
val secretKey = this.encodeKey(keyString);
return Keys.hmacShaKeyFor(Decoders.BASE64.decode(secretKey));
}

public AppToken issueNewTokens(Long userId, Long playgroundId) {
val accessToken = this.encodeJwtToken(userId, playgroundId);
val refreshToken = this.encodeJwtRefreshToken(userId);
return AppToken.builder().accessToken(accessToken).refreshToken(refreshToken).build();
}

private String encodeJwtToken(Long userId, Long playgroundId) {
val now = LocalDateTime.now();
val nowDate = Date.from(now.atZone(ZoneId.systemDefault()).toInstant());
val nowDate1PlusDays = Date.from(now.plusDays(1).atZone(ZoneId.systemDefault()).toInstant());
return Jwts.builder()
.setHeaderParam(Header.TYPE, Header.JWT_TYPE)
.setIssuer("sopt-makers")
.setIssuedAt(nowDate)
.setSubject(userId.toString())
.setExpiration(nowDate1PlusDays)
.claim("id", userId)
.claim("playgroundId", playgroundId)
.claim("roles", "USER")
.signWith(getSigningKey(JWT_SECRET), SignatureAlgorithm.HS256)
.compact();
}

private String encodeJwtRefreshToken(Long userId) {
val now = LocalDateTime.now();
val nowDate = Date.from(now.atZone(ZoneId.systemDefault()).toInstant());
val nowDate30PlusDays = Date.from(now.plusDays(30).atZone(ZoneId.systemDefault()).toInstant());
return Jwts.builder()
.setIssuedAt(nowDate)
.setSubject(userId.toString())
.setExpiration(nowDate30PlusDays)
.claim("id", userId)
.claim("roles", "USER")
.signWith(getSigningKey(JWT_SECRET), SignatureAlgorithm.HS256)
.compact();
}

public Long getUserIdFromJwtToken(String token) {
try {
val claims = Jwts.parser()
.setSigningKey(this.encodeKey(JWT_SECRET))
.parseClaimsJws(token)
.getBody();
return Long.parseLong(claims.getSubject());
} catch (ExpiredJwtException e) {
throw new UnauthorizedException(ErrorCode.TOKEN_EXPIRED);
} catch (Exception e) {
throw new UnauthorizedException(ErrorCode.INVALID_ACCESS_TOKEN);
}
}

public Authentication getAuthentication(String token) {
val userDetails = customUserDetailsService.loadUserByUsername(this.getUserIdFromJwtToken(token).toString());
return new UsernamePasswordAuthenticationToken(userDetails, "", userDetails.getAuthorities());
}


public Boolean validateToken(String token) {
try {
val claims = Jwts.parser().setSigningKey(this.encodeKey(JWT_SECRET)).parseClaimsJws(token);

return !claims.getBody().getExpiration().before(new Date());
} catch (Exception e) {
return false;
}
}

public String getToken(HttpServletRequest request) {
return request.getHeader("Authorization");
}
}
// package org.sopt.app.application.auth;
//
// import io.jsonwebtoken.*;
// import io.jsonwebtoken.io.Decoders;
// import io.jsonwebtoken.security.Keys;
// import java.nio.charset.StandardCharsets;
// import java.security.Key;
// import java.time.LocalDateTime;
// import java.time.ZoneId;
// import java.util.Base64;
// import java.util.Date;
// import jakarta.servlet.http.HttpServletRequest;
// import lombok.RequiredArgsConstructor;
// import lombok.val;
//
// import org.sopt.app.application.auth.dto.PlaygroundAuthTokenInfo.AppToken;
// import org.sopt.app.common.exception.UnauthorizedException;
// import org.sopt.app.common.response.ErrorCode;
// import org.springframework.beans.factory.annotation.Value;
// import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
// import org.springframework.security.core.Authentication;
// import org.springframework.stereotype.Component;
//
// @Component
// @RequiredArgsConstructor
// public class JwtTokenService {
//
// private final CustomUserDetailService customUserDetailsService;
// @Value("${jwt.secret}")
// private String JWT_SECRET;
//
// private String encodeKey(String secretKey) {
// return Base64.getEncoder().encodeToString(secretKey.getBytes(StandardCharsets.UTF_8));
// }
//
// private Key getSigningKey(String keyString) {
// val secretKey = this.encodeKey(keyString);
// return Keys.hmacShaKeyFor(Decoders.BASE64.decode(secretKey));
// }
//
// public AppToken issueNewTokens(Long userId, Long playgroundId) {
// val accessToken = this.encodeJwtToken(userId, playgroundId);
// val refreshToken = this.encodeJwtRefreshToken(userId);
// return AppToken.builder().accessToken(accessToken).refreshToken(refreshToken).build();
// }
//
// private String encodeJwtToken(Long userId, Long playgroundId) {
// val now = LocalDateTime.now();
// val nowDate = Date.from(now.atZone(ZoneId.systemDefault()).toInstant());
// val nowDate1PlusDays = Date.from(now.plusDays(1).atZone(ZoneId.systemDefault()).toInstant());
// return Jwts.builder()
// .setHeaderParam(Header.TYPE, Header.JWT_TYPE)
// .setIssuer("sopt-makers")
// .setIssuedAt(nowDate)
// .setSubject(userId.toString())
// .setExpiration(nowDate1PlusDays)
// .claim("id", userId)
// .claim("playgroundId", playgroundId)
// .claim("roles", "USER")
// .signWith(getSigningKey(JWT_SECRET), SignatureAlgorithm.HS256)
// .compact();
// }
//
// private String encodeJwtRefreshToken(Long userId) {
// val now = LocalDateTime.now();
// val nowDate = Date.from(now.atZone(ZoneId.systemDefault()).toInstant());
// val nowDate30PlusDays = Date.from(now.plusDays(30).atZone(ZoneId.systemDefault()).toInstant());
// return Jwts.builder()
// .setIssuedAt(nowDate)
// .setSubject(userId.toString())
// .setExpiration(nowDate30PlusDays)
// .claim("id", userId)
// .claim("roles", "USER")
// .signWith(getSigningKey(JWT_SECRET), SignatureAlgorithm.HS256)
// .compact();
// }
//
// public Long getUserIdFromJwtToken(String token) {
// try {
// val claims = Jwts.parser()
// .setSigningKey(this.encodeKey(JWT_SECRET))
// .parseClaimsJws(token)
// .getBody();
// return Long.parseLong(claims.getSubject());
// } catch (ExpiredJwtException e) {
// throw new UnauthorizedException(ErrorCode.TOKEN_EXPIRED);
// } catch (Exception e) {
// throw new UnauthorizedException(ErrorCode.INVALID_ACCESS_TOKEN);
// }
// }
//
// public Authentication getAuthentication(String token) {
// val userDetails = customUserDetailsService.loadUserByUsername(this.getUserIdFromJwtToken(token).toString());
// return new UsernamePasswordAuthenticationToken(userDetails, "", userDetails.getAuthorities());
// }
//
//
// public Boolean validateToken(String token) {
// try {
// val claims = Jwts.parser().setSigningKey(this.encodeKey(JWT_SECRET)).parseClaimsJws(token);
//
// return !claims.getBody().getExpiration().before(new Date());
// } catch (Exception e) {
// return false;
// }
// }
//
// public String getToken(HttpServletRequest request) {
// return request.getHeader("Authorization");
// }
// }
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@
import java.util.Optional;
import java.util.Set;
import lombok.RequiredArgsConstructor;

import org.sopt.app.application.platform.PlatformService;
import org.sopt.app.application.playground.PlaygroundAuthService;
import org.sopt.app.application.playground.dto.PlaygroundProfileInfo.*;
import org.sopt.app.application.playground.user_finder.PlaygroundUserIdsProvider;
import org.sopt.app.application.user.UserProfile;
import org.sopt.app.application.user.UserService;
import org.sopt.app.common.utils.RandomPicker;
import org.sopt.app.domain.entity.User;
import org.sopt.app.domain.enums.FriendRecommendType;
import org.sopt.app.presentation.poke.PokeResponse.*;
import org.springframework.stereotype.Service;
Expand All @@ -24,22 +25,23 @@ public class FriendRecommender {
private final UserService userService;
private final FriendService friendService;
private final PlaygroundUserIdsProvider playgroundUserIdsProvider;
private final PlatformService platformService;

public RecommendedFriendsRequest recommendFriendsByTypeList(List<FriendRecommendType> typeList, int size,
User user) {
Long userId) {
typeList = this.adjustTypeList(typeList);

OwnPlaygroundProfile ownProfile = playgroundAuthService.getOwnPlaygroundProfile(user.getPlaygroundToken());
OwnPlaygroundProfile ownProfile = playgroundAuthService.getOwnPlaygroundProfile(userId);
FriendFilter friendFilter =
new FriendFilter(friendService.findAllFriendIdsByUserId(user.getId()), user.getId());
new FriendFilter(friendService.findAllFriendIdsByUserId(userId), userId);
List<RecommendedFriendsByType> recommendedFriends = new ArrayList<>();
for (FriendRecommendType type : typeList) {
List<UserProfile> recommendableUserProfiles =
this.getRecommendableUserProfiles(type, ownProfile, friendFilter);
if (!recommendableUserProfiles.isEmpty()) {
List<UserProfile> pickedUserProfiles = RandomPicker.pickRandom(recommendableUserProfiles, size);
List<SimplePokeProfile> simplePokeProfiles =
this.convertUserProfilesToSimplePokeProfiles(pickedUserProfiles, user);
this.convertUserProfilesToSimplePokeProfiles(pickedUserProfiles);

recommendedFriends.add(RecommendedFriendsByType.builder()
.randomType(type)
Expand All @@ -52,10 +54,9 @@ public RecommendedFriendsRequest recommendFriendsByTypeList(List<FriendRecommend
}

private List<SimplePokeProfile> convertUserProfilesToSimplePokeProfiles(
List<UserProfile> pickedUserProfiles, User user) {
List<Long> pickedPlaygroundIds = pickedUserProfiles.stream().map(UserProfile::getPlaygroundId).toList();
List<PlaygroundProfile> pickedPlaygroundProfiles = playgroundAuthService.getPlaygroundMemberProfiles(
user.getPlaygroundToken(), pickedPlaygroundIds);
List<UserProfile> pickedUserProfiles) {
List<Long> pickedPlaygroundIds = pickedUserProfiles.stream().map(UserProfile::getUserId).toList();
List<PlaygroundProfile> pickedPlaygroundProfiles = playgroundAuthService.getPlaygroundMemberProfiles(pickedPlaygroundIds);
return this.makeSimplePokeProfilesForNotFriend(pickedPlaygroundProfiles, pickedUserProfiles);
}

Expand All @@ -64,7 +65,7 @@ private List<SimplePokeProfile> makeSimplePokeProfilesForNotFriend(
) {
return userProfiles.stream()
.map(userProfile -> playgroundProfiles.stream()
.filter(profile -> profile.getMemberId().equals(userProfile.getPlaygroundId()))
.filter(profile -> profile.getMemberId().equals(userProfile.getUserId()))
.findFirst()
.map(playgroundProfile ->
SimplePokeProfile.createNonFriendPokeProfile(playgroundProfile, userProfile))
Expand All @@ -75,14 +76,16 @@ private List<SimplePokeProfile> makeSimplePokeProfilesForNotFriend(

private List<UserProfile> getRecommendableUserProfiles(
FriendRecommendType type, OwnPlaygroundProfile ownProfile, FriendFilter friendFilter) {
Set<Long> playgroundIds;
Set<Long> userIds;
if (type == FriendRecommendType.ALL_USER) {
playgroundIds = Set.copyOf(userService.getAllPlaygroundIds());
userIds = Set.copyOf(userService.getAllUserIds());
} else {
playgroundIds = playgroundUserIdsProvider.findPlaygroundIdsByType(ownProfile, type);
userIds = playgroundUserIdsProvider.findPlaygroundIdsByType(ownProfile, type);
}
List<UserProfile> unFilteredUserProfiles = userService.getUserProfilesByPlaygroundIds(
List.copyOf(playgroundIds));

List <UserProfile> unFilteredUserProfiles = platformService.getPlatformUserInfosResponse(List.copyOf(userIds))
.stream()
.map(user -> UserProfile.of((long)user.userId(), user.name())).toList();
return friendFilter.excludeAlreadyFriendUserIds(unFilteredUserProfiles);
}

Expand Down
Loading