Skip to content

Commit

Permalink
feat(#5) : 애플 로그인 초기 설정
Browse files Browse the repository at this point in the history
  • Loading branch information
aeeazip committed Jul 2, 2023
1 parent 20807ce commit c2c3f7b
Show file tree
Hide file tree
Showing 17 changed files with 381 additions and 0 deletions.
20 changes: 20 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,31 @@ repositories {
mavenCentral()
}

ext {
// springCloudVersion과 spring boot 버전 충돌나지 않도록 주의
set('springCloudVersion', '2021.0.5')
}

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

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-web'

// 애플 로그인을 위한 FeignClient 연동
implementation 'org.springframework.cloud:spring-cloud-starter-openfeign'

// JWT
implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5'

compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.h2database:h2'
runtimeOnly 'com.mysql:mysql-connector-j'
Expand Down
10 changes: 10 additions & 0 deletions src/main/java/trothly/trothcam/config/FeignClientConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package trothly.trothcam.config;

import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Configuration;
import trothly.trothcam.TrothcamApplication;

@Configuration
@EnableFeignClients(basePackageClasses = TrothcamApplication.class)
public class FeignClientConfig {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package trothly.trothcam.controller.auth;

import lombok.RequiredArgsConstructor;
import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import trothly.trothcam.exception.custom.base.BaseResponse;
import trothly.trothcam.dto.auth.apple.LoginReqDto;
import trothly.trothcam.dto.auth.apple.LoginResDto;
import trothly.trothcam.service.OAuthService;

@RestController
@RequiredArgsConstructor
public class OAuthController {
private final OAuthService oauthService;

// 애플 로그인
@PostMapping("/appple/login")
public BaseResponse<LoginResDto> appleLogin(@RequestBody @Validated LoginReqDto loginReqDto, BindingResult bindingResult){
// BindingResult = 검증 오류가 발생할 경우 오류 내용을 보관하는 객체
if(bindingResult.hasErrors()) {
ObjectError objectError = bindingResult.getAllErrors().stream().findFirst().get();
return new BaseResponse<>(400, objectError.getDefaultMessage(), null);
}

LoginResDto result = oauthService.appleLogin(loginReqDto);
return new BaseResponse<>(result);
}
}
24 changes: 24 additions & 0 deletions src/main/java/trothly/trothcam/domain/core/BaseTimeEntity.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package trothly.trothcam.domain.core;

import lombok.Getter;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import javax.persistence.Column;
import javax.persistence.EntityListeners;
import javax.persistence.MappedSuperclass;
import java.time.LocalDateTime;

@Getter
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseTimeEntity {
@CreatedDate
@Column(name = "created_at", updatable = false, nullable = false)
private LocalDateTime createdAt;

@LastModifiedDate
@Column(name = "last_modified_at", nullable = false)
private LocalDateTime lastModifiedAt;
}
52 changes: 52 additions & 0 deletions src/main/java/trothly/trothcam/domain/member/Member.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package trothly.trothcam.domain.member;

import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import trothly.trothcam.domain.core.BaseTimeEntity;

import javax.persistence.*;
import javax.validation.constraints.Email;
import java.time.LocalDateTime;

@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity
@Table(name = "member")
public class Member extends BaseTimeEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "member_id")
private Long id;

@Email
@Column(name = "email", length = 255, nullable = false)
private String email;

@Column(name = "name", nullable = false)
private String name;

@Column(name = "imaage", length = 255, nullable = true)
private String image;

@Column(name = "provider", nullable = false)
@Enumerated(EnumType.STRING)
private Provider provider;

@Column(name = "refresh_token", nullable = false)
private String refreshToken;

@Column(name = "refresh_token_expires_at", nullable = false)
private LocalDateTime refreshTokenExpiresAt;

@Builder
private Member(String email, String name, String image, Provider provider) {
this.email = email;
this.name = name;
this.image = image;
this.provider = provider;
this.refreshToken = "";
this.refreshTokenExpiresAt = LocalDateTime.now();
}
}
5 changes: 5 additions & 0 deletions src/main/java/trothly/trothcam/domain/member/Provider.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package trothly.trothcam.domain.member;

public enum Provider {
APPLE, GOOGLE
}
18 changes: 18 additions & 0 deletions src/main/java/trothly/trothcam/dto/auth/apple/ApplePublicKey.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package trothly.trothcam.dto.auth.apple;

import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
@Getter
public class ApplePublicKey {
private String kty;
private String kid;
private String use;
private String alg;
private String n;
private String e;
}
23 changes: 23 additions & 0 deletions src/main/java/trothly/trothcam/dto/auth/apple/ApplePublicKeys.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package trothly.trothcam.dto.auth.apple;

import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.util.List;

@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
@Getter
public class ApplePublicKeys {
private List<ApplePublicKey> keys;

public ApplePublicKey getMatchesKey(String alg, String kid) {
return this.keys
.stream()
.filter(k -> k.getAlg().equals(alg) && k.getKid().equals(kid))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("Apple id_token 값의 alg, kid 정보가 올바르지 않습니다."));
}
}
16 changes: 16 additions & 0 deletions src/main/java/trothly/trothcam/dto/auth/apple/LoginReqDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package trothly.trothcam.dto.auth.apple;

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

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;

@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class LoginReqDto {
@NotBlank(message = "애플 토큰 값이 존재하지 않습니다.")
private String idToken;
}
19 changes: 19 additions & 0 deletions src/main/java/trothly/trothcam/dto/auth/apple/LoginResDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package trothly.trothcam.dto.auth.apple;

import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class LoginResDto {
private String accessToken;
private String refreshToken;

@Builder
public LoginResDto(String accessToken, String refreshToken) {
this.accessToken = accessToken;
this.refreshToken = refreshToken;
}
}
12 changes: 12 additions & 0 deletions src/main/java/trothly/trothcam/exception/base/BaseException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package trothly.trothcam.exception.custom.base;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
@AllArgsConstructor
public class BaseException extends Exception {
private BaseResponseStatus status; //BaseResoinseStatus 객체에 매핑
}
45 changes: 45 additions & 0 deletions src/main/java/trothly/trothcam/exception/base/BaseResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package trothly.trothcam.exception.custom.base;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import lombok.AllArgsConstructor;
import lombok.Getter;

import static trothly.trothcam.exception.custom.base.BaseResponseStatus.SUCCESS;


@Getter
@AllArgsConstructor
@JsonPropertyOrder({"isSuccess", "code", "message", "result"})
public class BaseResponse<T> {//BaseResponse 객체를 사용할때 성공, 실패 경우
@JsonProperty("isSuccess")
private Boolean isSuccess;
private final String message;
private final int code;
@JsonInclude(JsonInclude.Include.NON_NULL)
private T result;

// 요청에 성공한 경우
public BaseResponse(T result) {
this.isSuccess = SUCCESS.isSuccess();
this.message = SUCCESS.getMessage();
this.code = SUCCESS.getCode();
this.result = result;
}

// 요청에 실패한 경우 #1
public BaseResponse(BaseResponseStatus status) {
this.isSuccess = status.isSuccess();
this.message = status.getMessage();
this.code = status.getCode();
}

// 요청에 실패한 경우 #2
public BaseResponse(int code, String message, T result) {
this.code = code;
this.message = message;
this.result = result;
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package trothly.trothcam.exception.custom.base;

import lombok.Getter;

/**
* 에러 코드 관리
*/
@Getter
public enum BaseResponseStatus {
/**
* 1000 : 요청 성공
*/
SUCCESS(true, 1000, "요청에 성공하였습니다."),


/**
* 2000 : Request 오류
*/
// Common
REQUEST_ERROR(false, 2000, "입력값을 확인해주세요."),
EMPTY_JWT(false, 2001, "JWT를 입력해주세요."),
INVALID_JWT(false, 2002, "유효하지 않은 JWT입니다."),
INVALID_USER_JWT(false,2003,"권한이 없는 유저의 접근입니다."),


/**
* 3000 : Response 오류
*/
// Common
RESPONSE_ERROR(false, 3000, "값을 불러오는데 실패하였습니다."),


/**
* 4000 : Database, Server 오류
*/
DATABASE_ERROR(false, 4000, "데이터베이스 연결에 실패하였습니다."),
SERVER_ERROR(false, 4001, "서버와의 연결에 실패하였습니다.");


/**
* 5000 : 필요시 만들어서 쓰세요
*/


/**
* 6000 : 필요시 만들어서 쓰세요
*/


private final boolean isSuccess;
private final int code;
private final String message;

private BaseResponseStatus(boolean isSuccess, int code, String message) { //BaseResponseStatus 에서 각 해당하는 코드를 생성자로 맵핑
this.isSuccess = isSuccess;
this.code = code;
this.message = message;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package trothly.trothcam.exception.custom;

public class InvalidTokenException extends RuntimeException {
public InvalidTokenException(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package trothly.trothcam.exception.custom;

public class TokenExpiredException extends RuntimeException {
public TokenExpiredException(String message) {
super(message);
}
}
16 changes: 16 additions & 0 deletions src/main/java/trothly/trothcam/feign/AppleClient.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package trothly.trothcam.feign;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import trothly.trothcam.config.FeignClientConfig;
import trothly.trothcam.dto.auth.apple.ApplePublicKeys;

@FeignClient(
name = "apple-public-key-client",
url = "https://appleid.apple.com/auth",
configuration = FeignClientConfig.class
)
public interface AppleClient {
@GetMapping("/keys")
ApplePublicKeys getApplePublicKeys();
}
Loading

0 comments on commit c2c3f7b

Please sign in to comment.