Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
86f4eec
refactor: manager, provider, userDetails 적용
nayonsoso Jan 26, 2025
262f2a9
refactor: 패키지 이동
nayonsoso Jan 27, 2025
c5c692c
test: Authentication 테스트 작성
nayonsoso Jan 27, 2025
e017f67
test: userDetailsService 테스트 작성
nayonsoso Jan 27, 2025
7362f1c
test: AuthenticationProvider 테스트 작성
nayonsoso Jan 27, 2025
c98c996
feat: 인증 정보를 객체로 바꾸는 resolver 구현
nayonsoso Jan 27, 2025
a47c9f5
test: resolver 테스트 작성
nayonsoso Jan 27, 2025
e064bed
refactor: 탈퇴한 사용자인지 검증 추가, 함수 분리
nayonsoso Jan 28, 2025
9f1ed60
test: 탈퇴한 사용자 검증 테스트 작성
nayonsoso Jan 28, 2025
7bc4f83
style: 불필요한 개행 삭제
nayonsoso Jan 28, 2025
23073a8
Merge remote-tracking branch 'origin/main' into refactor/add-authenti…
nayonsoso Feb 3, 2025
215f9fa
refactor: 리졸버 이름 변경
nayonsoso Feb 4, 2025
c48b412
feat: 리졸버 활성화
nayonsoso Feb 4, 2025
7a43316
Merge remote-tracking branch 'origin/main' into refactor/add-authenti…
nayonsoso Feb 4, 2025
c7160fb
refactor: 지원서 관련 코드에 리졸버 적용
nayonsoso Feb 4, 2025
69d5408
refactor: 인증 관련 코드에 리졸버 적용
nayonsoso Feb 4, 2025
7c27acf
refactor: 댓글 관련 코드에 리졸버 적용
nayonsoso Feb 4, 2025
4c0c0f1
refactor: 사용자 관련 코드에 리졸버 적용
nayonsoso Feb 4, 2025
9b34811
refactor: 게시글 관련 코드에 리졸버 적용
nayonsoso Feb 4, 2025
65932d6
refactor: 대학 관련 코드에 리졸버 적용
nayonsoso Feb 4, 2025
e182af4
refactor: 성적 관련 코드에 리졸버 적용
nayonsoso Feb 4, 2025
7865789
refactor: S3 관련 코드에 리졸버 적용
nayonsoso Feb 4, 2025
5a0b99e
test: 사용자와 성적이 양방향 관계가 되도록 수정
nayonsoso Feb 4, 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
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import com.example.solidconnection.application.dto.ApplyRequest;
import com.example.solidconnection.application.service.ApplicationQueryService;
import com.example.solidconnection.application.service.ApplicationSubmissionService;
import com.example.solidconnection.custom.resolver.AuthorizedUser;
import com.example.solidconnection.siteuser.domain.SiteUser;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
Expand All @@ -16,8 +18,6 @@
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.security.Principal;

@RequiredArgsConstructor
@RequestMapping("/application")
@RestController
Expand All @@ -29,30 +29,33 @@ public class ApplicationController {
// 지원서 제출하기 api
@PostMapping()
public ResponseEntity<ApplicationSubmissionResponse> apply(
Principal principal,
@Valid @RequestBody ApplyRequest applyRequest) {
boolean result = applicationSubmissionService.apply(principal.getName(), applyRequest);
@AuthorizedUser SiteUser siteUser,
@Valid @RequestBody ApplyRequest applyRequest
) {
boolean result = applicationSubmissionService.apply(siteUser, applyRequest);
return ResponseEntity
.status(HttpStatus.OK)
.body(new ApplicationSubmissionResponse(result));
}

@GetMapping
public ResponseEntity<ApplicationsResponse> getApplicants(
Principal principal,
@AuthorizedUser SiteUser siteUser,
@RequestParam(required = false, defaultValue = "") String region,
@RequestParam(required = false, defaultValue = "") String keyword) {
applicationQueryService.validateSiteUserCanViewApplicants(principal.getName());
ApplicationsResponse result = applicationQueryService.getApplicants(principal.getName(), region, keyword);
@RequestParam(required = false, defaultValue = "") String keyword
) {
applicationQueryService.validateSiteUserCanViewApplicants(siteUser);
ApplicationsResponse result = applicationQueryService.getApplicants(siteUser, region, keyword);
return ResponseEntity
.ok(result);
}

@GetMapping("/competitors")
public ResponseEntity<ApplicationsResponse> getApplicantsForUserCompetitors(
Principal principal) {
applicationQueryService.validateSiteUserCanViewApplicants(principal.getName());
ApplicationsResponse result = applicationQueryService.getApplicantsByUserApplications(principal.getName());
@AuthorizedUser SiteUser siteUser
) {
applicationQueryService.validateSiteUserCanViewApplicants(siteUser);
ApplicationsResponse result = applicationQueryService.getApplicantsByUserApplications(siteUser);
return ResponseEntity
.ok(result);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import com.example.solidconnection.cache.annotation.ThunderingHerdCaching;
import com.example.solidconnection.custom.exception.CustomException;
import com.example.solidconnection.siteuser.domain.SiteUser;
import com.example.solidconnection.siteuser.repository.SiteUserRepository;
import com.example.solidconnection.type.VerifyStatus;
import com.example.solidconnection.university.domain.University;
import com.example.solidconnection.university.domain.UniversityInfoForApply;
Expand All @@ -34,7 +33,6 @@ public class ApplicationQueryService {

private final ApplicationRepository applicationRepository;
private final UniversityInfoForApplyRepository universityInfoForApplyRepository;
private final SiteUserRepository siteUserRepository;
private final UniversityFilterRepositoryImpl universityFilterRepository;

@Value("${university.term}")
Expand All @@ -49,9 +47,7 @@ public class ApplicationQueryService {
* */
@Transactional(readOnly = true)
@ThunderingHerdCaching(key = "application:query:{1}:{2}", cacheManager = "customCacheManager", ttlSec = 86400)
public ApplicationsResponse getApplicants(String email, String regionCode, String keyword) {
SiteUser siteUser = siteUserRepository.getByEmail(email);

public ApplicationsResponse getApplicants(SiteUser siteUser, String regionCode, String keyword) {
// 국가와 키워드와 지역을 통해 대학을 필터링한다.
List<University> universities
= universityFilterRepository.findByRegionCodeAndKeywords(regionCode, List.of(keyword));
Expand All @@ -64,9 +60,7 @@ public ApplicationsResponse getApplicants(String email, String regionCode, Strin
}

@Transactional(readOnly = true)
public ApplicationsResponse getApplicantsByUserApplications(String email) {
SiteUser siteUser = siteUserRepository.getByEmail(email);

public ApplicationsResponse getApplicantsByUserApplications(SiteUser siteUser) {
Application userLatestApplication = applicationRepository.getApplicationBySiteUserAndTerm(siteUser, term);
List<University> userAppliedUniversities = Arrays.asList(
Optional.ofNullable(userLatestApplication.getFirstChoiceUniversity())
Expand All @@ -91,8 +85,7 @@ public ApplicationsResponse getApplicantsByUserApplications(String email) {
// 학기별로 상태가 관리된다.
// 금학기에 지원이력이 있는 사용자만 지원정보를 확인할 수 있도록 한다.
@Transactional(readOnly = true)
public void validateSiteUserCanViewApplicants(String email) {
SiteUser siteUser = siteUserRepository.getByEmail(email);
public void validateSiteUserCanViewApplicants(SiteUser siteUser) {
VerifyStatus verifyStatus = applicationRepository.getApplicationBySiteUserAndTerm(siteUser, term).getVerifyStatus();
if (verifyStatus != VerifyStatus.APPROVED) {
throw new CustomException(APPLICATION_NOT_APPROVED);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ public class ApplicationSubmissionService {

private final ApplicationRepository applicationRepository;
private final UniversityInfoForApplyRepository universityInfoForApplyRepository;
private final SiteUserRepository siteUserRepository;
private final GpaScoreRepository gpaScoreRepository;
private final LanguageTestScoreRepository languageTestScoreRepository;

Expand All @@ -48,8 +47,7 @@ public class ApplicationSubmissionService {
// 학점 및 어학성적이 모두 유효한 경우에만 지원서 등록이 가능하다.
// 기존에 있던 status field 우선 APRROVED로 입력시킨다.
@Transactional
public boolean apply(String email, ApplyRequest applyRequest) {
SiteUser siteUser = siteUserRepository.getByEmail(email);
public boolean apply(SiteUser siteUser, ApplyRequest applyRequest) {
UniversityChoiceRequest universityChoiceRequest = applyRequest.universityChoiceRequest();
validateUniversityChoices(universityChoiceRequest);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@
import com.example.solidconnection.auth.service.AuthService;
import com.example.solidconnection.auth.service.SignInService;
import com.example.solidconnection.auth.service.SignUpService;
import com.example.solidconnection.custom.resolver.AuthorizedUser;
import com.example.solidconnection.custom.resolver.ExpiredToken;
import com.example.solidconnection.custom.security.authentication.ExpiredTokenAuthentication;
import com.example.solidconnection.siteuser.domain.SiteUser;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
Expand All @@ -17,8 +21,6 @@
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.security.Principal;

@RequiredArgsConstructor
@RequestMapping("/auth")
@RestController
Expand All @@ -29,32 +31,42 @@ public class AuthController {
private final SignInService signInService;

@PostMapping("/kakao")
public ResponseEntity<KakaoOauthResponse> processKakaoOauth(@RequestBody KakaoCodeRequest kakaoCodeRequest) {
public ResponseEntity<KakaoOauthResponse> processKakaoOauth(
@RequestBody KakaoCodeRequest kakaoCodeRequest
) {
KakaoOauthResponse kakaoOauthResponse = signInService.signIn(kakaoCodeRequest);
return ResponseEntity.ok(kakaoOauthResponse);
}

@PostMapping("/sign-up")
public ResponseEntity<SignUpResponse> signUp(@Valid @RequestBody SignUpRequest signUpRequest) {
public ResponseEntity<SignUpResponse> signUp(
@Valid @RequestBody SignUpRequest signUpRequest
) {
SignUpResponse signUpResponseDto = signUpService.signUp(signUpRequest);
return ResponseEntity.ok(signUpResponseDto);
}

@PostMapping("/sign-out")
public ResponseEntity<Void> signOut(Principal principal) {
authService.signOut(principal.getName());
public ResponseEntity<Void> signOut(
@ExpiredToken ExpiredTokenAuthentication expiredToken
) {
authService.signOut(expiredToken.getToken());
return ResponseEntity.ok().build();
}

@PatchMapping("/quit")
public ResponseEntity<Void> quit(Principal principal) {
authService.quit(principal.getName());
public ResponseEntity<Void> quit(
@AuthorizedUser SiteUser siteUser
) {
authService.quit(siteUser);
return ResponseEntity.ok().build();
}

@PostMapping("/reissue")
public ResponseEntity<ReissueResponse> reissueToken(Principal principal) {
ReissueResponse reissueResponse = authService.reissue(principal.getName());
public ResponseEntity<ReissueResponse> reissueToken(
@ExpiredToken ExpiredTokenAuthentication expiredToken
) {
ReissueResponse reissueResponse = authService.reissue(expiredToken.getSubject());
return ResponseEntity.ok(reissueResponse);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import com.example.solidconnection.auth.dto.ReissueResponse;
import com.example.solidconnection.custom.exception.CustomException;
import com.example.solidconnection.siteuser.domain.SiteUser;
import com.example.solidconnection.siteuser.repository.SiteUserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
Expand All @@ -23,11 +22,8 @@
@Service
public class AuthService {

public static final String SIGN_OUT_VALUE = "signOut";

private final RedisTemplate<String, String> redisTemplate;
private final TokenProvider tokenProvider;
private final SiteUserRepository siteUserRepository;

/*
* 로그아웃 한다.
Expand All @@ -48,8 +44,7 @@ public void signOut(String accessToken) {
* - e.g. 2024-01-01 18:00 탈퇴 시, 2024-01-02 00:00 가 탈퇴일이 된다.
* */
@Transactional
public void quit(String email) {
SiteUser siteUser = siteUserRepository.getByEmail(email);
public void quit(SiteUser siteUser) {
LocalDate tomorrow = LocalDate.now().plusDays(1);
siteUser.setQuitedAt(tomorrow);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,15 @@
import com.example.solidconnection.auth.dto.kakao.KakaoOauthResponse;
import com.example.solidconnection.auth.dto.kakao.KakaoUserInfoDto;
import com.example.solidconnection.auth.domain.TokenType;
import com.example.solidconnection.siteuser.domain.AuthType;
import com.example.solidconnection.siteuser.domain.SiteUser;
import com.example.solidconnection.siteuser.repository.SiteUserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Optional;

@RequiredArgsConstructor
@Service
public class SignInService {
Expand All @@ -35,30 +38,30 @@ public class SignInService {
public KakaoOauthResponse signIn(KakaoCodeRequest kakaoCodeRequest) {
KakaoUserInfoDto kakaoUserInfoDto = kakaoOAuthClient.processOauth(kakaoCodeRequest.code());
String email = kakaoUserInfoDto.kakaoAccountDto().email();
boolean isAlreadyRegistered = siteUserRepository.existsByEmail(email);
Optional<SiteUser> optionalSiteUser = siteUserRepository.findByEmailAndAuthType(email, AuthType.KAKAO);

if (isAlreadyRegistered) {
resetQuitedAt(email);
return getSignInInfo(email);
if (optionalSiteUser.isPresent()) {
SiteUser siteUser = optionalSiteUser.get();
resetQuitedAt(siteUser);
return getSignInInfo(siteUser);
}

return getFirstAccessInfo(kakaoUserInfoDto);
}

// 계적 복구 기한이 지난 회원은 자정마다 삭제된다. (UserRemovalScheduler 참고)
// 따라서 DB 에서 조회되었다면 아직 기한이 지나지 않았다는 뜻이므로, 탈퇴 날짜를 초기화한다.
private void resetQuitedAt(String email) {
SiteUser siteUser = siteUserRepository.getByEmail(email);
private void resetQuitedAt(SiteUser siteUser) {
if (siteUser.getQuitedAt() == null) {
return;
}

siteUser.setQuitedAt(null);
}

private SignInResponse getSignInInfo(String email) {
String accessToken = tokenProvider.generateToken(email, TokenType.ACCESS);
String refreshToken = tokenProvider.generateToken(email, TokenType.REFRESH);
private SignInResponse getSignInInfo(SiteUser siteUser) {
String accessToken = tokenProvider.generateToken(siteUser, TokenType.ACCESS);
String refreshToken = tokenProvider.generateToken(siteUser, TokenType.REFRESH);
tokenProvider.saveToken(refreshToken, TokenType.REFRESH);
return new SignInResponse(true, accessToken, refreshToken);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import com.example.solidconnection.repositories.InterestedCountyRepository;
import com.example.solidconnection.repositories.InterestedRegionRepository;
import com.example.solidconnection.repositories.RegionRepository;
import com.example.solidconnection.siteuser.domain.AuthType;
import com.example.solidconnection.siteuser.domain.SiteUser;
import com.example.solidconnection.siteuser.repository.SiteUserRepository;
import com.example.solidconnection.type.Role;
Expand Down Expand Up @@ -45,6 +46,7 @@ public class SignUpService {
* - 관심 국가와 지역은 site_user_id를 참조하므로, 사용자 저장 후 저장한다.
* - 바로 로그인하도록 액세스 토큰과 리프레시 토큰을 발급한다.
* */
// todo: 여러가지 가입 방법 적용해야 함
Copy link
Collaborator Author

@nayonsoso nayonsoso Feb 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 PR에서는 SignUpService 나 SignInService 같은 코드들을 가입의 종류가 다양하도록 바꾸진 않았어요.
그래서 일단 카카오 인증만 다루면서 함수 이름이 signUp인 등.. 만족스럽지 않은 부분들이 있긴 한데,
이 부분은 다른 가입 방식을 추가하며 구현해보겠습니다.

@Transactional
public SignUpResponse signUp(SignUpRequest signUpRequest) {
// 검증
Expand All @@ -62,14 +64,14 @@ public SignUpResponse signUp(SignUpRequest signUpRequest) {
saveInterestedCountry(signUpRequest, savedSiteUser);

// 토큰 발급
String accessToken = tokenProvider.generateToken(email, TokenType.ACCESS);
String refreshToken = tokenProvider.generateToken(email, TokenType.REFRESH);
String accessToken = tokenProvider.generateToken(siteUser, TokenType.ACCESS);
String refreshToken = tokenProvider.generateToken(siteUser, TokenType.REFRESH);
tokenProvider.saveToken(refreshToken, TokenType.REFRESH);
return new SignUpResponse(accessToken, refreshToken);
}

private void validateUserNotDuplicated(String email) {
if (siteUserRepository.existsByEmail(email)) {
if (siteUserRepository.existsByEmailAndAuthType(email, AuthType.KAKAO)) {
throw new CustomException(USER_ALREADY_EXISTED);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.example.solidconnection.auth.domain.TokenType;
import com.example.solidconnection.config.security.JwtProperties;
import com.example.solidconnection.siteuser.domain.SiteUser;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
Expand All @@ -12,8 +13,8 @@
import java.util.Date;
import java.util.concurrent.TimeUnit;

import static com.example.solidconnection.util.JwtUtils.parseSubjectIgnoringExpiration;
import static com.example.solidconnection.util.JwtUtils.parseSubject;
import static com.example.solidconnection.util.JwtUtils.parseSubjectIgnoringExpiration;

@RequiredArgsConstructor
@Component
Expand All @@ -22,8 +23,13 @@ public class TokenProvider {
private final RedisTemplate<String, String> redisTemplate;
private final JwtProperties jwtProperties;

public String generateToken(String email, TokenType tokenType) {
Claims claims = Jwts.claims().setSubject(email);
public String generateToken(SiteUser siteUser, TokenType tokenType) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오버로딩을 하신건 아직 email을 사용하는 로직이 남아있어서 그런건가요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아뇨 가입 시 발급하는 토큰을 만들 때, String을 인자로 받아서 그랬습니다. 이 부분 헷갈릴 수 있으니 분리하는게 좋겠네요! 짚어주셔서 감사합니다😊

String subject = siteUser.getId().toString();
return generateToken(subject, tokenType);
}

public String generateToken(String string, TokenType tokenType) {
Claims claims = Jwts.claims().setSubject(string);
Date now = new Date();
Date expiredDate = new Date(now.getTime() + tokenType.getExpireTime());
return Jwts.builder()
Expand Down
Loading