Skip to content

Commit

Permalink
MATE-78 : [FIX] JWT 토큰 생성 및 테스트 코드 검증 (#72)
Browse files Browse the repository at this point in the history
* MATE-78 : [FEAT] 네이버 로그인 서비스 기존 회원 검증 여부 추가

* MATE-78 : [FEAT] 자체 회원가입 DTO 수정

* MATE-78 : [FEAT] 자체 로그인 기능 서비스 구현

* MATE-78 : [FEAT] 자체 로그인 기능 컨트롤러 구현

* MATE-61 : [FEAT] @AuthenticationPrincipal 객체 AuthMember 추가
  • Loading branch information
jooinjoo authored Dec 3, 2024
1 parent 8f2db93 commit e6979a7
Show file tree
Hide file tree
Showing 12 changed files with 135 additions and 54 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.example.mate.common.security.auth;

import com.example.mate.common.error.CustomException;
import com.example.mate.common.error.ErrorCode;
import com.example.mate.domain.member.entity.Member;
import com.example.mate.domain.member.repository.MemberRepository;
import java.security.Principal;
import lombok.Getter;
import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
public class AuthMember implements Principal {

private final String userId;

@Getter
private final Long memberId; // memberId 반환

// 사용자 ID 반환
@Override
public String getName() {
return this.userId;
}

public Member validateAuthMember(MemberRepository memberRepository) {
return memberRepository.findById(memberId)
.orElseThrow(() -> new CustomException(ErrorCode.MEMBER_NOT_FOUND_BY_ID));
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.example.mate.common.security.filter;

import com.example.mate.common.security.auth.CustomUserPrincipal;
import com.example.mate.common.security.auth.AuthMember;
import com.example.mate.common.security.util.JwtUtil;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
Expand Down Expand Up @@ -91,7 +91,7 @@ private void setAuthentication(Map<String, Object> claims) {
Long memberId = Long.valueOf(claims.get("memberId").toString());

UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
new CustomUserPrincipal(userId, memberId),
new AuthMember(userId, memberId),
null, // 이미 인증되었으므로 null
Arrays.stream(roles)
.map(role -> new SimpleGrantedAuthority("ROLE_" + role))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.example.mate.domain.auth.config.OAuthConfig;
import com.example.mate.domain.auth.dto.response.LoginResponse;
import com.example.mate.domain.auth.dto.response.NaverProfileResponse;
import com.example.mate.domain.member.repository.MemberRepository;
import com.nimbusds.jose.shaded.gson.JsonObject;
import com.nimbusds.jose.shaded.gson.JsonParser;
import java.util.Map;
Expand All @@ -24,6 +25,7 @@ public class NaverAuthService {

private final OAuthConfig oAuthConfig;
private final RestTemplate restTemplate;
private final MemberRepository memberRepository;

/**
* 네이버 로그인 연결 URL을 생성
Expand Down Expand Up @@ -154,8 +156,13 @@ private LoginResponse createLoginTokenResponse(Map<String, String> tokens, Naver
.grantType("Bearer")
.accessToken(tokens.get("accessToken"))
.refreshToken(tokens.get("refreshToken"))
.isNewMember(true)
.isNewMember(isNewMemberByEmail(userInfo.getEmail()))
.naverProfileResponse(userInfo)
.build();
}

// email을 통해 새로운 회원인지 검증
private boolean isNewMemberByEmail(String email) {
return !memberRepository.existsByEmail(email);
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package com.example.mate.domain.member.controller;

import com.example.mate.common.jwt.JwtToken;
import com.example.mate.common.response.ApiResponse;
import com.example.mate.common.security.auth.CustomUserPrincipal;
import com.example.mate.common.security.auth.AuthMember;
import com.example.mate.domain.member.dto.request.JoinRequest;
import com.example.mate.domain.member.dto.request.MemberInfoUpdateRequest;
import com.example.mate.domain.member.dto.request.MemberLoginRequest;
import com.example.mate.domain.member.dto.response.JoinResponse;
import com.example.mate.domain.member.dto.response.MemberLoginResponse;
import com.example.mate.domain.member.dto.response.MemberProfileResponse;
import com.example.mate.domain.member.dto.response.MyProfileResponse;
import com.example.mate.domain.member.service.MemberService;
Expand Down Expand Up @@ -56,11 +56,11 @@ public ResponseEntity<ApiResponse<JoinResponse>> join(
*/
@Operation(summary = "CATCH Mi 서비스 로그인", description = "캐치미 서비스에 로그인합니다.")
@PostMapping("/login")
public ResponseEntity<ApiResponse<JwtToken>> catchMiLogin(
public ResponseEntity<ApiResponse<MemberLoginResponse>> catchMiLogin(
@Parameter(description = "회원 로그인 요청 정보", required = true) @Valid @RequestBody MemberLoginRequest request
) {
JwtToken token = memberService.loginByEmail(request);
return ResponseEntity.ok(ApiResponse.success(token));
MemberLoginResponse response = memberService.loginByEmail(request);
return ResponseEntity.ok(ApiResponse.success(response));
}

// TODO : 2024/11/29 - 내 프로필 조회 : 추후 @AuthenticationPrincipal Long memberId 받음
Expand Down Expand Up @@ -101,8 +101,8 @@ public ResponseEntity<Void> deleteMember(@RequestParam Long memberId) {
}

@GetMapping("/test")
public String test(@AuthenticationPrincipal CustomUserPrincipal principal) {
return "principal getName == " + principal.getName() + " || " + "principal getMemberId == "
+ principal.getMemberId();
public String test(@AuthenticationPrincipal AuthMember authMember) {
return "principal getName == " + authMember.getName() + " || " + "principal getMemberId == "
+ authMember.getMemberId();
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package com.example.mate.domain.member.dto.request;

import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Builder;
import lombok.Getter;
Expand All @@ -10,15 +12,29 @@
@Builder
public class JoinRequest {

@Schema(description = "사용자 이름", example = "홍길동")
@NotBlank(message = "이름은 필수 항목입니다.")
@Size(max = 10, message = "이름은 최대 10자까지 입력 가능합니다.")
private String name;

@Schema(description = "사용자 이메일", example = "[email protected]")
@NotBlank(message = "이메일은 필수 항목입니다.")
@Size(max = 40, message = "이메일은 최대 40자까지 입력 가능합니다.")
private String email;

@Schema(description = "사용자 성별", example = "M")
private String gender;

@Schema(description = "사용자 출생연도", example = "2000")
private String birthyear;

@Schema(description = "선택한 마이팀 ID", example = "1")
@Min(value = 0, message = "teamId는 0 이상이어야 합니다.")
@Max(value = 10, message = "teamId는 10 이하이어야 합니다.")
private Long teamId;

@Schema(description = "사용자 닉네임", example = "tester")
@NotBlank(message = "이메일은 필수 항목입니다.")
@Size(max = 20, message = "nickname은 최대 20자까지 입력할 수 있습니다.")
private String nickname;
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,24 @@
package com.example.mate.domain.member.dto.request;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import lombok.AllArgsConstructor;
import jakarta.validation.constraints.Size;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MemberLoginRequest {

@Schema(description = "사용자 이메일", example = "[email protected]")
@NotBlank(message = "이메일은 필수 항목입니다.")
@Size(max = 40, message = "이메일은 최대 40자까지 입력 가능합니다.")
private String email;

@JsonCreator
public MemberLoginRequest(@JsonProperty("email") String email) {
this.email = email;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class JoinResponse {

private Long memberId;
private String name;
private String nickname;
private String email;
Expand All @@ -21,6 +22,7 @@ public class JoinResponse {

public static JoinResponse from(Member member) {
return JoinResponse.builder()
.memberId(member.getId())
.name(member.getName())
.nickname(member.getNickname())
.email(member.getEmail())
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.example.mate.domain.member.dto.response;

import com.example.mate.domain.member.entity.Member;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;

@Builder
@Getter
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class MemberLoginResponse {

private final Long memberId;
private final String grantType;
private final String accessToken;
private final String refreshToken;

// TODO : 파라미터로 JwtToken 추가 및 토큰 매핑
public static MemberLoginResponse from(Member member) {
return MemberLoginResponse.builder()
.memberId(member.getId())
.grantType("Bearer")
.accessToken("accessToken")
.refreshToken("refreshToken")
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,7 @@ public interface MemberRepository extends JpaRepository<Member, Long> {

boolean existsByNickname(String nickname);

boolean existsByEmail(String email);

Optional<Member> findByEmail(String email);
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import com.example.mate.domain.member.dto.request.MemberInfoUpdateRequest;
import com.example.mate.domain.member.dto.request.MemberLoginRequest;
import com.example.mate.domain.member.dto.response.JoinResponse;
import com.example.mate.domain.member.dto.response.MemberLoginResponse;
import com.example.mate.domain.member.dto.response.MemberProfileResponse;
import com.example.mate.domain.member.dto.response.MyProfileResponse;
import com.example.mate.domain.member.entity.Member;
Expand Down Expand Up @@ -48,6 +49,32 @@ public JoinResponse join(JoinRequest request) {
return JoinResponse.from(savedMember);
}

// TODO : JWT 토큰 발급
// 자체 로그인 기능
public MemberLoginResponse loginByEmail(MemberLoginRequest request) {
Member member = findByEmail(request.getEmail());
// 토큰 발급한 뒤 member와 함께 넘기기
JwtToken jwtToken = makeToken(member);
return MemberLoginResponse.from(member);
}

// JWT 토큰 생성
private JwtToken makeToken(Member member) {
Map<String, Object> payloadMap = member.getPayload();
String accessToken = jwtUtil.createToken(payloadMap, 60 * 24 * 3); // 3일 유효
String refreshToken = jwtUtil.createToken(Map.of("memberId", member.getId()), 60 * 24 * 7); // 7일 유효
return JwtToken.builder()
.grantType("Bearer")
.accessToken(accessToken)
.refreshToken(refreshToken)
.build();
}

private Member findByEmail(String email) {
return memberRepository.findByEmail(email)
.orElseThrow(() -> new CustomException(ErrorCode.MEMBER_NOT_FOUND_BY_EMAIL));
}

// TODO : JWT 도입 이후 본인만 접근할 수 있도록 수정
// 내 프로필 조회
public MyProfileResponse getMyProfile(Long memberId) {
Expand Down Expand Up @@ -123,23 +150,4 @@ private Member findByMemberId(Long memberId) {
return memberRepository.findById(memberId)
.orElseThrow(() -> new CustomException(ErrorCode.MEMBER_NOT_FOUND_BY_ID));
}

public JwtToken loginByEmail(MemberLoginRequest request) {
Member member = memberRepository.findByEmail(request.getEmail())
.orElseThrow(() -> new CustomException(ErrorCode.MEMBER_NOT_FOUND_BY_EMAIL));
return makeToken(member);
}

// JWT 토큰 생성
private JwtToken makeToken(Member member) {
Map<String, Object> payloadMap = member.getPayload();
String accessToken = jwtUtil.createToken(payloadMap, 60 * 24 * 3); // 3일 유효
String refreshToken = jwtUtil.createToken(Map.of("memberId", member.getId()), 60 * 24 * 7); // 7일 유효
return JwtToken.builder()
.grantType("Bearer")
.accessToken(accessToken)
.refreshToken(refreshToken)
.build();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import com.example.mate.common.error.ErrorCode;
import com.example.mate.domain.auth.config.OAuthConfig;
import com.example.mate.domain.auth.dto.response.LoginResponse;
import com.example.mate.domain.member.repository.MemberRepository;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
Expand All @@ -34,6 +35,9 @@ public class NaverAuthServiceTest {
@Mock
private RestTemplate restTemplate;

@Mock
private MemberRepository memberRepository;

@Test
@DisplayName("네이버 로그인 연결 URL 생성")
public void getAuthUrl_Success() {
Expand Down

0 comments on commit e6979a7

Please sign in to comment.