본문 바로가기

프로젝트/[더공] 구현기능

3. JwtVerificationFilter

 

JwtVerificationFilter 의 역할

JwtVerificationFilter 클래스는 JWT 토큰의 유효성을 검사하고, 검증된 토큰에서 claim 정보를 추출해 사용자 인증 및 권한정보를 SecurityContextHolder에 저장하는 역할을 한다.

 

 

JwtVerificationFilter(토큰 유효성 검증 로직) 에 대한 설명

 

커스텀필터에서 JwtAuthenticationFilter 다음으로 JwtVerificationFilter가 실행되도록 했다. JwtVerificationFilter는  OncePerRequestFilter를 상속하는데, 이런 경우 Spring Security는 JwtVerificationFilter를 실행하기 전, suouldNotFilter 메소드를 먼저 호출하여 JwtVerificationFilter의 실행 여부를 결정한다. 즉, 로그인 요청시(= 토큰의 유효성 검증이 필요없는 요청)  JwtVerificationFilter는 동작하지 않고 다음 필터로 건너 뛰게 된다. 한마디로 JwtVerificationFilter는 로그인 인증에 성공한 사용자들의 요청에서 주어진 JWT 토큰이 유효한지 검증하는 역할을 한다.

 

@RequiredArgsConstructor
@Slf4j
public class JwtVerificationFilter extends OncePerRequestFilter {
    private final JwtTokenizer jwtTokenizer;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
                                    FilterChain filterChain) throws ServletException, IOException {
        try {
            Map<String, Object> claims = verifyJws(request);
            setAuthenticationToContext(claims);
        }
        catch (SignatureException se) {
            request.setAttribute("exception", se);
        }
        catch (ExpiredJwtException ee) {
            request.setAttribute("exception", ee);
        }
        catch (Exception e) {
            request.setAttribute("exception", e);
        }
        filterChain.doFilter(request,response);
    }


    private Map<String, Object> verifyJws(HttpServletRequest request) { // 요청헤더 토큰추출
        String jws = request.getHeader("Authorization").replace("Bearer ","");
        String base64EncodedSecretKey = jwtTokenizer.encodedBase64SecretKey(jwtTokenizer.getSecretKey());
        Map<String, Object> claims = jwtTokenizer.getClaims(jws, base64EncodedSecretKey).getBody();
        return claims;
    }


    private void setAuthenticationToContext(Map<String, Object> claims) { // 사용자 인증•권한정보 생성해서 SecurityContextHolder 에 저장
        Map<String, Object> principal = new HashMap<>();
        //log.info("memberId : "+claims.get("memberId"));
        //log.info("username : "+claims.get("username"));
        //log.info("isAdmin : "+claims.get("isAdmin"));

        principal.put("memberId", claims.get("memberId"));
        principal.put("username", claims.get("username"));
        principal.put("isAdmin", claims.get("isAdmin")); //추가 2

        Authentication authentication = new UsernamePasswordAuthenticationToken(principal, null);
        SecurityContextHolder.getContext().setAuthentication(authentication);
    }


    @Override
    protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException { // 토큰이 유효한헤더를 포함하는지 확인
        String authorization = request.getHeader("Authorization");
        return authorization == null || !authorization.startsWith("Bearer");
    }
}

 

❶ shouldNotFilter

JwtVerificationFilter는  OncePerRequestFilter를 상속한다. 이런 경우 Spring Security는 JwtVerificationFilter를 실행하기 전, suouldNotFilter 메소드를 먼저 호출하여 JwtVerificationFilter의 실행 여부를 결정하기때문에 shouldNotFilter 가 먼저 호출된다.

👉 JwtVerificationFilter는 로그인 인증에 성공한 사용자들의 요청에서 주어진 JWT 토큰이 유효한지 검증하기 때문!

 

 

❷ doFilterInternal 

OncePerRequestFilter를 상속한 필터의 핵심 메소드.

- verifyJws로 요청헤더에서 토큰을 추출하고 유효성을 검사한 뒤 유효한 경우 클레임정보를 반환한다. 

- 또한 JWT토큰 서명이 올바르지 않을때, JWT토큰이 만료된 경우, 그 외 다른 예외에 대한 처리를 담당한다.

- 그리고 setAuthenticationToContext 로 클레임 기반으로 생성한 인증 및 권한정보를 SecurityContextHolder에 저장한다.

SignatureException (JWT 토큰 서명이 올바르지 않을 때 발생, 토큰의 무결성을 검사하는데 사용됨)
ExpiredJwtException(JWT토큰이 만료된 경우 발생, 만료토큰 처리시 사용됨)

 

 

 

verifyJws  
요청헤더에서 JWT토큰을 추출하고 해당 토큰의 유효성 검사.
Authorization 헤더에서 Brearer 접두사를 제거하여 JWT 토큰 문자열을 가져온 뒤,
JwtTokenizer를 사용해 토큰을 검증하고 유효한 경우 클레임 정보를 반환한다.

 

 

setAuthenticationToContext()
클레임 정보를 기반으로 사용자의 인증 및 권한 정보를 생성하고 SecurityContextHolder에 저장한다.

이 과정을 통해 해당 사용자의 인증정보를 나중에 사용할 수 있게 된다.

 

 


질문노트

🆀 SecurityContextHolder 란?

Spring Security에서 현재 사용자의 보안 컨텍스트를 저장하고 관리하는 클래스. 이 컨텍스트는 현재 인증된 사용자의 정보 및 권한과 관련된 데이터를 포함하며 웹응용 프로그램에서 사용자 인증 및 권한관리를 위한 주요 도구 중 하나다. SecurityContextHolder를 사용하면 현재 사용자에 대한 보안정보에 쉽게 접근하고 변경할 수 있으며, Spring Security를 통해 인증 및 권한관리를 수행할 때 중요한 역할을 한다. 보안컨텍스트는 요청을 처리하는 동안 사용자의 인증상태 및 권한을 추적하는데 사용되며, 이를 통해 인증된 사용자에게 특정 리소스 또는 기능에 접근권한을 부여하거나 거부할 수 있다.

SecurityContextHolder는 주로 두가지 타입의 보안 컨텍스트를 관리한다.

 

1 -- Authentication

현재 사용자의 인증정보를 저장한다. 이 정보에는 사용자ID, 인증상태, 권한목록 등이 포함된다.
대표적으로 UsernamePaswordAuthenticationToken이나
JwtAuthenticationToken과 같은 클래스를 사용해 사용자의 인증정보를 나타낸다.

2 -- SecurityContext
Authentication 객체를 포함하는 보안컨텍스트.
현재 요청 또는 스레드에 대한 보안 정보를 제공하며,
Authentication객체를 추출하고 설정하는 메서드를 제공한다.

 

 

 


추가 1

참고블로그 : https://ohtaeg.tistory.com/8

아래 이미지와 글은 위의 참고블로그의 내용을 학습목적으로 그대로 작성하였음을 밝힙니다.

인증된 사용자 정보인 Principal을 Authentication에서 관리하고

Authentication을 SecurityContext가 관리하고,

SecurityContext는 SecurityContextHolder가 관리한다.

→ 즉, SecurityContextHolder란 Authentication을 담고 있는 Holder

 

 

👉 ThreadLocal(한 쓰레드 내에서 사용하는 공용저장소) 을 사용해

     Authentication을 한 쓰레드 내에서 공유할 수 있다.

     물론 쓰레드가 달라지면 제대로 된 인증정보를 가져올 수 없다. 

 


추가 2 ⭐️⭐️⭐️

참고블로그 : https://wildeveloperetrain.tistory.com/163

아래 글은 위의 참고블로그의 내용을 학습목적으로 그대로 작성하였음을 밝힙니다.

 

Authentication은 인증정보와 권한을 담는 인터페이스로, SpringSecurity에서는 인증시 Id와 Password를 이용한 credectial 기반의 인증을 사용하며, 인증 후 최종 인증결과를 담아 Security Context에 보관한다. 이를 통해 필요할 때 전역적으로 참조가 가능하다.

 

SecurityContext는 Authentication객체가 보관되는 저장소 역할을 한다. 필요시 Security Context로부터 Authentication 객체를 꺼내 사용할 수 있다. 각 스레드마다 할당되는 고유공간인 ThreadLocal에 저장되기 때문에 동일한 스레드인 경우 필요한 아무곳에서나 참조가 가능하다. 다시 말해, 서버에 접속해서 생성되는 각 Thread는 각각의 ThreadLocal에 SecurityContext를 가지고 있는 것이다.

(SecurityContext는 ThreadLocal에 저장되어 있으면서 동시에 HttpSession에도 저장되어 있다.)

 

SecurityContextHolder 는 SecurityContext객체를 보관하고 있는 wrapper클래스로, ThreadLocal의 전략을 설정할 수 있다.

 


고민노트

🆀 ThreadLocal의 전략을 설정하는 이유가 뭘까? 스레드라는 개념이 아직 와닿지 않는다. 
🅰 나의 결론 : 
우선 내가 작성한 코드에 대한 포스팅과 진행중인 CS 공부를 끝낸 뒤 다시 찬찬히 알아보자.