스프링 부트 백엔드 개발 강좌, JWT로 로그인 로그아웃 구현, 컨트롤러 추가하기

1. 서론

현대 웹 애플리케이션에서 보안은 가장 중요한 요소 중 하나입니다. 사용자 인증 및 권한 부여는 이러한 보안을 유지하는 핵심 기능입니다.
이번 강좌에서는 스프링 부트를 이용한 백엔드 개발에서 JWT(JSON Web Token)를 사용하여 로그인 및 로그아웃 기능을 구현하는 방법을 자세히 알아보겠습니다.
이 강좌에서는 기본적인 스프링 부트 애플리케이션을 설정하고, JWT 기반 인증 시스템을 구현하며, 컨트롤러를 추가하여 RESTful API를 완성하는 과정을 다룹니다.

2. 스프링 부트란?

스프링 부트는 자바 기반의 프레임워크인 스프링(SPRING)을 보다 쉽게 사용할 수 있도록 도와주는 도구입니다.
이를 통해 개발자는 설정과 구성을 최소화하고, 빠르게 애플리케이션을 구축할 수 있습니다.
스프링 부트는 독립 실행 가능한 JAR 파일로 패키징되어 실행될 수 있으며, RESTful 서비스를 효율적으로 개발할 수 있습니다.
스프링 부트의 주요 특성은 다음과 같습니다:

  • 자동 구성: 스프링 부트는 개발자가 필요로 하는 기본 설정을 자동으로 구성해 줍니다.
  • 스타터 패키지: 개발자는 필요한 기능을 빠르게 추가할 수 있는 스타터 패키지를 사용할 수 있습니다.
  • 내장 서버: 스프링 부트는 톰캣, 제트티, 언더타우 등과 같은 내장 서버를 제공하여, 쉽게 애플리케이션을 실행할 수 있습니다.
  • 의존성 관리: Maven 또는 Gradle을 사용해 의존성을 소스 코드 내에서 간편하게 관리할 수 있습니다.

3. JWT란?

JWT( JSON Web Token)는 안전한 정보 전송을 위한 인터넷 표준 RFC 7519입니다. JWT는 JSON 객체를 사용하여 주체(sub), 발행자(iss), 만료 시간(exp) 등의 정보를 암호화하여 전달합니다.
JWT는 세 부분으로 구성됩니다:

  1. 헤더(header): JWT의 유형과 사용할 서명 알고리즘을 지정합니다.
  2. 페이로드(payload): 전송할 정보와 그 정보를 설명하는 메타데이터를 포함합니다.
  3. 서명(signature): 헤더와 페이로드를 안전하게 하여 변조를 방지합니다. 비밀 키를 사용하여 생성됩니다.

JWT는 트래픽이 높은 환경에서 API 인증으로 많이 사용됩니다. 세션을 서버에 저장할 필요가 없기 때문에 효율적이며, 클라이언트에 상태 정보를 보관할 수 있어
서버의 부하를 줄일 수 있습니다.

4. 프로젝트 설정

4.1. 스프링 부트 프로젝트 생성

스프링 부트 프로젝트를 생성하기 위해 Spring Initializr를 사용합니다.
필요한 설정을 다음과 같이 입력합니다:

  • Project: Maven Project
  • Language: Java
  • Spring Boot: 2.6.6 (가장 최신 버전)
  • Project Metadata:
    • Group: com.example
    • Artifact: jwt-demo
    • Name: jwt-demo
    • Description: JWT Authentication Demo
    • Packaging: Jar
    • Java: 11

그리고 다음의 의존성을 추가합니다:

  • Spring Web
  • Spring Security
  • Spring Data JPA
  • H2 Database
  • jjwt (Java JWT)

4.2. 프로젝트 구조

프로젝트 생성 후, 기본 패키지 구조는 다음과 같습니다:

    └── src
        └── main
            ├── java
            │   └── com
            │       └── example
            │           └── jwt_demo
            │               ├── JwtDemoApplication.java
            │               ├── controller
            │               ├── model
            │               ├── repository
            │               ├── security
            │               └── service
            └── resources
                ├── application.properties
                └── static
    

5. 데이터베이스 설정

H2 데이터베이스를 사용하여 사용자 정보를 저장할 수 있습니다.
application.properties 파일에 다음과 같이 설정합니다:

    spring.h2.console.enabled=true
    spring.datasource.url=jdbc:h2:mem:testdb
    spring.datasource.driverClassName=org.h2.Driver
    spring.datasource.username=sa
    spring.datasource.password=
    spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
    

6. 사용자 모델 만들기

사용자의 정보를 담기 위한 User 모델 클래스를 생성합니다.

    package com.example.jwt_demo.model;

    import javax.persistence.*;

    @Entity
    @Table(name = "users")
    public class User {
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;

        @Column(nullable = false, unique = true)
        private String username;

        @Column(nullable = false)
        private String password;

        // Getters and Setters...

        public User() {}

        public User(String username, String password) {
            this.username = username;
            this.password = password;
        }
    }
    

7. 사용자 리포지토리 생성하기

사용자 정보를 데이터베이스에서 관리하기 위해 JPA 리포지토리 인터페이스를 생성합니다.

    package com.example.jwt_demo.repository;

    import com.example.jwt_demo.model.User;
    import org.springframework.data.jpa.repository.JpaRepository;
    import org.springframework.stereotype.Repository;

    @Repository
    public interface UserRepository extends JpaRepository {
        User findByUsername(String username);
    }
    

8. 보안 설정

스프링 시큐리티를 통해 JWT 인증을 구현하겠습니다. 이를 위해 WebSecurityConfigurerAdapter을 상속하는 SecurityConfig 클래스를 생성합니다.

    package com.example.jwt_demo.security;

    import com.example.jwt_demo.filter.JwtRequestFilter;
    import com.example.jwt_demo.service.UserDetailsServiceImpl;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Bean;
    import org.springframework.security.authentication.AuthenticationManager;
    import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
    import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
    import org.springframework.security.config.http.SessionCreationPolicy;
    import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

    @EnableWebSecurity
    public class SecurityConfig extends WebSecurityConfigurerAdapter {

        @Autowired
        private UserDetailsServiceImpl userDetailsService;

        @Autowired
        private JwtRequestFilter jwtRequestFilter;

        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth.userDetailsService(userDetailsService);
        }

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.csrf().disable()
                .authorizeRequests()
                .antMatchers("/authenticate").permitAll()
                .anyRequest().