-
Notifications
You must be signed in to change notification settings - Fork 4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
회원가입 및 로그인 & 인가 기능 구현 #82
Changes from 16 commits
076d96d
f3ac5d1
0b5e2ed
aa19fd6
0335db6
6391141
147bad7
149f1ed
b4015f7
b9f6d3e
326f668
37b54f0
861a836
7f6a981
c856828
435348b
fd4730e
9361516
45ef172
3f4a538
b26ea29
33d2a2b
6efed21
1e6ab82
085d37b
b3e2d2d
d0553ba
059534a
bbac2a0
dc3e480
62605a7
c35ae22
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,29 @@ | ||||||
package com.votogether.domain.auth.controller; | ||||||
|
||||||
import com.votogether.domain.auth.dto.LoginResponse; | ||||||
import com.votogether.domain.auth.service.AuthService; | ||||||
import io.swagger.v3.oas.annotations.Operation; | ||||||
import io.swagger.v3.oas.annotations.responses.ApiResponse; | ||||||
import io.swagger.v3.oas.annotations.tags.Tag; | ||||||
import lombok.RequiredArgsConstructor; | ||||||
import org.springframework.http.ResponseEntity; | ||||||
import org.springframework.web.bind.annotation.GetMapping; | ||||||
import org.springframework.web.bind.annotation.RequestParam; | ||||||
import org.springframework.web.bind.annotation.RestController; | ||||||
|
||||||
@Tag(name = "회원가입", description = "회원가입 및 로그인 API") | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 동의합니다. |
||||||
@RequiredArgsConstructor | ||||||
@RestController | ||||||
public class AuthController { | ||||||
|
||||||
private final AuthService authService; | ||||||
|
||||||
@Operation(summary = "카카오 로그인 하기", description = "카카오 계정으로 로그인을 한다.") | ||||||
@ApiResponse(responseCode = "200", description = "로그인 성공") | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 믿으실지 모르겠지만 이거 지적 안나오면 리뷰 재요청하려고 했습니다. |
||||||
@GetMapping("/auth/kakao/callback") | ||||||
public ResponseEntity<LoginResponse> loginByKakao(@RequestParam("code") final String code) { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Q : code와 같이 매핑할 파라미터 이름이 같으면 @RequestParam의 name 속성은 생략해도 되는 것으로 알고 있는데, "code"를 다시 한번 지정해준 이유가 있을까요? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 명시적 표현을 위해서 지정해줬는데 제거하는 것도 깔끔하고 좋아보이네요! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 좋은 지적 감사합니다 ㅎㅎ |
||||||
final String token = authService.register(code); | ||||||
return ResponseEntity.ok(new LoginResponse(token)); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 소셜타입별로 추상화를 해보고자 처음에 이렇게 설계했는데, |
||||||
} | ||||||
|
||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package com.votogether.domain.auth.dto; | ||
|
||
import com.fasterxml.jackson.annotation.JsonProperty; | ||
|
||
public record KakaoMemberResponse( | ||
@JsonProperty("id") Long id, | ||
@JsonProperty("kakao_account") KakaoAccount kakaoAccount | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 꼼꼼한 학습까지 너무 좋습니다 :) |
||
) { | ||
|
||
public record KakaoAccount( | ||
@JsonProperty("email") String email, | ||
@JsonProperty("age_range") String ageRange, | ||
@JsonProperty("birthday") String birthday, | ||
@JsonProperty("gender") String gender | ||
) { | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 해당 record내부에서만 사용할 것이고, 추가적인 확장성이 없다고 판단해서 내부에 선언했습니다. |
||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package com.votogether.domain.auth.dto; | ||
|
||
public record LoginResponse( | ||
String accessToken | ||
) { | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package com.votogether.domain.auth.dto; | ||
|
||
import com.fasterxml.jackson.annotation.JsonProperty; | ||
|
||
public record OAuthAccessTokenResponse( | ||
@JsonProperty("token_type") String tokenType, | ||
@JsonProperty("access_token") String accessToken, | ||
@JsonProperty("expires_in") Integer expiresIn, | ||
@JsonProperty("refresh_token") String refreshToken, | ||
@JsonProperty("refresh_token_expires_in") Integer refreshTokenExpiresIn | ||
) { | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
package com.votogether.domain.auth.service; | ||
|
||
import com.votogether.domain.auth.dto.KakaoMemberResponse; | ||
import com.votogether.domain.member.entity.Member; | ||
import com.votogether.domain.member.service.MemberService; | ||
import com.votogether.global.jwt.TokenProcessor; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.stereotype.Service; | ||
|
||
@RequiredArgsConstructor | ||
@Service | ||
public class AuthService { | ||
|
||
private final KakaoOAuthClient kakaoOAuthClient; | ||
private final MemberService memberService; | ||
private final TokenProcessor tokenProcessor; | ||
|
||
public String register(final String code) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 엄청난 문제가 발생할 것 같네요... |
||
final String accessToken = kakaoOAuthClient.getAccessToken(code); | ||
final KakaoMemberResponse response = kakaoOAuthClient.getMemberInfo(accessToken); | ||
|
||
final Member member = Member.createKakaoMember(response); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 규칙 적용해보겠습니다~ |
||
final Member registeredMember = memberService.register(member); | ||
|
||
return tokenProcessor.generateToken(registeredMember); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 저번에 OAuth 흐름도에 비추어 봤을 때, 이해하기 굉장히 편하도록 추상화가 잘 되어있네요 👍👍 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이해가 잘 되신다니 다행입니다...ㅎㅎ |
||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
package com.votogether.domain.auth.service; | ||
|
||
import com.votogether.domain.auth.dto.KakaoMemberResponse; | ||
import com.votogether.domain.auth.dto.OAuthAccessTokenResponse; | ||
import lombok.Getter; | ||
import org.springframework.boot.context.properties.ConfigurationProperties; | ||
import org.springframework.http.HttpEntity; | ||
import org.springframework.http.HttpHeaders; | ||
import org.springframework.http.HttpMethod; | ||
import org.springframework.http.MediaType; | ||
import org.springframework.stereotype.Component; | ||
import org.springframework.util.LinkedMultiValueMap; | ||
import org.springframework.util.MultiValueMap; | ||
import org.springframework.web.client.RestTemplate; | ||
|
||
@Getter | ||
@ConfigurationProperties(prefix = "oauth.kakao") | ||
@Component | ||
public class KakaoOAuthClient { | ||
|
||
private static final RestTemplate restTemplate = new RestTemplate(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 빈으로 등록하면 추가적인 작업을 커스텀하여 할 수 있을 것 같은데, |
||
|
||
private final MultiValueMap<String, String> info = new LinkedMultiValueMap<>(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Q : Map 대신 MultiValueMap을 사용한 이유가 restTemplate의 postForObject 메서드에 MultiValueMap만 들어갈 수 있어서인가요? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 그런 이유는 아니예요. 보편적으로 MultiValueMap이 사용되는 것입니다. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 맞습니다 yml파일에 있는 환경변수들을 매핑해주기 위해서 사용하고 있습니다. |
||
|
||
public String getAccessToken(final String code) { | ||
info.add("code", code); | ||
|
||
final OAuthAccessTokenResponse response = restTemplate.postForObject( | ||
"https://kauth.kakao.com/oauth/token", | ||
info, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
OAuthAccessTokenResponse.class | ||
); | ||
return response.accessToken(); | ||
} | ||
|
||
public KakaoMemberResponse getMemberInfo(final String accessToken) { | ||
final HttpHeaders headers = new HttpHeaders(); | ||
headers.setBearerAuth(accessToken); | ||
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); | ||
Comment on lines
+37
to
+39
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
final HttpEntity<Void> request = new HttpEntity<>(headers); | ||
|
||
final KakaoMemberResponse response = restTemplate.exchange( | ||
"https://kapi.kakao.com/v2/user/me", | ||
HttpMethod.GET, | ||
request, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 사실 메서드를 여러가지 활용해보기 위함도 있었는데요, 직접 사용해보면서 장단점을 알게되었습니다. |
||
KakaoMemberResponse.class | ||
).getBody(); | ||
return response; | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
package com.votogether.domain.member.entity; | ||
|
||
import com.votogether.domain.auth.dto.KakaoMemberResponse; | ||
import com.votogether.domain.common.BaseEntity; | ||
import jakarta.persistence.Column; | ||
import jakarta.persistence.Entity; | ||
|
@@ -8,7 +9,6 @@ | |
import jakarta.persistence.GeneratedValue; | ||
import jakarta.persistence.GenerationType; | ||
import jakarta.persistence.Id; | ||
import java.time.LocalDateTime; | ||
import lombok.AccessLevel; | ||
import lombok.Builder; | ||
import lombok.Getter; | ||
|
@@ -23,15 +23,18 @@ public class Member extends BaseEntity { | |
@GeneratedValue(strategy = GenerationType.IDENTITY) | ||
private Long id; | ||
|
||
@Column(length = 15, nullable = false) | ||
@Column(length = 15, unique = true, nullable = false) | ||
private String nickname; | ||
|
||
@Enumerated(value = EnumType.STRING) | ||
@Column(length = 20, nullable = false) | ||
private Gender gender; | ||
|
||
@Column(nullable = false) | ||
private LocalDateTime birthDate; | ||
private String ageRange; | ||
|
||
@Column(nullable = false) | ||
private String birthday; | ||
|
||
@Enumerated(value = EnumType.STRING) | ||
@Column(length = 20, nullable = false) | ||
|
@@ -46,20 +49,35 @@ public class Member extends BaseEntity { | |
@Builder | ||
private Member( | ||
final String nickname, | ||
final LocalDateTime birthDate, | ||
final Gender gender, | ||
final String ageRange, | ||
final String birthday, | ||
final SocialType socialType, | ||
final String socialId, | ||
final Integer point | ||
) { | ||
this.nickname = nickname; | ||
this.birthDate = birthDate; | ||
this.gender = gender; | ||
this.ageRange = ageRange; | ||
this.birthday = birthday; | ||
this.socialType = socialType; | ||
this.socialId = socialId; | ||
this.point = point; | ||
} | ||
|
||
public static Member createKakaoMember(final KakaoMemberResponse response) { | ||
final NicknameNumberGenerator nicknameNumberGenerator = new NicknameNumberGenerator(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 레전드 오류를 발견하셨네요... |
||
return Member.builder() | ||
.nickname("익명의 손님" + nicknameNumberGenerator.generate()) | ||
.gender(Gender.valueOf(response.kakaoAccount().gender().toUpperCase())) | ||
.ageRange(response.kakaoAccount().ageRange()) | ||
.birthday(response.kakaoAccount().birthday()) | ||
.socialType(SocialType.KAKAO) | ||
.socialId(String.valueOf(response.id())) | ||
.point(0) | ||
.build(); | ||
} | ||
|
||
public void plusPoint(final int point) { | ||
this.point = this.point + point; | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package com.votogether.domain.member.entity; | ||
|
||
public class NicknameNumberGenerator implements NumberGenerator { | ||
|
||
private int number = 0; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Q 제가 알기로는 number를 static으로 해놔야 위에서 final NicknameNumberGenerator nicknameNumberGenerator = new NicknameNumberGenerator(); 이 코드를 실행해도 증가값이 유지되는 것으로 알고 있습니다. 이 상태라면 위의 createKakaoMember() 메서드에서 new NicknameNumberGenerator() 로 생성될 때마다 number가 0으로 초기화 될 듯 합니다. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 저의 레전드 오류를 찾아주셔서 감사합니다. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. static을 붙여서 서버가 실행되는 도중에는 값이 바뀌지 않도록 변경했습니다. |
||
|
||
@Override | ||
public int generate() { | ||
return ++number; | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package com.votogether.domain.member.entity; | ||
|
||
public interface NumberGenerator { | ||
|
||
int generate(); | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,7 +2,7 @@ | |
|
||
public enum SocialType { | ||
|
||
GOOGLE, | ||
KAKAO, | ||
; | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,12 @@ | ||
package com.votogether.domain.member.repository; | ||
|
||
import com.votogether.domain.member.entity.Member; | ||
import com.votogether.domain.member.entity.SocialType; | ||
import java.util.Optional; | ||
import org.springframework.data.jpa.repository.JpaRepository; | ||
|
||
public interface MemberRepository extends JpaRepository<Member, Long> { | ||
|
||
Optional<Member> findBySocialIdAndSocialType(final String socialId, final SocialType socialType); | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
package com.votogether.domain.member.service; | ||
|
||
import com.votogether.domain.member.entity.Member; | ||
import com.votogether.domain.member.repository.MemberRepository; | ||
import java.util.Optional; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.stereotype.Service; | ||
import org.springframework.transaction.annotation.Transactional; | ||
|
||
@Transactional(readOnly = true) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 어떤 방식이든 통일성있게 간다면 좋을 것 같다는 생각이었습니다. |
||
@RequiredArgsConstructor | ||
@Service | ||
public class MemberService { | ||
|
||
private final MemberRepository memberRepository; | ||
|
||
@Transactional | ||
public Member register(final Member member) { | ||
final Optional<Member> maybeMember = memberRepository.findBySocialIdAndSocialType( | ||
member.getSocialId(), | ||
member.getSocialType() | ||
); | ||
return maybeMember.orElseGet(() -> memberRepository.save(member)); | ||
} | ||
|
||
public Member findById(final Long memberId) { | ||
return memberRepository.findById(memberId) | ||
.orElseThrow(() -> new IllegalArgumentException("해당 Id를 가지 회원은 존재하지 않습니다.")); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P1 : 가지 -> 가진 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 시력테스트였습니다^^ |
||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package com.votogether.global.jwt; | ||
|
||
import java.lang.annotation.ElementType; | ||
import java.lang.annotation.Retention; | ||
import java.lang.annotation.RetentionPolicy; | ||
import java.lang.annotation.Target; | ||
|
||
@Target(ElementType.PARAMETER) | ||
@Retention(RetentionPolicy.RUNTIME) | ||
public @interface Auth { | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Q
: 0.11.5 버전을 사용하신 이유가 궁금합니다!There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
jjwt 최신 버전으로 가져왔습니다!
최신버전이 배포된 지 1년이 지난 시점이라 안정적이게 사용할 수 있을 것이라 판단한 것도 있습니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
배포된지 오래되어야 안정적일까요 아니면 배포가 자주 일어나야 안정적일까요 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
오 그렇게까지 생각을 못해본 것 같아요..!
배포가 자주 일어나는 것이 안정적일 것 같다는 생각이 드네요.
최신버전이 배포된 지 1년이 지난 시점이라 안정적이게 사용할 수 있을 것이라 판단한 것도 있습니다.
에 말을 좀 덧붙이자면최신 버전을 배포하고 1년동안 사람들이 문제없이 사용해왔다면 안정적이라고 할 수 있을 것 같다는 의미였습니다.