
이번 단계에서는 CoreERP 프로젝트에 인증 시스템을 붙였다. 기존에는 품목, 거래처, 창고, 발주, 입고, 출고 같은 ERP 내부 기능이 먼저 완성된 상태였고, 이제 그 기능들을 실제 사용자 기준으로 보호할 수 있도록 로그인, 회원가입, 권한 관리, 토큰 재발급 구조를 추가했다.
CoreERP는 React 프론트엔드와 Spring Boot 백엔드가 분리된 REST API 구조다. 이 구조에서는 Session 방식보다 JWT 방식이 더 자연스럽다. 프론트엔드는 로그인 후 access token을 받아 API 요청마다 Authorization Header에 담아 보내고, 백엔드는 그 토큰을 검증해 사용자를 식별하게 된다.
또한 access token만 사용하는 구조는 만료 시 UX가 떨어지기 때문에, refresh token까지 함께 적용해 로그인 유지 흐름도 만들었다.
권한은 다음 3단계로 구성했다.
회원가입 시 기본 권한은 EMPLOYEE로 고정했고, 사용자 목록 조회나 권한 변경, 상태 변경 같은 관리 기능은 MASTER만 접근할 수 있게 설정했다.
회원가입은 공개 가입 방식이 아니라 내부 시스템 등록 방식으로 설계했다. 사용자가 companyCode를 입력하면 서버 설정값과 비교해 일치할 때만 가입이 가능하다. 사원번호는 회사마다 체계가 달라 필수 검증 대상에서는 제외했고, 단순 저장용으로만 처리했다.
인증 기능은 auth와 user 모듈로 나누어 구성했다.
기존 CoreERP 구조와 맞추기 위해 Domain(Entity)은 class, DTO는 record 기준으로 정리했다.
사용자 권한은 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()
)
원인
Authorization: Bearer {accessToken} 전체를 넣고 있었다.결과
/api/auth/me 호출이 정상 동작했다.원인
/api/auth/me 는 GET API인데 POST로 호출했다.결과
원인
결과
Swagger와 Postman으로 아래 흐름을 모두 확인했다.
/api/auth/me 정상 조회/api/users 접근 시 403이번 작업으로 CoreERP는 단순 CRUD 프로젝트에서 실제 로그인과 권한 개념이 있는 업무 시스템 구조로 올라왔다. 특히 사용자별 접근 제어, 관리자 전용 기능, 로그인 유지, 로그아웃 처리까지 연결되면서 프론트엔드와 연동할 수 있는 인증 백엔드 기반이 완성되었다.
이제 다음 단계는 React 프론트엔드와 인증 구조를 연결하는 것이다.
이번 단계에서 가장 중요했던 점은 인증이 단순 로그인 기능이 아니라, 프로젝트 전체 구조를 다시 정리하는 기준이라는 점이었다. 회원가입 정책, 권한 설계, 예외 메시지, 사용자 상태, 토큰 재발급과 로그아웃 흐름까지 정리하면서 CoreERP가 실제 서비스 구조에 가까워졌다고 느꼈다.