본문 바로가기

개발 이야기/Spring

[Spring Boot / JWT] Spring Boot에서 JWT를 활용한 인증 시스템 구현

이번에 제가 Study Record라는 프로젝트를 진행하고 있는데 거기서 인증 시스템을 구현 해야하는 상황이 생겼습니다. 그래서 제가 JWT를 활용해 구현했는데 JWT가 뭔지 어떻게 구현했는지 보여드리도록 하겠습니다.

목차

1. JWT 소개 및 개념

  • 1-1. JWT란?
  • 1-2. JWT를 사용하는 이유

2. JWT vs 세션 기반 인증

  • 2-1. 세션 기반 인증의 한계
  • 2-2. JWT의 장점
  • 2-3. JWT의 단점과 해결 방안
  • 2-4. 토큰 무효화
  • 2-5. 추가적인 토큰 무효화 방법

3. Access Token과 Refresh Token

  • 3-1. 두 토큰의 필요성
  • 3-2. Access Token
  • 3-3. Refresh Token
  • 3-4. Access Token을 짧게 설정하는 이유
  • 3-5. 실제 동작 프로세스
  • 3-6. 보안 강화 효과

4. Spring Security와 JWT 연동

  • 4-1. Spring Security란?
  • 4-2. Spring Security와 JWT를 연동하는 이유
  • 4-3. 연동 구현의 핵심 요소
  • 4-4. 실제 인증 흐름
  • 4-5. 주의사항 및 고려사항
  • 4-6. 결론

5. JWT 동작 원리와 구조

  • 5-1. JWT 구조
  • 5-2. 인증 프로세스 흐름

6. Spring Boot 구현 예제

  • 6-1. 의존성 설정
  • 6-2. JWT 유틸리티 클래스
  • 6-3. JWT 인증 필터
  • 6-4. 인증 서비스 구현
  • 6-5. 컨트롤러 구현

7. 보안 고려사항

  • 7-1. 토큰 보안
  • 7-2. 프론트엔드에서의 토큰 관리
  • 7-3. 토큰 갱신 전략

8. 보안

  • 토큰 저장
  • 토큰 만료 시간
  • 보안 헤더 설정

 

1. JWT 소개 및 개념

1-1. JWT란 ?

JWT(JSON Web Token)는 당사자 간 정보를 JSON 형태로 안전하게 전송하기 위한 개방형 표준입니다. 토큰 자체에 모든 필요한 정보를 포함하는 자가수용적 방식으로 인증을 처리합니다.

1-2. JWT를 사용하는 이유

1. 서버의 무상태성 보장
JWT는 서버가 클라이언트의 상태를 저장할 필요가 없게 만듭니다. 이는 다음과 같은 이점을 제공합니다

  • 서버 확장성 향상: 서버가 상태를 저장하지 않기 때문에 로드 밸런싱이 더 쉬워지고 서버를 쉽게 확장할 수 있습니다.
  • 서버 자원 효율적 사용: 세션 저장소가 필요 없어 서버의 메모리와 컴퓨팅 자원을 절약할 수 있습니다.
    더보기
    더보기
    로드 밸런싱은 네트워크 트래픽을 여러 서버에 균등하게 분산시키는 기술입니다.

2. 마이크로서비스 아키텍처 지원
JWT는 마이크로서비스 환경에서 특히 유용합니다:

  • 서비스 간 인증 정보 공유 용이: JWT에 필요한 모든 정보가 포함되어 있어, 여러 서비스 간에 인증 정보를 쉽게 공유할 수 있습니다.
  • 단일 인증으로 여러 서비스 접근 가능: 하나의 JWT로 여러 마이크로서비스에 접근할 수 있어서 단일 로그인 구현이 용이합니다.
    더보기
    더보기
    - 마이크로서비스 환경은 애플리케이션을 작고 독립적인 서비스들의 집합으로 구성하는 소프트웨어 아키텍처 접근 방식입니다.
    - 마이크로서비스는 소프트웨어 개발을 위한 아키텍처 접근 방식입니다.

3. 모바일 애플리케이션 지원
JWT는 모바일 환경에서도 효과적입니다:

  • 네이티브 앱과 웹 서비스 간 인증 통합 용이: JWT는 모바일 앱과 웹 서비스 간의 인증을 일관되게 처리할 수 있게 해줍니다.
  • 토큰 기반의 유연한 인증 처리: 모바일 앱에서 JWT를 안전하게 저장하고 사용할 수 있어, 유연한 인증 처리가 가능합니다.

추가적으로 JWT는 크로스-도메인 인증을 지원하고 자체적으로 필요한 모든 정보를 포함하고 있어 데이터베이스 조회 횟수를 줄일 수 있다는 장점도 있습니다.

 

 

2. JWT vs 세션 기반 인증

2-1. 세션 기반 인증의 한계

  • 서버 메모리 부하: 사용자 증가에 따라 서버 메모리 사용량이 증가합니다.
  • 확장성 제한: 세션 클러스터링이 필요하며 서버 간 세션 동기화 문제가 발생할 수 있습니다.
  • CORS 이슈: 쿠키 기반 인증은 도메인 제한으로 다중 도메인 지원이 어렵습니다.
    더보기
    더보기
    세션 클러스터링은 여러 서버에 걸쳐 세션 데이터를 분산 저장하고 관리하는 기술입니다.

2-2. JWT의 장점

  • 확장성: Stateless 특성으로 서버 부하 분산이 용이하고 별도의 세션 저장소가 불필요합니다.
  • 유연성: 다양한 클라이언트 플랫폼 지원과 Cross-Origin 요청 처리가 용이합니다.
  • 성능: 데이터베이스 조회를 최소화하여 빠른 인증 처리가 가능합니다.
    더보기
    더보기
    - Stateless 특성은 서버가 클라이언트의 이전 상태나 요청 정보를 저장하지 않는 것을 의미합니다.
    - Cross-Origin은 웹 애플리케이션에서 현재 페이지의 출처(프로토콜, 도메인, 포트 번호)와 다른 출처의 리소스에 접근하는 것을 의미합니다.

2-3. JWT의 단점과 해결 방안

  • 토큰 크기: JWT 토큰 크기는 세션 ID보다 크기가 큽니다. 필요한 정보만 포함하여 최적화할 수 있습니다.
  • 토큰 탈취 위험: Access Token과 Refresh Token 분리하고 토큰 만료 시간 적절한 설정으로 위험을 줄일 수 있습니다.

2-4. 토큰 무효화

  • 블랙리스트 관리: 유효하지 않은 토큰을 블랙리스트에 추가하여 관리합니다.
  • Refresh Token 활용: Access Token의 수명을 짧게 유지하고 필요시 Refresh Token을 활용하여 새로운 Access Token을 발급받습니다.

2-5. 추가적인 토큰 무효화 방법

  • 토큰 버전 관리: 사용자별로 토큰 버전을 관리하여 이전 버전의 토큰을 무효화합니다.
    • 이 방법은 각 사용자의 토큰에 버전 번호를 붙이는 것입니다.
    • 작동 방식
      • 각 토큰에 버전 번호를 넣습니다 (예: 버전 1, 버전 2 등).
      • 서버는 각 사용자의 현재 유효한 버전 번호를 기억합니다.
      • 토큰을 확인할 때 서버는 토큰의 버전이 현재 유효한 버전과 같은지 확인합니다.
      • 토큰을 무효화하고 싶으면 서버에서 유효한 버전 번호를 올립니다.
    • 예시
      • 철수가 로그인하면 버전 1 토큰을 받습니다.
      • 철수의 계정이 해킹되어 모든 토큰을 무효화하고 싶다면 서버에서 철수의 유효 버전을 2로 바꿉니다.
      • 이제 철수의 버전 1 토큰은 모두 사용할 수 없게 됩니다.
  • 중앙 집중식 이벤트 시스템: 토큰 무효화 이벤트를 분산 시스템에 전파하여 모든 서비스에서 동시에 토큰을 무효화합니다.
    • 이 방법은 토큰 무효화 정보를 모든 서비스에 빠르게 알리는 시스템입니다.
    • 작동 방식
      • 중앙에 메시지를 주고받는 시스템을 만듭니다.
      • 토큰을 무효화해야 할 때 이 시스템에 메시지를 보냅니다.
      • 모든 서비스는 이 메시지를 받아 들을 준비를 하고 있습니다.
      • 메시지를 받으면 각 서비스는 해당 토큰을 사용할 수 없게 만듭니다.
    • 예시
      • 영희가 로그아웃하면 "영희의 토큰 사용 중지" 메시지를 보냅니다.
      • 모든 서비스가 이 메시지를 받습니다.
      • 이제 어떤 서비스에서도 영희의 옛날 토큰은 사용할 수 없습니다.

 

 

3. Access Token과 Refresh Token

3-1. 두 토큰의 필요성

JWT 기반 인증에서 단일 토큰만 사용할 경우 다음과 같은 문제가 발생합니다

  • 토큰의 유효기간이 길면 → 보안에 취약함
  • 토큰의 유효기간이 짧으면 → 사용자가 자주 로그인 해야함

이러한 딜레마를 해결하기 위해 Access Token과 Refresh Token이라는 이중 토큰 시스템을 사용합니다.

3-2. Access Token

Access Token은 실제로 리소스에 접근할 때 사용되는 토큰입니다.

더보기
더보기

여기서 말하는 리소스는 예를 들어 이런것들이 있습니다.

  1. 보호된 API 엔드포인트
  2. 제한된 서비스
  3. 인증이 필요한 데이터
  4. 보안 기능
  5. 권한이 필요한 작업
  • 특징
    • 짧은 유효기간 (보통 15분~1시간)
    • API 요청시 헤더에 포함되어 전송
    • 메모리에 저장 (보안상 localStorage 사용 지양)
    • 모든 인증이 필요한 요청에 포함

3-3. Refresh Token

Refresh Token은 새로운 Access Token을 발급받기 위한 토큰입니다.

  • 특징
    • 긴 유효기간 (보통 1주~2주)
    • 안전한 저장소에 보관 (HttpOnly 쿠키 권장)
    • Access Token이 만료되었을 때만 사용
    • 보안상 중요한 토큰이므로 안전하게 보관

3-4. Access Token을 짧게 설정하는 이유

  • 보안성 강화
    • 토큰이 탈취되더라도 피해 시간 최소화
    • 공격자의 활동 시간을 제한
    • 용자 권한 변경 시 빠르게 적용됨
  • 시스템 제어
    • 비정상적인 접근 빠른 차단 가능
    • 권한 변경을 신속하게 반영
    • 보안 이슈 발생 시 빠른 대응

3-5. 실제 동작 프로세스

  • 초기 로그인서버 
사용자 → 로그인 요청
Access Token(30분) + Refresh Token(2주) 발급
  • 일반적인 API 요청서버
클라이언트 → Access Token으로 API 요청
서버 → 토큰 검증 후 응답
  • Access Token 만료 시
클라이언트 → 만료된 Access Token으로 요청
서버 → 401 Unauthorized 응답
클라이언트 → Refresh Token으로 새 Access Token 요청
서버 → 새로운 Access Token 발급

3-6. 보안 강화 효과

  • 토큰 탈취 대응
    • Access Token이 탈취되어도 30분 후 만료
    • Refresh Token은 안전하게 보관되어 탈취 위험 감소
  • 권한 관리
    • 권한 변경 사항 30분 내 적용
    • 악의적인 접근 차단 용이
  • 시스템 보안
    • 이중 검증 구조로 보안 강화
    • 각 토큰의 역할 분리로 리스크 분산

이러한 Access Token과 Refresh Token의 이중 토큰 전략은 보안성과 사용자 경험의 균형을 맞추는 효과적인 방법입니다. Access Token의 짧은 수명은 보안의 핵심 요소이며 Refresh Token의 조합을 통해 안전하고 효율적인 인증 시스템을 구현할 수 있습니다.

 

 

4. Spring Security와 JWT 연동

4-1. Spring Security란?

Spring Security는 Spring 기반 애플리케이션의 보안(인증과 권한, 인가 등)을 담당하는 스프링 하위 프레임워크입니다.
주로 다음과 같은 보안 기능을 제공합니다:

  • 인증 
    • 사용자가 누구인지 확인하는 과정
    • 로그인 같은 신원 확인 절차
  • 권한 부여 
    • 인증된 사용자가 어떤 것을 할 수 있는지 결정
    • 특정 리소스에 대한 접근 제어
  • 보안 위협 방어
    • CSRF, XSS 등 일반적인 보안 공격 방어
    • 세션 고정 공격 방지

4-2. Spring Security와 JWT를 연동하는 이유

  • 기존 세션 기반 인증의 한계
// 기존 세션 기반 인증
@Configuration
public class SessionSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .sessionManagement()
            .maximumSessions(1)      // 동시 세션 제한
            .maxSessionsPreventsLogin(true);  // 새로운 로그인 차단
    }
}

이러한 세션 방식은 다음과 같은 문제가 있습니다.
- 서버 메모리 부하
- 확장성 제한
- 모바일 앱 지원의 어려움

  • JWT 연동의 장점
@Configuration
public class JwtSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .sessionManagement()
            .sessionCreationPolicy(SessionCreationPolicy.STATELESS)  // 세션 사용 안함
            .and()
            .addFilterBefore(new JwtAuthenticationFilter(), 
                           UsernamePasswordAuthenticationFilter.class);
    }
}

JWT 연동시 얻을 수 있는 이점

  • 서버의 무상태성(Stateless) 보장
  • 확장성 향상
  • 모바일 환경 지원 용이
  • Cross-Origin 요청 처리 간편화

4-3. 연동 구현의 핵심 요소

  • JWT 처리를 위한 필터 추가
@Component
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {
    private final JwtUtil jwtUtil;
    
    @Override
    protected void doFilterInternal(...) {
        // JWT 토큰 검증 및 인증 처리
        String token = extractJwtFromRequest(request);
        if (jwtUtil.validateToken(token)) {
            // Security Context에 인증 정보 설정
            SecurityContextHolder.getContext()
                .setAuthentication(createAuthentication(token));
        }
    }
}
  • Security Context 활용
    • JWT에서 추출한 사용자 정보를 Spring Security의 Context에 저장
    • 이를 통해 Spring Security의 권한 체크 기능을 그대로 활용 가능
더보기
더보기

Security Context는 Spring Security에서 현재 애플리케이션을 사용하는 사용자의 보안 정보를 저장하는 핵심 컴포넌트입니다.

4-4. 실제 인증 흐름

  • 로그인 요청 처리
@RestController
@RequestMapping("/api/auth")
@RequiredArgsConstructor
public class AuthController {
    private final AuthService authService;
    
    @PostMapping("/login")
    public ResponseEntity<TokenResponse> login(@RequestBody LoginRequest request) {
        // 인증 처리 및 토큰 발급
        TokenResponse tokens = authService.login(request);
        return ResponseEntity.ok(tokens);
    }
}
  • API 요청 처리
클라이언트 요청
    → JWT 필터에서 토큰 검증
    → Security Context에 인증 정보 설정
    → Spring Security의 권한 체크
    → API 처리

4-5. 주의사항 및 고려사항

  • 보안 설정
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    return http
        .csrf().disable()  // JWT 사용시 CSRF 보호 불필요
        .cors()           // CORS 설정 필요
        .and()
        .authorizeRequests()
        .antMatchers("/api/public/**").permitAll()
        .anyRequest().authenticated()
        .and()
        .build();
}
  • 토큰 관리
    • Access Token은 짧은 유효기간 설정
    • Refresh Token은 안전한 저장소에 보관
    • 토큰 갱신 로직 구현 필요
  • 예외 처리
@RestControllerAdvice
public class JwtExceptionHandler {
    @ExceptionHandler(ExpiredJwtException.class)
    public ResponseEntity<ErrorResponse> handleExpiredJwtException() {
        return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
            .body(new ErrorResponse("토큰이 만료되었습니다."));
    }
}

4-6. 결론

Spring Security와 JWT의 연동은 현대적인 웹 애플리케이션에서 필수적인 보안 요구사항을 충족시키는 효과적인 방법입니다. Spring Security의 강력한 보안 기능과 JWT의 유연성을 결합함으로써 안전하면서도 확장 가능한 인증 시스템을 구축할 수 있습니다.

 

 

5. JWT 동작 원리와 구조

5-1. JWT 구조

Header.Payload.Signature

// 1. Header (헤더)
{
  "alg": "HS256",  // 서명 알고리즘
  "typ": "JWT"     // 토큰 타입
}

// 2. Payload (페이로드)
{
  "sub": "1234567890",  // Subject (주제)
  "name": "John Doe",   // 사용자 이름
  "iat": 1516239022    // 발행 시간
}

// 3. Signature (서명)
HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret
)

5-2. 인증 프로세스 흐름

sequenceDiagram
    Client->>+Server: 1. 로그인 요청 (username/password)
    Server->>Server: 2. 사용자 검증
    Server->>Server: 3. JWT 토큰 생성
    Server-->>-Client: 4. JWT 토큰 반환
    Note right of Client: 5. JWT 저장
    Client->>+Server: 6. API 요청 with JWT
    Note right of Client: Authorization: Bearer {token}
    Server->>Server: 7. JWT 검증
    Server-->>-Client: 8. 응답

 

 

6. Spring Boot 구현 예제

6-1. 의존성 설정

<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>

6-2. JWT 유틸리티 클래스

@Component
public class JwtUtil {
    @Value("${jwt.secret}")
    private String secret;
    
    // Access Token 유효기간: 30분
    private final long ACCESS_TOKEN_VALIDITY = 30 * 60 * 1000;
    
    // JWT 토큰 생성
    public String generateToken(UserDetails userDetails) {
        Map<String, Object> claims = new HashMap<>();
        claims.put("username", userDetails.getUsername());
        
        return Jwts.builder()
            .setClaims(claims)  // 클레임 정보 설정
            .setIssuedAt(new Date(System.currentTimeMillis()))  // 토큰 발행 시간
            .setExpiration(new Date(System.currentTimeMillis() + ACCESS_TOKEN_VALIDITY))  // 만료 시간
            .signWith(SignatureAlgorithm.HS256, secret)  // 서명 알고리즘과 비밀키
            .compact();
    }
    
    // JWT 토큰 검증
    public boolean validateToken(String token) {
        try {
            Jwts.parser().setSigningKey(secret).parseClaimsJws(token);
            return true;
        } catch (JwtException | IllegalArgumentException e) {
            return false;
        }
    }
    
    // 토큰에서 사용자 이름 추출
    public String extractUsername(String token) {
        return extractClaim(token, Claims::getSubject);
    }
}

6.3 JWT 인증 필터

@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
    private final JwtUtil jwtUtil;
    private final UserDetailsService userDetailsService;

    @Override
    protected void doFilterInternal(
            HttpServletRequest request,
            HttpServletResponse response,
            FilterChain filterChain
    ) throws ServletException, IOException {
        // Authorization 헤더에서 JWT 토큰 추출
        final String authHeader = request.getHeader("Authorization");
        
        if (authHeader == null || !authHeader.startsWith("Bearer ")) {
            filterChain.doFilter(request, response);
            return;
        }

        // Bearer 제거 후 순수 토큰만 추출
        String jwt = authHeader.substring(7);
        String username = jwtUtil.extractUsername(jwt);

        // 인증 정보가 없을 경우에만 처리
        if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
            UserDetails userDetails = userDetailsService.loadUserByUsername(username);
            
            // 토큰 유효성 검증
            if (jwtUtil.validateToken(jwt)) {
                UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
                    userDetails,
                    null,
                    userDetails.getAuthorities()
                );
                // Security Context에 인증 정보 저장
                SecurityContextHolder.getContext().setAuthentication(authToken);
            }
        }
        
        filterChain.doFilter(request, response);
    }
}

6-4. 인증 서비스 구현

@Service
@RequiredArgsConstructor
public class AuthService {
    private final AuthenticationManager authenticationManager;
    private final JwtUtil jwtUtil;
    private final UserRepository userRepository;

    // 로그인 처리
    public AuthResponse login(LoginRequest request) {
        try {
            // 사용자 인증 시도
            Authentication authentication = authenticationManager.authenticate(
                new UsernamePasswordAuthenticationToken(
                    request.getUsername(),
                    request.getPassword()
                )
            );

            // 인증된 사용자 정보로 JWT 토큰 생성
            UserDetails userDetails = (UserDetails) authentication.getPrincipal();
            String token = jwtUtil.generateToken(userDetails);
            
            // 사용자 정보 조회
            User user = userRepository.findByUsername(userDetails.getUsername())
                .orElseThrow(() -> new UsernameNotFoundException("User not found"));

            // 응답 데이터 생성
            return AuthResponse.builder()
                .token(token)
                .user(AuthResponse.UserDto.builder()
                    .id(user.getId())
                    .username(user.getUsername())
                    .name(user.getName())
                    .build())
                .build();
        } catch (AuthenticationException e) {
            throw new RuntimeException("Invalid username/password");
        }
    }
}

6-5. 컨트롤러 구현

@RestController
@RequestMapping("/api/auth")
@RequiredArgsConstructor
public class AuthController {
    private final AuthService authService;

    // 로그인 엔드포인트
    @PostMapping("/login")
    public ResponseEntity<AuthResponse> login(@RequestBody LoginRequest request) {
        return ResponseEntity.ok(authService.login(request));
    }

    // 토큰 갱신 엔드포인트
    @PostMapping("/refresh")
    public ResponseEntity<AuthResponse> refresh(
        @CookieValue("refreshToken") String refreshToken
    ) {
        return ResponseEntity.ok(authService.refreshToken(refreshToken));
    }
}

 

 

7. 보안 고려사항

7-1.토큰 보안

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .csrf().disable()  // JWT를 사용하므로 CSRF 비활성화
            .sessionManagement()
            .sessionCreationPolicy(SessionCreationPolicy.STATELESS)  // 세션 사용 안함
            .and()
            .authorizeRequests()
            .antMatchers("/api/auth/**").permitAll()  // 인증 관련 엔드포인트는 모두 허용
            .anyRequest().authenticated()  // 나머지는 인증 필요
            .and()
            .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
    }
}

7-2. 프론트엔드에서의 토큰 관리

// 토큰 저장 및 관리
class AuthService {
    // 로그인 처리
    async login(credentials) {
        try {
            const response = await api.post('/auth/login', credentials);
            const { token, user } = response.data;
            
            // Access Token은 메모리에 저장
            this.setAccessToken(token);
            
            // 사용자 정보 저장
            this.setUser(user);
            
            return user;
        } catch (error) {
            throw new Error('로그인 실패');
        }
    }

    // API 요청에 토큰 추가
    setAccessToken(token) {
        // axios 인터셉터에 토큰 설정
        api.interceptors.request.use(
            config => {
                if (token) {
                    config.headers['Authorization'] = `Bearer ${token}`;
                }
                return config;
            },
            error => {
                return Promise.reject(error);
            }
        );
    }
}

7-3. 토큰 갱신 전략

// 토큰 갱신 인터셉터
api.interceptors.response.use(
    response => response,
    async error => {
        const originalRequest = error.config;

        // 토큰 만료 에러이고 재시도하지 않은 요청일 경우
        if (error.response.status === 401 && !originalRequest._retry) {
            originalRequest._retry = true;

            try {
                // 토큰 갱신 요청
                const response = await authService.refreshToken();
                const { token } = response.data;

                // 새로운 토큰 설정
                authService.setAccessToken(token);

                // 실패한 요청 재시도
                return api(originalRequest);
            } catch (refreshError) {
                // 갱신 실패시 로그아웃
                authService.logout();
                return Promise.reject(refreshError);
            }
        }

        return Promise.reject(error);
    }
);

 

 

8. 보안

  • 토큰 저장
    • Access Token: 메모리 저장
    • Refresh Token: HttpOnly 쿠키
  • 토큰 만료 시간
    • Access Token: 15-30분
    • Refresh Token: 2주-1달
  • 보안 헤더 설정
@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
            .allowedOrigins("http://localhost:3000")
            .allowedMethods("*")
            .allowedHeaders("*")
            .allowCredentials(true)
            .exposedHeaders("Authorization");
    }
}

 

 

 

JWT를 활용한 인증 시스템은 현대적인 웹 애플리케이션에서 효과적인 인증 솔루션을 제공합니다. 특히 마이크로서비스 아키텍처와 SPA에서 그 장점이 두드러집니다. 하지만 적절한 보안 조치와 구현 전략이 필수적이며 프로젝트의 요구사항에 따라 세션 기반 인증과의 적절한 선택이 필요합니다.