IT정보/초보자를 위한 IT 팁

Spring Boot로 구현하는 REST API와 토큰 인증

IdeaBin 2024. 12. 31. 17:02
반응형

이번 글에서는 Spring Boot를 사용하여 Access TokenRefresh Token을 활용한 REST API 인증 시스템을 구현하는 방법을 알아보겠습니다. 간단한 로그인, 토큰 발급, API 요청 처리, 그리고 토큰 갱신 과정을 코드로 작성합니다.


전체적인 흐름

  1. 로그인 요청: 사용자가 로그인하면 Access Token과 Refresh Token을 발급.
  2. API 요청: Access Token을 사용해 인증된 요청 처리.
  3. 토큰 갱신: Access Token이 만료되었을 때 Refresh Token으로 새 Access Token 발급.

준비사항

  1. Spring Boot 프로젝트 생성: Spring Initializr를 사용하여 Spring Boot 프로젝트를 생성합니다.
    • 의존성
      • Spring Web
      • Spring Security
      • JJwt (JSON Web Token 라이브러리)
  2. Maven 의존성 추가
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt-api</artifactId>
        <version>0.11.5</version>
    </dependency>
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt-impl</artifactId>
        <version>0.11.5</version>
    </dependency>
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt-jackson</artifactId>
        <version>0.11.5</version>
    </dependency>

 

코드 작성

1. JWT 유틸리티 클래스

import io.jsonwebtoken.*;
import org.springframework.stereotype.Component;

import java.util.Date;

@Component
public class JwtUtil {

    private final String SECRET_KEY = "your_secret_key";

    public String generateToken(String username, long expiration) {
        return Jwts.builder()
                .setSubject(username)
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + expiration))
                .signWith(SignatureAlgorithm.HS256, SECRET_KEY)
                .compact();
    }

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

    public boolean isTokenExpired(String token) {
        return extractClaims(token).getExpiration().before(new Date());
    }

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

2. 사용자 데이터 및 서비스

import org.springframework.stereotype.Service;

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

@Service
public class UserService {

    private final Map<String, String> users = new HashMap<>();

    public UserService() {
        users.put("user123", "password123"); // 테스트 사용자
    }

    public boolean authenticate(String username, String password) {
        return users.containsKey(username) && users.get(username).equals(password);
    }
}

3. 로그인 및 API 요청 처리

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

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

@RestController
@RequestMapping("/auth")
public class AuthController {

    @Autowired
    private JwtUtil jwtUtil;

    @Autowired
    private UserService userService;

    private final long ACCESS_TOKEN_EXPIRATION = 15 * 60 * 1000; // 15분
    private final long REFRESH_TOKEN_EXPIRATION = 24 * 60 * 60 * 1000; // 24시간

    @PostMapping("/login")
    public ResponseEntity<Map<String, String>> login(@RequestBody Map<String, String> request) {
        String username = request.get("username");
        String password = request.get("password");

        if (userService.authenticate(username, password)) {
            String accessToken = jwtUtil.generateToken(username, ACCESS_TOKEN_EXPIRATION);
            String refreshToken = jwtUtil.generateToken(username, REFRESH_TOKEN_EXPIRATION);

            Map<String, String> tokens = new HashMap<>();
            tokens.put("access_token", accessToken);
            tokens.put("refresh_token", refreshToken);
            return ResponseEntity.ok(tokens);
        }

        return ResponseEntity.status(401).body(Map.of("message", "Invalid credentials"));
    }

    @PostMapping("/refresh")
    public ResponseEntity<Map<String, String>> refresh(@RequestBody Map<String, String> request) {
        String refreshToken = request.get("refresh_token");

        try {
            if (jwtUtil.isTokenExpired(refreshToken)) {
                return ResponseEntity.status(401).body(Map.of("message", "Refresh token expired"));
            }

            String username = jwtUtil.extractUsername(refreshToken);
            String newAccessToken = jwtUtil.generateToken(username, ACCESS_TOKEN_EXPIRATION);

            return ResponseEntity.ok(Map.of("access_token", newAccessToken));
        } catch (Exception e) {
            return ResponseEntity.status(401).body(Map.of("message", "Invalid refresh token"));
        }
    }
}

4. 인증된 API 엔드포인트

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserController {

    @Autowired
    private JwtUtil jwtUtil;

    @GetMapping("/users")
    public ResponseEntity<String> getUsers(@RequestHeader("Authorization") String token) {
        try {
            token = token.replace("Bearer ", "");
            if (jwtUtil.isTokenExpired(token)) {
                return ResponseEntity.status(401).body("Token expired");
            }

            String username = jwtUtil.extractUsername(token);
            return ResponseEntity.ok("Hello, " + username + "! Here is your data.");
        } catch (Exception e) {
            return ResponseEntity.status(401).body("Invalid token");
        }
    }
}

 

5. 테스트 및 결과

      1. 로그인 요청
        curl -X POST http://localhost:8080/auth/login -H "Content-Type: application/json" -d '{"username": "user123", "password": "password123"}'

        응답
        {
          "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
          "refresh_token": "dGhpcyBpcyByZWZyZXNoIHRv..."
        }
      2. 인증된 API 요청
        curl -X GET http://localhost:8080/users -H "Authorization: Bearer <access_token>"

        응답
        Hello, user123! Here is your data.
      3. Access Token 갱신
        curl -X POST http://localhost:8080/auth/refresh -H "Content-Type: application/json" -d '{"refresh_token": "<refresh_token>"}'

        응답
        {
          "access_token": "new_access_token"
        }


결론

Spring Boot를 사용해 REST API와 토큰 인증을 간단히 구현했습니다. 이 코드는 기본적인 인증 흐름을 다루며, 실제 프로젝트에서는 데이터베이스 연동, 사용자 권한 관리 등 추가적인 보안 조치가 필요합니다.

#JWTJWT 태그 삭제#spring securityspring security 태그 삭제#access tokenaccess token 태그 삭제#REST APIREST API 태그 삭제#Spring BootSpring Boot 태그 삭제#JSON Web TokenJSON Web Token 태그 삭제#인증 시스템인증 시스템 태그 삭제#토큰 인증토큰 인증 태그 삭제#Refresh TokenRefresh Token 태그 삭제#토큰 발급토큰 발급

반응형