본문 바로가기

Back-end/Spring Security

[Spring Security] Security + JWT 설정 (JwtFilter편)

✅ Security Config + Jwt Filter추가

 

//JwtFilter 추가
.addFilterBefore(new JwtFilter(jwtUtil), UsernamePasswordAuthenticationFilter.class)

 

Security설정에서 JwtFilter를 설정한다 Security를 타기전에 먼저 JwtFilter를 먼저 타서 로그인후에 토큰을 받으면

해당 토큰으로 Jwt 인증과 인가를 제공한다.

 

UsernamePasswordAuthenticationFilter

로그인 요청은 가장 먼저 Application Filters 로 들어오고, 그 필터들 중 AuthenticationFilter 로 들어온다.
그리고 AuthenticationFilter 필터들 중 최종적으로 UsernamePasswordAuthenticationFilter 에 도착하게 된다.

이를 위해 AuthenticationFilter 가 UsernamePasswordAuthenticationFilter 를 상속받도록 했다.
(JWT 토큰을 이용하므로, 클래스 명을 JwtAuthenticationFilter 로 했다.)

 

✅JwtFilter

@Slf4j
@RequiredArgsConstructor
public class JwtFilter extends OncePerRequestFilter {

    private final JwtUtil jwtUtil;

    @Override
    protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
        String path = request.getRequestURI();
        log.info("check uri: " + path);

        // Pre-flight 요청은 필터를 타지 않도록 설정
        if (request.getMethod().equals("OPTIONS")) {
            return true;
        }
        // /api/member/로 시작하는 요청은 필터를 타지 않도록 설정
        if (path.startsWith("/api/member/*")) {
            return true;
        }
        // todo book만들떄 url체크 수정필요 바뀌면
        if (path.startsWith("/api/book/view/")) {
            return true;
        }

        // Swagger UI 경로 제외 설정
        if (path.startsWith("/swagger-ui/") || path.startsWith("/v3/api-docs")) {
            return true;
        }
        // h2-console 경로 제외 설정
        if (path.startsWith("/h2-console")) {
            return true;
        }

        return false;
    }

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

        log.info("------------------JWTCheckFilter.................");
        log.info("request.getServletPath(): {}", request.getServletPath());
        log.info("..................................................");


        //cookie들을 불러온 뒤 Authorization Key에 담긴 쿠키를 찾음
        String authorization = null;
        Cookie[] cookies = request.getCookies();
        if(cookies !=null ) {
            for (Cookie cookie : cookies) {

                System.out.println(cookie.getName());
                if (cookie.getName().equals("AccessToken")) {
                    if ("AccessToken".equals(cookie.getName())) {
                        authorization = cookie.getValue();
                        break;
                    }
                }
            }
        }
        //  헤더에서도 읽기 (Bearer 토큰)
        if (authorization == null || authorization.isBlank()) {
            String bearerToken = request.getHeader("Authorization");
            if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
                authorization = bearerToken.substring(7);
            }
        }
        //Authorization 헤더 검증
        if (authorization == null) {

            System.out.println("token null");
            filterChain.doFilter(request, response);

            //조건이 해당되면 메소드 종료 (필수)
            return;
        }

        //토큰
        String token = authorization;

        //토큰 소멸 시간 검증
        if (jwtUtil.isExpired(token)) {

            System.out.println("token expired");
            filterChain.doFilter(request, response);

            //조건이 해당되면 메소드 종료 (필수)
            return;
        }

        //토큰에서 email 획득
        Member member = jwtUtil.getMember(token);

        //UserDetails에 회원 정보 객체 담기
        CustomUserDetails customOAuth2User =  new CustomUserDetails(member);

        //스프링 시큐리티 인증 토큰 생성
        Authentication authToken = new UsernamePasswordAuthenticationToken(customOAuth2User, null, customOAuth2User.getAuthorities());
        //세션에 사용자 등록
        SecurityContextHolder.getContext().setAuthentication(authToken);

        filterChain.doFilter(request, response);
    }
}

 

 

✅ doFilterInternal

if(cookies !=null ) {
    for (Cookie cookie : cookies) {

        System.out.println(cookie.getName());
        if (cookie.getName().equals("AccessToken")) {
            if ("AccessToken".equals(cookie.getName())) {
                authorization = cookie.getValue();
                break;
            }
        }
    }
}

 

쿠키에서 AccessToken이있을 경우 해당 토큰을 가져온다

 

if (authorization == null || authorization.isBlank()) {
    String bearerToken = request.getHeader("Authorization");
    if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
        authorization = bearerToken.substring(7);
    }
}

 

그다음에 헤더의 Authorzation에서 토크값을 가져온다

 

//토큰 소멸 시간 검증
if (jwtUtil.isExpired(token)) {

    System.out.println("token expired");
    filterChain.doFilter(request, response);

    //조건이 해당되면 메소드 종료 (필수)
    return;
}

 

JwtUtil에서 현재 시간하고 비교해서 소멸시간을 검증한다.

 

CustomUserDetails customOAuth2User =  new CustomUserDetails(member);

Authentication authToken = new UsernamePasswordAuthenticationToken(
    customOAuth2User, 
    null, 
    customOAuth2User.getAuthorities()
);

 

  • CustomUserDetails는 UserDetails를 구현한 커스텀 객체.
  • UsernamePasswordAuthenticationToken을 사용해 인증 객체 생성:
    • 첫 번째 인자: 사용자 정보
    • 두 번째 인자: 비밀번호 (우리는 JWT 기반이므로 null)
    • 세 번째 인자: 권한 리스트 (ROLE_...)
SecurityContextHolder.getContext().setAuthentication(authToken);

 

 

 

  • 생성한 인증 객체를 Spring Security의 SecurityContext에 저장.
  • 이후 컨트롤러나 서비스에서 @AuthenticationPrincipal 또는 SecurityContextHolder.getContext().getAuthentication()로 사용자 정보 접근 가능.