AuthServiceImpl.java
package com.deveagles.be15_deveagles_be.features.auth.command.application.service;
import com.deveagles.be15_deveagles_be.common.exception.BusinessException;
import com.deveagles.be15_deveagles_be.common.exception.ErrorCode;
import com.deveagles.be15_deveagles_be.common.jwt.JwtTokenProvider;
import com.deveagles.be15_deveagles_be.features.auth.command.application.dto.request.CheckEmailRequest;
import com.deveagles.be15_deveagles_be.features.auth.command.application.dto.request.EmailVerifyRequest;
import com.deveagles.be15_deveagles_be.features.auth.command.application.dto.request.LoginRequest;
import com.deveagles.be15_deveagles_be.features.auth.command.application.dto.response.TokenResponse;
import com.deveagles.be15_deveagles_be.features.users.command.domain.aggregate.Staff;
import com.deveagles.be15_deveagles_be.features.users.command.repository.UserRepository;
import jakarta.mail.MessagingException;
import java.time.Duration;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
@Slf4j
@Service
@RequiredArgsConstructor
public class AuthServiceImpl implements AuthService {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
private final JwtTokenProvider jwtTokenProvider;
private final RefreshTokenService refreshTokenService;
private final RedisTemplate<String, String> redisTemplate;
private final MailService mailService;
@Value("${spring.mail.properties.auth-code-expiration-millis}")
private long expireMinute;
@Override
public TokenResponse login(LoginRequest request) {
Staff staff =
userRepository
.findStaffByLoginId(request.loginId())
.orElseThrow(() -> new BusinessException(ErrorCode.USER_NAME_NOT_FOUND));
if (!passwordEncoder.matches(request.password(), staff.getPassword())) {
throw new BusinessException(ErrorCode.USER_INVALID_PASSWORD);
}
String accessToken = jwtTokenProvider.createToken(staff.getLoginId());
String refreshToken = jwtTokenProvider.createRefreshToken(staff.getLoginId());
refreshTokenService.saveRefreshToken(staff.getLoginId(), refreshToken);
return TokenResponse.builder().accessToken(accessToken).refreshToken(refreshToken).build();
}
@Override
public TokenResponse refreshToken(String refreshToken) {
// 리프레시 토큰 유효성 검사
jwtTokenProvider.validateToken(refreshToken);
String username = jwtTokenProvider.getUsernameFromJWT(refreshToken);
// Redis에서 저장된 refresh token 가져오기
String redisKey = "RT:" + username;
String storedToken = redisTemplate.opsForValue().get(redisKey);
if (storedToken == null) {
throw new BadCredentialsException("해당 유저로 저장된 리프레시 토큰 없음");
}
if (!storedToken.equals(refreshToken)) {
throw new BadCredentialsException("리프레시 토큰 일치하지 않음");
}
Staff staff =
userRepository
.findStaffByLoginId(username)
.orElseThrow(() -> new BusinessException(ErrorCode.USER_NAME_NOT_FOUND));
// 새로운 토큰 재발급
String newAccessToken = jwtTokenProvider.createToken(staff.getLoginId());
String newRefreshToken = jwtTokenProvider.createRefreshToken(staff.getLoginId());
redisTemplate
.opsForValue()
.set(
redisKey,
newRefreshToken,
jwtTokenProvider.getRefreshExpiration(),
TimeUnit.MILLISECONDS);
return TokenResponse.builder()
.accessToken(newAccessToken)
.refreshToken(newRefreshToken)
.build();
}
@Override
public void logout(String refreshToken, String accessToken) {
// 토큰 검증
jwtTokenProvider.validateToken(refreshToken);
String username = jwtTokenProvider.getUsernameFromJWT(refreshToken);
refreshTokenService.deleteRefreshToken(username);
long remainTime = jwtTokenProvider.getRemainingExpiration(accessToken);
redisTemplate.opsForValue().set("BL:" + accessToken, "logout", Duration.ofMillis(remainTime));
}
@Override
public void sendPatchPwdEmail(CheckEmailRequest request) {
userRepository
.findStaffForGetPwd(request.staffName(), request.email())
.orElseThrow(() -> new BusinessException(ErrorCode.USER_NOT_FOUND));
if (getAuthCode(request.email()) != null) {
throw new BusinessException(ErrorCode.DUPLICATE_SEND_AUTH_EXCEPTION);
}
String authCode = UUID.randomUUID().toString().substring(0, 6);
saveAuthCode(request.email(), authCode);
try {
mailService.sendFindPwdEmail(request.email(), authCode);
} catch (MessagingException e) {
throw new BusinessException(ErrorCode.SEND_EMAIL_FAILURE_EXCEPTION);
}
}
@Override
public void verifyAuthCode(EmailVerifyRequest request) {
String authCode = getAuthCode(request.email());
if (authCode == null || !authCode.equals(request.authCode())) {
throw new BusinessException(ErrorCode.INVALID_AUTH_CODE);
}
deleteAuthCode(authCode);
}
private void saveAuthCode(String email, String code) {
redisTemplate.opsForValue().set(email, code, Duration.ofMillis(expireMinute));
}
private String getAuthCode(String email) {
return redisTemplate.opsForValue().get(email);
}
private void deleteAuthCode(String code) {
redisTemplate.delete(code);
}
}