JWT 토큰을 발급하기 위해서 JwtUtil을 만들었다

package com.b210.damda.util;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

import java.util.Date;

public class JwtUtil {

    // 유저 이메일 꺼내기
    public static String getUserEmail(String token, String secretKey){
        return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token)
                .getBody().get("email", String.class);
    }

    // 토큰 만료 체크
    public static boolean isExpired(String token, String secretKey){
        return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token)
                .getBody().getExpiration().before(new Date());
        // 만료 기간이 지금 시간보다 전이면 만료가 된 상태이다.
    }
		
		// 액세스 토큰 생성
    public static String createJwt(String email, String secretKey, Long expiredMs){
        Claims claims = Jwts.claims();
        claims.put("email", email);

        return Jwts.builder() // 토큰을 생성
                .setClaims(claims) // 유저의 이메일
                .setIssuedAt(new Date(System.currentTimeMillis())) // 현재 시간
                .setExpiration(new Date(System.currentTimeMillis() + expiredMs)) // 언제까지
                .signWith(SignatureAlgorithm.HS256, secretKey) // 뭐로 사인됐는지
                .compact();
    }

		// 리프레쉬 토큰 생성
    public static String createRefreshToken(String secretKey, Long expiredMs){
        Claims claims = Jwts.claims();

        return Jwts.builder() // 토큰을 생성
                .setClaims(claims) // claim은 비어있음
                .setIssuedAt(new Date(System.currentTimeMillis())) // 현재 시간
                .setExpiration(new Date(System.currentTimeMillis() + expiredMs)) // 언제까지
                .signWith(SignatureAlgorithm.HS256, secretKey) // 어떤 키로 사인할지
                .compact();
    }
}

그리고 Jwt를 사용하면서 스프링 시큐리티를 도입했는데

package com.b210.damda.config;

import com.b210.damda.domain.user.filter.JwtFilter;
import com.b210.damda.domain.user.service.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@EnableWebSecurity // 이거 설정해놓으면 시큐리티가 모든 요청을 막아버림.
@Configuration
@RequiredArgsConstructor
// 변경 전에는 WebSecurityConfigurerAdapter 이거 상속받았는데
// 이제는 상속받지 않고 사용함.
public class SecurityConfig {

    private final UserService userService;
    @Value("${jwt.secret}")
    private String secretKey;

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception{
        return httpSecurity
                .httpBasic().disable()
                .csrf().disable()
                .cors().and()
                .authorizeRequests()
                .antMatchers("/api/regist", "/api/login").permitAll() // 회원가입과 로그인은 언제나 가능
                .antMatchers(HttpMethod.PATCH, "/api/update").authenticated() // 해당 요청을 인증 필수로 막아놓음.
                .anyRequest().permitAll() // 모든 요청 허가
                .and()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .addFilterBefore(new JwtFilter(userService, secretKey), UsernamePasswordAuthenticationFilter.class)
                .build();
    }

}

시큐리티 config 클래스를 하나 만들었다.

처음에 모든 요청이 들어오면 config 설정을 보는데 여기서 허가를 해준 곳만 요청이 받아진다.

그리고 유저 domain 안에 JwtFilter를 만들었다.

package com.b210.damda.domain.user.filter;

import com.b210.damda.domain.user.service.UserService;
import com.b210.damda.util.JwtUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;

@RequiredArgsConstructor
@Slf4j
public class JwtFilter extends OncePerRequestFilter { // 모든 요청에 대해 토큰 유효성 검증을 진행

    private final UserService userService;
    private final String secretKey;

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

        final String authorization = request.getHeader(HttpHeaders.AUTHORIZATION);
        log.info("authorization : {}", authorization);

        // 토큰이 없거나 Bearer로 시작하지 않는 경우
        if(authorization == null || !authorization.startsWith("Bearer ")){
            log.error("authorization을 잘못 보냈습니다.");
            filterChain.doFilter(request, response); // 다음 필터로 전달함.
            return;
        }

        // 토큰 꺼내기(첫 번째가 토큰이다. bearer 제외)
        String token = authorization.split(" ")[1];

        // 토큰 만료됐는지 확인
        if(JwtUtil.isExpired(token, secretKey)){
            log.error("토큰이 만료되었습니다.");
            filterChain.doFilter(request, response); // 다음 필터로 전달함.
            return;
        }

        // userEmail 토큰에서 꺼냄.
        String userEmail = JwtUtil.getUserEmail(token, secretKey);
        log.info("userEmail:{}", userEmail);

        // 권한 부여
        UsernamePasswordAuthenticationToken authenticationToken =
                new UsernamePasswordAuthenticationToken(userEmail, null, List.of(new SimpleGrantedAuthority("USER")));
        // Detail을 넣어준다.
        authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        filterChain.doFilter(request, response);
    }
}

모든 요청이 오면 클라이언트에서 액세스 토큰을 받게 되는데

JwtFilter를 거치게 된다. 그러면 토큰의 유효성을 검사하게 된다.