티스토리 뷰

🍀 Spring Boot

Spring Boot - JWT 설정

James Wetzel 2025. 2. 6. 12:29
728x90
반응형

 

JWT 설정 방법

  1. 단계: JwtProvider 생성
  2. 단계: JwtAuthenticationFilter 생성
  3. 단계: SecurityConfig 생성
  4. 단계: 인증 및 토큰 생성

 

 

  1. 단계: JwtProvider 생성

 

application.properties 설정

# JWT 설정
jwt.secret=SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
jwt.expirationMillis=86400000
 

pom.xml 설정

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-api</artifactId>
    <version>0.12.3</version>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-impl</artifactId>
    <version>0.12.3</version>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-jackson</artifactId>
    <version>0.12.3</version>
    <scope>runtime</scope>
</dependency>
 

JwtProvider.java

package com.example.demo.provider;

import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.crypto.SecretKey;
import java.nio.charset.StandardCharsets;
import java.util.Date;

@Component
public class JwtProvider {
    private final SecretKey secretKey;
    private final long expirationMillis;

    public JwtProvider(@Value("${jwt.secret}") String secret, @Value("${jwt.expirationMillis}") long expirationMillis) {
        this.secretKey = Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));
        this.expirationMillis = expirationMillis;
    }

    public String generateToken(String subject) {
        return Jwts.builder()
                .subject(subject)
                .issuedAt(new Date())
                .expiration(new Date(System.currentTimeMillis() + expirationMillis))
                .signWith(secretKey)
                .compact();
    }

    public String extractUsername(String token) {
        return Jwts.parser()
                .verifyWith(secretKey)
                .build()
                .parseSignedClaims(token)
                .getPayload()
                .getSubject();
    }

    public boolean validateToken(String token) {
        try {
            Jwts.parser()
                    .verifyWith(secretKey)
                    .build()
                    .parseSignedClaims(token);
            return true;
        } catch (Exception e) {
            return false;
        }
    }
}
 

2. 단계: JwtAuthenticationFilter 생성

package com.example.demo.filter;

import com.example.demo.dto.ApiResult;
import com.example.demo.entity.User;
import com.example.demo.service.UserService;
import com.example.demo.provider.JwtProvider;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;
import java.util.Optional;

@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {

    private final JwtProvider jwtProvider;
    private final UserService userService;

    public JwtAuthenticationFilter(JwtProvider jwtProvider, UserService userService) {
        this.jwtProvider = jwtProvider;
        this.userService = userService;
    }

    @Override
    protected void doFilterInternal(
            HttpServletRequest request,
            HttpServletResponse response,
            FilterChain filterChain
    ) throws ServletException, IOException {
        try {
            extractToken(request)
                    .filter(jwtProvider::validateToken)
                    .map(jwtProvider::extractUsername)
                    .flatMap(userService::findByEmail)
                    .ifPresent(this::setAuthentication);

            filterChain.doFilter(request, response);
        } catch (UsernameNotFoundException ex) {
            handleException(response, "User not found: " + ex.getMessage(), HttpStatus.NOT_FOUND);
        } catch (Exception ex) {
            handleException(response, "Authentication error: " + ex.getMessage(), HttpStatus.UNAUTHORIZED);
        }
    }

    private Optional<String> extractToken(HttpServletRequest request) {
        String header = request.getHeader("Authorization");
        if (header != null && header.startsWith("Bearer ")) {
            return Optional.of(header.substring(7));
        }
        return Optional.empty();
    }

    private void setAuthentication(User user) {
        UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
                user, null, user.getAuthorities()
        );
        SecurityContextHolder.getContext().setAuthentication(authentication);
    }

    // 예외 응답 처리 메서드 추가
    private void handleException(HttpServletResponse response, String message, HttpStatus status) throws IOException {
        ApiResult<Void> apiResult = new ApiResult<>(false, message, null);
        response.setStatus(status.value());
        response.setContentType("application/json");
        response.getWriter().write(new ObjectMapper().writeValueAsString(apiResult));
    }
}
 

3. 단계: SecurityConfig 생성

package com.example.demo.config;

import com.example.demo.filter.JwtAuthenticationFilter;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {

    private final JwtAuthenticationFilter jwtAuthenticationFilter;

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
                // 기본 HTTP 인증과 CSRF 보호 비활성화
                .httpBasic(AbstractHttpConfigurer::disable)
                .csrf(AbstractHttpConfigurer::disable)
                // JWT 기반 인증을 사용하므로 상태 없는 세션 정책 적용
                .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                // URL 접근 권한 설정
                .authorizeHttpRequests(auth -> auth
                        .requestMatchers("/api/auth/**").permitAll()  // 인증이 필요없는 경로
                        .anyRequest().authenticated()                  // 그 외 모든 요청은 인증 필요
                )
                // JWT 인증 필터를 UsernamePasswordAuthenticationFilter 이전에 실행
                .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);

        return http.build();
    }

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

4단계: 인증 및 토큰 생성

package com.example.demo.controller;

import com.example.demo.dto.ApiResult;
import com.example.demo.dto.SignUpRequestDTO;
import com.example.demo.dto.sign.in.SignInRequestDTO;
import com.example.demo.dto.sign.in.SignInResponseDTO;
import com.example.demo.entity.User;
import com.example.demo.provider.JwtProvider;
import com.example.demo.service.UserService;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.*;

import java.util.Set;

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

    private final UserService userService;
    private final PasswordEncoder passwordEncoder;
    private final JwtProvider jwtUtils;

    @PostMapping("/signIn")
    public ResponseEntity<ApiResult<SignInResponseDTO>> signIn(@RequestBody @Valid SignInRequestDTO request) {
        return userService.findByEmail(request.getEmail())
                .filter(user -> passwordEncoder.matches(request.getPassword(), user.getPassword()))
                .map(user -> {
                    String token = jwtUtils.generateToken(request.getEmail());
                    SignInResponseDTO responseDTO = new SignInResponseDTO();
                    responseDTO.setToken(token);
                    return ResponseEntity.ok(new ApiResult<>(true, "Success", responseDTO));
                })
                .orElseGet(() -> ResponseEntity.ok(new ApiResult<>(false, "이메일 또는 비밀번호가 올바르지 않습니다.", null)));
    }

}
 

 

 

728x90
반응형