[SpringBoot] 자체 로그인 구현하기 1부

신창호·2024년 3월 13일
2

Spring

목록 보기
8/9

프로젝트를 진행하면서 점점 기능을 구현하다보니 처음에 작업해놓은 기획안은 있지만,
틀만 만들고 어떻게 구현할지는 구현하면서 생각해보자 하고 만든 형태다 보니, 구체적인 설계도가 필요해졌다.

물론, 전체적으로 다시 기획안부터 만들어도 좋은 방법이지만, 해당 기능을 구현하려고 들어갈때마다, 어떻게 할지 고민하고 배워서 적용해봤기 때문에 다 만들어 보는 방법보단 조각조각 하나하나 퍼즐처럼 만들어서 마지막에 합쳐보고자 한다.

이미 작업했던 기능들을 적을까하지만, 현재 진행중인 기능을 적는게 좋겠다 생각하[현재기능 > 이미 구현한 기능] 이번 포스팅에선 자체로그인을 구현하는 로직을 풀어보려고 한다.

1. 자체로그인 구현

이번 프로젝트에서 구현할 자체로그인은 이메일비밀번호만 있으면 회원가입이 되는 프로젝트이다.
그렇기때문에, 이메일을 통해 인증하는 과정이 필요하고, 아이디찾기 없이 비밀번호 찾기만 구현할 예정이다.
비밀번호 찾는 방법은 새로운 비밀번호로 변경하는 방식으로만 진행할 것이며, 해당 방법 또한 이메일로만 다 해결할 수 있게 구현하면된다.

전체 체크리스트

1️⃣ 로그인 로직
2️⃣ 회원가입 로직
3️⃣ 비밀번호 찾기 로직
4️⃣ 비밀번호 바꾸기 로직



1️⃣ [로그인] Detail CheckList

로그인이 되는지 안되는지 체크해 주면된다.
로그인이 되는 경우라면, 원래 로직대로 응답해주면된다.(로그인 유지를 위해 jwt로 응답한다.)
로그인이 안되는 경우라면, 해당 에러를 잡아 핸들링하여 응답해주면 된다.

예외 핸들링 리스트

  1. 이메일, 비밀번호가 올바른 형태인지 체크!(입력값 검증)
  2. 회원 DB에 이메일이 없는지 체크!
  3. 비밀번호 체크!(아이디는 있으나, 비밀번호가 다른 경우)
  4. [선택사항] 블랙리스트 계정인지 체크(현재 로그인하면 안되는 유저의 경우 핸들링)

로그인이 되었을때

  1. 고유 회원이라는 식별 데이터와 함께 JWT로 응답을 해준다.

    사용자 로그인을 유지하기위해 여러 방법이 있지만, 나는 트래픽 부담이 적은 JWT방식으로 채택했다.



2️⃣ [회원가입] Detail CheckList

본인인증을 위한 과정중에 이메일만 사용하기 때문에, 이메일 인증이 필수로 필요하다.
인증을 하기위해선 인증코드를 서버에서 보관해야되기 때문에 다양한 방법중에 만료시간(TTL)을 관리하기 좋고 동시성 덕에 제일 확장성이 좋고 빠른 Redis를 선택하였다.

이메일 인증코드 보내기

  1. 이메일이 올바른 형태인지 체크!(입력값 검증)
  2. 해당 이메일이 중복된 이메일인지 DB 체크!(소셜로그인한 이메일 제외)
  3. 인증코드 생성!
    • 인증코드 만료시간
    • 서버가 알 수 있게 Redis에 저장
    • 인증코드를 요청한 이메일로 전송

이메일 인증코드 확인하기

  1. 인증코드 확인이 잘됐을 때
    • Redis에 이메일과 인증코드가 일치하면 삭제
  2. 인증코드 확인이 안됐을 때
    • 인증코드 만료 시 체크!
    • 올바르지않는 인증코드시 체크!
    • 6자리가 아닐 시 체크!

회원가입하기

  1. 이메일과 비밀번호, 회원가입타입("site")라 지정하여 DB에 저장
    • 회원가입 성공 응답 보내주기
    • 실패시, 실패응답(서버오류)


3️⃣ [비밀번호 찾기] Detail CheckList

비밀번호 찾기는 해당 이메일이 자체 회원가입한 회원 이메일인지 먼저 확인한 후, 있다면 이메일로 비밀번호 수정할 수 있는 Uri가 담긴 이메일을 전송해준다.

회원 유무 체크

  1. 이메일이 올바른 형태인지 체크!(입력값 검증)
  2. 자체회원가입한 회원 DB에 이메일이 있는지 체크!

이메일 전송

  1. 만료시간5분인 인증코드 생성하여 Redis에 저장!
  2. 인증코드를 요청한 이메일로 RedirectUri 쿼리파라미터로 포함하여 전송


4️⃣ [비밀번호 바꾸기] Detail CheckList

이메일과 코드 확인

  1. 해당 요청에 이메일과 인증코드가 Redis에 존재하는지 체크!
  • 올바르지않거나,인증코드 만료시 실패응답

비밀번호 변경

  1. 해당 이메일의 비밀번호를 변경하여 저장
    • 성공시, 비밀번호 변경 성공 응답
    • 실패시, 실패응답(서버오류)
    • [선택사항] 블랙리스트 계정일지 실패응답(현재 로그인하면 안되는 유저의 경우 핸들링)


2. 자체로그인 구현 시행착오!!

전체적인 아키텍처를 그려보면 아래와 같다.

물론 다 알고 있는 상태에서 보면 그닥 어려운 내용은 아니니, 하나하나 파헤쳐보면서 구현해보자.
구현 방식은 체크리스트와 다르게, 처음 사용하는 사용자 입장 순서대로 진행해보고자 한다.

    1. 회원가입
    1. 로그인
    1. 비밀번호 찾기
    1. 비밀번호 변경


1️⃣ 회원가입 구현하기

먼저 회원가입 로직이다. 입력값 검증의 경우, 매 요청마다 처리하기때문에 N이라는 번호로 넣어줬고, 사실상 예외 핸들링 처리도 다 포함하고 있다고 보면 된다.

환경 세팅하기

기본적으로 해당 환경은 DB와 서버는 환경설정이 되어 있는 상태라고 본다.
서버 : Spring boot 3.x.x
DB : MySQL

  • 위 상황에서 필요한 세팅은 메일전송 서버인 SMTPRedis설정이다.

SMTP 선택

  • 먼저 SMTP은 simple Mail Transfer Prorocol의 약자로 간단히 요약해서 설명하자면, 이메일메시지를 보내는 데 필요한 통신 프로토콜이라고 보면 된다. (이메일을 보내려면 필요하다는 것이다.)
  • SMTP을 선택하기전에 이메일의 종류부터 선택해야된다.
    • 크게는 개인이메일과 비즈니스(회사)용 이메일이 있다.
    • 비즈니스 이메일란, 내가 원하는 도메인으로 이메일 주소를 만드는 것으로 service@gloom.com으로 이메일을 보낼 수 있는 경우를 말한다.
      • 이경우 가비아 하이웍스, 네이버 웍스, AWS workMail등 웹메일 서비스를 구매하여 사용하는 형태
    • 개인 이메일은 우리가 흔히 알고 있는 네이버,구글,카카오(다음)을 말한다.

나는 무료로 사용할 수 있고 국내에서 가장 많이 이용되는 네이버를 사용했다.

네이버 SMTP 설정

  • 네이버 메일 -> 환경설정 -> POP3/IMAP설정에서 인증번호를 보낼때에는 SMTP만 있으면 되기에 아무거나 해도 사용함으로 체크하면 된다.

Spring 설정하기

이제 SpringBoot에서 네이버 SMTP에 접근하면 된다.

  • build.gradle(의존성 추가)
    • java 메일 핸들링하는데 필요한 라이브러리
    //mail
    implementation 'org.springframework.boot:spring-boot-starter-mail'
  • 어플리케이션 설정(yaml)
    • 실제 네이버 메일 보내는 계정의 비밀번호를 입력해주면된다.
  mail:
    naver:
      password: 네이버메일 비밀번호
  • MailConfig 파일 세팅
    • 추상화되어 있는 메일 라이브러리를 내가 네이버메일을 사용하기때문에 맞춰서 구현해야된다.
@Configuration
public class MailConfig {

    @Value("${mail.naver.password}")
    private String password;

    @Bean
    public JavaMailSender javaMailSender() {
        JavaMailSenderImpl mailSender = new JavaMailSenderImpl();

        mailSender.setHost("smtp.naver.com");
        mailSender.setUsername("위에 가려진 계정정보 아이디");
        mailSender.setPort(465);
        mailSender.setPassword(password);
        mailSender.setJavaMailProperties(getMailProperties());
        
        return mailSender;
    }

    // java메일 API를 사용하기 위한 다양한 속성 설정  application.yml에서 해도 됨
    private Properties getMailProperties() {
        Properties properties = new Properties();
        properties.setProperty("mail.transport.protocol", "smtp"); // 필수
        properties.setProperty("mail.smtp.auth", "true"); // 인증 필수
        properties.setProperty("mail.smtp.starttls.enable", "true"); //보안 필수
        properties.setProperty("mail.debug", "true"); // 선택
        properties.setProperty("mail.smtp.ssl.trust","smtp.naver.com"); //필수
        properties.setProperty("mail.smtp.ssl.enable","true"); // 보안 필수
        return properties;
    }
}
  • MailService 구현
    • 인증코드 만들기
    • 메일 내용(받는이, 제목, 본문(인증코드) 등) 만들기
    • 메일 보내기
@Slf4j // 로그 찍어보기
@Service
@RequiredArgsConstructor
public class MailService {

    private final JavaMailSender javaMailSender;
 
    // 보낼 이메일 만드는 서비스
    public MimeMessage createCodeEmail(String email, String code) throws Exception {
        MimeMessage message = javaMailSender.createMimeMessage();
        message.addRecipients(Message.RecipientType.TO, email);
        message.setSubject("인증 번호입니다.");
        message.setText("이메일 인증코드 : " + code);
        message.setFrom("보내는사람의 이메일");
        return message;
    }
    
    // 이메일 보내는 서비스
    public void sendMail(MimeMessage email) throws Exception {
        try {
            log.info("이메일 전송 중");
            javaMailSender.send(email);
        } catch (MailException mailException) {
            mailException.printStackTrace();
            throw new IllegalAccessException();
        }
    }

    // 인증 번호 만드는 서비스
    private String createAuthNumber() {
        return UUID.randomUUID().toString().substring(0, 6); 
        //랜덤 인증번호 uuid를 이용!
    }
    
    // 위 메소드를 다 활용한 메소드 Controller에서는 이 메소드만 사용하면됨.
    public String sendCertificationMail(String email) throws Exception {
        String code = createAuthNumber();
        sendMail(createCodeEmail(email, code));
        return code;
    }
}
  • Controller
    • 이메일은 개인정보임으로 @Post요청으로 진행
    • Dto는 이메일 형식으로 입력되었는지 검증하는 로직으로 만들었다.
   @PostMapping("/email-code/request")
    public String mailAuthentication(@Valid @RequestBody EmailAuthenticationDto emailReq) throws Exception {
        return mailService.sendCertificationMail(emailReq.getEmail());
    }

Redis 설정하기

  • build.gradle(의존성 추가)
    • Redis를 핸들링 하는데 필요한 라이브러리
      //redis
      implementation 'org.springframework.boot:spring-boot-starter-data-redis'
  • 어플리케이션 설정(yaml)
    • Redis를 사용할 포트랑 host 입력
    redis:
      host: localhost
      port: 6379
  • Redis 핸들링하는 클래스 bean등록
@Component
@RequiredArgsConstructor
public class RedisUtil {

    private final StringRedisTemplate stringRedisTemplate;

    // Redis에 데이터 저장
    public void setData(String key, String value) {
        stringRedisTemplate.opsForValue().set(key, value);
    }
    // 데이터 조회
    public String getData(String key) {
        return stringRedisTemplate.opsForValue().get(key);
    }
    // 데이터 삭제
    public void deleteData(String key) {
        stringRedisTemplate.delete(key);
    }
    // 데이터 기한과 함께 저장
    public void setDataExpire(String key, String value, long time) {
        stringRedisTemplate.opsForValue().set(key, value, time, java.util.concurrent.TimeUnit.SECONDS); // 초단위로 계산
    }
}
  • MailService 수정
    • 메일 전송시, Redis에 저장하는 로직 추가
    • 인증번호 확인하는 로직 추가
@Slf4j
@Service
@RequiredArgsConstructor
public class MailService {

     // .. 기존 내용 생략
    private final RedisUtil redisUtil; //추가
   
    // ..중략
   
    public String sendCertificationMail(String email) throws Exception {
        String code = createAuthNumber();
        //메일 생성
        sendMail(createCodeEmail(email, code));
        saveRedisAuthNumber(email, code, 180L); //3분
        return code;
    }
    
    public String verifyEmailCode(String email, String code) {
        String redisCode = redisUtil.getData(email);
        if (redisCode == null) {
            return "인증번호가 만료되었습니다.";
        }
        if (redisCode.equals(code)) {
            redisUtil.deleteData(email);
            return "인증 성공";
        }
        return "인증번호가 틀렸습니다.";
    }
  • Controller
    • 여기 또한 @Post요청으로 진행
    • 이메일 확인이 잘되면 잘되었다고 응답보내주면됨.

    @PostMapping("/email-code/verify")
    public String verifyEmailCode(@Valid EmailCodeVerifyDto dto) {
        return mailService.verifyEmailCode(dto.getEmail(), dto.getCode());
    }

1부 마치며,

  • 내용이 좀 길어져 구현부분은 회원가입부분만 적었는데, 사실 회원가입을 구현했다면 나머지도 금방 구현할 수 있다. 다만, Mail부분과 로그인 회원가입 기능부분을 나눠서 정리하고 싶다보니까 다음 포스팅에서는 폴더 및 파일 구조에 대해서도 언급되면서 나머지 기능을 정리할 것 같다.

  • 확실히 정리하고 나니, 반복되는 부분이나 정리해야되는 부분이 생기는 것같다.

profile
한단계씩 올라가는 개발자

0개의 댓글