본문 바로가기
SpringBoot/보안과 인증

JWT(Json Web Token) 인증

by DEVLIB 2025. 4. 15.
728x90

JWT 인증이란?

정의

**JWT(Json Web Token)**은 인증 정보를 담은 서명된 토큰을 클라이언트에 전달하여,
세션 없이 Stateless하게 사용자 인증을 유지하는 방식입니다.


🧱 JWT 구조

JWT는 3개의 부분으로 구성됩니다:

HEADER.PAYLOAD.SIGNATURE

 

시:

eyJhbGciOiJIUzI1NiJ9.
eyJ1c2VybmFtZSI6ImRhbmJpIiwicm9sZXMiOlsiVVNFUiJdfQ.
sdf934jfsd98fsdljf934jsdf
구성 요소 설명
Header 토큰 타입 + 알고리즘 정보
Payload 사용자 정보(Claims)
Signature 비밀 키를 사용한 서명

JWT 인증 전체 흐름

  1. 사용자가 로그인 시도 (ID/PW)
  2. 서버가 인증 후 JWT 생성
  3. 클라이언트에 JWT 전달 (HTTP 응답)
  4. 이후 요청마다 Authorization: Bearer {token} 헤더에 포함
  5. 서버는 토큰을 검증하여 사용자 인증

1. 의존성 추가 (Gradle)

implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5'

2. JWT 토큰 생성 및 검증 유틸

@Component
public class JwtTokenProvider {

    private final String secretKey = "secret_key_example"; // 반드시 길게!

    // 토큰 유효 시간 (1시간)
    private final long validityInMilliseconds = 3600000;

    // 토큰 생성
    public String createToken(String username, List<String> roles) {
        Claims claims = Jwts.claims().setSubject(username);
        claims.put("roles", roles);

        Date now = new Date();
        Date validity = new Date(now.getTime() + validityInMilliseconds);

        return Jwts.builder()
            .setClaims(claims)
            .setIssuedAt(now)
            .setExpiration(validity)
            .signWith(Keys.hmacShaKeyFor(secretKey.getBytes()), SignatureAlgorithm.HS256)
            .compact();
    }

    // 토큰에서 사용자 이름 추출
    public String getUsername(String token) {
        return Jwts.parserBuilder()
            .setSigningKey(secretKey.getBytes())
            .build()
            .parseClaimsJws(token)
            .getBody()
            .getSubject();
    }

    // 토큰 유효성 검사
    public boolean validateToken(String token) {
        try {
            Jwts.parserBuilder()
                .setSigningKey(secretKey.getBytes())
                .build()
                .parseClaimsJws(token);
            return true;
        } catch (JwtException | IllegalArgumentException e) {
            return false;
        }
    }
}

3. JWT 필터 생성

public class JwtAuthenticationFilter extends OncePerRequestFilter {

    private final JwtTokenProvider jwtTokenProvider;

    public JwtAuthenticationFilter(JwtTokenProvider jwtTokenProvider) {
        this.jwtTokenProvider = jwtTokenProvider;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain filterChain) throws ServletException, IOException {

        String token = resolveToken(request);

        if (token != null && jwtTokenProvider.validateToken(token)) {
            String username = jwtTokenProvider.getUsername(token);
            Authentication auth = new UsernamePasswordAuthenticationToken(username, "", List.of());
            SecurityContextHolder.getContext().setAuthentication(auth);
        }

        filterChain.doFilter(request, response);
    }

    private String resolveToken(HttpServletRequest request) {
        String bearer = request.getHeader("Authorization");
        if (bearer != null && bearer.startsWith("Bearer ")) {
            return bearer.substring(7);
        }
        return null;
    }
}

4. SecurityConfig에 필터 등록

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    private final JwtTokenProvider jwtTokenProvider;

    public SecurityConfig(JwtTokenProvider jwtTokenProvider) {
        this.jwtTokenProvider = jwtTokenProvider;
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .csrf().disable()
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()
            .authorizeHttpRequests((authz) -> authz
                .requestMatchers("/api/auth/**").permitAll()
                .anyRequest().authenticated()
            )
            .addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider),
                             UsernamePasswordAuthenticationFilter.class);

        return http.build();
    }
}

5. 로그인 API에서 JWT 발급

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

    private final JwtTokenProvider jwtTokenProvider;
    private final MemberRepository memberRepository;
    private final PasswordEncoder passwordEncoder;

    @PostMapping("/login")
    public ResponseEntity<?> login(@RequestBody MemberDto dto) {
        Member member = memberRepository.findByUsername(dto.getUsername())
                .orElseThrow(() -> new RuntimeException("사용자 없음"));

        if (!passwordEncoder.matches(dto.getPassword(), member.getPassword())) {
            throw new RuntimeException("비밀번호 불일치");
        }

        String token = jwtTokenProvider.createToken(member.getUsername(), List.of("ROLE_USER"));

        return ResponseEntity.ok(Map.of("token", token));
    }
}

6. 클라이언트 요청 시 토큰 포함

GET /api/user/info HTTP/1.1
Host: localhost:8080
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9...

마무리 요약

항목 설명
JWT 사용자 정보를 담은 서명된 토큰
구성 Header.Payload.Signature
사용 방식 로그인 시 토큰 발급 → 클라이언트가 요청마다 토큰 포함
스프링 필수 구성 JwtTokenProvider, JwtAuthenticationFilter, SecurityConfig
LIST

'SpringBoot > 보안과 인증' 카테고리의 다른 글

OAuth2 로그인(Google, Kakao 등)  (0) 2025.04.15
로그인 & 회원가입 구현  (0) 2025.04.15
Spring Security 기본 설정  (0) 2025.04.15