Skip to content

Commit

Permalink
Merge pull request #8 from YAPP-Github/feature/#7
Browse files Browse the repository at this point in the history
feat: 카카오 로그인 구현
  • Loading branch information
CChuYong authored Jun 30, 2024
2 parents 622d379 + f0e336d commit e0a2357
Show file tree
Hide file tree
Showing 24 changed files with 540 additions and 6 deletions.
21 changes: 19 additions & 2 deletions user-service/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,39 @@ repositories {
}

dependencies {
annotationProcessor("org.springframework.boot:spring-boot-configuration-processor")
implementation("org.springframework.boot:spring-boot-starter-actuator")
implementation("org.springframework.boot:spring-boot-starter-data-r2dbc")
implementation("org.springframework.boot:spring-boot-starter-security")
implementation("org.springframework.boot:spring-boot-starter-webflux")
implementation("org.flywaydb:flyway-core")
implementation("org.flywaydb:flyway-mysql")
// https://mvnrepository.com/artifact/com.mysql/mysql-connector-j
implementation("com.mysql:mysql-connector-j:8.4.0")

implementation("org.springframework:spring-jdbc")
implementation("org.springdoc:springdoc-openapi-starter-webflux-ui:2.5.0")
runtimeOnly("org.mariadb:r2dbc-mariadb:1.1.3")
runtimeOnly("org.mariadb.jdbc:mariadb-java-client")
implementation("io.asyncer:r2dbc-mysql:1.1.0")
testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation("io.projectreactor:reactor-test")
testImplementation("org.springframework.security:spring-security-test")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
implementation("org.projectlombok:lombok:1.18.32")
implementation("com.github.f4b6a3:ulid-creator:5.2.3")
annotationProcessor("org.projectlombok:lombok:1.18.32")

val jjwtVersion = "0.12.5"
implementation("io.jsonwebtoken:jjwt-api:$jjwtVersion")
runtimeOnly("io.jsonwebtoken:jjwt-impl:$jjwtVersion")
runtimeOnly("io.jsonwebtoken:jjwt-jackson:$jjwtVersion")

implementation("io.projectreactor.tools:blockhound:1.0.9.RELEASE")
}

tasks.withType<Test>().all {
if (JavaVersion.current().isCompatibleWith(JavaVersion.VERSION_13)) {
jvmArgs("-XX:+AllowRedefinitionToAddDeleteMethods")
}
}

tasks.withType<Test> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
import reactor.blockhound.BlockHound;

@ConfigurationPropertiesScan
@SpringBootApplication
public class UserServiceApplication {

public static void main(String[] args) {
// BlockHound.install();
SpringApplication.run(UserServiceApplication.class, args);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package kr.mafoo.user.config;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;
import com.fasterxml.jackson.databind.module.SimpleModule;
import io.jsonwebtoken.io.AbstractSerializer;
import io.jsonwebtoken.lang.Assert;

import java.io.OutputStream;

public class JacksonSerializer<T> extends AbstractSerializer<T> {

static final String MODULE_ID = "jjwt-jackson";
static final Module MODULE;

static {
SimpleModule module = new SimpleModule(MODULE_ID);
// module.addSerializer(JacksonSupplierSerializer.INSTANCE);
MODULE = module;
}

static final ObjectMapper DEFAULT_OBJECT_MAPPER = newObjectMapper();

/**
* Creates and returns a new ObjectMapper with the {@code jjwt-jackson} module registered and
* {@code JsonParser.Feature.STRICT_DUPLICATE_DETECTION} enabled (set to true) and
* {@code DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES} disabled (set to false).
*
* @return a new ObjectMapper with the {@code jjwt-jackson} module registered and
* {@code JsonParser.Feature.STRICT_DUPLICATE_DETECTION} enabled (set to true) and
* {@code DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES} disabled (set to false).
*
* @since 0.12.4
*/
// package protected on purpose, do not expose to the public API
static ObjectMapper newObjectMapper() {
return new ObjectMapper()
.registerModule(MODULE)
.configure(JsonParser.Feature.STRICT_DUPLICATE_DETECTION, true) // https://github.com/jwtk/jjwt/issues/877
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); // https://github.com/jwtk/jjwt/issues/893
}

protected final ObjectMapper objectMapper;

/**
* Constructor using JJWT's default {@link ObjectMapper} singleton for serialization.
*/
public JacksonSerializer() {
this(DEFAULT_OBJECT_MAPPER);
}

/**
* Creates a new Jackson Serializer that uses the specified {@link ObjectMapper} for serialization.
*
* @param objectMapper the ObjectMapper to use for serialization.
*/
public JacksonSerializer(ObjectMapper objectMapper) {
Assert.notNull(objectMapper, "ObjectMapper cannot be null.");
this.objectMapper = objectMapper.registerModule(MODULE);
}

@Override
protected void doSerialize(T t, OutputStream out) throws Exception {
Assert.notNull(out, "OutputStream cannot be null.");
ObjectWriter writer = this.objectMapper.writer().without(JsonGenerator.Feature.AUTO_CLOSE_TARGET);
writer.writeValue(out, t);
}
}
13 changes: 13 additions & 0 deletions user-service/src/main/java/kr/mafoo/user/config/WebFluxConfig.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package kr.mafoo.user.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.config.EnableWebFlux;
import org.springframework.web.reactive.config.WebFluxConfigurer;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.reactive.result.method.annotation.ArgumentResolverConfigurer;

@EnableWebFlux
Expand All @@ -12,4 +14,15 @@ public class WebFluxConfig implements WebFluxConfigurer {
public void configureArgumentResolvers(ArgumentResolverConfigurer configurer) {
configurer.addCustomResolver(new MemberIdParameterResolver());
}

@Bean("externalWebClient")
public WebClient externalServiceWebClient() {
return WebClient.builder()
.codecs(clientCodecConfigurer -> {
clientCodecConfigurer
.defaultCodecs()
.maxInMemorySize(16 * 1024 * 1024); // 16MB
})
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package kr.mafoo.user.config.properties;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.ConfigurationPropertiesBinding;

@ConfigurationProperties(prefix = "app.oauth.kakao")
@ConfigurationPropertiesBinding
public record KakaoOAuthProperties(
String clientId,
String redirectUri,
String clientSecret
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,27 @@
import kr.mafoo.user.controller.dto.request.KakaoLoginRequest;
import kr.mafoo.user.controller.dto.request.TokenRefreshRequest;
import kr.mafoo.user.controller.dto.response.LoginResponse;
import kr.mafoo.user.service.AuthService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;

@RequiredArgsConstructor
@RestController
public class AuthController implements AuthApi {
private final AuthService authService;

@Override
public Mono<LoginResponse> loginWithKakao(KakaoLoginRequest request) {
return Mono.just(new LoginResponse("test_access_token", "test_refresh_token"));
return authService
.loginWithKakao(request.code())
.map(authToken -> new LoginResponse(authToken.accessToken(), authToken.refreshToken()));
}

@Override
public Mono<LoginResponse> loginWithRefreshToken(TokenRefreshRequest request) {
return Mono.just(new LoginResponse("test_access_token", request.refreshToken()));
return authService
.loginWithRefreshToken(request.refreshToken())
.map(authToken -> new LoginResponse(authToken.accessToken(), authToken.refreshToken()));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package kr.mafoo.user.controller.dto.response;

public record KakaoLoginInfo(
String id,
String nickname,
String email
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package kr.mafoo.user.domain;

public record AuthToken(
String accessToken,
String refreshToken
){

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import lombok.Setter;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.Transient;
import org.springframework.data.domain.Persistable;
import org.springframework.data.relational.core.mapping.Column;
import org.springframework.data.relational.core.mapping.Table;

Expand All @@ -14,7 +16,7 @@
@Setter
@NoArgsConstructor
@Table("member")
public class MemberEntity {
public class MemberEntity implements Persistable<String> {
@Id
@Column("member_id")
private String id;
Expand All @@ -26,6 +28,9 @@ public class MemberEntity {
@Column("created_at")
private LocalDateTime createdAt;

@Transient
private boolean isNew = false;

@Override
public boolean equals(Object obj) {
if (this == obj) return true;
Expand All @@ -40,4 +45,12 @@ public boolean equals(Object obj) {
public int hashCode() {
return id.hashCode();
}

public static MemberEntity newMember(String id, String name) {
MemberEntity member = new MemberEntity();
member.id = id;
member.name = name;
member.isNew = true;
return member;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package kr.mafoo.user.domain;

import kr.mafoo.user.enums.IdentityProvider;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.Transient;
import org.springframework.data.domain.Persistable;
import org.springframework.data.relational.core.mapping.Column;
import org.springframework.data.relational.core.mapping.Table;

import java.time.LocalDateTime;
import java.util.Objects;

@Getter
@Setter
@NoArgsConstructor
@Table("social_member")
public class SocialMemberEntity implements Persistable<SocialMemberEntityKey> {
@Column("identity_provider")
private IdentityProvider identityProvider;

@Column("identifier")
private String id;

@CreatedDate
@Column("created_at")
private LocalDateTime createdAt;

@Column("member_id")
private String memberId;

@Transient
private boolean isNew = false;

@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;

SocialMemberEntity that = (SocialMemberEntity) obj;

return id.equals(that.id) && identityProvider.equals(that.identityProvider);
}

public SocialMemberEntityKey getId() {
return new SocialMemberEntityKey(identityProvider, id);
}

@Override
public int hashCode() {
return Objects.hash(id, identityProvider);
}

public static SocialMemberEntity newSocialMember(IdentityProvider identityProvider, String id, String memberId) {
SocialMemberEntity socialMember = new SocialMemberEntity();
socialMember.identityProvider = identityProvider;
socialMember.id = id;
socialMember.memberId = memberId;
socialMember.isNew = true;
return socialMember;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package kr.mafoo.user.domain;

import kr.mafoo.user.enums.IdentityProvider;
import lombok.Data;
import lombok.RequiredArgsConstructor;

import java.io.Serializable;

@RequiredArgsConstructor
@Data
public class SocialMemberEntityKey implements Serializable {
private final IdentityProvider identityProvider;
private final String id;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package kr.mafoo.user.enums;

public enum IdentityProvider {
KAKAO
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,13 @@
@Getter
@RequiredArgsConstructor
public enum ErrorCode {
MEMBER_NOT_FOUND("ME0001", "사용자를 찾을 수 없습니다");
MEMBER_NOT_FOUND("ME0001", "사용자를 찾을 수 없습니다"),
KAKAO_LOGIN_FAILED("EX0001", "카카오 로그인에 실패했습니다"),

TOKEN_TYPE_MISMATCH("AU0001", "토큰 타입이 일치하지 않습니다. (아마 AccessToken?)"),
TOKEN_EXPIRED("AU0002", "토큰이 만료되었습니다"),
TOKEN_INVALID("AU0003", "토큰이 유효하지 않습니다"),
;
private final String code;
private final String message;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package kr.mafoo.user.exception;

public class InvalidTokenException extends DomainException {
public InvalidTokenException() {
super(ErrorCode.TOKEN_INVALID);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package kr.mafoo.user.exception;

public class KakaoLoginFailedException extends DomainException {
public KakaoLoginFailedException() {
super(ErrorCode.KAKAO_LOGIN_FAILED);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package kr.mafoo.user.exception;

public class TokenExpiredException extends DomainException {
public TokenExpiredException() {
super(ErrorCode.TOKEN_EXPIRED);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package kr.mafoo.user.exception;

public class TokenTypeMismatchException extends DomainException {
public TokenTypeMismatchException() {
super(ErrorCode.TOKEN_TYPE_MISMATCH);
}
}
Loading

0 comments on commit e0a2357

Please sign in to comment.