Skip to content

Commit

Permalink
feat: 회원가입시 문자인증 기능 구현
Browse files Browse the repository at this point in the history
- 회원가입시 사용자 번호로 문자 전송 기능

- 인증번호 입력시 검증 후 가입

- 전화번호 중복여부 검사

- 인증번호 만료 시간 설정
  • Loading branch information
jieun5119 committed Oct 9, 2024
1 parent 65d1f89 commit d276649
Show file tree
Hide file tree
Showing 12 changed files with 213 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,13 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {

// 경로별 인가
http.authorizeHttpRequests((authorize)->
// authorize.requestMatchers("/**").permitAll()
// .requestMatchers("/reissue").permitAll()

authorize.requestMatchers("/login","/","api/users/join").permitAll()
.requestMatchers("/admin").hasRole("ADMIN")
authorize.requestMatchers("/**").permitAll()
.requestMatchers("/reissue").permitAll()
.anyRequest().authenticated()

// authorize.requestMatchers("/login","/","api/users/join").permitAll()
// .requestMatchers("/admin").hasRole("ADMIN")
// .requestMatchers("/reissue").permitAll()
// .anyRequest().authenticated()
);

http.addFilterAfter(new JWTFilter(jwtUtil), LoginFilter.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ public enum ExceptionCode {
NOT_FOUND_NEARBY_TRASHCANS(404, "근처에 쓰레기통이 존재하지 않습니다."),
NOT_EXIST_REFRESH_TOKEN(400, "리프레시 토큰이 존재하지 않습니다."),
EXPIRED_REFRESH_TOKEN(400, "리프레시 토큰이 만료되었습니다."),
INVALID_REFRESH_TOKEN(400, "유효하지 않은 리프레시 토큰입니다.");
INVALID_REFRESH_TOKEN(400, "유효하지 않은 리프레시 토큰입니다.")
,UNAUTHORIZED(401, "인증에 실패했습니다.");

private final int code;
private final String message;
Expand Down
38 changes: 38 additions & 0 deletions src/main/java/com/sscanner/team/user/SmsCertificationUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.sscanner.team.user;

import jakarta.annotation.PostConstruct;
import net.nurigo.sdk.NurigoApp;
import net.nurigo.sdk.message.model.Message;
import net.nurigo.sdk.message.request.SingleMessageSendingRequest;
import net.nurigo.sdk.message.service.DefaultMessageService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class SmsCertificationUtil {
@Value("${coolsms.api.key}")
private String apiKey;

@Value("${coolsms.api.secret}")
private String apiSecret;

@Value("${coolsms.api.number}")
private String fromNumber;

DefaultMessageService messageService;

@PostConstruct // 의존성 주입이 완료된 후 초기화 수행
public void init(){
this.messageService = NurigoApp.INSTANCE.initialize(apiKey, apiSecret, "https://api.coolsms.co.kr");
}

// 단일 메시지 발송
public void sendSMS(String to, String certificationCode){
Message message = new Message();
message.setFrom(fromNumber);
message.setTo(to);
message.setText("본인확인 인증번호는 " + certificationCode + "입니다.");

this.messageService.sendOne(new SingleMessageSendingRequest(message));
}
}
41 changes: 41 additions & 0 deletions src/main/java/com/sscanner/team/user/controller/SmsController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.sscanner.team.user.controller;

import com.sscanner.team.global.common.response.ApiResponse;
import com.sscanner.team.global.exception.BadRequestException;
import com.sscanner.team.global.exception.ExceptionCode;
import com.sscanner.team.user.requestDto.SmsRequestDto;
import com.sscanner.team.user.requestDto.SmsVerifyRequestDto;
import com.sscanner.team.user.service.SmsService;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
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;

@RestController
@RequiredArgsConstructor
@RequestMapping("/sms")
public class SmsController {

private final SmsService smsService;

@PostMapping("/send")
public ApiResponse<?> SendSMS(@RequestBody @Valid SmsRequestDto smsRequestDto){
smsService.SendSms(smsRequestDto);
return new ApiResponse<>(200,"문자를 전송했습니다",null);
}

@PostMapping("/verify")
public ApiResponse<?> verifyCode(@RequestBody @Valid SmsVerifyRequestDto req) {
boolean verify = smsService.verifyCode(req);
if (verify) {
return new ApiResponse<>(200,"인증이 완료되었습니다.",null);
} else {
throw new BadRequestException(ExceptionCode.UNAUTHORIZED);
}
}
}



39 changes: 39 additions & 0 deletions src/main/java/com/sscanner/team/user/repository/SmsRepository.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.sscanner.team.user.repository;

import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Repository;

import java.time.Duration;

@RequiredArgsConstructor
@Repository
public class SmsRepository {

private final String PREFIX = "sms:"; // 키

private final StringRedisTemplate stringRedisTemplate;

// 인증 정보 저장
public void createSmsCertification(String phone, String code) {
int LIMIT_TIME = 60 * 120; // 유효시간 (2분)
stringRedisTemplate.opsForValue()
.set(PREFIX + phone, code, Duration.ofSeconds(LIMIT_TIME));
}

// 인증 정보 조회
public String getSmsCertification(String phone) {
return stringRedisTemplate.opsForValue().get(PREFIX + phone);
}

//인증 정보 삭제
public void deleteSmsCertification(String phone) {
stringRedisTemplate.delete(PREFIX + phone);
}

// 인증 정보 Redis에 존재 확인
public boolean hasKey(String phone) {
return Boolean.TRUE.equals(stringRedisTemplate.hasKey(PREFIX + phone)); // Redis에서 해당 키의 존재 여부 확인
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ public interface UserRepository extends JpaRepository<User, String> {
boolean existsByNickname(String nickname);
boolean existsByPhone(String phone);
Optional<User> findByEmail(String email);
Optional<User> findByPhone(String phone);

}

10 changes: 10 additions & 0 deletions src/main/java/com/sscanner/team/user/requestDto/SmsRequestDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.sscanner.team.user.requestDto;

import jakarta.validation.constraints.NotEmpty;

public record SmsRequestDto(
@NotEmpty(message = "휴대폰 번호를 입력해주세요")
String phoneNum
) {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.sscanner.team.user.requestDto;

import jakarta.validation.constraints.NotNull;

public record SmsVerifyRequestDto (

@NotNull(message = "휴대폰 번호를 입력해주세요.")
String phoneNum,
@NotNull(message = "인증번호를 입력해주세요.")
String code
){
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,11 @@ public record UserJoinRequestDto(
String nickname,

@NotBlank(message = "전화번호가 비어있습니다.")
String phone
String phone,

@NotBlank(message = "인증번호가 비어있습니다.")
String smsCode


) {

Expand Down
47 changes: 47 additions & 0 deletions src/main/java/com/sscanner/team/user/service/SmsService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.sscanner.team.user.service;

import com.sscanner.team.global.exception.BadRequestException;
import com.sscanner.team.global.exception.ExceptionCode;
import com.sscanner.team.user.SmsCertificationUtil;
import com.sscanner.team.user.repository.SmsRepository;
import com.sscanner.team.user.repository.UserRepository;
import com.sscanner.team.user.requestDto.SmsRequestDto;
import com.sscanner.team.user.requestDto.SmsVerifyRequestDto;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

@RequiredArgsConstructor
@Service
public class SmsService {

private final SmsCertificationUtil smsCertificationUtil;
private final SmsRepository smsRepository;
private final UserRepository userRepository;

public void SendSms(SmsRequestDto smsRequestDto) {
String phoneNum = smsRequestDto.phoneNum();

if (userRepository.findByPhone(phoneNum).isPresent()) {
throw new BadRequestException(ExceptionCode.DUPLICATED_PHONE);
}

String certificationCode = Integer.toString((int)(Math.random() * (999999 - 100000 + 1)) + 100000); // 인증 코드(6자리랜덤)
smsCertificationUtil.sendSMS(phoneNum, certificationCode);
smsRepository.createSmsCertification(phoneNum, certificationCode);
}

public boolean verifyCode(SmsVerifyRequestDto smsVerifyDto) {
if (isVerify(smsVerifyDto.phoneNum(), smsVerifyDto.code())) {
smsRepository.deleteSmsCertification(smsVerifyDto.phoneNum());
return true;
} else {
return false;
}
}

public boolean isVerify(String phoneNum, String code) {
return smsRepository.hasKey(phoneNum) && // 전화번호에 대한 키가 존재하고
smsRepository.getSmsCertification(phoneNum).equals(code); // 저장된 인증 코드와 입력된 인증 코드가 일치하는지 확인
}

}
7 changes: 7 additions & 0 deletions src/main/java/com/sscanner/team/user/service/UserService.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.sscanner.team.global.exception.DuplicateException;
import com.sscanner.team.global.exception.ExceptionCode;
import com.sscanner.team.user.repository.UserRepository;
import com.sscanner.team.user.requestDto.SmsVerifyRequestDto;
import com.sscanner.team.user.requestDto.UserJoinRequestDto;
import com.sscanner.team.user.responseDto.UserJoinResponseDto;
import lombok.RequiredArgsConstructor;
Expand All @@ -17,6 +18,7 @@ public class UserService {

private final UserRepository userRepository;
private final BCryptPasswordEncoder passwordEncoder;
private final SmsService smsService;

// 이메일 중복 체크
private void checkDuplicatedEmail(final String email) {
Expand Down Expand Up @@ -53,6 +55,11 @@ public UserJoinResponseDto join(UserJoinRequestDto req){
checkDuplicatedNickname(req.nickname());
checkDuplicatedPhone(req.phone());

if (!smsService.verifyCode(new SmsVerifyRequestDto(req.phone(), req.smsCode()))) {
throw new IllegalArgumentException("핸드폰 인증에 실패하였습니다."); // 인증 실패 시 예외 던짐
}


confirmPassword(req.password(), req.passwordCheck());

User userEntity = req.toEntity(passwordEncoder.encode(req.password()));
Expand Down
5 changes: 5 additions & 0 deletions src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,5 +56,10 @@ logging:
jwt:
secret: ${JWT_SECRET}

coolsms:
api:
key: "${COOLSMS_API_KEY}"
secret: "${COOLSMS_API_SECRET}"
number: "${COOLSMS_PHONE_NUMBER}"


0 comments on commit d276649

Please sign in to comment.