Skip to content

Commit

Permalink
feat: 카카오 로그인 및 회원가입 기능 추가 (#1)
Browse files Browse the repository at this point in the history
* feat: 소셜 회원가입 기능 추가

* feat: 카카오 로그인 및 회원가입 기능 추가
  • Loading branch information
0703kyj authored Mar 17, 2024
1 parent 543604c commit d621bf6
Show file tree
Hide file tree
Showing 19 changed files with 335 additions and 21 deletions.
10 changes: 10 additions & 0 deletions subprojects/api/build.gradle
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
ext {
springCloudVersion = "2023.0.0"
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.cloud:spring-cloud-starter-openfeign'
testImplementation 'org.springframework.security:spring-security-test'
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.mysql:mysql-connector-j'
Expand All @@ -22,6 +26,12 @@ dependencies {
implementation project(":member")
}

dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:$springCloudVersion"
}
}

tasks.named('test') {
useJUnitPlatform()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.money.config;

import com.money.ApiApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Configuration;

@Configuration
@EnableFeignClients(basePackageClasses = ApiApplication.class)
public class FeignClientConfig {
}
19 changes: 16 additions & 3 deletions subprojects/api/src/main/java/com/money/controller/AuthApi.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.money.controller;

import com.money.dto.request.EmailRequest;
import com.money.dto.request.SocialLoginRequest;
import com.money.dto.request.SocialRegisterRequest;
import com.money.dto.response.ErrorResponse;
import com.money.dto.response.MemberRegisterResponse;
import com.money.dto.response.TokenResponse;
Expand All @@ -13,6 +15,7 @@
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
Expand All @@ -38,12 +41,22 @@ ResponseEntity<TokenResponse> login(

@Operation(summary = "네이티브 소셜 로그인", description = "네이티브(apple 등) 소셜 로그인을 진행합니다.")
@PostMapping("/login/{provider}")
void oauth2Login(@PathVariable("provider") @Parameter(example = "GOOGLE", description = "oAuth 제공자 이름") String provider);
ResponseEntity<TokenResponse> socialLogin(
@PathVariable("provider") @Parameter(example = "GOOGLE", description = "oAuth 제공자 이름") String provider,
@RequestBody @Valid SocialLoginRequest request
);

@Operation(summary = "회원가입", description = "이메일 회원가입을 진행합니다.")
@Operation(summary = "이메일 회원가입", description = "이메일 회원가입을 진행합니다.")
@PostMapping(value = "/register/email")
ResponseEntity<MemberRegisterResponse> registerToEmail(
@Valid @RequestBody EmailRequest request
@RequestBody @Valid EmailRequest request
);

@Operation(summary = "소셜 회원가입", description = "소셜 회원가입을 진행합니다.")
@PostMapping(value = "/register/{provider}")
ResponseEntity<MemberRegisterResponse> register(
@PathVariable("provider") @Parameter(example = "GOOGLE", description = "oAuth 제공자 이름") String provider,
@RequestBody @Valid SocialRegisterRequest request
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import com.money.config.jwt.JwtFilter;
import com.money.controller.AuthApi;
import com.money.dto.request.EmailRequest;
import com.money.dto.request.SocialLoginRequest;
import com.money.dto.request.SocialRegisterRequest;
import com.money.dto.response.MemberRegisterResponse;
import com.money.dto.response.TokenResponse;
import com.money.service.AuthService;
Expand All @@ -24,15 +26,24 @@ public ResponseEntity<TokenResponse> login(EmailRequest request) {

HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add(JwtFilter.AUTHORIZATION_HEADER, "Bearer " + response.token());

return ResponseEntity
.status(HttpStatus.OK)
.headers(httpHeaders)
.body(response);
}

@Override
public void oauth2Login(String provider) {
public ResponseEntity<TokenResponse> socialLogin(String provider, SocialLoginRequest request) {
TokenResponse response = authService.socialLogin(provider, request.token());

HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add(JwtFilter.AUTHORIZATION_HEADER, "Bearer " + response.token());

return ResponseEntity
.status(HttpStatus.OK)
.headers(httpHeaders)
.body(response);
}

@Override
Expand All @@ -42,4 +53,12 @@ public ResponseEntity<MemberRegisterResponse> registerToEmail(EmailRequest reque

return ResponseEntity.ok(response);
}

@Override
public ResponseEntity<MemberRegisterResponse> register(String provider, SocialRegisterRequest request) {
MemberRegisterResponse response = authService.registerToOauth2(
provider, request.platformId(), request.name(), request.phoneNumber(), request.birth());

return ResponseEntity.ok(response);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.money.dto.request;

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class KakaoUserRequest {

private String secure_resource;
private String property_keys;

public KakaoUserRequest(String propertyKeys) {
this.secure_resource = "true";
this.property_keys = propertyKeys;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.money.dto.request;

import jakarta.validation.constraints.NotBlank;

public record SocialLoginRequest(
@NotBlank(message = "공백일 수 없습니다.")
String token
) {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.money.dto.request;

import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Pattern;
import java.time.LocalDate;
import org.springframework.format.annotation.DateTimeFormat;

public record SocialRegisterRequest(
String platformId,
String name,
@Schema(example = "010-1234-5678")
@Pattern(regexp = "^\\d{3}-\\d{3,4}-\\d{4}$", message = "휴대폰 번호 양식에 맞지 않습니다.")
String phoneNumber,
@DateTimeFormat(pattern = "yyyy-MM-dd")
LocalDate birth
) {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.money.dto.response;

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
@Getter
public class KakaoUser {

private Long id;
@JsonProperty("kakao_account")
private KakaoAccount kakaoAccount;

public String getEmail() {
return kakaoAccount.getEmail();
}

@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
private static class KakaoAccount {

private String email;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.money.dto.response;

public record SocialMemberResponse(
String email,
String platformId
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@

public record TokenResponse(
Long memberId,
String token
String token,
Boolean isRegistered
) {
public static TokenResponse of(final Long memberId, final String token) {
return new TokenResponse(memberId, token);

public static TokenResponse of(final Long memberId, final String token,
final Boolean isRegistered) {
return new TokenResponse(memberId, token, isRegistered);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.money.exception.auth;

import com.money.exception.ErrorCode;
import com.money.exception.MainException;

public class InvalidPlatformException extends MainException {

public InvalidPlatformException() {
super(ErrorCode.INVALID_PLATFORM);
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
package com.money.exception;
package com.money.exception.auth;

import com.money.exception.ErrorCode;
import com.money.exception.MainException;

public class PasswordMismatchException extends MainException {

Expand Down
62 changes: 54 additions & 8 deletions subprojects/api/src/main/java/com/money/service/AuthService.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,18 @@

import com.money.config.jwt.TokenProvider;
import com.money.domain.Member;
import com.money.domain.Platform;
import com.money.dto.response.MemberRegisterResponse;
import com.money.dto.response.SocialMemberResponse;
import com.money.dto.response.TokenResponse;
import com.money.exception.MemberAlreadyExistException;
import com.money.exception.NotFoundMemberException;
import com.money.exception.PasswordMismatchException;
import com.money.exception.auth.PasswordMismatchException;
import com.money.repository.MemberRepository;
import java.time.LocalDate;
import java.util.Locale;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
Expand All @@ -23,31 +26,57 @@ public class AuthService {

private final TokenProvider tokenProvider;
private final MemberRepository memberRepository;
private final AuthenticationManagerBuilder authenticationManagerBuilder;
private final PasswordEncoder passwordEncoder;
private final SocialMemberProvider socialMemberProvider;

public TokenResponse login(String email, String password) {
Member findMember = memberRepository.findByEmail(email)
Member findMember = memberRepository.findByEmailAndPlatform(email, Platform.EMAIL)
.orElseThrow(NotFoundMemberException::new);
validatePassword(findMember, password);

String token = issueToken(findMember);
return TokenResponse.of(findMember.getId(), token);
return TokenResponse.of(findMember.getId(), token, true);
}

public TokenResponse socialLogin(String provider, String token) {
Platform platform = Platform.fromString(provider);
SocialMemberResponse platformMember = socialMemberProvider.getPlatformMember(platform, token);

return generateOAuthTokenResponse(
platform,
platformMember.email(),
platformMember.platformId()
);
}

@Transactional
public MemberRegisterResponse registerToEmail(String email, String password){
if(Boolean.TRUE.equals(memberRepository.existsByEmail(email))){
public MemberRegisterResponse registerToEmail(String email, String password) {
Platform emailPlatform = Platform.EMAIL;

if (Boolean.TRUE.equals(memberRepository.existsByEmailAndPlatform(email, emailPlatform))) {
throw new MemberAlreadyExistException();
}
String encodedPassword = encodingPassword(password);

Member registeredMember = Member.of(email, encodedPassword);
Member registeredMember = Member.of(email, encodedPassword, emailPlatform);
memberRepository.save(registeredMember);

return MemberRegisterResponse.from(registeredMember);
}

@Transactional
public MemberRegisterResponse registerToOauth2(
String provider, String platformId,
String name, String phoneNumber, LocalDate birth
) {
Platform platform = Platform.fromString(provider);
Member registeredMember = memberRepository.findByPlatformAndPlatformId(platform, platformId)
.orElseThrow(NotFoundMemberException::new);

registeredMember.registerSocialMember(name, phoneNumber, birth);
return MemberRegisterResponse.from(registeredMember);
}

private void validatePassword(final Member findMember, final String password) {
if (!passwordEncoder.matches(password, findMember.getPassword())) {
throw new PasswordMismatchException();
Expand All @@ -61,4 +90,21 @@ private String encodingPassword(String password) {
private String issueToken(final Member findMember) {
return tokenProvider.createToken(findMember.getId());
}

private TokenResponse generateOAuthTokenResponse(Platform platform, String email, String platformId) {
return memberRepository.findByPlatformAndPlatformId(platform, platformId)
.map(member -> {
String token = issueToken(member);
if (Boolean.TRUE.equals(member.isActivated())) {
return new TokenResponse(member.getId(), token, true);
}
return new TokenResponse(member.getId(), token, false);
})
.orElseGet(() -> {
Member oauthMember = Member.of(email, platform, platformId, Locale.KOREA);
Member savedMember = memberRepository.save(oauthMember);
String token = issueToken(savedMember);
return new TokenResponse(savedMember.getId(), token, false);
});
}
}
Loading

0 comments on commit d621bf6

Please sign in to comment.