Skip to content

Commit

Permalink
v1.2.0 (#262)
Browse files Browse the repository at this point in the history
  • Loading branch information
kdomo authored Jan 31, 2024
2 parents fb4afe1 + c4c7703 commit 261f64d
Show file tree
Hide file tree
Showing 43 changed files with 471 additions and 356 deletions.
2 changes: 1 addition & 1 deletion .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -1 +1 @@
@kdomo @uwoobeat @uiurihappy
* @kdomo @uwoobeat @uiurihappy
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,15 @@ public class AuthController {
private final AuthService authService;
private final CookieUtil cookieUtil;

@Operation(summary = "회원가입", description = "회원가입을 진행합니다.")
@Deprecated
@Operation(summary = "회원가입", description = "회원가입을 진행합니다. (현재 사용하지 않습니다.)")
@PostMapping("/register")
public ResponseEntity<Void> memberRegister(@Valid @RequestBody MemberRegisterRequest request) {
authService.registerMember(request);
// do nothing
return ResponseEntity.ok().build();
}

@Deprecated
@Operation(summary = "아이디/비밀번호 임시 회원가입", description = "아이디/비밀번호 임시 회원가입을 진행합니다.")
@PostMapping("/temp-register")
public ResponseEntity<TokenPairResponse> memberTempRegister(
Expand All @@ -46,6 +48,7 @@ public ResponseEntity<TokenPairResponse> memberTempRegister(
return ResponseEntity.status(HttpStatus.CREATED).headers(tokenHeaders).body(response);
}

@Deprecated
@Operation(summary = "로그인", description = "토큰 발급을 위해 로그인을 진행합니다.")
@PostMapping("/login")
public ResponseEntity<TokenPairResponse> memberLogin(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,11 @@
import com.depromeet.domain.auth.application.nickname.NicknameGenerationStrategy;
import com.depromeet.domain.auth.domain.OauthProvider;
import com.depromeet.domain.auth.dto.request.IdTokenRequest;
import com.depromeet.domain.auth.dto.request.MemberRegisterRequest;
import com.depromeet.domain.auth.dto.request.UsernamePasswordRequest;
import com.depromeet.domain.auth.dto.response.SocialLoginResponse;
import com.depromeet.domain.auth.dto.response.TokenPairResponse;
import com.depromeet.domain.member.dao.MemberRepository;
import com.depromeet.domain.member.domain.Member;
import com.depromeet.domain.member.domain.MemberRole;
import com.depromeet.domain.member.domain.MemberStatus;
import com.depromeet.domain.member.domain.OauthInfo;
import com.depromeet.global.error.exception.CustomException;
Expand All @@ -36,19 +34,14 @@ public class AuthService {
private final IdTokenVerifier idTokenVerifier;
private final NicknameGenerationStrategy nicknameGenerationStrategy;

public void registerMember(MemberRegisterRequest request) {
final Member member = memberUtil.getCurrentMember();
member.register(request.nickname());
}

@Deprecated
public TokenPairResponse registerWithUsernameAndPassword(UsernamePasswordRequest request) {
Optional<Member> member = memberRepository.findByUsername(request.username());

// 첫 회원가입
if (member.isEmpty()) {
String encodedPassword = passwordEncoder.encode(request.password());
final Member savedMember =
Member.createGuestMember(request.username(), encodedPassword);
final Member savedMember = Member.createNormalMember(null, null); // do nothing
memberRepository.save(savedMember);
return getLoginResponse(savedMember);
}
Expand All @@ -74,10 +67,9 @@ public TokenPairResponse loginMember(UsernamePasswordRequest request) {
return getLoginResponse(member);
}

@Deprecated
private void validateNotGuestMember(Member member) {
if (member.getRole() == MemberRole.GUEST) {
throw new CustomException(ErrorCode.GUEST_MEMBER_REQUIRES_REGISTRATION);
}
// do nothing
}

private void validateNormalMember(Member member) {
Expand Down Expand Up @@ -105,23 +97,22 @@ public SocialLoginResponse socialLoginMember(IdTokenRequest request, OauthProvid
member.updateLastLoginAt();

TokenPairResponse loginResponse = getLoginResponse(member);
boolean isGuest = member.getRole() == MemberRole.GUEST;

return SocialLoginResponse.from(loginResponse, isGuest);
return SocialLoginResponse.from(loginResponse);
}

private Member fetchOrCreate(OidcUser oidcUser) {
return memberRepository
.findByOauthInfo(extractOauthInfo(oidcUser))
.orElseGet(() -> saveAsGuest(oidcUser));
.orElseGet(() -> saveMember(oidcUser));
}

private Member saveAsGuest(OidcUser oidcUser) {
private Member saveMember(OidcUser oidcUser) {

OauthInfo oauthInfo = extractOauthInfo(oidcUser);
String nickname = generateRandomNickname();
Member guest = Member.createGuestMember(oauthInfo, nickname);
return memberRepository.save(guest);
Member member = Member.createNormalMember(oauthInfo, nickname);
return memberRepository.save(member);
}

private String generateRandomNickname() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,87 +1,112 @@
package com.depromeet.domain.auth.application;

import static com.depromeet.global.common.constants.SecurityConstants.TOKEN_ROLE_NAME;

import com.depromeet.domain.auth.dao.RefreshTokenRepository;
import com.depromeet.domain.auth.domain.RefreshToken;
import com.depromeet.domain.auth.dto.response.AccessToken;
import com.depromeet.domain.member.dao.MemberRepository;
import com.depromeet.domain.member.domain.Member;
import com.depromeet.domain.auth.dto.AccessTokenDto;
import com.depromeet.domain.auth.dto.RefreshTokenDto;
import com.depromeet.domain.member.domain.MemberRole;
import com.depromeet.global.security.JwtTokenProvider;
import com.depromeet.global.security.PrincipalDetails;
import java.util.NoSuchElementException;
import com.depromeet.global.util.JwtUtil;
import io.jsonwebtoken.ExpiredJwtException;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class JwtTokenService {

private final JwtTokenProvider jwtTokenProvider;
private final MemberRepository memberRepository;
private final JwtUtil jwtUtil;
private final RefreshTokenRepository refreshTokenRepository;

public String createAccessToken(Long memberId, MemberRole memberRole) {
return jwtTokenProvider.generateAccessToken(memberId, memberRole);
return jwtUtil.generateAccessToken(memberId, memberRole);
}

public AccessTokenDto createAccessTokenDto(Long memberId, MemberRole memberRole) {
return jwtUtil.generateAccessTokenDto(memberId, memberRole);
}

public String createRefreshToken(Long memberId) {
String token = jwtTokenProvider.generateRefreshToken(memberId);
String token = jwtUtil.generateRefreshToken(memberId);
RefreshToken refreshToken =
RefreshToken.builder()
.memberId(memberId)
.token(token)
.ttl(jwtTokenProvider.getRefreshTokenExpirationTime())
.ttl(jwtUtil.getRefreshTokenExpirationTime())
.build();
refreshTokenRepository.save(refreshToken);
return token;
}

public String reissueAccessToken(String refreshToken) {
Member member = getMemberFrom(refreshToken);
return createAccessToken(member.getId(), member.getRole());
public RefreshTokenDto createRefreshTokenDto(Long memberId) {
RefreshTokenDto refreshTokenDto = jwtUtil.generateRefreshTokenDto(memberId);
saveRefreshTokenToRedis(memberId, refreshTokenDto.tokenValue(), refreshTokenDto.ttl());
return refreshTokenDto;
}

public String reissueAccessToken(AccessToken accessToken) {
return createAccessToken(accessToken.memberId(), accessToken.memberRole());
private void saveRefreshTokenToRedis(
Long memberId, String refreshTokenDto, Long refreshTokenDto1) {
RefreshToken refreshToken =
RefreshToken.builder()
.memberId(memberId)
.token(refreshTokenDto)
.ttl(refreshTokenDto1)
.build();
refreshTokenRepository.save(refreshToken);
}

public String reissueRefreshToken(String refreshToken) {
Member member = getMemberFrom(refreshToken);
return createRefreshToken(member.getId());
public AccessTokenDto retrieveAccessToken(String accessTokenValue) {
try {
return jwtUtil.parseAccessToken(accessTokenValue);
} catch (Exception e) {
return null;
}
}

public String reissueRefreshToken(AccessToken accessToken) {
return createRefreshToken(accessToken.memberId());
}
public RefreshTokenDto retrieveRefreshToken(String refreshTokenValue) {
RefreshTokenDto refreshTokenDto = parseRefreshToken(refreshTokenValue);

private Member getMemberFrom(String refreshToken) throws NoSuchElementException {
Long memberId = jwtTokenProvider.parseRefreshToken(refreshToken);
return memberRepository.findById(memberId).orElseThrow();
}
if (refreshTokenDto == null) {
return null;
}

public Authentication getAuthentication(String accessToken) {
AccessToken parsedAccessToken = jwtTokenProvider.parseAccessToken(accessToken);
// 파싱된 DTO와 일치하는 토큰이 Redis에 저장되어 있는지 확인
Optional<RefreshToken> refreshToken = getRefreshTokenFromRedis(refreshTokenDto.memberId());

UserDetails userDetails =
new PrincipalDetails(
parsedAccessToken.memberId(), parsedAccessToken.memberRole().name());
// Redis에 토큰이 존재하고, 쿠키의 토큰과 값이 일치하면 DTO 반환
if (refreshToken.isPresent()
&& refreshTokenDto.tokenValue().equals(refreshToken.get().getToken())) {
return refreshTokenDto;
}

return new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
// Redis에 토큰이 존재하지 않거나, 쿠키의 토큰과 값이 일치하지 않으면 null 반환
return null;
}

public boolean isAccessTokenExpired(String accessToken) {
return jwtTokenProvider.isAccessTokenExpired(accessToken);
private Optional<RefreshToken> getRefreshTokenFromRedis(Long memberId) {
return refreshTokenRepository.findById(memberId);
}

public boolean isRefreshTokenExpired(String refreshToken) {
return jwtTokenProvider.isRefreshTokenExpired(refreshToken);
private RefreshTokenDto parseRefreshToken(String refreshTokenValue) {
try {
return jwtUtil.parseRefreshToken(refreshTokenValue);
} catch (Exception e) {
return null;
}
}

public AccessToken parseAccessToken(String accessToken) {
return jwtTokenProvider.parseAccessToken(accessToken);
public AccessTokenDto reissueAccessTokenIfExpired(String accessTokenValue) {
// AT가 만료된 경우 AT 재발급, 만료되지 않은 경우 null 반환
try {
jwtUtil.parseAccessToken(accessTokenValue);
return null;
} catch (ExpiredJwtException e) {
Long memberId = Long.parseLong(e.getClaims().getSubject());
MemberRole memberRole =
MemberRole.valueOf(e.getClaims().get(TOKEN_ROLE_NAME, String.class));
return createAccessTokenDto(memberId, memberRole);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.depromeet.domain.auth.dto;

import com.depromeet.domain.member.domain.MemberRole;

public record AccessTokenDto(Long memberId, MemberRole memberRole, String tokenValue) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package com.depromeet.domain.auth.dto;

public record RefreshTokenDto(Long memberId, String tokenValue, Long ttl) {}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
public record SocialLoginResponse(
@Schema(description = "엑세스 토큰", defaultValue = "accessToken") String accessToken,
@Schema(description = "리프레시 토큰", defaultValue = "refreshToken") String refreshToken,
@Schema(description = "게스트 여부", defaultValue = "true") boolean isGuest) {
@Schema(description = "게스트 여부", defaultValue = "false") boolean isGuest) {

public static SocialLoginResponse from(TokenPairResponse tokenPairResponse, boolean isGuest) {
public static SocialLoginResponse from(TokenPairResponse tokenPairResponse) {
return new SocialLoginResponse(
tokenPairResponse.accessToken(), tokenPairResponse.refreshToken(), isGuest);
tokenPairResponse.accessToken(), tokenPairResponse.refreshToken(), false);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
Expand Down Expand Up @@ -45,10 +46,21 @@ public PresignedUrlResponse memberProfilePresignedUrlCreate(
return imageService.createMemberProfilePresignedUrl(request);
}

@Operation(summary = "회원 프로필 이미지 업로드 완료처리", description = "회원 프로필 이미지 업로드 완료 시 호출하시면 됩니다.")
@Deprecated
@Operation(summary = "회원 프로필 이미지 업로드 완료처리 V1", description = "회원 프로필 이미지 업로드 완료 시 호출하시면 됩니다.")
@PostMapping("/members/me/upload-complete")
public void memberProfileUploaded(
@Valid @RequestBody MemberProfileImageUploadCompleteRequest request) {
imageService.uploadCompleteMemberProfile(request);
}

@Operation(
summary = "회원 프로필 이미지 업로드 완료처리 V2",
description = "V1과 동일합니다. 단, 요청 바디에 닉네임을 넣더라도 무시됩니다.")
@PostMapping("/members/me/upload-complete/v2")
public ResponseEntity<Void> memberProfileUploadedV2(
@Valid @RequestBody MemberProfileImageUploadCompleteRequest request) {
imageService.uploadCompleteMemberProfileV2(request);
return ResponseEntity.ok().build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,27 @@ public void uploadCompleteMemberProfile(MemberProfileImageUploadCompleteRequest
currentMember.updateProfile(Profile.createProfile(request.nickname(), imageUrl));
}

public void uploadCompleteMemberProfileV2(MemberProfileImageUploadCompleteRequest request) {
final Member currentMember = memberUtil.getCurrentMember();
String imageUrl = null;
if (request.imageFileExtension() != null) {
Image image =
findImage(
ImageType.MEMBER_PROFILE,
currentMember.getId(),
request.imageFileExtension());
imageUrl =
createReadImageUrl(
ImageType.MEMBER_PROFILE,
currentMember.getId(),
image.getImageKey(),
request.imageFileExtension());
}
// 닉네임 변경은 무시됩니다 (현재 닉네임을 그대로 사용)
String currentNickname = currentMember.getProfile().getNickname();
currentMember.updateProfile(Profile.createProfile(currentNickname, imageUrl));
}

private Image findImage(
ImageType imageType, Long targetId, ImageFileExtension imageFileExtension) {
return imageRepository
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@

import com.depromeet.domain.image.domain.ImageFileExtension;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;

public record MemberProfileImageUploadCompleteRequest(
@Schema(description = "이미지 파일의 확장자", defaultValue = "JPEG")
ImageFileExtension imageFileExtension,
@NotNull(message = "닉네임은 비워둘 수 없습니다.") @Schema(description = "닉네임", defaultValue = "당근조이")
String nickname) {}
@Schema(description = "닉네임", defaultValue = "당근조이") String nickname) {}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.depromeet.domain.auth.dto.request.UsernameCheckRequest;
import com.depromeet.domain.member.application.MemberService;
import com.depromeet.domain.member.dto.request.NicknameCheckRequest;
import com.depromeet.domain.member.dto.request.NicknameUpdateRequest;
import com.depromeet.domain.member.dto.response.MemberFindOneResponse;
import com.depromeet.domain.member.dto.response.MemberSearchResponse;
import com.depromeet.domain.member.dto.response.MemberSocialInfoResponse;
Expand Down Expand Up @@ -70,4 +71,12 @@ public ResponseEntity<MemberSocialInfoResponse> memberSocialInfoFind() {
MemberSocialInfoResponse response = memberService.findMemberSocialInfo();
return ResponseEntity.ok(response);
}

@Operation(summary = "회원 닉네임 변경", description = "회원 닉네임을 변경합니다.")
@PutMapping("/me/nickname")
public ResponseEntity<Void> memberNicknameUpdate(
@Valid @RequestBody NicknameUpdateRequest reqest) {
memberService.updateMemberNickname(reqest);
return ResponseEntity.ok().build();
}
}
Loading

0 comments on commit 261f64d

Please sign in to comment.