
user.setPassword(passwordEncoder.encode(request.getPassword()));
회원가입 시 입력한 비밀번호는 BCryptPasswordEncoder를 통해 암호화되어 DB에 저장된다.
해시 기반으로 복호화가 불가능하며, 같은 비밀번호라도 다른 해시를 생성하여 안전성을 높인다.
비밀번호는 관리자도 확인 못하도록 암호화처리를 해야한다.
BCryptPasswordEncoder을 사용하면 관리자도 DB에서 사용자의 비밀번호를 확인 할 수 없다.
if (request.getNewPassword() != null && !request.getNewPassword().isBlank()) {
if (request.getCurrentPassword() == null || !passwordEncoder.matches(request.getCurrentPassword(), user.getPassword())) {
throw new IllegalArgumentException("현재 비밀번호가 일치하지 않습니다.");
}
user.setPassword(passwordEncoder.encode(request.getNewPassword()));
}
비밀번호를 변경할 때는 기존 비밀번호 일치를 먼저 확인한다.
이는 세션 탈취 등 보안 위협에 대비한 이중 인증 절차로 실무에서도 권장되는 방식이다.
로그인을 해 놓으면 누구든 쉽게 비밀번호를 변경 할 수 있다. 이것을 포함해 다양한 해킹 방법을 막기 위해 꼭 비밀번호 변경 시 기존 비밀번호를 입력해야 한다.
public void generateVerificationToken() {
this.emailVerificationToken = UUID.randomUUID().toString();
this.tokenGeneratedAt = LocalDateTime.now();
}
mailService.sendVerificationEmail(user);
user.setEnabled(false);
user.generateVerificationToken();
userRepository.save(user);
사용자가 가입하거나 이메일을 바꿀 경우, 인증 토큰을 생성하여 이메일로 발송한다.
인증이 완료되기 전까지는 enabled = false로 설정되어 로그인이 제한된다.
요즘에는 이메일이나 휴대전화번호로 인증을 많이 한다. 이 서비스는 사용자의 편의를 위해서 회원가입 시 등록한 이메일로 인증메일을 보낸다.
public boolean isTokenExpired() {
return tokenGeneratedAt != null && tokenGeneratedAt.isBefore(LocalDateTime.now().minusHours(1));
}
인증 토큰은 생성 후 1시간 동안만 유효한다.
유효 시간이 초과되면 인증 요청이 거부되며, 사용자는 새 토큰을 요청해야 한다.
이메일 인증에 재한시간을 두는 보안이다. 보통 5분 내외로 걸지만, 시간으로도 걸 수 있다는 것을 보기위해 1시간으로 설정했다.
session.setAttribute("userId", user.getId()); // 로그인 시
session.invalidate(); // 로그아웃 및 탈퇴 시
사용자의 로그인 상태는 세션에 저장된 userId로 관리된다.
인증되지 않은 사용자는 민감한 API 접근이 제한된다.
로그인한 사용자를 세션으로 관리하므로 개인적인 정보 접근에 남이 접근 하는 것을 방지하고, 반대로 남의 개인 정보에 접근 할 수 없다.
User admin = userService.findById(userId);
if (!admin.isAdmin()) {
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("관리자만 접근할 수 있습니다.");
}
/admin/users 등의 엔드포인트에 접근 시, 로그인한 사용자가 관리자(isAdmin = true)인지 확인한다. 아니면 관리자만 접근할 수 있다는 예외를 던진다.세션과 비슷한 개념으로 이해하고 있다. 하지만 일반 계정과 관리자 계정으로 접근과 보여지는 페이지 자체를 나눌 수 있어 서비스 규모가 클 수록 필요하다고 생각한다.
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<Map<String, String>> handleIllegalArgument(IllegalArgumentException ex) {
Map<String, String> error = new HashMap<>();
error.put("error", ex.getMessage());
return ResponseEntity.badRequest().body(error);
}
}
컨트롤러나 서비스에서 발생하는 예외를 하나의 클래스에서 통합 처리한다.
예외 응답은 JSON 형태로 반환되며, 응답 구조가 일관되어 API 사용성이 좋아진다.
예외 처리가 많아 질수록 컨트롤러나 서비스에 예외 처리를 같이 넣으면 코드가 길어지고 가독성이 떨어진다, 그러므로 예외 처리를 하나로 묶어 관리한다.
if (request.getEmail() != null && !request.getEmail().equals(user.getEmail())) {
user.setEmail(request.getEmail());
user.setEnabled(false);
user.generateVerificationToken();
mailService.sendVerificationEmail(user);
}
회원 가입에서 이메일 인증을 사용해 회원의 각종 정보를 이메일을 통해 인증하고 관리하는 만큼 이메일 주소가 변경되는것도 인증을 통해서만 가능하다.