Skip to content

Commit

Permalink
Merge pull request #27 from Leets-Official/feat/#22-예외-처리-자동화-구현
Browse files Browse the repository at this point in the history
[feat] 예외처리 자동화
  • Loading branch information
ehs208 authored Jan 14, 2025
2 parents 37749f4 + 4e0a13a commit 2aadd33
Show file tree
Hide file tree
Showing 11 changed files with 361 additions and 199 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ public enum MeetingStatus {
ACTIVE("활성화"),
INACTIVE("비활성화"),
;

private final String text;

MeetingStatus(String text) {
Expand Down
274 changes: 136 additions & 138 deletions src/main/java/com/example/eatmate/global/auth/jwt/JwtService.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
package com.example.eatmate.global.auth.jwt;

import java.util.Arrays;
import java.util.Date;
import java.util.Optional;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
Expand All @@ -8,154 +16,144 @@
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.example.eatmate.app.domain.member.domain.repository.MemberRepository;
import com.example.eatmate.global.config.error.exception.custom.UserNotFoundException;

import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Arrays;
import java.util.Date;
import java.util.Optional;

@Service
@RequiredArgsConstructor
@Getter
@Slf4j
public class JwtService {

@Value("${jwt.secretKey}")
private String secretKey;

@Value("${jwt.access.expiration}")
private Long accessTokenExpirationPeriod;

@Value("${jwt.refresh.expiration}")
private Long refreshTokenExpirationPeriod;

private static final String ACCESS_TOKEN_SUBJECT = "AccessToken";
private static final String REFRESH_TOKEN_SUBJECT = "RefreshToken";
private static final String EMAIL_CLAIM = "email";
private static final String ROLE_CLAIM = "role";

private final MemberRepository memberRepository;

/**
* 토큰 생성 메서드
*/
private String createToken(String subject, long expirationTime, String email, String role) {
Date now = new Date();
var jwtBuilder = JWT.create()
.withSubject(subject)
.withExpiresAt(new Date(now.getTime() + expirationTime))
.withClaim(EMAIL_CLAIM, email);

if (role != null) {
jwtBuilder.withClaim(ROLE_CLAIM, role); // Role 클레임 추가
}

return jwtBuilder.sign(Algorithm.HMAC512(secretKey));
}

/**
* Access Token 생성 (Role 포함)
*/
public String createAccessToken(String email, String role) {
return createToken(ACCESS_TOKEN_SUBJECT, accessTokenExpirationPeriod, email, role);
}

/**
* Refresh Token 생성 (Role 정보 없음)
*/
public String createRefreshToken() {
return createToken(REFRESH_TOKEN_SUBJECT, refreshTokenExpirationPeriod, null, null);
}

/**
* 공통: 쿠키에서 토큰 추출
*/
private Optional<String> extractTokenFromCookie(HttpServletRequest request, String cookieName) {
return Arrays.stream(Optional.ofNullable(request.getCookies()).orElse(new Cookie[0]))
.filter(cookie -> cookie.getName().equals(cookieName))
.map(Cookie::getValue)
.findFirst();
}

/**
* 쿠키에서 Access Token 추출
*/
public Optional<String> extractAccessTokenFromCookie(HttpServletRequest request) {
return extractTokenFromCookie(request, "AccessToken");
}

/**
* 쿠키에서 Refresh Token 추출
*/
public Optional<String> extractRefreshTokenFromCookie(HttpServletRequest request) {
return extractTokenFromCookie(request, "RefreshToken");
}

/**
* Access Token에서 Email 추출
*/
public Optional<String> extractEmail(String accessToken) {
return extractClaim(accessToken, EMAIL_CLAIM);
}

/**
* Access Token에서 Role 추출
*/
public Optional<String> extractRole(String accessToken) {
return extractClaim(accessToken, ROLE_CLAIM);
}

/**
* 공통: 토큰에서 Claim 추출
*/
private Optional<String> extractClaim(String token, String claim) {
try {
JWTVerifier verifier = JWT.require(Algorithm.HMAC512(secretKey)).build();
return Optional.ofNullable(verifier.verify(token).getClaim(claim).asString());
} catch (Exception e) {
log.error("토큰에서 {} 추출 실패: {}", claim, e.getMessage());
return Optional.empty();
}
}

/**
* 토큰 유효성 검증
*/
public boolean isTokenValid(String token) {
try {
JWT.require(Algorithm.HMAC512(secretKey)).build().verify(token);
return true;
} catch (TokenExpiredException e) {
log.warn("토큰 만료: {}", e.getMessage());
} catch (SignatureVerificationException e) {
log.warn("서명 검증 실패: {}", e.getMessage());
} catch (JWTDecodeException e) {
log.warn("토큰 디코딩 실패: {}", e.getMessage());
} catch (Exception e) {
log.error("알 수 없는 토큰 검증 오류: {}", e.getMessage());
}
return false;
}

/**
* Refresh Token 업데이트
*/
@Transactional
public void updateRefreshToken(String email, String refreshToken) {
memberRepository.findByEmail(email).ifPresentOrElse(
member -> {
member.updateRefreshToken(refreshToken);
memberRepository.saveAndFlush(member);
},
() -> {
throw new UserNotFoundException();
});
}
private static final String ACCESS_TOKEN_SUBJECT = "AccessToken";
private static final String REFRESH_TOKEN_SUBJECT = "RefreshToken";
private static final String EMAIL_CLAIM = "email";
private static final String ROLE_CLAIM = "role";
private final MemberRepository memberRepository;
@Value("${jwt.secretKey}")
private String secretKey;
@Value("${jwt.access.expiration}")
private Long accessTokenExpirationPeriod;
@Value("${jwt.refresh.expiration}")
private Long refreshTokenExpirationPeriod;

/**
* 토큰 생성 메서드
*/
private String createToken(String subject, long expirationTime, String email, String role) {
Date now = new Date();
var jwtBuilder = JWT.create()
.withSubject(subject)
.withExpiresAt(new Date(now.getTime() + expirationTime))
.withClaim(EMAIL_CLAIM, email);

if (role != null) {
jwtBuilder.withClaim(ROLE_CLAIM, role); // Role 클레임 추가
}

return jwtBuilder.sign(Algorithm.HMAC512(secretKey));
}

/**
* Access Token 생성 (Role 포함)
*/
public String createAccessToken(String email, String role) {
return createToken(ACCESS_TOKEN_SUBJECT, accessTokenExpirationPeriod, email, role);
}

/**
* Refresh Token 생성 (Role 정보 없음)
*/
public String createRefreshToken() {
return createToken(REFRESH_TOKEN_SUBJECT, refreshTokenExpirationPeriod, null, null);
}

/**
* 공통: 쿠키에서 토큰 추출
*/
private Optional<String> extractTokenFromCookie(HttpServletRequest request, String cookieName) {
return Arrays.stream(Optional.ofNullable(request.getCookies()).orElse(new Cookie[0]))
.filter(cookie -> cookie.getName().equals(cookieName))
.map(Cookie::getValue)
.findFirst();
}

/**
* 쿠키에서 Access Token 추출
*/
public Optional<String> extractAccessTokenFromCookie(HttpServletRequest request) {
return extractTokenFromCookie(request, "AccessToken");
}

/**
* 쿠키에서 Refresh Token 추출
*/
public Optional<String> extractRefreshTokenFromCookie(HttpServletRequest request) {
return extractTokenFromCookie(request, "RefreshToken");
}

/**
* Access Token에서 Email 추출
*/
public Optional<String> extractEmail(String accessToken) {
return extractClaim(accessToken, EMAIL_CLAIM);
}

/**
* Access Token에서 Role 추출
*/
public Optional<String> extractRole(String accessToken) {
return extractClaim(accessToken, ROLE_CLAIM);
}

/**
* 공통: 토큰에서 Claim 추출
*/
private Optional<String> extractClaim(String token, String claim) {
try {
JWTVerifier verifier = JWT.require(Algorithm.HMAC512(secretKey)).build();
return Optional.ofNullable(verifier.verify(token).getClaim(claim).asString());
} catch (Exception e) {
log.error("토큰에서 {} 추출 실패: {}", claim, e.getMessage());
return Optional.empty();
}
}

/**
* 토큰 유효성 검증
*/
public boolean isTokenValid(String token) {
try {
JWT.require(Algorithm.HMAC512(secretKey)).build().verify(token);
return true;
} catch (TokenExpiredException e) {
log.warn("토큰 만료: {}", e.getMessage());
} catch (SignatureVerificationException e) {
log.warn("서명 검증 실패: {}", e.getMessage());
} catch (JWTDecodeException e) {
log.warn("토큰 디코딩 실패: {}", e.getMessage());
} catch (Exception e) {
log.error("알 수 없는 토큰 검증 오류: {}", e.getMessage());
}
return false;
}

/**
* Refresh Token 업데이트
*/
@Transactional
public void updateRefreshToken(String email, String refreshToken) {
memberRepository.findByEmail(email).ifPresentOrElse(
member -> {
member.updateRefreshToken(refreshToken);
memberRepository.saveAndFlush(member);
},
() -> {
throw new UserNotFoundException();
});
}
}
Original file line number Diff line number Diff line change
@@ -1,21 +1,24 @@
package com.example.eatmate.global.auth.login.controller;

import com.example.eatmate.global.auth.jwt.JwtService;
import com.example.eatmate.global.auth.login.dto.UserLoginResponseDto;
import com.example.eatmate.global.auth.login.service.LoginService;
import com.example.eatmate.global.config.error.exception.CommonException;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.GetMapping;
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;

import com.example.eatmate.app.domain.member.dto.MemberSignUpRequestDto;
import com.example.eatmate.app.domain.member.service.MemberService;
import com.example.eatmate.global.auth.jwt.JwtService;
import com.example.eatmate.global.auth.login.dto.UserLoginResponseDto;
import com.example.eatmate.global.auth.login.service.LoginService;
import com.example.eatmate.global.response.GlobalResponseDto;

import io.swagger.v3.oas.annotations.Operation;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;

Expand Down Expand Up @@ -50,6 +53,4 @@ public ResponseEntity<GlobalResponseDto<UserLoginResponseDto>> getUserInfo(HttpS
return ResponseEntity.ok(GlobalResponseDto.success(userInfo, HttpStatus.OK.value()));
}



}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.example.eatmate.global.auth.login.dto;

import com.example.eatmate.app.domain.member.domain.Role;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
Expand All @@ -10,6 +11,6 @@
@NoArgsConstructor
public class UserLoginResponseDto {

private String email;
private Role role;
private String email;
private Role role;
}
Loading

0 comments on commit 2aadd33

Please sign in to comment.