CoreERP 인증 시스템 구축 기록

최병현·2026년 3월 16일

coreerp project

목록 보기
32/44

이번 단계에서는 CoreERP 프로젝트에 인증 시스템을 붙였다. 기존에는 품목, 거래처, 창고, 발주, 입고, 출고 같은 ERP 내부 기능이 먼저 완성된 상태였고, 이제 그 기능들을 실제 사용자 기준으로 보호할 수 있도록 로그인, 회원가입, 권한 관리, 토큰 재발급 구조를 추가했다.


1. 이번 단계의 핵심 목표

  • Spring Security + JWT 기반 인증 구조 도입
  • 회원가입 / 로그인 / 내 정보 조회 API 구현
  • MASTER / ADMIN / EMPLOYEE 권한 구조 적용
  • Refresh Token 기반 로그인 유지 구조 추가
  • MASTER 전용 사용자 관리 API 구현

2. 인증 방식을 JWT로 선택한 이유

CoreERP는 React 프론트엔드와 Spring Boot 백엔드가 분리된 REST API 구조다. 이 구조에서는 Session 방식보다 JWT 방식이 더 자연스럽다. 프론트엔드는 로그인 후 access token을 받아 API 요청마다 Authorization Header에 담아 보내고, 백엔드는 그 토큰을 검증해 사용자를 식별하게 된다.

또한 access token만 사용하는 구조는 만료 시 UX가 떨어지기 때문에, refresh token까지 함께 적용해 로그인 유지 흐름도 만들었다.


3. 권한 구조 설계

권한은 다음 3단계로 구성했다.

  • MASTER: 최상위 관리자
  • ADMIN: 운영 관리자
  • EMPLOYEE: 일반 사원

회원가입 시 기본 권한은 EMPLOYEE로 고정했고, 사용자 목록 조회나 권한 변경, 상태 변경 같은 관리 기능은 MASTER만 접근할 수 있게 설정했다.


4. 회원가입 정책

회원가입은 공개 가입 방식이 아니라 내부 시스템 등록 방식으로 설계했다. 사용자가 companyCode를 입력하면 서버 설정값과 비교해 일치할 때만 가입이 가능하다. 사원번호는 회사마다 체계가 달라 필수 검증 대상에서는 제외했고, 단순 저장용으로만 처리했다.

  • companyCode 일치 시 가입 가능
  • loginId 중복 불가
  • employeeNo는 선택 입력
  • 가입 시 role은 EMPLOYEE

5. 패키지 구조 정리

인증 기능은 auth와 user 모듈로 나누어 구성했다.

  • auth: 로그인, 회원가입, 토큰 발급, refresh, logout
  • user: 사용자 생성, 조회, 권한 변경, 상태 변경

기존 CoreERP 구조와 맞추기 위해 Domain(Entity)은 class, DTO는 record 기준으로 정리했다.


6. 주요 코드

사용자 권한은 enum으로 관리했다.

public enum UserRole {
    MASTER,
    ADMIN,
    EMPLOYEE
}

사용자 상태도 enum으로 분리

public enum UserStatus {
    ACTIVE,
    INACTIVE,
    LOCKED
}

JWT 생성은 별도 provider에서 처리

public String createToken(Long userId, String loginId, String role) {
    Date now = new Date();
    Date expiry = new Date(now.getTime() + accessExpiration);

    return Jwts.builder()
            .setSubject(loginId)
            .claim("userId", userId)
            .claim("role", role)
            .setIssuedAt(now)
            .setExpiration(expiry)
            .signWith(key, SignatureAlgorithm.HS256)
            .compact();
}

로그인 시에는 access token과 refresh token을 함께 발급하도록 구성

public AuthTokenResponse login(LoginRequest req) {
    User user = userService.findByLoginId(req.loginId());

    validateLoginUser(user, req.password());
    userService.updateLastLogin(user);

    String accessToken = jwtTokenProvider.createToken(
            user.getId(),
            user.getLoginId(),
            user.getRole().name()
    );

    RefreshToken refreshToken = refreshTokenService.create(user.getId());

    return new AuthTokenResponse(
            accessToken,
            refreshToken.getToken(),
            user.getId(),
            user.getLoginId(),
            user.getName(),
            user.getRole().name()
    );
}

현재 로그인 사용자 조회는 인증된 사용자의 loginId를 기준으로 처리

@GetMapping("/me")
public MeResponse me(Authentication authentication) {
    return authService.me(authentication.getName());
}

사용자 관리 API는 MASTER만 접근 가능하게 SecurityConfig에서 제한

.authorizeHttpRequests(auth -> auth
        .requestMatchers(
                "/api/auth/signup",
                "/api/auth/login",
                "/api/auth/refresh",
                "/api/auth/logout"
        ).permitAll()
        .requestMatchers(HttpMethod.GET, "/api/users").hasRole("MASTER")
        .requestMatchers(HttpMethod.PATCH, "/api/users/**").hasRole("MASTER")
        .anyRequest().authenticated()
)

7. 트러블슈팅

1) /api/auth/me 호출 시 401 발생

원인

  • Bearer Token 입력칸에 실제 JWT 문자열이 아니라 Authorization: Bearer {accessToken} 전체를 넣고 있었다.

결과

  • 로그인 응답에서 받은 실제 access token 문자열만 넣도록 수정했고, 이후 /api/auth/me 호출이 정상 동작했다.

2) /api/auth/me 를 POST로 호출한 문제

원인

  • /api/auth/me 는 GET API인데 POST로 호출했다.

결과

  • GET 방식으로 다시 테스트했고, 추가로 405 예외 처리도 넣어 메서드 오류를 더 명확히 확인할 수 있게 했다.

3) 예외 메시지가 영어로 내려가는 문제

원인

  • Service에서 던진 예외 문자열이 그대로 응답 메시지로 내려갔다.

결과

  • 회사 코드 오류, 로그인 실패, 인증 실패, 권한 없음 등 사용자에게 보이는 메시지를 모두 한글로 통일했다.

8. 테스트 결과

Swagger와 Postman으로 아래 흐름을 모두 확인했다.

  • MASTER 로그인 성공
  • EMPLOYEE 회원가입 성공
  • EMPLOYEE 로그인 성공
  • /api/auth/me 정상 조회
  • EMPLOYEE의 /api/users 접근 시 403
  • MASTER의 사용자 목록 조회 성공
  • 권한 변경, 상태 변경, enabled 변경 성공
  • LOCKED / disabled 계정 로그인 차단 성공
  • refresh token 재발급 성공
  • logout 후 refresh 차단 성공

9. 이번 단계의 의미

이번 작업으로 CoreERP는 단순 CRUD 프로젝트에서 실제 로그인과 권한 개념이 있는 업무 시스템 구조로 올라왔다. 특히 사용자별 접근 제어, 관리자 전용 기능, 로그인 유지, 로그아웃 처리까지 연결되면서 프론트엔드와 연동할 수 있는 인증 백엔드 기반이 완성되었다.


10. 다음 단계

이제 다음 단계는 React 프론트엔드와 인증 구조를 연결하는 것이다.

  • authApi.ts 구성
  • axios interceptor 적용
  • LoginPage / SignupPage 구현
  • 로그인 유지 및 사용자 복구 처리
  • Route Guard 및 권한별 메뉴 제어

11. 마무리

이번 단계에서 가장 중요했던 점은 인증이 단순 로그인 기능이 아니라, 프로젝트 전체 구조를 다시 정리하는 기준이라는 점이었다. 회원가입 정책, 권한 설계, 예외 메시지, 사용자 상태, 토큰 재발급과 로그아웃 흐름까지 정리하면서 CoreERP가 실제 서비스 구조에 가까워졌다고 느꼈다.

profile
Develop

0개의 댓글