[TroubleShooting] JWT를 이용한 로그인 과정에서 이메일 인증하기

이동엽·2023년 4월 15일
3

troubleshooting

목록 보기
1/1
post-thumbnail

💡 개요

필자는 졸업 작품으로 병원 리뷰 플랫폼을 만들고 있다.
지난 2주동안에는 로그인 과정을 이해하고 이를 구현하는 데에 애를 먹었던 시간이였다.


나는 도큐먼트나 정리가 잘 된 블로그를 참고해 당장 제대로 동작한다고 해서 마냥 기뻐하는 개발자가 되긴 싫었다.

그건 내 코드가 아닐 뿐더러, 동작방식은 커녕 하다못해 변수명을 왜 그렇게 지었는지 조차 설명하지 못한다면 그걸 개발자라고 할 수는 있을까?

적어도 내 프로젝트에 여러 자료를 참조해 적용했다면, 동작 방식을 이해하고 내 입맛에 맞게 수정해야 한다고 생각한다.



프로젝트에서 인증 및 인가에 대한 로직을 나중으로 미루어두고, 기능 구현을 먼저 시작했기에 CRUD에서 반복적인 작업이 계속되었다. 기능 구현 초안을 마무리하고, 로그인을 구현하니 재미를 느끼기 시작했다!

  • 기능 구현에 대한 API를 만드는 것은 인프런에서 김영한님의 강의를 많이 참고했지만, 해당 강의에서 로그인을 다루지는 않았다. 따라서 혼자 발품 팔듯 자료를 찾는 과정이였다.
  • 아직 스프링 시큐리티에 대한 이해도가 부족하기에, 시큐리티를 사용하지 않은 로그인으로 구현하려고 노력했다.
  • 여러 블로그들을 참고할 수 있었고, 여러 방법들이 제시되어 있었기에 내 입맛대로 골라가며 적용할 수 있었다.


최근에 이러한 과정을 거치면서 생긴 고민거리?들은 아래와 같다.


고민거리 1. 욕심이 생겨 OAuth를 도입해도 될까?

요구사항을 설계할 때 JWT를 이용한 일반 로그인만 고려했고, OAuth는 고려하지 않았다.

로그인 구현이 재밌었기에 금방 끝낼 수 있었고, 욕심이 생겨 OAuth도 별도의 실습 프로젝트에서 구현을 성공했다.

요구사항에 없던 OAuth를 갑자기 도입하자는 건 내 욕심일까?

  • 프로젝트는 나 혼자 진행하는 것이 아니다. 프론트엔드를 담당하는 세나와 둘 뿐이지만, 이건 명백한 팀 프로젝트이다.
  • 갑자기 도입을 하자는 것은 굉장히 부담이 될 수 있다고 생각이 들었다. 물론 나에게도..

예정에 없던 OAuth를 도입하게 된다면 일어날 변경 사항들

  • OAuth에서는 최소한의 정보들만 제공을 한다. 또한 OAuth API 플랫폼마다 제공하는 정보들도 다르다.
    • 우리의 프로젝트의 경우, 의료진 가입시 연락처 및 소속 병원, 병과가 필수로 입력되어야 한다.
    • 따라서 OAuth 인증 과정이 끝나면 추가 정보를 요구하는 페이지로 redirect하여 정보를 요구해야한다.

  • 요구사항이 변경되었으니 기존 테이블 설계 또한 변경해야 했다.
    • 회원 테이블에 OAuth에서 제공하는 oauth_id 등과 같은 필드는 존재하지 않았다.
    • 추가하면 되지 않겠냐고?
      • 기존에 없던 필드를 추가하는 것은 어렵지 않다.
      • 문제는 일반 로그인과 OAuth를 같이 제공할 경우였다.

고민거리 2. 일반 로그인과 OAuth를 같이 제공하려면 테이블을 어떻게 설계해야 할까?

일반 로그인의 경우 이메일 및 비밀번호가 필수로 요구된다. 이 과정에서 추가로 이메일 인증이 필요하다.
반면, OAuth의 경우에는 이메일은 이미 인증을 받은 상태이고, 비밀번호는 API에서 제공하지 않는다.

→ 단순하게 (아구를 맞추려고) password를 nullable = true로 바꾸고, oauth_id 속성을 추가해도 되는걸까?


고민거리 3. 그 외 고민들

  • 일반 로그인을 이용한 회원과 OAuth를 이용한 회원은 어떻게 구분해야할까?
  • OAuth 과정에서 인가코드를 요청하는 부분까지가 프론트엔드의 역할이고, 나머지는 백엔드가 구현하면 되는걸까?
    • 이런 역할의 선은 유동성있게 조정하면 되는건가?

머리가 아팠다.
잠시 바람을 쐬고 돌아와 앉아서 생각을 해보니 아직 일반 로그인도 사실은 완성된 게 아니였다..
(이메일 형식 검사, 비밀번호 4자 이상 검사, 이메일 인증 등등..)


결국 팀원과 상의를 했고 일반 로그인부터 제대로 해보고, OAuth는 당분간 생각하지 말자!고 결정했다.



💡 문제 직면(Trouble)

처음엔 스프링 도큐먼트에서 해당 내용을 찾아보았지만, 내용이 너무 간략화되어있었다.
(사실 내가 자료를 못찾은 것일지도 모른다.)


그러다 구글링을 통해 여러 블로그들을 찾아보았고, 여러 방식들이 존재했지만 대략적인 내용은 비슷했다.
내가 참고한 게시글들은 아래와 같다.


모든 블로그들이 대략적인 코드들이 비슷했기에, 크게 불안하지않고 실행을 시켜보려했으나.. 역시나 문제가 발생했다!


Stack trace :

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'EmailService': Unsatisfied dependency expressed through field 'emailSender': Error creating bean with name 'mailSender' defined in class path resource [org/springframework/boot/autoconfigure/mail/MailSenderPropertiesConfiguration.class]: Failed to instantiate [org.springframework.mail.javamail.JavaMailSenderImpl]: Factory method 'mailSender' threw exception with message: arraycopy: element type mismatch: can not cast one of the elements of java.lang.Object[] to the type of the destination array, jakarta.activation.MimeTypeRegistry

Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'mailSender' defined in class path resource [org/springframework/boot/autoconfigure/mail/MailSenderPropertiesConfiguration.class]: Failed to instantiate [org.springframework.mail.javamail.JavaMailSenderImpl]: Factory method 'mailSender' threw exception with message: arraycopy: element type mismatch: can not cast one of the elements of java.lang.Object[] to the type of the destination array, jakarta.activation.MimeTypeRegistry
	at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:657) ~[spring-beans-6.0.4.jar:6.0.4]
	at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:645) ~[spring-beans-6.0.4.jar:6.0.4]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1324) ~[spring-beans-6.0.4.jar:6.0.4]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1161) ~[spring-beans-6.0.4.jar:6.0.4]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:561) ~[spring-beans-6.0.4.jar:6.0.4]


오류 내용

EmailConfig.java 에서 JavaMailSender를 필요한 정보들을 담아 직접 Bean으로 등록하였다.
그리고 내가 작성한 EmailService는 이 JavaMailSender를 의존성 주입을 받는다.

그런데 이 과정에서 의존성 주입이 안되어 빈을 생성할 수 없다는 오류!



첫 번째 접근

처음엔 빈을 생성할 수 없다기에 ‘@Service 와 같은 어노테이션을 빠트렸나?’ 하였지만 그러지 않았다.



두 번째 접근

이틀에 걸쳐 구글링을 통해 수많은 블로그들을 참고하였고, 혹시나 내가 smtp 설정을 잘못 건드린건 아닌가 싶어 수십번을 검토했다.



그러던 중 'velog에는 최신 오류들이 있지않을까?' 싶어서 보니 나와 같은 오류를 해결한 게시글을 발견했다.
→ 다만 이렇게 서비스를 직접 빈으로 등록할 경우에, 내가 작성한 Service와 이름이 동일해 충돌이 발생하게 된다.


충돌을 막으려면 application.yml 설정에서 spring.main.allow-bean-definition-overriding: true 를 지정하면 된다고 하는데.. 뭔가 꺼림직해서 위에 첨부한 Stack trace다시 보니 이상한 점을 발견했다.



EmailService에 있는 emailSender 객체에 내가 등록하려했던 빈이 아닌, 다른 빈이 호출되고 있었다!

이 오류에 대해 해결해보고자 했지만.. 아쉽게도 여기서부터는 슬슬 뇌가 지치기 시작했다.



세 번째 접근

결국 Spring에 Issue로 다른 사람들이 동일한 질문이 있는지 찾아보았고, 결국 New Issue를 남겼다.

내가 받은 답변으로는 “Javax를 사용하고 있는 것 같으니.. 의존성을 Jakarta로 변경해라” 이다.

다만, 내가 첨부한 build.gradle 을 보면 javax에 대한 의존성은 갖고 있지 않았다..
→ 도움이 되지 않았던 답변..😭

그렇게 “이틀을 꼬박 날리는구나.. 하아아” 하고 아쉬워하며 잠들기 직전에 갑자기 머릿속에 번뜩이던 아이디어가 떠올랐다.

ChatGPT한테는 안물어봤네?



💡 문제 해결(Shooting)

Step - 1 : 내 오류 알려주기

우선.. 첫 질문에 대한 대답은 아쉽게도 내가 모두 시도해 본 상황이였다.



Step - 2 : 내 오류 메시지 보내기

이번에는 블로그에서 보지 못했기에 뭔가 솔깃한 대답들이였다.
우선 1. Jakarta Activation 의존성 추가를 했을 경우에는 마찬가지로 오류가 발생해 아쉬워했다.
*그리고 2. 의존성 충돌 확인에서야 드디어 답을 찾을 수 있었다..


“Spring Boot 2.5.x 이상에서는 jakarta.activation 라이브러리가 기본적으로 포함되어, 의존성 충돌이 발생할 수 있다. 따라서 아래와 같이 의존성 제외 설정을 추가하자 .
com.sun.activation 라이브러리가 제외되어 충돌 문제를 해결할 수 있다.”

implementation ('org.springframework.boot:spring-boot-starter-mail') {
    exclude group: 'com.sun.activation', module: 'javax.activation'
}

Gradle Dependencies를 확인

jakarta.activation 라이브러리가 기본적으로 포함되어 있었고, 의존성 충돌이 발생하고 있던 것이 맞았다..



💡 마치며

스프링부트 버전별로 라이브러리 의존성까지 알고있지는 않기에 오류를 찾는 데에 너무 오랜 시간이 걸렸다.

‘이런 충돌 문제는 스프링부트에서 알아서 해줬으면 참 편리할텐데..’ 와 ‘ChatGPT는 이걸 어떻게 알았지?’ 라는 생각이 동시에 드는 순간이다.

최근들어 스프링부트 3.0.2를 쓰고 있기에 2.x 버전과는 많이 달라져 자잘한 의존성 및 코드를 변경하는 데에 시간을 많이 쓰는 것 같다. 이 또한 성장하는 시간이 되길 바라며 마무리!

profile
백엔드 개발자로 등 따숩고 배 부르게 되는 그 날까지

0개의 댓글