[Backend] 03. 회원가입/로그인 API 구현 - BCrypt 암호화와 SSAFY 금융망 연동

이기혁·2026년 3월 11일

naeda

목록 보기
4/10

인증 시스템의 뼈대(Security + JWT + Redis)를 다 만들었으니, 이제 실제로 사용자가 호출할 API를 만들 차례다. 회원가입, 로그인, 토큰 재발급, 로그아웃 이렇게 4개.

근데 우리 프로젝트는 단순한 회원가입이 아니다. SSAFY에서 제공하는 금융 API를 쓰려면, 우리 서비스에 가입할 때 SSAFY 금융망에도 동시에 회원가입을 해야 한다. 이 과정에서 발급받는 userKey가 있어야 계좌 조회, 이체 같은 금융 API를 호출할 수 있다.

User 엔티티

@Entity
@Table(name = "\"user\"")
@Builder
@Getter
public class User {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long userNo;

    @Column(length = 100, nullable = false, unique = true)
    private String userId;          // 이메일 형식

    @Column(nullable = false)
    private String password;        // BCrypt 해시

    private String username;        // 실명
    private String residentNo;      // 주민번호 앞 7자리
    private String phone;
    private String institutionCode; // 금융기관 코드
    private String userKey;         // SSAFY 금융망 userKey
    private Boolean faceRegistered; // 얼굴 등록 여부
    private String pinPassword;     // 간편 비밀번호 (BCrypt)
}

user가 postgreSQL의 예약어여서 테이블명을 "\"user\"" 이렇게 이스케이프 해야했다.

회원가입 흐름

클라이언트 → POST /api/auth/signup
↓
1. 아이디 중복 검사
2. 비밀번호 BCrypt 암호화 → DB 저장
3. SSAFY 금융망 회원가입 API 호출 → userKey 발급
4. userKey를 DB에 업데이트
5. JWT (Access + Refresh) 발급
6. Refresh Token Redis 저장
7. 응답 반환

@Transactional
public SignupResponse signup(SignupRequest request) {
    // 1. 아이디 중복 검증
    if (userRepository.findByUserId(request.getUserId()).isPresent()) {
        throw new DuplicateException("이미 존재하는 아이디입니다.");
    }

    // 2. 비밀번호 암호화 후 저장
    User user = User.builder()
            .userId(request.getUserId())
            .password(passwordEncoder.encode(request.getPassword()))
            .username(request.getUsername())
            .pinPassword(passwordEncoder.encode(request.getPin()))
            .build();
    User saved = userRepository.save(user);

    // 3. SSAFY 금융망 회원가입 → userKey 발급
    String userKey = registerSsafyMember(saved.getUserId());
    saved.updateUserKey(userKey);

    // 4. JWT 토큰 발급 + Redis 저장
    String accessToken = jwtTokenProvider.createAccessToken(saved.getUserId(), userKey);
    String refreshToken = jwtTokenProvider.createRefreshToken(saved.getUserId(), userKey);

    redisTemplate.opsForValue().set(
            "refresh:" + saved.getUserId(), refreshToken,
            refreshTokenExpiry, TimeUnit.MILLISECONDS);

    return SignupResponse.of(saved, accessToken, refreshToken);
}

passwordEndcoder.encode()가 BCrypt 해싱을 해준다. 같은 비밀번호를 두 번 암호화해도 매번 다른 해시가 나온다.(salt가 랜덤이기 때문에).
그래서 비교할때는 passwordEncoder.matches(raw,encoded)를 써야한다.

SSAFY 금융망 연동

이 부분이 좀 까다로웠다. SSAFY 금융 API 형식이 좀 독특하다.

금융망 API에 대한 내용은 대외비기 때문에 쓸 수 없지만,
회원가입과 로그인 두 개의 로직 모두 자체 회원가입, 로그인 이후에 SSAFY 금융망 회원가입, 로그인을 진행해야했다.

SSAFY 금융망 API를 사용하기 위해서는 특정 키가 꼭 필요했는데, 이 키가 SSAFY의 회원가입을 성공해야 발급 받을 수 있다.

사용자를 검증할 때에는 에러 메시지를 모두 "아이디 또는 비밀번호가 없습니다."로 통일 하였다. 해킹, 공격자가 "이 아이디는 존재하는구나" 라는 사실 조차 알지 못하게 하기 위함이었다.

삽질 포인트

  1. SSAFY API KEY가 안 먹힘
    application.yaml 에서 ${SSAFY_API_KEY}로 환경변수를 받는데, .env 파일을 프로젝트 루트에 두지 않았더니 빈 문자열이 들어왔다.
    @PostConstruct에서 API Key가 로드됐는지 확인하는 로그를 찍어 잡았다.

  2. @Transactional 범위
    회원가입 중간에 SSAFY API 호출이 실패하면 DB에 저장된 유저도 롤백되어야한다. @Transactional을 서비스 메서드에 걸어서 외부 API 호출이 실패하면 전체가 롤백되도록 하였다.

  3. USER 테이블 예약어 문제
    PostgreSQL에서 user는 예약어라서 CREATE TABLE user (...) 하면 에러가 난다. JPA에서 @Table(name = "\"user\"") 이렇게 이스케이프 해야 한다. 다른 이름으로 바꿀까도 했는데, 이미 ERD를 다 짜놨어서 그냥 이스케이프로 해결했다.

API 테스트 결과

Swagger에서 테스트한 결과:

POST /api/auth/signup → 201 Created + JWT 토큰
POST /api/auth/login → 200 OK + JWT 토큰
POST /api/auth/refresh → 200 OK + 새 토큰
POST /api/auth/logout → 200 OK

인증 관련된
Backend 파트는 여기서 마무리하고, 다음부터는 이걸 실제로 EC2에 배포하는 인프라 작업을 다룬다.

0개의 댓글