[D&X:W] conference 프로젝트를 진행중이라, 개발을 진행하였음

소셜 로그인(구글) 부분을 맡았고, JWT 토큰 구현 완료하였음

JwtAuthenticationFilter

package conference.clerker.global.jwt;

import conference.clerker.global.oauth2.service.CustomUserDetailsService;
import io.jsonwebtoken.ExpiredJwtException;
import jakarta.servlet.ServletException;
import lombok.RequiredArgsConstructor;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
@RequiredArgsConstructor
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {

    private final JwtProvider jwtProvider;
    private final CustomUserDetailsService customUserDetailsService;  // CustomUserDetailsService가 주입됩니다.

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws IOException, ServletException {
        try {
            String jwt = getJwtFromRequest(request);
            if (jwt != null && jwtProvider.validateToken(jwt)) {
                String email = jwtProvider.getEmailFromToken(jwt);

                UserDetails userDetails = customUserDetailsService.loadUserByUsername(email); // 사용자 정보 가져오기
                if (userDetails != null) {
                    Authentication authentication = new UsernamePasswordAuthenticationToken(
                            userDetails, null, userDetails.getAuthorities());
                    // 인증 정보 설정
                    SecurityContextHolder.getContext().setAuthentication(authentication);
                }
            }
        } catch (ExpiredJwtException e) {
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        } catch (Exception e) {
            response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        }
        filterChain.doFilter(request, response);
    }

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

JwtProvider

package conference.clerker.global.jwt;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.security.Key;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@Slf4j
@Component
public class JwtProvider {

    @Value("${jwt.secret-key}")
    private String secretKey;

    @Value("${jwt.access-token.expiration}")
    private long tokenValidityInSeconds;

    private Key getSigningKey() {
        return Keys.hmacShaKeyFor(secretKey.getBytes());
    }

    // JWT 토큰 생성 (추가 정보 포함)
    public String generateToken(Map<String, Object> additionalClaims) {
        Date now = new Date();
        long jwtExpirationMs = TimeUnit.HOURS.toMillis(tokenValidityInSeconds); // 시간을 밀리초로 변환
        Date expiryDate = new Date(now.getTime() + jwtExpirationMs);

        return Jwts.builder()
                .setIssuedAt(now)
                .setExpiration(expiryDate)
                .addClaims(additionalClaims) // 추가 정보 (custom claims) 설정
                .signWith(getSigningKey(), SignatureAlgorithm.HS512)
                .compact();
    }

    // 이메일 추출
    public String getEmailFromToken(String token) {
        return parseClaims(token).get("email", String.class); // email 키로 이메일 추출
    }

    // 사용자 이름 추출
    public String getUsernameFromToken(String token) {
        return parseClaims(token).get("username", String.class); // username 키로 사용자 이름 추출
    }

    // 토큰에서 Claims 추출
    private Claims parseClaims(String token) {
        return Jwts.parserBuilder()
                .setSigningKey(getSigningKey())
                .build()
                .parseClaimsJws(token)
                .getBody();
    }

    // 토큰 유효성 검증
    public boolean validateToken(String token) {
        try {
            Claims claims = parseClaims(token);
            return !claims.getExpiration().before(new Date());
        } catch (Exception e) {
            log.error("Invalid JWT token: ", e);
            return false;
        }
    }
}

✏️. 소감

개발을 하면서 느끼는 건, 혼자만의 힘으로는 모든 것을 해결하기 어렵다는 점이다. 특히 내가 구현하려는 로직이나 워크 플로우에 대해 깊이 이해하고 있지 않다면, 그 과정이 더더욱 복잡하고 어려워진다. 다행히 GPT와 같은 도구의 도움을 받으면서 문제를 하나하나 해결할 수 있었고, 이를 통해 개발을 조금씩 앞으로 나아가고 있다.

이번에 JWT 구현을 성공적으로 마무리하면서 정말 큰 성취감을 느꼈다. 처음에는 막연하고 복잡하게만 느껴졌던 JWT도, 차근차근 단계를 밟아가며 해결해나가니 어느새 하나의 완성된 기능이 되어 있었다. 물론, 쉽지 않은 여정이었지만, 그만큼 보람도 크다. 또한, 이렇게 개발에만 집중할 수 있는 시간을 가질 수 있다는 것에 큰 감사함을 느낀다. 바쁜 일상 속에서도 내가 몰두할 수 있는 시간이 있다는 것은 생각보다 소중한 경험이다. 오늘 하루는 그런 의미에서 정말 뜻깊은 하루였다. 앞으로도 이런 시간을 소중히 여기며, 더 많은 성장을 이루고 싶다.