메서드 명을 ResponseEntity<User> <- 이렇게 하는 이유
esponseEntity는 Spring에서 HTTP 응답을 생성하는데 사용하는 클래스로,
HTTP 응답의 본문(body), 상태 코드(status code), 헤더(headers) 등을 포함
HTTP 응답을 생성하고 그 응답의 본문에 User 객체를 포함시키는 역할
return ResponseEntity.ok(user); <- 이렇게 리턴시켜서 상태코드 확인 가능
**====void 반환 타입====**
장점:
메서드가 단일 목적(여기서는 프로필 업데이트)에 집중하도록 .
호출하는 측에서 메서드의 결과를 기대하거나 처리할 필요가 없음
즉, 프로필 업데이트의 성공 여부만 중요하고,
업데이트된 사용자 정보를 즉시 필요로 하지 않을 때 적합
단점:
업데이트된 사용자 정보가 필요한 경우, 별도의 조회 작업이 필요합
메서드가 성공적으로 수행되었는지만 알 수 있고, 어떤 변경이 일어났는지 알수 없음
**====User 객체 반환 타입====**
장점:
메서드가 성공적으로 수행된 후, 업데이트된 사용자 정보를 즉시 반환하여 호출하는 측에서 활용할 수 있음
이는 추가적인 조회 작업 없이 바로 업데이트된 결과를 확인하고,
다음 로직에 사용 가능
업데이트된 객체 정보를 통해 어떤 변경이 일어났는지를 직접 확인 가능
단점:
메서드가 추가적인 반환 값을 관리해야 하므로, 구현이 조금 더 복잡
모든 사용 사례에서 업데이트된 객체 정보가 필요한 것은 아니므로,
경우에 따라서는 반환된 객체를 활용하지 않는 상황도 발생
결정하기
프로필 업데이트 후 업데이트된 User 객체 정보를 즉시 필요로 하는 경우,
또는 업데이트의 결과를 바탕으로 추가적인 로직을 수행해야 한다면
객체를 반환하는 것이 좋음
반면, 업데이트의 성공 여부만 중요하고,
업데이트된 정보를 즉시 사용할 필요가 없다면 void 반환
@Slf4j
@RestController
@RequiredArgsConstructor
public class LoginController {
private final JwtTokenProvider jwtTokenProvider;
private final JWTUserRepository jwtUserRepository;
private final JwtService jwtService;
private DefaultMessageService messageService;
@org.springframework.beans.factory.annotation.Value("${cool-sms.api-key}")
private String apiKey;
@org.springframework.beans.factory.annotation.Value("${cool-sms.api-secret}")
private String apiSecret;
@org.springframework.beans.factory.annotation.Value("${cool-sms.api-url}")
private String apiUrl;
@PostConstruct
private void init() {
this.messageService = NurigoApp.INSTANCE.initialize(apiKey, apiSecret, apiUrl);
}
//api연동
@PostMapping("/SendForLogin")
@ResponseBody
public ResponseEntity<Object> loginAndSendSMS(@RequestBody Map<String, String> user, HttpSession session) {
// 휴대폰 번호를 받음
String phoneNumber = user.get("username");
log.info("PhoneNumber = {}", phoneNumber);
// 사용자 검색
UserEntity member = jwtUserRepository.findByUsername(user.get("username"));
if (member != null) {
// 인증번호 생성 및 세션에 인증번호, 인증번호 발급 시각, 사용자 번호 저장
String numStr = generateRandomNumber();
session.setAttribute("authNum", numStr);
session.setAttribute("creationTime", System.currentTimeMillis());
session.setAttribute("username", phoneNumber);
log.info("Session attribute - creationTime: {}", session.getAttribute("creationTime"));
// SMS 전송
Message message = new Message();
message.setFrom("01064371608"); // 테스트할 때만 사용
message.setTo(phoneNumber);
message.setText("[Ishere] 이즈히어 인증번호는 [" + numStr + "] 입니다.");
SingleMessageSentResponse response = messageService.sendOne(new SingleMessageSendingRequest(message));
log.info("SMS Response: {}", response);
// 응답에는 필요한 정보를 추가하여 반환
Map<String, String> responseMap = new HashMap<>();
responseMap.put("status", "success");
responseMap.put("message", "SMS sent successfully");
return ResponseEntity.ok(responseMap);
} else {
// 사용자가 존재하지 않는 경우
Map<String, String> responseMap = new HashMap<>();
responseMap.put("status", "error");
responseMap.put("message", "User not found");
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(responseMap);
}
}
@PostMapping("/verifyForLogin")
@ResponseBody
public ResponseEntity<Object> verifyAndLogin(@RequestBody Map<String, String> body, HttpSession session) {
// 세션에 저장된 인증번호와 생성 시간
String authNum = (String) session.getAttribute("authNum");
long creationTime = (Long) session.getAttribute("creationTime");
String phoneNumber = (String) session.getAttribute("username");
// 사용자가 입력한 인증번호를 요청 본문으로부터 가져옴
String inputNum = body.get("inputNum");
// 현재 시간(밀리초 단위)을 가져온 후 대입
long currentTime = System.currentTimeMillis();
// 3분이 지났는지 확인
if (currentTime - creationTime > 3 * 60 * 1000) {
// 3분이 지났다면 세션에서 인증번호와 생성 시간을 삭제하고 실패 응답 반환
session.removeAttribute("authNum");
session.removeAttribute("creationTime");
Map<String, String> responseMap = new HashMap<>();
responseMap.put("status", "error");
responseMap.put("message", "Verification expired");
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(responseMap);
}
// 사용자가 입력한 인증번호와 세션에 저장된 인증번호가 일치하는지 확인
boolean isMatch = inputNum.equals(authNum);
if (isMatch) {
// 인증번호가 일치하면 세션에서 인증번호와 생성 시간, 사용자 정보를 삭제
session.removeAttribute("authNum");
session.removeAttribute("creationTime");
session.removeAttribute("username");
// 사용자 검색
UserEntity member = jwtUserRepository.findByUsername(phoneNumber);
if (member != null) {
// 토큰 생성
Token tokenDto = jwtTokenProvider.createAccessToken(member.getUsername(), member.getRole());
log.info("getrole = {}", member.getRole());
jwtService.login(tokenDto);
// 토큰을 헤더에 추가하여 응답
return ResponseEntity.ok()
.header("Authorization", "Bearer " + tokenDto.getAccessToken())
.body(tokenDto);
} else {
// 사용자가 존재하지 않는 경우
Map<String, String> responseMap = new HashMap<>();
responseMap.put("status", "error");
responseMap.put("message", "User not found");
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(responseMap);
}
} else {
// 인증 실패 시 실패 응답 반환
Map<String, String> responseMap = new HashMap<>();
responseMap.put("status", "error");
responseMap.put("message", "Verification failed");
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(responseMap);
}
}
private String generateRandomNumber() {
Random rand = new Random();
StringBuilder numStr = new StringBuilder();
for (int i = 0; i < 6; i++) {
numStr.append(rand.nextInt(10));
}
return numStr.toString();
}
}
3분간 유지되는 ‘세션’에 정보 저장(인증번호 authNum, 세션생성시간, 사용자의 휴대폰번호(username))
**서비스
@Service**
public class TokenBlacklistService {
private final TokenBlacklistRepository tokenBlacklistRepository;
private final Set<String> blacklistedTokens = new HashSet<>();
public TokenBlacklistService(TokenBlacklistRepository tokenBlacklistRepository) {
this.tokenBlacklistRepository = tokenBlacklistRepository;
}
public void addToBlacklist(String token, LocalDateTime expirationTime) {
TokenBlacklist tokenBlacklist = new TokenBlacklist();
tokenBlacklist.setToken(token);
tokenBlacklist.setExpirationTime(expirationTime); // 만료 시간 설정
tokenBlacklistRepository.save(tokenBlacklist);
}
public boolean isTokenBlacklisted(String token) {
return blacklistedTokens.contains(token);
}
}
**doFilter 필터에 추가**
if (token != null) {
// 토큰이 블랙리스트에 있는지 확인
if (tokenBlacklistService.isTokenBlacklisted(token)) {
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "토큰이 블랙리스트에 있어 유효하지 않습니다");
}
// 유효한 토큰이라면, 토큰으로부터 유저 정보를 받아옴
if (jwtTokenProvider.validateToken(token)) {
Authentication authentication = jwtTokenProvider.getAuthentication(token);
// SecurityContext에 Authentication 객체를 저장
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
@**PathVariable**
URI 경로의 일부를 메서드의 파라미터로 가져오는 데 사용
@**RequetParam**
1개의 HTTP 파라미터를 얻기 위해 사용되며 기본값을 지정할 수 있음필수 여부가 true이기 때문에 반드시 필요한 경우가 아니라면 required=false 설정이 필요함
@**RequestBody**
Json(application/json) 형태의 HTTP Body 데이터를 MessageConverter를 통해 Java 객체로 변환시킴기본 생성자로 객체를 만들고, Getter나 Setter 등의 메소드로 필드를 찾아 Reflection으로 값을 설정함
@**ModelAttribute**
폼 형태(form)의 HTTP Body와 요청 파라미터들을 객체에 바인딩시킴기본적으로 생성자로 값이 설정되고, 생성자로 설정되지 않은 필드는 Setter로 설정됨
@**NoArgsConstructor**
파라미터가 없는 기본 생성자를 자동생성
@**RequiredArgsConstructor**
클래스 내부에 선언된 final이나 @NonNull이 지정된 필드들에 대한 생성자를 자동으로 생성
@**Columndefault**
데이터베이스의 특정 컬럼에 기본값을 설정하고 싶을 때 사용
@**Builder**
클래스나 메소드, 생성자 위에 지정하면, 해당 클래스에 대한 빌더 객체를 생성하고, 이를 통해 객체를 생성하거나 변경
**@DynamicInsert**
insert 시 null 인 필드 제외
**@DynamicUpdate**
update 시 null 인 필드 제외
**@Component**
Bean으로 등록할 때 사용, Spring이 시작될 때 메모리에 로드되어, 애플리케이션 전체에서 사용가능
Component가 붙은 클래스의 객체를 다른 클래스에서 @Autowired 등의 어노테이션을 사용하여
쉽게 주입받아 사용
@**RestController(json, xml등 데이터를 반환)**는
@**Controller(html과 같은 뷰를 반환)**와 비슷하지만, @**ResponseBody** 어노테이션이 내장되어 있어,
반환 값이 바로 HTTP 응답 본문에 작성됨
@**Schema**
swagger에서 API요청에 의한 데이터 모델을 설명하는 어노테이션
@**EnableWebSecurity
@Override**
@Override 어노테이션은 부모 클래스 또는 구현된 인터페이스의 메소드를 오버라이드
**@Data**
@Data 어노테이션은 @Getter, @Setter, @ToString,
@EqualsAndHashCode, @RequiredArgsConstructor,@NoArgsConstructor 등을 포함
**@JsonManagedReference와 @JsonBackReference**
조인된 양방향 관계에서 사용 시 리커션 오류 해결(임시방편)