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

Feat #24 학교 이메일 인증 구현 #24

Merged
merged 43 commits into from
Jan 10, 2025

Conversation

koreaioi
Copy link
Member

@koreaioi koreaioi commented Jan 8, 2025

📌 관련 이슈

관련 이슈 번호 #22
Close #22

🚀 작업 내용

PR에서 작업한 내용을 설명

  • AWS SES 사용
  • 이메일 인증 번호 전송 API
  • 이메일 인증 번호 검증 및 유저 정보 업데이트 API
  • SES 이메일 템플릿 생성 API
  • JwtAuthenticationFilter 엣지 케이스 해결

AWS SES 사용

  • 이메일 전송을 위해 AWS SES를 사용했습니다.
    구글 SMTP를 사용하니 사용자의 스팸함으로 메일이 들어가 사용자 경험이 좋지 못할거라 판단했습니다.
    따라서 AWS SES로 이메일 전송 방법을 바꿨습니다.

  • 아직 샌드박스 모드이기 때문에 AWS SES에서 자격 증명이 된 이메일에만 메일을 보낼 수 있습니다.
    (팀원들이 테스트 하기엔 충분합니다.)
    하지만 실제 운영을 위해서는 도메인을 구매하여 Route 53에 등록하고 해당 도메인으로 자격 증명을 합니다.
    그 뒤, 샌드박스 모드 해제를 요청하도록 하겠습니다.

이메일 인증 번호 전송 API

  • 기존에 JwtRedisUtil을 RedisUtil로 변경하고 저장 메서드를 추가했습니다.
  • 프론트에서 이메일을 보내면 해당 이메일이 가천대학교 이메일인 지 확인합니다.
  • 위 과정에서 Dto 유효성 검증, 이메일 뒷 형식이 gachon.ac.kr 인 지 확인합니다.
  • 랜덤으로 생성한 인증 코드는 Redis에 저장합니다.

이메일 인증 번호 검증

사용자가 입력한 인증 코드와 Redis에서 조회한 인증 코드를 비교합니다.

유저 정보 업데이트 API

사용자가 입력한 인증 코드가 올바르면 임시 유저의 Email 필드를 업데이트합니다.
업데이트 가능성으로 PATCH Http Method를 사용했습니다.

SES 이메일 템플릿 생성 API

이메일을 전송할 때 html 파일을 제대로 꾸미지 못해 디자인이 별로입니다.
추후 디자이너께 부탁드려 html을 가꾸고 해당 html을 사용할 수 있는 SES 이메일 템플릿을 생성해야합니다.
따라서 SES 이메일 템플릿 생성 API를 구현했습니다.

템플릿 제거는 추후 구현하겠습니다.

JwtAuthenticationFilter 엣지 케이스 해결

강혁님의 발견으로 JwtAuthenticationFilter에서 일어나는 엣지 케이스를 발견했습니다.

허용된 경로 (permitAll) 임에도 잘못된 토큰을 들고 있다면 통과되지 못하는 엣지 케이스

문제는 토큰을 검증하고 파싱하는 부분에서 만료 된 토큰이면 예외를 처리하도록 parseClaimsJws가 설계되어 있었습니다.

원래 permitAll()을 할 경우 필터 체인 동작 과정에서 인증/인가 예외가 발생하더라도 ExceptionTranslationFilter를 거치치 않고 통과되어 API 호출이 일어납니다.

하지만 parseClaimsJws() 부분에서 예외가 던져지고 있어 filterChain.doFilter(request, response)가 호출되지 않음 + permitAll 여부와 상관 없이 바로 Exception TranlationFilter로 처리가 되어 넘어가버림.

이를 해결하기 위해서 parseClaimsJws()에서 던지는 예외를 try catch로 잡아서 catch단에서 claims를 null로 반환합니다.

추가로 claims를 한번 더 파싱(만료기간체크)합니다.
이 과정에서 claims가 null이면 예외를 던지므로 isExpired가 false를 반환할 수 있도록 조건 처리를 해줬습니다.

    private Claims parseClaims(String token) {
        JwtParser parser = Jwts.parserBuilder()
                .setSigningKey(key)
                .build();
        Claims claims = null;
        try{
            claims = parser.parseClaimsJws(token).getBody();
        }catch (JwtException e){
            log.error(e.getMessage());
        }
        return claims;
    }
    public Boolean isExpired(String token) {
        Claims claims = parseClaims(token);
        if(claims == null) return true;
        return claims.getExpiration().before(new Date());
    }

📸 스크린샷

구현한 API에 따른 스크린 샷입니다.


이메일 인증 코드 전송

image


구글 계정의 Gmail에서 받은 메일함 확인

image


이메일 인증 성공 시

image


이메일 인증 실패 시

image

가천대 이메일 형식을 지키지 않았을 경우

  • 이메일을 koreaioi로 보낼 경우 Dto의 @Email에 의해 처리됩니다.
    image

이메일 템플릿 생성

image
image


📢 리뷰 요구사항

리뷰어가 특별히 봐주었으면 하는 부분이 있다면 작성

프론트 측에서 userId를 넘겨주길 희망하여 성공 시 반환하고 있습니다.

추가 개선 사항

  • JwtAuthenticationFilter의 validateJwtToken 개선
    JwtAuthenticationFilter의 validateJwtToken를 유효 한지 아닌지 처리가 아닌 parseClaimsJws가 던지는 예외들(코드 참고)
    Jws<Claims> parseClaimsJws(String var1) throws ExpiredJwtException, UnsupportedJwtException, MalformedJwtException, SignatureException, IllegalArgumentException;

을 예외 상수로 반환하게 하여 개선한다면 한번의 유효성 검증으로 세부적인 예외 처리가 가능!

@koreaioi koreaioi added the 🐞 Bug 버그 수정 label Jan 8, 2025
@koreaioi koreaioi added the ✨ Feature 기능 개발 및 요구사항 변경 반영 label Jan 8, 2025
@koreaioi koreaioi self-assigned this Jan 8, 2025
@koreaioi koreaioi linked an issue Jan 8, 2025 that may be closed by this pull request
2 tasks
Copy link
Member

@huncozyboy huncozyboy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

고생하셨습니다 !
SES 사용해서 이메일 템플릿 기반으로 인증 코드를 발송해주는 부분도 잘 구현된거같아요
이메일 인증은 한번도 해보지 않았었는데 많이 배웠습니다

Copy link
Member

@hyxklee hyxklee left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

구현 고생하셨어요!
코멘트 몇가지 달아뒀으니 읽어 주시면 감사하겠습니다!

그리고 REDIS_EMAIL_AUTH_CODE_EXPIRATION 해당 환경변수가 아직 노션에 업데이트 되지 않은 것 같아요!

Copy link
Collaborator

@soyesenna soyesenna left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

고생하셨습니다!!
리뷰 확인해주세요!!

Copy link
Member

@hyxklee hyxklee left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

주석 처리된 부분만 삭제하고 머지하면 될 것 같아요!
JWT 토큰 검증 관련된 부분은 차후 리팩토링 부탁 드릴게요!

@@ -57,4 +64,15 @@ private void saveAuthentcation(String token) {
Authentication authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authentication);
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 부분은 삭제 되어도 되지 않을까요??

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

삭제 후 반영하도록 하겠습니다!!

@koreaioi koreaioi merged commit d8a19b6 into dev Jan 10, 2025
2 checks passed
@koreaioi koreaioi deleted the feat/#22/학교-이메일-인증-구현 branch January 26, 2025 12:47
@koreaioi koreaioi changed the title Feat #26 학교 이메일 인증 구현 Feat #24 학교 이메일 인증 구현 Feb 4, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
🐞 Bug 버그 수정 ✨ Feature 기능 개발 및 요구사항 변경 반영
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Feat] #22 학교 이메일 인증 구현
4 participants