SECUTIRY

Kingyj·2025년 6월 17일

  • 이 포스트는 지금 작업중인 프로젝트의 회원 보안을 정리한 것
    (LIFE IN SEOUL)

🐤 비밀번호 암호화 저장

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)

@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);
}
  • 사용자가 이메일을 변경하면 자동으로 인증 상태가 false로 바뀌고, 새 이메일로 인증 메일이 전송된다.

회원 가입에서 이메일 인증을 사용해 회원의 각종 정보를 이메일을 통해 인증하고 관리하는 만큼 이메일 주소가 변경되는것도 인증을 통해서만 가능하다.


0개의 댓글