Skip to content
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

#101 [feat] 전화번호 인증 api 구현 #103

Merged
merged 8 commits into from
Jan 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ repositories {
}

dependencies {
// coolsms
implementation group: 'io.awspring.cloud', name: 'spring-cloud-aws-messaging', version: '2.3.1'

//JWT
implementation group: 'io.jsonwebtoken', name: 'jjwt-api', version: '0.11.2'
implementation group: 'io.jsonwebtoken', name: 'jjwt-impl', version: '0.11.2'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@
@Getter
@ToString
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class SuccessNonDataResponse<T> {
public class SuccessNonDataResponse {
private final int code;
private final String message;

public static SuccessNonDataResponse success(SuccessCode successCode) {
return new SuccessNonDataResponse<>(successCode.getHttpStatus().value(), successCode.getMessage());
return new SuccessNonDataResponse(successCode.getHttpStatus().value(), successCode.getMessage());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ public enum ErrorCode {
INVALID_TOKEN_EXCEPTION(HttpStatus.BAD_REQUEST, "유효하지 않은 토큰을 입력했습니다."),
EMPTY_KAKAO_CODE_EXCEPTION(HttpStatus.BAD_REQUEST, "카카오 코드 값을 입력해 주세요."),
INVALID_KAKAO_CODE_EXCEPTION(HttpStatus.BAD_REQUEST, "유효하지 않은 카카오 코드를 입력했습니다."),
NOT_MATCH_VERIFICATION_CODE_EXCEPTION(HttpStatus.BAD_REQUEST, "인증번호가 일치하지 않습니다."),
EXPIRE_VERIFICATION_CODE_EXCEPTION(HttpStatus.BAD_REQUEST, "만료된 인증 코드입니다."),

// 401
TOKEN_NOT_CONTAINED_EXCEPTION(HttpStatus.UNAUTHORIZED, "Access Token이 필요합니다."),
Expand All @@ -30,6 +32,7 @@ public enum ErrorCode {
NOT_FOUND_APPLICATION_EXCEPTION(HttpStatus.NOT_FOUND, "해당 지원서를 찾을 수 없습니다."),
NOT_FOUND_RESOURCE_EXCEPTION(HttpStatus.NOT_FOUND, "해당 자원을 찾을 수 없습니다."),
NOT_FOUND_REGION_EXCEPTION(HttpStatus.NOT_FOUND, "해당 지역을 찾을 수 없습니다."),
NOT_FOUND_VERIFICATION_CODE_EXCEPTION(HttpStatus.NOT_FOUND, "인증 코드가 존재하지 않습니다."),

// 405 METHOD_NOT_ALLOWED
METHOD_NOT_ALLOWED_EXCEPTION(HttpStatus.METHOD_NOT_ALLOWED, "지원하지 않는 메소드 입니다."),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@ public enum SuccessCode {
OPEN_CHAT_GET_SUCCESS(HttpStatus.OK, "오픈채팅방 연결 성공"),
USER_MY_PAGE_SUCCESS(HttpStatus.OK, "마이페이지 유저 정보 조회 성공입니다."),
OFFER_ACCEPT_SUCCESS(HttpStatus.OK, "제안서 승락 성공입니다."),
POST_OFFER_SUCCESS(HttpStatus.OK, "제안서 작성 성공입니다."),
SEND_VERIFICATION_CODE_SUCCESS(HttpStatus.OK, "전화번호 인증 요청 성공입니다."),
FIND_REGION_LIST_SUCCESS(HttpStatus.OK, "희망 지역 리스트 조회 성공입니다."),
POST_OFFER_SUCCESS(HttpStatus.OK, "제안서 작성 성공입니다.");
VERIFICATION_CODE_MATCH_SUCCESS(HttpStatus.OK, "전화번호 인증 성공입니다.");

private final HttpStatus httpStatus;
private final String message;
Expand Down
15 changes: 15 additions & 0 deletions src/main/java/com/moddy/server/common/util/SmsUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.moddy.server.common.util;

import com.moddy.server.external.sms.SmsService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;

@Component
@RequiredArgsConstructor
public class SmsUtil {
private final SmsService smsService;

public boolean sendVerificationCode(String to, String verificationCode) {
return smsService.sendSms(to, verificationCode);
}
}
27 changes: 27 additions & 0 deletions src/main/java/com/moddy/server/config/sms/AwsSnsConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.moddy.server.config.sms;

import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.services.sns.AmazonSNSClient;
import com.amazonaws.services.sns.AmazonSNSClientBuilder;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AwsSnsConfig {
@Value("${cloud.aws.credentials.accessKey}")
private String accessKey;
@Value("${cloud.aws.credentials.secretKey}")
private String secretKey;

@Bean
public AmazonSNSClient amazonSNSClient() {
BasicAWSCredentials credentials = new BasicAWSCredentials(accessKey, secretKey);
return (AmazonSNSClient) AmazonSNSClientBuilder
.standard()
.withRegion("ap-northeast-1")
.withCredentials(new AWSStaticCredentialsProvider(credentials))
.build();
}
}
42 changes: 40 additions & 2 deletions src/main/java/com/moddy/server/controller/auth/AuthController.java
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
package com.moddy.server.controller.auth;

import com.moddy.server.common.dto.ErrorResponse;
import com.moddy.server.common.dto.SuccessNonDataResponse;
import com.moddy.server.common.dto.SuccessResponse;
import com.moddy.server.common.exception.enums.SuccessCode;
import com.moddy.server.common.util.SmsUtil;
import com.moddy.server.config.resolver.kakao.KakaoCode;
import com.moddy.server.controller.model.dto.request.ModelCreateRequest;
import com.moddy.server.controller.auth.dto.request.PhoneNumberRequestDto;
import com.moddy.server.controller.auth.dto.request.VerifyCodeRequestDto;
import com.moddy.server.controller.auth.dto.response.LoginResponseDto;
import com.moddy.server.controller.designer.dto.response.UserCreateResponse;
import com.moddy.server.controller.auth.dto.response.RegionResponse;
import com.moddy.server.controller.designer.dto.request.DesignerCreateRequest;
import com.moddy.server.controller.designer.dto.response.UserCreateResponse;
import com.moddy.server.controller.model.dto.request.ModelCreateRequest;
import com.moddy.server.service.auth.AuthService;
import com.moddy.server.service.designer.DesignerService;
import com.moddy.server.service.model.ModelService;
Expand All @@ -32,7 +36,9 @@

import java.util.List;

import static com.moddy.server.common.exception.enums.SuccessCode.SEND_VERIFICATION_CODE_SUCCESS;
import static com.moddy.server.common.exception.enums.SuccessCode.SOCIAL_LOGIN_SUCCESS;
import static com.moddy.server.common.exception.enums.SuccessCode.VERIFICATION_CODE_MATCH_SUCCESS;

@Tag(name = "Auth Controller", description = "로그인 및 회원 가입 관련 API 입니다.")
@RestController
Expand All @@ -43,6 +49,7 @@ public class AuthController {
private final AuthService authService;
private final DesignerService designerService;
private final ModelService modelService;
private final SmsUtil smsUtil;

@Operation(summary = "[KAKAO CODE] 로그인 API")
@ApiResponses(value = {
Expand Down Expand Up @@ -102,4 +109,35 @@ public SuccessResponse<UserCreateResponse> createModel(
return SuccessResponse.success(SuccessCode.MODEL_CREATE_SUCCESS, modelService.createModel(request.getHeader(ORIGIN), kakaoCode, modelCreateRequest));
}

@Operation(summary = "[SMS 기능 미완성] 인증번호 요청 API", description = "인증번호 요청 API입니다.")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "전화번호 인증 요청 성공입니다."),
@ApiResponse(responseCode = "400", description = "유효하지 않은 카카오 코드를 입력했습니다.", content = @Content(schema = @Schema(implementation = ErrorResponse.class))),
@ApiResponse(responseCode = "404", description = "유효하지 않은 값을 입력했습니다.", content = @Content(schema = @Schema(implementation = ErrorResponse.class))),
@ApiResponse(responseCode = "500", description = "서버 내부 오류", content = @Content(schema = @Schema(implementation = ErrorResponse.class)))
})
@PostMapping("/phoneNumber")
public SuccessNonDataResponse sendVerificationCodeMessageToUser(@RequestBody PhoneNumberRequestDto phoneNumberRequestDto) {
authService.sendVerificationCodeMessageToUser(phoneNumberRequestDto.phoneNumber());
return SuccessNonDataResponse.success(SEND_VERIFICATION_CODE_SUCCESS);
}

@Operation(summary = "전화번호 인증 API", description = "전화번호 인증 API")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "전화번호 인증 성공입니다."),
@ApiResponse(
responseCode = "400",
description = "1. 인증번호가 일치하지 않습니다."
+ "2. 만료된 인증 코드입니다.",
content = @Content(schema = @Schema(implementation = ErrorResponse.class))
),
@ApiResponse(responseCode = "404", description = "인증 코드가 존재하지 않습니다.", content = @Content(schema = @Schema(implementation = ErrorResponse.class))),
@ApiResponse(responseCode = "500", description = "서버 내부 오류", content = @Content(schema = @Schema(implementation = ErrorResponse.class)))
})
@PostMapping("/phoneNumber/verify")
public SuccessNonDataResponse verifyCode(@RequestBody VerifyCodeRequestDto verifyCodeRequestDto) {
authService.verifyCode(verifyCodeRequestDto.phoneNumber(), verifyCodeRequestDto.verifyCode());
return SuccessNonDataResponse.success(VERIFICATION_CODE_MATCH_SUCCESS);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package com.moddy.server.controller.auth.dto.request;

public record PhoneNumberRequestDto(String phoneNumber) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package com.moddy.server.controller.auth.dto.request;

public record VerifyCodeRequestDto(String phoneNumber, String verifyCode) {
}
2 changes: 2 additions & 0 deletions src/main/java/com/moddy/server/domain/BaseTimeEntity.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import jakarta.persistence.EntityListeners;
import jakarta.persistence.MappedSuperclass;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
import org.springframework.data.annotation.CreatedDate;
Expand All @@ -11,6 +12,7 @@

import java.time.LocalDateTime;

@Getter
@SuperBuilder
@MappedSuperclass
@NoArgsConstructor
Expand Down
36 changes: 36 additions & 0 deletions src/main/java/com/moddy/server/domain/verify/UserVerification.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.moddy.server.domain.verify;

import com.moddy.server.domain.BaseTimeEntity;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;

import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;

@Entity
@Getter
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
public class UserVerification extends BaseTimeEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotNull
private String phoneNumber;
@NotNull
private String verificationCode;

public boolean isExpireCode(LocalDateTime now) {
long minutesDifference = ChronoUnit.MINUTES.between(this.getCreatedAt(), now);
return minutesDifference >= 3;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.moddy.server.domain.verify.repository;

import com.moddy.server.domain.verify.UserVerification;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

public interface UserVerificationRepository extends JpaRepository<UserVerification, Long> {
Optional<UserVerification> findByPhoneNumber(String phoneNumber);

void deleteByPhoneNumber(String phoneNumber);
}
7 changes: 7 additions & 0 deletions src/main/java/com/moddy/server/external/sms/SmsService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.moddy.server.external.sms;

public interface SmsService {
String MESSAGE_FORM = "[%s] moddy에서 보낸 인증번호입니다. 해당 인증번호는 3분간 유효합니다.";

boolean sendSms(String to, String verificationCode);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.moddy.server.external.sms.impl;

import com.amazonaws.services.sns.AmazonSNSClient;
import com.amazonaws.services.sns.model.PublishRequest;
import com.moddy.server.external.sms.SmsService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;

@Component
@RequiredArgsConstructor
public class AwsSmsServiceImpl implements SmsService {
private final AmazonSNSClient amazonSNSClient;
private static final String KR = "+82";

@Override
public boolean sendSms(String to, String verificationCode) {
try {
PublishRequest request = new PublishRequest();
request.setMessage(String.format(MESSAGE_FORM, verificationCode));
request.setPhoneNumber(KR + to);

amazonSNSClient.publish(request);
return true;
} catch (Exception e) {
System.out.println(e);
return false;
}
}
}
49 changes: 47 additions & 2 deletions src/main/java/com/moddy/server/service/auth/AuthService.java
Original file line number Diff line number Diff line change
@@ -1,30 +1,43 @@
package com.moddy.server.service.auth;

import com.moddy.server.common.dto.TokenPair;
import com.moddy.server.common.exception.model.BadRequestException;
import com.moddy.server.common.exception.model.NotFoundException;
import com.moddy.server.common.util.SmsUtil;
import com.moddy.server.common.util.VerificationCodeGenerator;
import com.moddy.server.config.jwt.JwtService;
import com.moddy.server.controller.auth.dto.response.LoginResponseDto;
import com.moddy.server.controller.auth.dto.response.RegionResponse;
import com.moddy.server.controller.designer.dto.response.UserCreateResponse;
import com.moddy.server.domain.region.repository.RegionJpaRepository;
import com.moddy.server.domain.user.User;
import com.moddy.server.domain.user.repository.UserRepository;
import com.moddy.server.domain.verify.UserVerification;
import com.moddy.server.domain.verify.repository.UserVerificationRepository;
import com.moddy.server.external.kakao.service.KakaoSocialService;
import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

import static com.moddy.server.common.exception.enums.ErrorCode.EXPIRE_VERIFICATION_CODE_EXCEPTION;
import static com.moddy.server.common.exception.enums.ErrorCode.NOT_FOUND_VERIFICATION_CODE_EXCEPTION;
import static com.moddy.server.common.exception.enums.ErrorCode.NOT_MATCH_VERIFICATION_CODE_EXCEPTION;
import static com.moddy.server.common.exception.enums.ErrorCode.USER_NOT_FOUND_EXCEPTION;

@Service
@RequiredArgsConstructor
public class AuthService {
private final SmsUtil smsUtil;
private final JwtService jwtService;
private final KakaoSocialService kakaoSocialService;
private final UserRepository userRepository;
private final RegionJpaRepository regionJpaRepository;
private final UserVerificationRepository userVerificationRepository;

public LoginResponseDto login(final String baseUrl, final String kakaoCode) {
String kakaoId = kakaoSocialService.getIdFromKakao(baseUrl, kakaoCode);
Expand All @@ -34,7 +47,7 @@ public LoginResponseDto login(final String baseUrl, final String kakaoCode) {
return new LoginResponseDto(tokenPair.accessToken(), tokenPair.refreshToken(), user.getRole().name());
}

public List<RegionResponse> getRegionList(){
public List<RegionResponse> getRegionList() {

List<RegionResponse> regionResponseList = regionJpaRepository.findAll().stream().map(region -> {
RegionResponse regionResponse = new RegionResponse(
Expand All @@ -47,12 +60,44 @@ public List<RegionResponse> getRegionList(){
return regionResponseList;
}

public UserCreateResponse createUserToken(String useId){
public UserCreateResponse createUserToken(String useId) {

TokenPair tokenPair = jwtService.generateTokenPair(useId);
UserCreateResponse userCreateResponse = new UserCreateResponse(tokenPair.accessToken(), tokenPair.refreshToken());

return userCreateResponse;
}

@Transactional
public void sendVerificationCodeMessageToUser(String phoneNumber) {
Optional<UserVerification> userVerification = userVerificationRepository.findByPhoneNumber(phoneNumber);
if (userVerification.isPresent()) {
userVerificationRepository.deleteByPhoneNumber(phoneNumber);
}

String verificationCode = VerificationCodeGenerator.generate();
// smsUtil.sendVerificationCode(phoneNumber, verificationCode);

UserVerification newUserVerification = UserVerification.builder()
.phoneNumber(phoneNumber)
.verificationCode(verificationCode)
.build();
userVerificationRepository.save(newUserVerification);
}

@Transactional
public void verifyCode(String phoneNumber, String verificationCode) {
UserVerification userVerification = userVerificationRepository.findByPhoneNumber(phoneNumber)
.orElseThrow(() -> new NotFoundException(NOT_FOUND_VERIFICATION_CODE_EXCEPTION));

if (userVerification.isExpireCode(LocalDateTime.now())) {
userVerificationRepository.deleteByPhoneNumber(phoneNumber);
throw new BadRequestException(EXPIRE_VERIFICATION_CODE_EXCEPTION);
}

if (!verificationCode.equals(userVerification.getVerificationCode()))
throw new BadRequestException(NOT_MATCH_VERIFICATION_CODE_EXCEPTION);

userVerificationRepository.deleteByPhoneNumber(phoneNumber);
}
}
Loading