스택큐힙리스트

자바 스프링 개발 시작하기 - 12일차 로그인·회원가입·토큰 재발급 플로우 본문

개발

자바 스프링 개발 시작하기 - 12일차 로그인·회원가입·토큰 재발급 플로우

스택큐힙리스트 2025. 7. 19. 11:08
반응형

Spring Security와 JWT, OAuth 2.0을 결합해 완전 무상태(Stateless) 보안 아키텍처를 구현하는 방법을 한눈에 정리했습니다. 오늘 따라가면 REST API 백엔드에서도 소셜 로그인 → 회원가입 → JWT 발급 → 만료 → 토큰 재발급 흐름을 손쉽게 완성할 수 있어요.


1. 왜 세션 대신 JWT인가?

  • 수평 확장성 : 서버 간 세션 공유 필요 X, 로드밸런싱에도 끄떡없음.
  • 네이티브·웹·모바일 클라이언트 통합 : 토큰만 전송하면 동일 로직 재사용 가능.

2. 핵심 개념 한눈에

  • Access Token : 사용자 정보와 만료 시각(짧게) 포함. HTTP Authorization: Bearer.
  • Refresh Token : 재발급 전용·장수 토큰. DB·Redis에 저장해 탈취 시 강제 로그아웃 가능.
  • OAuth 2.0 : 카카오·구글 등 외부 IdP 인증 후 우리 서비스용 JWT로 교환.

3. 프로젝트 구조 스케치

security/
 ├─ JwtTokenProvider.java      // 토큰 생성·검증 로직
 ├─ JwtAuthenticationFilter.java
 ├─ SecurityConfig.java        // 필터 체인 등록 & OAuth2 설정
auth/
 ├─ AuthController.java        // /auth/signup, /auth/login, /auth/refresh
 └─ RefreshTokenRepository.java

4. 구현 순서

  1. 의존성 추가
    implementation 'org.springframework.boot:spring-boot-starter-security'
    implementation 'io.jsonwebtoken:jjwt-api:0.12.5'
    implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
    implementation 'org.springframework.boot:spring-boot-starter-data-redis'
  2. JwtTokenProvider – 토큰 생성 & 검증
    public String createAccessToken(String userId) {
        Date now = new Date();
        return Jwts.builder()
             .setSubject(userId)
             .setIssuedAt(now)
             .setExpiration(new Date(now.getTime() + accessExp))
             .signWith(key, SignatureAlgorithm.HS256)
             .compact();
    }

  3. SecurityConfig – 필터 체인 정리
    http
      .csrf().disable()
      .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
      .and()
      .oauth2Login()
          .userInfoEndpoint().userService(oAuth2UserService)
      .and()
      .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);


  4. AuthController – 로그인 & 재발급
    @PostMapping("/login")
    public TokenResponse login(@RequestBody LoginRequest req) {
        // 비밀번호 검증 후
        String access = jwt.createAccessToken(user.getId());
        String refresh = jwt.createRefreshToken(user.getId());
        refreshRepo.save(refresh, user.getId());
        return new TokenResponse(access, refresh);
    }
    
    @PostMapping("/refresh")
    public TokenResponse refresh(@RequestBody RefreshRequest r) {
        jwt.validateRefreshToken(r.getRefresh());      // 만료·위조 체크
        String userId = jwt.getSubject(r.getRefresh());
        if (!refreshRepo.match(r.getRefresh(), userId)) throw new Unauthorized();
        return new TokenResponse(jwt.createAccessToken(userId), r.getRefresh());
    }

  5. 로그아웃 : Refresh-Token 삭제 후 Access-Token 블랙리스트(선택) 등록.

5. 실전 Tip

  • Access 만료 15 분·Refresh 2 주 로 시작, 실제 트래픽에 맞춰 조정.
  • HTTPS 강제 : 토큰 탈취 방지.
  • Redis TTL로 Refresh 토큰 자동 만료 관리, 수동 정리 필요 ↓.
  • OAuth2 인가 코드는 백엔드에서 교환 → 클라이언트에 JWT만 내려주면 보안 강화.
  • JWT Claim 최소화 : 개인정보·권한을 과도하게 넣으면 토큰 탈취 시 피해 확대.

6. 마무리

이제 회원가입부터 토큰 재발급까지 끝났습니다. 다음 단계에서는 “인가(Authorization) 세분화”—@PreAuthorize, RBAC 설계—를 추천드려요.

반응형
Comments