-
Notifications
You must be signed in to change notification settings - Fork 0
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
[feat #4] : 시큐리티 세팅 및 소셜 로그인 API 구현 #13
Changes from all commits
23591d7
1a86bf4
be602ff
4c542bb
4a5dab1
87ab12a
8fbffb3
df1d40e
3c65791
4351bf7
e5e4127
00bbd45
546755c
a14d0f7
41665d5
2cdd4b7
e706c88
d3b7fe7
6435102
1859885
d6189ed
60de8a7
e6e2b26
e723028
e314026
55d4697
f658a13
c6c62e9
e7eb2d6
19a3123
33bf7df
4762d46
e8fa553
2492908
9926aa2
449ae2b
004e4ed
147c8d0
037e74c
46ce511
dfeacfc
63594c5
30ca76e
6a2fa1d
8d8cbec
0da91e5
32bf08b
f850cc0
ca4a3d9
043fbdf
320eeed
a304057
7873142
4079520
9234f33
b4f1165
c9d5ce7
0dd84ad
210147b
d7f3332
326d708
271b595
ff3ca70
740a4dd
88b629b
d9403ec
9b68deb
fc4ba01
ab67793
f594720
42a30c0
7c0a8a5
99510b9
d7eb1ad
c4b6e0d
75a63fa
95a5875
90a4ee3
90745c3
9006df8
9bac4dc
1c70ad0
288f1a1
2d1b09b
7e50d2e
4806bcb
e99cf34
8b58c67
adce9d9
e9808eb
2fb8677
f12de11
3f3ae1f
128ce98
250b9ca
b565df1
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,27 @@ | ||
package com.dnd.gongmuin.auth.cotroller; | ||
|
||
import java.net.URI; | ||
|
||
import org.springframework.http.HttpHeaders; | ||
import org.springframework.http.HttpStatus; | ||
import org.springframework.http.ResponseEntity; | ||
import org.springframework.web.bind.annotation.GetMapping; | ||
import org.springframework.web.bind.annotation.RequestMapping; | ||
import org.springframework.web.bind.annotation.RestController; | ||
|
||
import lombok.RequiredArgsConstructor; | ||
|
||
@RestController | ||
@RequiredArgsConstructor | ||
@RequestMapping("/api/auth") | ||
public class AuthController { | ||
|
||
@GetMapping("signin/kakao") | ||
public ResponseEntity<?> kakaoLoginRedirect() { | ||
HttpHeaders httpHeaders = new HttpHeaders(); | ||
// 카카오 로그인 페이지로 리다이렉트 | ||
httpHeaders.setLocation(URI.create("/oauth2/authorization/kakao")); | ||
return new ResponseEntity<>(httpHeaders, HttpStatus.MOVED_PERMANENTLY); | ||
} | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
package com.dnd.gongmuin.auth.domain; | ||
|
||
import static jakarta.persistence.ConstraintMode.*; | ||
|
||
import com.dnd.gongmuin.member.domain.Member; | ||
|
||
import jakarta.persistence.Column; | ||
import jakarta.persistence.Entity; | ||
import jakarta.persistence.EnumType; | ||
import jakarta.persistence.Enumerated; | ||
import jakarta.persistence.FetchType; | ||
import jakarta.persistence.ForeignKey; | ||
import jakarta.persistence.GeneratedValue; | ||
import jakarta.persistence.GenerationType; | ||
import jakarta.persistence.Id; | ||
import jakarta.persistence.JoinColumn; | ||
import jakarta.persistence.OneToOne; | ||
import lombok.AccessLevel; | ||
import lombok.Builder; | ||
import lombok.Getter; | ||
import lombok.NoArgsConstructor; | ||
|
||
@Entity | ||
@NoArgsConstructor(access = AccessLevel.PROTECTED) | ||
@Getter | ||
public class Auth { | ||
|
||
@Id | ||
@GeneratedValue(strategy = GenerationType.IDENTITY) | ||
private Long id; | ||
|
||
@Enumerated(EnumType.STRING) | ||
@Column(name = "provider", nullable = false) | ||
private Provider provider; | ||
|
||
@Enumerated(EnumType.STRING) | ||
@Column(name = "status", nullable = false) | ||
private AuthStatus status; | ||
|
||
@OneToOne(fetch = FetchType.LAZY) | ||
@JoinColumn(name = "member_id", | ||
nullable = false, | ||
foreignKey = @ForeignKey(NO_CONSTRAINT)) | ||
private Member member; | ||
|
||
@Builder | ||
private Auth(Provider provider, AuthStatus status, Member member) { | ||
this.provider = provider; | ||
this.status = status; | ||
this.member = member; | ||
} | ||
|
||
public static Auth of(Provider provider, AuthStatus status, Member member) { | ||
return Auth.builder() | ||
.provider(provider) | ||
.status(status) | ||
.member(member) | ||
.build(); | ||
} | ||
|
||
public void updateStatus() { | ||
this.status = AuthStatus.OLD; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package com.dnd.gongmuin.auth.domain; | ||
|
||
import lombok.Getter; | ||
import lombok.RequiredArgsConstructor; | ||
|
||
@Getter | ||
@RequiredArgsConstructor | ||
public enum AuthStatus { | ||
|
||
NEW("신규"), | ||
OLD("기존"); | ||
|
||
private final String label; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
package com.dnd.gongmuin.auth.domain; | ||
|
||
import java.util.Arrays; | ||
|
||
import com.dnd.gongmuin.auth.exception.AuthErrorCode; | ||
import com.dnd.gongmuin.common.exception.runtime.NotFoundException; | ||
|
||
import lombok.Getter; | ||
import lombok.RequiredArgsConstructor; | ||
|
||
@Getter | ||
@RequiredArgsConstructor | ||
public enum Provider { | ||
|
||
KAKAO("kakao"), | ||
NAVER("naver"); | ||
|
||
private final String provider; | ||
|
||
public static Provider fromProviderName(String providerName) { | ||
return Arrays.stream(values()) | ||
.filter(provider -> provider.getProvider().equalsIgnoreCase(providerName)) | ||
.findFirst() | ||
.orElseThrow(() -> new NotFoundException(AuthErrorCode.NOT_FOUND_PROVIDER)); | ||
} | ||
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. 이것도 정적 팩토리 메서드의 일종인 것 같은데 다른 enum들처럼 네이밍을 통일해도 괜찮을 것 같습니다! 이 글을 보고 아예 from으로 통일할까도 생각이 드네요 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. 정적 팩토리 메서드에 대해 새로 논의하여 결정된 부분으로 Member, Auth 등 Builder를 통한 객체 생성 -> 정적 팩토리 메서드를 이용한 객체 생성으로 수정했습니다! |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package com.dnd.gongmuin.auth.exception; | ||
|
||
import com.dnd.gongmuin.common.exception.ErrorCode; | ||
|
||
import lombok.Getter; | ||
import lombok.RequiredArgsConstructor; | ||
|
||
@Getter | ||
@RequiredArgsConstructor | ||
public enum AuthErrorCode implements ErrorCode { | ||
|
||
UNSUPPORTED_SOCIAL_LOGIN("해당 소셜 로그인은 지원되지 않습니다.", "AUHT_001"), | ||
NOT_FOUND_PROVIDER("알맞은 Provider를 찾을 수 없습니다.", "AUTH_002"), | ||
NOT_FOUND_AUTH("회원의 AUTH를 찾을 수 없습니다.", "AUTH_003"); | ||
|
||
private final String message; | ||
private final String code; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package com.dnd.gongmuin.auth.repository; | ||
|
||
import java.util.Optional; | ||
|
||
import org.springframework.data.jpa.repository.JpaRepository; | ||
|
||
import com.dnd.gongmuin.auth.domain.Auth; | ||
import com.dnd.gongmuin.member.domain.Member; | ||
|
||
public interface AuthRepository extends JpaRepository<Auth, Long> { | ||
|
||
Optional<Auth> findByMember(Member member); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
package com.dnd.gongmuin.auth.service; | ||
|
||
import java.util.Objects; | ||
|
||
import org.springframework.stereotype.Service; | ||
|
||
import com.dnd.gongmuin.auth.domain.Auth; | ||
import com.dnd.gongmuin.auth.domain.AuthStatus; | ||
import com.dnd.gongmuin.auth.domain.Provider; | ||
import com.dnd.gongmuin.auth.exception.AuthErrorCode; | ||
import com.dnd.gongmuin.auth.repository.AuthRepository; | ||
import com.dnd.gongmuin.common.exception.runtime.NotFoundException; | ||
import com.dnd.gongmuin.member.domain.Member; | ||
import com.dnd.gongmuin.member.service.MemberService; | ||
|
||
import lombok.RequiredArgsConstructor; | ||
|
||
@Service | ||
@RequiredArgsConstructor | ||
public class AuthService { | ||
|
||
private final AuthRepository authRepository; | ||
private final MemberService memberService; | ||
|
||
public void saveOrUpdate(Member savedMember) { | ||
hyun2371 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
Auth findedOrCreatedAuth = authRepository.findByMember(savedMember) | ||
.map(auth -> { | ||
if (!memberService.isOfficialEmail(savedMember)) { | ||
auth.updateStatus(); | ||
} | ||
return auth; | ||
}) | ||
.orElse(createAuth(savedMember)); | ||
|
||
authRepository.save(findedOrCreatedAuth); | ||
} | ||
|
||
public boolean isAuthStatusOld(Member member) { | ||
Auth findAuth = authRepository.findByMember(member) | ||
.orElseThrow(() -> new NotFoundException(AuthErrorCode.NOT_FOUND_AUTH)); | ||
|
||
return Objects.equals(findAuth.getStatus(), AuthStatus.OLD); | ||
} | ||
|
||
private Auth createAuth(Member savedMember) { | ||
String providerName = memberService.parseProviderFromSocialEmail(savedMember); | ||
Provider provider = Provider.fromProviderName(providerName); | ||
|
||
return Auth.of(provider, AuthStatus.NEW, savedMember); | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package com.dnd.gongmuin.common.exception.runtime; | ||
|
||
import com.dnd.gongmuin.common.exception.ErrorCode; | ||
|
||
import lombok.Getter; | ||
|
||
@Getter | ||
public class CustomJwtException extends RuntimeException { | ||
|
||
private final String code; | ||
|
||
public CustomJwtException(ErrorCode errorCode) { | ||
super(errorCode.getMessage()); | ||
this.code = errorCode.getCode(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
package com.dnd.gongmuin.member.controller; | ||
|
||
import org.springframework.http.ResponseEntity; | ||
import org.springframework.security.core.annotation.AuthenticationPrincipal; | ||
import org.springframework.web.bind.annotation.PostMapping; | ||
import org.springframework.web.bind.annotation.RequestBody; | ||
import org.springframework.web.bind.annotation.RequestMapping; | ||
import org.springframework.web.bind.annotation.RestController; | ||
|
||
import com.dnd.gongmuin.member.dto.request.AdditionalInfoRequest; | ||
import com.dnd.gongmuin.member.dto.request.ValidateNickNameRequest; | ||
import com.dnd.gongmuin.member.dto.response.SignUpResponse; | ||
import com.dnd.gongmuin.member.dto.response.ValidateNickNameResponse; | ||
import com.dnd.gongmuin.member.service.MemberService; | ||
import com.dnd.gongmuin.security.oauth2.CustomOauth2User; | ||
|
||
import lombok.RequiredArgsConstructor; | ||
|
||
@RestController | ||
@RequiredArgsConstructor | ||
@RequestMapping("/api/auth") | ||
public class MemberController { | ||
|
||
private final MemberService memberService; | ||
|
||
@PostMapping("/check-nickname") | ||
public ResponseEntity<ValidateNickNameResponse> checkNickName( | ||
@RequestBody ValidateNickNameRequest validateNickNameRequest) { | ||
return ResponseEntity.ok(memberService.isDuplicatedNickname(validateNickNameRequest)); | ||
} | ||
|
||
@PostMapping("/member") | ||
public ResponseEntity<SignUpResponse> signUp(@RequestBody AdditionalInfoRequest request, | ||
@AuthenticationPrincipal CustomOauth2User loginMember) { | ||
SignUpResponse response = memberService.signUp(request, loginMember.getEmail()); | ||
|
||
return ResponseEntity.ok(response); | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -25,31 +25,31 @@ public class Member extends TimeBaseEntity { | |
@Column(name = "member_id") | ||
private Long id; | ||
|
||
@Column(name = "nickname", nullable = false) | ||
@Column(name = "nickname") | ||
private String nickname; | ||
|
||
@Column(name = "social_name", nullable = false) | ||
private String socialName; | ||
|
||
@Enumerated(STRING) | ||
@Column(name = "job_group", nullable = false) | ||
@Column(name = "job_group") | ||
private JobGroup jobGroup; | ||
|
||
@Enumerated(STRING) | ||
@Column(name = "job_category", nullable = false) | ||
@Column(name = "job_category") | ||
private JobCategory jobCategory; | ||
|
||
@Column(name = "social_email", nullable = false) | ||
private String socialEmail; | ||
|
||
@Column(name = "official_email", nullable = false) | ||
@Column(name = "official_email") | ||
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. 지난 논의 사항 때 회원 1자 저장을 위해 officialEmail 필드를 포함한 추가 정보 관련 필드는 null 값 허용을 위해 제거했습니다! |
||
private String officialEmail; | ||
|
||
@Column(name = "credit", nullable = false) | ||
private int credit; | ||
|
||
@Builder | ||
public Member(String nickname, String socialName, JobGroup jobGroup, JobCategory jobCategory, String socialEmail, | ||
private Member(String nickname, String socialName, JobGroup jobGroup, JobCategory jobCategory, String socialEmail, | ||
String officialEmail, int credit) { | ||
this.nickname = nickname; | ||
this.socialName = socialName; | ||
|
@@ -59,4 +59,38 @@ public Member(String nickname, String socialName, JobGroup jobGroup, JobCategory | |
this.officialEmail = officialEmail; | ||
this.credit = credit; | ||
} | ||
|
||
public static Member of(String socialName, String socialEmail, int credit) { | ||
return Member.builder() | ||
.socialName(socialName) | ||
.socialEmail(socialEmail) | ||
.credit(credit) | ||
.build(); | ||
} | ||
|
||
public static Member of(String nickname, String socialName, JobGroup jobGroup, JobCategory jobCategory, | ||
String socialEmail, String officialEmail, int credit) { | ||
return Member.builder() | ||
.nickname(nickname) | ||
.socialName(socialName) | ||
.jobGroup(jobGroup) | ||
.jobCategory(jobCategory) | ||
.socialEmail(socialEmail) | ||
.officialEmail(officialEmail) | ||
.credit(credit) | ||
.build(); | ||
} | ||
|
||
public void updateSocialEmail(String socialEmail) { | ||
this.socialEmail = socialEmail; | ||
} | ||
|
||
public void updateAdditionalInfo(String nickname, String officialEmail, | ||
JobGroup jobGroup, JobCategory jobCategory) { | ||
this.nickname = nickname; | ||
this.officialEmail = officialEmail; | ||
this.jobGroup = jobGroup; | ||
this.jobCategory = jobCategory; | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package com.dnd.gongmuin.member.dto.request; | ||
|
||
import jakarta.validation.constraints.NotBlank; | ||
import jakarta.validation.constraints.Size; | ||
|
||
public record AdditionalInfoRequest( | ||
|
||
@NotBlank(message = "공무원 이메일은 필수 입력 항목입니다.") | ||
String officialEmail, | ||
@NotBlank(message = "닉네임은 필수 입력 항목입니다.") | ||
@Size(min = 2, max = 12, message = "닉네임은 최소 2자리 이상 최대 12자 이하입니다.") | ||
String nickname, | ||
@NotBlank(message = "직군은 필수 입력 항목입니다.") | ||
String jobGroup, | ||
@NotBlank(message = "직렬은 필수 입력 항목입니다.") | ||
String jobCategory | ||
) { | ||
} |
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.
private으로 되어있는데 Builder로 생성할 수 있나요? 궁금해서 여쭤봅니다!