Java 기반의 SpringBoot + Vue + UniApp를 활용한 실종물 찾기 플랫폼 개발 설계 및 구현

시스템 아키텍처 개요

본 프로젝트는 실종된 물건을 등록하고 조회할 수 있는 포털 시스템으로, 백엔드는 Spring Boot, 프론트엔드는 Vue.js, 모바일 클라이언트는 UniApp를 사용하여 다중 플랫폼 호환성을 확보하였습니다. 전체 시스템은 모듈화된 구조로 설계되어 유지보수성과 확장성이 뛰어납니다.

기술 스택 구성

  • 백엔드: Spring Boot 3.x 기반, 내장형 Tomcat 서버, 자동 설정 기능 제공
  • 데이터베이스 접근: MyBatis-Plus 3.5+, SQL 생성 최소화, 코드 생성기 포함
  • 프론트엔드: Vue 3 + Vite, 컴포넌트 기반 개발, 반응형 상태 관리
  • 모바일 앱: UniApp (Vue 기반 하이브리드 앱), 하나의 코드로 안드로이드/아이폰/웹 지원
  • 인증 보안: JWT 기반 토큰 인증, 역할 기반 접근 제어 (RBAC)

핵심 기능 설계

사용자 인증 및 세션 관리

로그인 요청 시 입력된 계정 정보와 데이터베이스 저장값을 비교하며, 비밀번호 일치 여부를 검증합니다. 성공 시 1시간 유효기간의 임시 토큰을 발급하고, 해당 토큰은 세션에 저장됩니다.


@PostMapping("/auth/login")
public ResponseEntity<Map> authenticateUser(@RequestParam String username, 
                                           @RequestParam String password,
                                           @RequestParam String captcha) {
    UsersEntity user = userService.findByUsername(username);
    
    if (user == null || !BCrypt.checkpw(password, user.getPassword())) {
        return ResponseEntity.status(401).body(Map.of("msg", "계정 또는 비밀번호가 잘못되었습니다."));
    }

    String token = jwtTokenProvider.generateToken(user.getId(), user.getRole(), Duration.ofHours(1));
    Map<String, Object> response = new HashMap<>();
    response.put("token", token);
    response.put("userId", user.getId());
    response.put("role", user.getRole());

    return ResponseEntity.ok(response);
}

JWT 인증 필터 구현

HTTP 요청 전에 토큰을 검증하는 인터셉터를 통해 권한 없는 접근을 차단합니다. 요청 헤더에서 Authorization: Bearer <token>를 추출하여 유효성 확인 후 세션에 사용자 정보를 저장합니다.


@Component
public class JwtAuthFilter implements Filter {

    @Autowired
    private JwtTokenProvider jwtTokenProvider;

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;

        String token = extractToken(httpRequest);

        if (token != null && jwtTokenProvider.validateToken(token)) {
            Long userId = jwtTokenProvider.getUserIdFromToken(token);
            String role = jwtTokenProvider.getRoleFromToken(token);
            
            // 세션에 사용자 정보 저장
            httpRequest.getSession().setAttribute("userId", userId);
            httpRequest.getSession().setAttribute("role", role);
        } else {
            httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            httpResponse.getWriter().write("{\"error\": \"인증되지 않은 요청입니다.\"}");
            return;
        }

        chain.doFilter(request, response);
    }

    private String extractToken(HttpServletRequest request) {
        String bearer = request.getHeader("Authorization");
        if (bearer != null && bearer.startsWith("Bearer ")) {
            return bearer.substring(7);
        }
        return null;
    }
}

데이터베이스 설계

실종물 정보, 사용자 정보, 로그인 토큰 등을 저장하기 위한 테이블 구조를 설계하였습니다.


-- 토큰 저장 테이블
CREATE TABLE auth_token (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    user_id BIGINT NOT NULL,
    username VARCHAR(100) NOT NULL,
    table_name VARCHAR(100),
    role VARCHAR(50) NOT NULL,
    token VARCHAR(255) NOT NULL UNIQUE,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    expires_at TIMESTAMP NOT NULL,
    INDEX idx_token (token),
    INDEX idx_user_id (user_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- 실종물 등록 정보 테이블
CREATE TABLE lost_item (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    title VARCHAR(200) NOT NULL,
    description TEXT,
    category VARCHAR(50),
    location VARCHAR(100),
    contact_info VARCHAR(100),
    status ENUM('등록', '확인됨', '처리완료') DEFAULT '등록',
    creator_id BIGINT NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    INDEX idx_status (status),
    INDEX idx_creator (creator_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

기능 테스트 사례

주요 기능에 대해 흑상자 테스트를 수행하여 정확성과 안정성을 검증했습니다.

테스트 항목 입력 값 예상 결과 실제 결과
로그인 실패 (비밀번호 오류) username: admin, password: wrong123 비밀번호 오류 메시지 출력 정상 출력됨
토큰 만료 후 접근 시도 유효기간 지난 토큰 사용 401 Unauthorized 응답 반환 정상 처리됨
관리자 권한 없이 삭제 시도 학생 계정으로 다른 사용자 삭제 요청 권한 부족 메시지 표시 정상 작동

시스템 운영 및 배포 가이드

배포 시에는 Docker 컨테이너 기반으로 백엔드와 MySQL DB를 분리 구성하며, Nginx를 통해 프론트엔드 정적 파일을 제공합니다. CI/CD 파이프라인은 GitHub Actions 기반으로 자동 빌드 및 테스트를 수행합니다.

  • 백엔드: mvn clean package -DskipTests → JAR 생성
  • DB 초기화: spring-boot:run 시 자동 스키마 생성
  • 프론트엔드: npm run build → dist 폴더 생성 → Nginx 매핑
  • 배포 환경: Linux + Docker + Nginx + MySQL 8.0+

결론

이 시스템은 요구사항에 부합하는 기능성과 안정성을 갖추며, 다양한 플랫폼에서 동작 가능하도록 설계되었습니다. 유저 경험 중심의 인터페이스와 강력한 인증 메커니즘을 통해 신뢰성 높은 서비스를 제공합니다.

태그: SpringBoot Vue.js UniApp MyBatis-Plus jwt

6월 6일 21:06에 게시됨