스프링 부트 백엔드 개발 강좌, JWT로 로그인 로그아웃 구현, JWT

안녕하세요! 이번 블로그에서는 스프링 부트로 백엔드 서비스를 개발하는 방법과 JSON Web Token (JWT)을 사용하여 로그인 및 로그아웃 기능을 구현하는 방법에 대해 설명하겠습니다. JWT는 웹 애플리케이션에서 인증 정보를 전송하고 검증하는 데 널리 사용되는 방법입니다. 본 강좌를 통해 JWT의 개념부터 구현까지 자세히 알아보겠습니다.

목차

  1. 1. JWT 개념
  2. 2. 스프링 부트 프로젝트 설정
  3. 3. 엔티티 구성
  4. 4. JWT 생성 및 검증
  5. 5. 로그인 기능 구현
  6. 6. 로그아웃 기능 구현
  7. 7. 종합 테스트
  8. 8. 결론

1. JWT 개념

JWT(JSON Web Token)는 두 당사자 간의 인증 정보를 안전하게 전송하는 데 사용되는 대표적인 방법입니다. JWT는 세 부분으로 구성되어 있습니다:

  • Header: 토큰의 유형(JWT)과 알고리즘 정보를 담고 있습니다.
  • Payload: 사용자 정보 및 기타 클레임(Claims) 정보를 포함합니다.
  • Signature: Header와 Payload를 바탕으로 생성된 서명으로, 무결성을 보장합니다.

JWT의 주요 장점은 클라이언트 측에서 정보를 유지하고, 서버가 상태를 관리할 필요가 없다는 것입니다. 이는 분산 시스템이나 마이크로서비스 아키텍처에서 매우 유용합니다.

2. 스프링 부트 프로젝트 설정

스프링 부트를 사용하여 간단한 RESTful API를 만드는 것부터 시작하겠습니다. 우리는 Maven을 사용하여 라이브러리를 관리할 것입니다.

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>jwt-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>jwt-demo</name>
    <description>JWT Demo Project</description>

    <properties>
        <java.version>17</java.version>
        <spring-boot.version>2.6.6</spring-boot.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

위와 같이 필요한 의존성을 설정한 후, 프로젝트 디렉토리를 생성하고 애플리케이션 클래스를 작성합니다.

src/main/java/com/example/jwtdemo/JwtDemoApplication.java

package com.example.jwtdemo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class JwtDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(JwtDemoApplication.class, args);
    }
}

3. 엔티티 구성

사용자를 관리하기 위해 User 엔티티를 구성합니다. 이 엔티티는 사용자 정보를 저장하는데 필요합니다.

src/main/java/com/example/jwtdemo/model/User.java

package com.example.jwtdemo.model;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;

@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String username;
    private String password;

    // Getters and setters
    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

4. JWT 생성 및 검증

이제 JWT를 생성하고 검증하는 유틸리티 클래스를 작성합니다. 이 클래스는 JWT 토큰을 만들고 검증하는 메소드를 포함합니다.

src/main/java/com/example/jwtdemo/util/JwtUtil.java

package com.example.jwtdemo.util;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

@Component
public class JwtUtil {
    private final String SECRET_KEY = "secret"; // 비밀 키
    private final int EXPIRATION_TIME = 1000 * 60 * 60; // 1시간

    // JWT 생성
    public String generateToken(String username) {
        Map claims = new HashMap<>();
        return createToken(claims, username);
    }

    private String createToken(Map claims, String subject) {
        return Jwts.builder()
                .setClaims(claims)
                .setSubject(subject)
                .setIssuedAt(new Date(System.currentTimeMillis()))
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
                .signWith(SignatureAlgorithm.HS256, SECRET_KEY)
                .compact();
    }

    // JWT 검증
    public boolean validateToken(String token, String username) {
        final String extractedUsername = extractUsername(token);
        return (extractedUsername.equals(username) && !isTokenExpired(token));
    }

    private boolean isTokenExpired(String token) {
        return extractExpiration(token).before(new Date());
    }

    private Date extractExpiration(String token) {
        return extractAllClaims(token).getExpiration();
    }

    private Claims extractAllClaims(String token) {
        return Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody();
    }

    public String extractUsername(String token) {
        return extractAllClaims(token).getSubject();
    }
}

5. 로그인 기능 구현

이제 로그인 기능을 구현합니다. 사용자가 유효한 자격 증명을 제공하면 JWT를 생성하여 반환합니다.

src/main/java/com/example/jwtdemo/controller/AuthController.java

package com.example.jwtdemo.controller;

import com.example.jwtdemo.model.User;
import com.example.jwtdemo.util.JwtUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping("/auth")
public class AuthController {
    @Autowired
    private JwtUtil jwtUtil;

    @PostMapping("/login")
    public Map login(@RequestBody User user) {
        // 이 부분은 데이터베이스에서 유저 정보 확인하는 로직이 필요합니다.
        if ("test".equals(user.getUsername()) && "password".equals(user.getPassword())) {
            String token = jwtUtil.generateToken(user.getUsername());
            Map response = new HashMap<>();
            response.put("token", token);
            return response;
        } else {
            throw new RuntimeException("Invalid credentials");
        }
    }
}

6. 로그아웃 기능 구현

로그아웃에는 실제로 클라이언트에서 JWT를 삭제하거나 무효화하는 방법을 사용합니다. 일반적으로 로그아웃은 클라이언트 측에서 수행됩니다.

다음은 클라이언트에서 JWT를 삭제하는 방법의 예시입니다:


localStorage.removeItem('token');

서버측에서는 사용자 상태를 관리하지 않기 때문에 별도의 로그아웃 엔드포인트가 필요하지 않습니다.

7. 종합 테스트

이제 모든 구현이 완료되었습니다. Postman 또는 CURL을 사용하여 API를 테스트할 수 있습니다.

  • 로그인 요청: POST http://localhost:8080/auth/login – Body에 JSON 형태로 사용자 정보를 전달합니다.

{
    "username": "test",
    "password": "password"
}
response

{
    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}

받은 JWT를 Authorization 헤더에 Bearer 형식으로 추가하여 다른 API 호출을 할 수 있습니다.

8. 결론

이번 강좌에서는 스프링 부트를 사용하여 JWT를 통한 로그인 및 로그아웃 기능을 구현하는 방법에 대해 알아보았습니다. JWT는 경량의 인증 메커니즘으로, 다양한 상황에 유용하게 적용할 수 있습니다. 실무에서 JWT를 사용하면 효율적인 인증과 권한 관리를 통해 보안을 강화할 수 있습니다. 앞으로 JWT를 기반으로 한 다양한 기능들을 경험해 보는 것을 추천합니다.

감사합니다! 아래 댓글로 질문이나 피드백 환영합니다.