2025년 7월 30일 수요일

[springboot]실제 JWT 발급 및 검증 구현

실제 JWT 발급 및 검증 구현

이전 단계에서 만든 임시 토큰을 실제 암호화된 JWT(JSON Web Token)로 대체하고, Spring Security 필터를 통해 API 요청을 보호하는 방법을 구현합니다.

Part 1: 백엔드 (Spring Boot) JWT 로직 구현

1단계: JWT Secret Key 설정

[수정] JWT 라이브러리의 보안 요구사항(256비트 이상)을 만족하는 더 길고 안전한 Secret Key 예시로 변경합니다.

backend/src/main/resources/application.properties 파일에 아래 내용을 추가합니다.

# JWT Secret Key 설정
# 256비트 이상의 길이를 가진 Base64 인코딩된 문자열이어야 합니다.
# 아래는 예시이며, 실제 운영 환경에서는 openssl rand -base64 32 명령 등으로 생성한 키를 사용하세요.
jwt.secret=256비트보타작은키는 오류

2단계: JWT 유틸리티 클래스 생성

JwtUtil.java

package dev.nerobong2.openehr.open_ehr.backend.jwt;
// ... (이전과 동일한 코드)

3단계: AuthService 수정

AuthService.java

package dev.nerobong2.openehr.open_ehr.backend.auth;

import dev.nerobong2.openehr.open_ehr.backend.jwt.JwtUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class AuthService {

    private final AuthenticationManager authenticationManager;
    private final JwtUtil jwtUtil;

    public String login(String username, String password) {
        // Spring Security를 통해 인증 시도
        Authentication authentication = authenticationManager.authenticate(
            new UsernamePasswordAuthenticationToken(username, password)
        );

        // 인증 성공 시 UserDetails 객체를 가져와 JWT 생성
        UserDetails userDetails = (UserDetails) authentication.getPrincipal();
        return jwtUtil.generateToken(userDetails);
    }
}

4단계: JWT 인증 필터 생성

JwtAuthFilter.java

package dev.nerobong2.openehr.open_ehr.backend.jwt;
// ... (이전과 동일한 코드)

5단계: SecurityConfig와 ApplicationConfig 분리

ApplicationConfig.java

package dev.nerobong2.openehr.open_ehr.backend.config;

import dev.nerobong2.openehr.open_ehr.backend.user.UserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

import java.util.ArrayList;

@Configuration
@RequiredArgsConstructor
public class ApplicationConfig {

    private final UserRepository userRepository;

    @Bean
    public UserDetailsService userDetailsService() {
        return username -> userRepository.findByUsername(username)
                .map(user -> new org.springframework.security.core.userdetails.User(
                        user.getUsername(),
                        user.getPassword(),
                        new ArrayList<>()))
                .orElseThrow(() -> new UsernameNotFoundException("User not found: " + username));
    }

    @Bean
    public AuthenticationProvider authenticationProvider() {
        DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
        authProvider.setUserDetailsService(userDetailsService());
        authProvider.setPasswordEncoder(passwordEncoder());
        return authProvider;
    }

    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
        return config.getAuthenticationManager();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

SecurityConfig.java

package dev.nerobong2.openehr.open_ehr.backend.config;

import dev.nerobong2.openehr.open_ehr.backend.jwt.JwtAuthFilter;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {

    private final JwtAuthFilter jwtAuthFilter;
    private final AuthenticationProvider authenticationProvider;

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .csrf(csrf -> csrf.disable())
            .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/api/auth/**").permitAll()
                .anyRequest().authenticated()
            )
            .authenticationProvider(authenticationProvider)
            .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class);

        return http.build();
    }
}

6단계: 인증 테스트용 API 생성

TestController.java

package dev.nerobong2.openehr.open_ehr.backend.test;
// ... (이전과 동일한 코드)

Part 2: 프론트엔드 (Vue) JWT 연동 및 테스트

(이전과 동일)

1단계: 상태 관리 (Pinia) 설정

frontend/src/stores/auth.ts

// ... (이전과 동일한 코드)

2단계: 인증된 API 호출 테스트

frontend/src/views/HomeView.vue

<script setup lang="ts">
// ... (이전과 동일한 코드)
</script>

<template>
  <main>
    <h1>메인 페이지</h1>
    <p>로그인에 성공했습니다.</p>
    <hr />
    <p><strong>{{ message }}</strong></p>
  </main>
</template>

다음 단계: 이제 백엔드 서버를 재시작하시면 오류 없이 정상적으로 실행될 것입니다. 그 후 다시 로그인을 테스트해 보세요.

댓글 없음:

댓글 쓰기

[springboot]실제 JWT 발급 및 검증 구현

실제 JWT 발급 및 검증 구현 이전 단계에서 만든 임시 토큰을 실제 암호화된 JWT(JSON Web Token)로 대체하고, Spring Security 필터를 통해 API 요청을 보호하는 방법을 구현합니다. Part 1: 백엔드 (Spring ...