diff --git a/src/main/java/com/kcy/fitapet/domain/member/api/MemberApi.java b/src/main/java/com/kcy/fitapet/domain/member/api/MemberApi.java index 3017b8de..09d712f6 100644 --- a/src/main/java/com/kcy/fitapet/domain/member/api/MemberApi.java +++ b/src/main/java/com/kcy/fitapet/domain/member/api/MemberApi.java @@ -5,6 +5,8 @@ import com.kcy.fitapet.domain.member.dto.sms.SmsReq; import com.kcy.fitapet.domain.member.dto.sms.SmsRes; import com.kcy.fitapet.domain.member.service.component.MemberAuthService; +import com.kcy.fitapet.global.common.resolver.access.AccessToken; +import com.kcy.fitapet.global.common.resolver.access.AccessTokenInfo; import com.kcy.fitapet.global.common.response.ErrorResponse; import com.kcy.fitapet.global.common.response.FailureResponse; import com.kcy.fitapet.global.common.response.SuccessResponse; @@ -60,7 +62,7 @@ public class MemberApi { @ApiResponse(responseCode = "4xx", description = "에러", content = @Content(schema = @Schema(implementation = ErrorResponse.class))), }) @PostMapping("/register") - public ResponseEntity register(@RequestHeader("Authorization") String accessToken, @RequestBody @Valid SignUpReq dto) { + public ResponseEntity register(@AccessTokenInfo AccessToken accessToken, @RequestBody @Valid SignUpReq dto) { Map tokens = memberAuthService.register(accessToken, dto); return getResponseEntity(tokens); } diff --git a/src/main/java/com/kcy/fitapet/domain/member/service/component/MemberAuthService.java b/src/main/java/com/kcy/fitapet/domain/member/service/component/MemberAuthService.java index d695352c..20823e5b 100644 --- a/src/main/java/com/kcy/fitapet/domain/member/service/component/MemberAuthService.java +++ b/src/main/java/com/kcy/fitapet/domain/member/service/component/MemberAuthService.java @@ -9,15 +9,16 @@ import com.kcy.fitapet.domain.member.service.module.MemberSaveService; import com.kcy.fitapet.domain.member.service.module.MemberSearchService; import com.kcy.fitapet.domain.member.service.module.SmsService; +import com.kcy.fitapet.global.common.resolver.access.AccessToken; import com.kcy.fitapet.global.common.response.code.ErrorCode; import com.kcy.fitapet.global.common.response.exception.GlobalErrorException; import com.kcy.fitapet.global.common.util.jwt.JwtUtil; import com.kcy.fitapet.global.common.util.jwt.entity.JwtUserInfo; +import com.kcy.fitapet.global.common.util.jwt.entity.SmsAuthInfo; import com.kcy.fitapet.global.common.util.redis.forbidden.ForbiddenTokenService; import com.kcy.fitapet.global.common.util.redis.refresh.RefreshToken; import com.kcy.fitapet.global.common.util.redis.refresh.RefreshTokenService; import com.kcy.fitapet.global.common.util.redis.sms.SmsCertificationService; -import com.nimbusds.oauth2.sdk.SuccessResponse; import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -46,26 +47,19 @@ public class MemberAuthService { private final PasswordEncoder bCryptPasswordEncoder; @Transactional - public Map register(String authHeader, SignUpReq dto) { - String parsedToken = jwtUtil.resolveToken(authHeader); - String authenticatedPhone = jwtUtil.getPhoneNumberFromToken(parsedToken); - - if (!smsCertificationService.isCorrectCertificationNumber(authenticatedPhone, parsedToken)) - throw new GlobalErrorException(ErrorCode.INVALID_AUTH_CODE); + public Map register(AccessToken accessToken, SignUpReq dto) { + String authenticatedPhone = jwtUtil.getPhoneNumberFromToken(accessToken.accessToken()); smsCertificationService.removeCertificationNumber(authenticatedPhone); Member requestMember = dto.toEntity(authenticatedPhone); requestMember.encodePassword(bCryptPasswordEncoder); + validateMember(requestMember); log.debug("회원가입 요청: {}", requestMember); - if (memberSearchService.isExistMemberByUidOrEmailOrPhone(requestMember.getUid(), requestMember.getEmail(), requestMember.getPhone())) - throw new GlobalErrorException(ErrorCode.DUPLICATE_USER_INFO_ERROR); - Member registeredMember = memberSaveService.saveMember(requestMember); log.debug("회원가입 완료: {}", registeredMember); - JwtUserInfo jwtUserInfo = JwtUserInfo.from(registeredMember); - return generateToken(jwtUserInfo); + return generateToken(JwtUserInfo.from(registeredMember)); } @Transactional @@ -74,9 +68,7 @@ public Map login(SignInReq dto) { if (member.checkPassword(dto.password(), bCryptPasswordEncoder)) throw new GlobalErrorException(ErrorCode.NOT_MATCH_PASSWORD_ERROR); - JwtUserInfo jwtUserInfo = JwtUserInfo.from(member); - - return generateToken(jwtUserInfo); + return generateToken(JwtUserInfo.from(member)); } @Transactional @@ -123,13 +115,15 @@ public SmsRes sendCertificationNumber(SmsReq dto) { @Transactional public String checkCertificationNumber(SmsReq smsReq, String requestCertificationNumber) { - String token = null; - if (smsCertificationService.isCorrectCertificationNumber(smsReq.to(), requestCertificationNumber)) { - log.info("인증번호 일치"); - token = smsCertificationService.issueSmsAuthToken(smsReq.to()); + if (!smsCertificationService.isCorrectCertificationNumber(smsReq.to(), requestCertificationNumber)) { + log.warn("인증번호 불일치"); + throw new GlobalErrorException(ErrorCode.INVALID_AUTH_CODE); } - return (token != null) ? token : ""; + String token = jwtUtil.generateSmsAuthToken(SmsAuthInfo.of(1L, smsReq.to())); + smsCertificationService.saveSmsAuthToken(smsReq.to(), token); + + return token; } private void checkSmsStatus(String requestPhone, SensRes sensRes) { diff --git a/src/main/java/com/kcy/fitapet/global/common/security/filter/JwtAuthenticationFilter.java b/src/main/java/com/kcy/fitapet/global/common/security/filter/JwtAuthenticationFilter.java index 97792104..f33943bb 100644 --- a/src/main/java/com/kcy/fitapet/global/common/security/filter/JwtAuthenticationFilter.java +++ b/src/main/java/com/kcy/fitapet/global/common/security/filter/JwtAuthenticationFilter.java @@ -129,7 +129,7 @@ private String reissueAccessToken(HttpServletRequest request, HttpServletRespons JwtUserInfo userInfo = jwtUtil.getUserInfoFromToken(requestRefreshToken); String reissuedAccessToken = jwtUtil.generateAccessToken(userInfo); - response.addHeader(ACCESS_TOKEN.getValue(), reissuedAccessToken); + response.addHeader(REISSUED_ACCESS_TOKEN.getValue(), reissuedAccessToken); return reissuedAccessToken; } catch (AuthErrorException e) { log.error("Failed to reissue access token: {}", e.getMessage()); diff --git a/src/main/java/com/kcy/fitapet/global/common/util/jwt/AuthConstants.java b/src/main/java/com/kcy/fitapet/global/common/util/jwt/AuthConstants.java index 4b825a00..d06f20e0 100644 --- a/src/main/java/com/kcy/fitapet/global/common/util/jwt/AuthConstants.java +++ b/src/main/java/com/kcy/fitapet/global/common/util/jwt/AuthConstants.java @@ -5,7 +5,8 @@ @Getter public enum AuthConstants { AUTH_HEADER("Authorization"), TOKEN_TYPE("Bearer "), - ACCESS_TOKEN("accessToken"), REFRESH_TOKEN("refreshToken"); + ACCESS_TOKEN("accessToken"), REFRESH_TOKEN("refreshToken"), + REISSUED_ACCESS_TOKEN("X-Access-Token"); private final String value; diff --git a/src/main/java/com/kcy/fitapet/global/common/util/jwt/JwtUtil.java b/src/main/java/com/kcy/fitapet/global/common/util/jwt/JwtUtil.java index 6d7ec4b8..5856e330 100644 --- a/src/main/java/com/kcy/fitapet/global/common/util/jwt/JwtUtil.java +++ b/src/main/java/com/kcy/fitapet/global/common/util/jwt/JwtUtil.java @@ -1,6 +1,7 @@ package com.kcy.fitapet.global.common.util.jwt; import com.kcy.fitapet.global.common.util.jwt.entity.JwtUserInfo; +import com.kcy.fitapet.global.common.util.jwt.entity.SmsAuthInfo; import com.kcy.fitapet.global.common.util.jwt.exception.AuthErrorException; import java.util.Date; @@ -30,10 +31,10 @@ public interface JwtUtil { /** * 사용자 정보 기반으로 SMS 인증 토큰을 생성하는 메서드 - * @param phoneNumber String : 수신자 번호 + * @param user SmsAuthInfo : 사용자 정보 * @return String : 토큰 */ - String generateSmsAuthToken(String phoneNumber); + String generateSmsAuthToken(SmsAuthInfo user); /** * token으로 부터 사용자 정보를 추출하는 메서드 diff --git a/src/main/java/com/kcy/fitapet/global/common/util/jwt/JwtUtilImpl.java b/src/main/java/com/kcy/fitapet/global/common/util/jwt/JwtUtilImpl.java index ed13f1a2..52553584 100644 --- a/src/main/java/com/kcy/fitapet/global/common/util/jwt/JwtUtilImpl.java +++ b/src/main/java/com/kcy/fitapet/global/common/util/jwt/JwtUtilImpl.java @@ -2,7 +2,9 @@ import com.kcy.fitapet.domain.member.domain.RoleType; import com.kcy.fitapet.global.common.util.exception.JwtErrorCodeUtil; +import com.kcy.fitapet.global.common.util.jwt.entity.JwtDto; import com.kcy.fitapet.global.common.util.jwt.entity.JwtUserInfo; +import com.kcy.fitapet.global.common.util.jwt.entity.SmsAuthInfo; import com.kcy.fitapet.global.common.util.jwt.exception.AuthErrorCode; import com.kcy.fitapet.global.common.util.jwt.exception.AuthErrorException; import io.jsonwebtoken.*; @@ -82,12 +84,12 @@ public String generateRefreshToken(JwtUserInfo user) { @Override @SuppressWarnings("deprecation") - public String generateSmsAuthToken(String phoneNumber) { + public String generateSmsAuthToken(SmsAuthInfo user) { final Date now = new Date(); return Jwts.builder() .setHeader(createHeader()) - .setClaims(Map.of(PHONE_NUMBER, phoneNumber)) + .setClaims(Map.of(PHONE_NUMBER, user.phoneNumber())) .signWith(SignatureAlgorithm.HS256, createSignature()) .setExpiration(createExpireDate(now, smsAuthExpirationTime.toMillis())) .compact(); diff --git a/src/main/java/com/kcy/fitapet/global/common/util/jwt/entity/JwtDto.java b/src/main/java/com/kcy/fitapet/global/common/util/jwt/entity/JwtDto.java new file mode 100644 index 00000000..27f54a5d --- /dev/null +++ b/src/main/java/com/kcy/fitapet/global/common/util/jwt/entity/JwtDto.java @@ -0,0 +1,4 @@ +package com.kcy.fitapet.global.common.util.jwt.entity; + +public interface JwtDto { +} diff --git a/src/main/java/com/kcy/fitapet/global/common/util/jwt/entity/JwtUserInfo.java b/src/main/java/com/kcy/fitapet/global/common/util/jwt/entity/JwtUserInfo.java index 08f8537c..6ffd8d94 100644 --- a/src/main/java/com/kcy/fitapet/global/common/util/jwt/entity/JwtUserInfo.java +++ b/src/main/java/com/kcy/fitapet/global/common/util/jwt/entity/JwtUserInfo.java @@ -6,10 +6,10 @@ import lombok.ToString; @Builder -public record JwtUserInfo( +public record JwtUserInfo ( Long id, RoleType role -) { +) implements JwtDto { public static JwtUserInfo of(Long id, RoleType role) { return new JwtUserInfo(id, role); } diff --git a/src/main/java/com/kcy/fitapet/global/common/util/jwt/entity/SmsAuthInfo.java b/src/main/java/com/kcy/fitapet/global/common/util/jwt/entity/SmsAuthInfo.java new file mode 100644 index 00000000..fad139d3 --- /dev/null +++ b/src/main/java/com/kcy/fitapet/global/common/util/jwt/entity/SmsAuthInfo.java @@ -0,0 +1,13 @@ +package com.kcy.fitapet.global.common.util.jwt.entity; + +import lombok.Builder; + +@Builder +public record SmsAuthInfo( + Long userId, + String phoneNumber +) implements JwtDto { + public static SmsAuthInfo of(Long userId, String phoneNumber) { + return new SmsAuthInfo(userId, phoneNumber); + } +} diff --git a/src/main/java/com/kcy/fitapet/global/common/util/jwt/exception/AuthErrorCode.java b/src/main/java/com/kcy/fitapet/global/common/util/jwt/exception/AuthErrorCode.java index 65b83aea..9da55174 100644 --- a/src/main/java/com/kcy/fitapet/global/common/util/jwt/exception/AuthErrorCode.java +++ b/src/main/java/com/kcy/fitapet/global/common/util/jwt/exception/AuthErrorCode.java @@ -33,7 +33,12 @@ public enum AuthErrorCode implements StatusCode { * 403 FORBIDDEN: 인증된 클라이언트가 권한이 없는 자원에 접근 */ FORBIDDEN_ACCESS_TOKEN(FORBIDDEN, "해당 토큰에는 엑세스 권한이 없습니다"), - MISMATCHED_REFRESH_TOKEN(FORBIDDEN, "리프레시 토큰의 유저 정보가 일치하지 않습니다"); + MISMATCHED_REFRESH_TOKEN(FORBIDDEN, "리프레시 토큰의 유저 정보가 일치하지 않습니다"), + + /** + * 500 INTERNAL_SERVER_ERROR: 서버 내부 에러 + */ + INVALID_JWT_DTO_FORMAT(INTERNAL_SERVER_ERROR, "서버 내부 에러가 발생했습니다."); private final HttpStatus httpStatus; private final String message; diff --git a/src/main/java/com/kcy/fitapet/global/common/util/redis/sms/SmsCertificationService.java b/src/main/java/com/kcy/fitapet/global/common/util/redis/sms/SmsCertificationService.java index aea76224..c85f6fa5 100644 --- a/src/main/java/com/kcy/fitapet/global/common/util/redis/sms/SmsCertificationService.java +++ b/src/main/java/com/kcy/fitapet/global/common/util/redis/sms/SmsCertificationService.java @@ -11,11 +11,11 @@ public interface SmsCertificationService { String issueCertificationNumber(String phoneNumber); /** - * SMS 인증 완료 후 계정 생성을 위한 토큰 발행 + * SMS 인증 완료 후 계정 생성을 위한 토큰 저장 * @param phoneNumber : String - * @return String : 토큰 + * @param accessToken : String */ - String issueSmsAuthToken(String phoneNumber); + void saveSmsAuthToken(String phoneNumber, String accessToken); /** * 인증번호 확인 diff --git a/src/main/java/com/kcy/fitapet/global/common/util/redis/sms/SmsCertificationServiceImpl.java b/src/main/java/com/kcy/fitapet/global/common/util/redis/sms/SmsCertificationServiceImpl.java index 9a9828c8..ed622dc9 100644 --- a/src/main/java/com/kcy/fitapet/global/common/util/redis/sms/SmsCertificationServiceImpl.java +++ b/src/main/java/com/kcy/fitapet/global/common/util/redis/sms/SmsCertificationServiceImpl.java @@ -18,8 +18,6 @@ public class SmsCertificationServiceImpl implements SmsCertificationService { private final SmsCertificationRepository smsCertificationRepository; private final RedisTemplate redisTemplate; - private final JwtUtil jwtUtil; - @Override public String issueCertificationNumber(String phoneNumber) { StringBuilder sb = new StringBuilder(); @@ -36,10 +34,8 @@ public String issueCertificationNumber(String phoneNumber) { } @Override - public String issueSmsAuthToken(String phoneNumber) { - String token = jwtUtil.generateSmsAuthToken(phoneNumber); - smsCertificationRepository.save(SmsCertification.of(phoneNumber, token)); - return token; + public void saveSmsAuthToken(String phoneNumber, String accessToken) { + smsCertificationRepository.save(SmsCertification.of(phoneNumber, accessToken)); } @Override