티스토리 뷰

제목의 의미

만약 토큰방식으로 로그인을 구현한 적이 있다면, 아래 코드를 이해하는데 어렵지 않을 것이다. 아래 코드는 로그인을 해야지 접근할 수 있는 url에 요청을 보냈을 때 토큰이 유효하는지 확인하는 filter다. (아래코드에서 토큰의 유효한지는 null인지만 확인하고 있다)

 

@RequiredArgsConstructor
@Slf4j
public class JwtAuthenticationFilter extends OncePerRequestFilter {

    private final JwtTokenProvider jwtTokenProvider;
    private final CustomUserDetailsService customUserDetailsService;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String accessToken = jwtTokenProvider.resolveAccessToken(request);

        if (accessToken != null) setAuthentication(accessToken);
        filterChain.doFilter(request, response);
    }

    private void setAuthentication(String accessToken) {
        UserDetails userDetails = customUserDetailsService.loadUserByUsername(jwtTokenProvider.getUserEmail(accessToken));
        UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, "", userDetails.getAuthorities());
        SecurityContextHolder.getContext().setAuthentication(authentication);
    }
}

 

하지만 회원가입과 로그인을 할 때도 위 filter가 수행된다. 분명히 위 문장에서는 "로그인을 해야지 접근이 가능한 url에 요청을 한 경우" 에만 filter가 수행된다고 했는데, 로그인을 하지 않아도 되는 회원가입, 로그인 접근에서도 동작한다는 뜻이다.

즉, Spring Security가 관여하지 않아도 되는 기능 (방금 말했던 회원가입과 로그인)도 Spring Security가 동작한다는 말이다. 

 

 

permitAll() 

아래코드의 의미는 회원가입 또는 로그인 url로 접근하는 모든 사용자에게 접근이 가능하도록 하는 코드다. 

 

.antMatchers("회원가입 url", "로그인 url").permitAll()

 

하지만 위 코드를 적용해도 토큰의 유효성 검사 필터는 동작한다. 왜 때문인지는 모르겠다. 하지만 예측을 해보자면 Spring Security를 활성화하는 것과 연관이 있는 것 같다. 

 

 

위 사진은 HTTP 요청에 대한 일반적인 보안 처리 흐름이다. 대충보자. 클라이언트가 요청을 보내면 Servlet에 오기 전에 여러 filter들을 만난다. 여기서 Spring Security를 추가하게 되면 아래와 같이 여러 인증 필터들이 추가된다. 

 

 

그럼 클라이언트에서 요청이 올 때마다 시큐리티 필터들을 거치게 되는 것이다. 그리고 토큰의 유효성을 검사하는 필터(AuthenticationFilter)를 만들어서 시큐리티 필터들 중 가장 먼저 거치는 UsernamePasswordAuthenticationFilter 앞에 놓았다. 

 

.addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider, customUserDetailsService),
                        UsernamePasswordAuthenticationFilter.class);

 

앞에 놓는 이유는 로그인 이후 요청이 들어왔을 때 요청에서 토큰이 있는지 판단하고 토큰이 유효하다면 뒤에 있는 시큐리티 필터를 건너뛰고 바로 컨트롤러로 넘어가게 된다. 즉, 토큰으로 로그인이 되어있는지 판단함으로써 로그인 유지가 가능한 것이다. 이렇게 맨 앞에서 요청을 가로채니까 회원가입을 할 때도 JwtAhenticationFilter가 작동되는 것 같다. 

 

결론은 permitAll()은 필터를 무시해주는 것이 아니고 그냥 모든 사용자가 접근이 가능하다는 것만 적용시키는 것이다. 

 

 

문제

그럼 다시 문제상황으로 돌아와서 회원가입할 때 AuthenticationFilter가 돌면 어떤 일이 벌어질까 ? 먼저 resolveAccessToken() 메서드를 호출한다. 

 

String accessToken = jwtTokenProvider.resolveAccessToken(request);

 

이 메서드는 요청에 쿠키가 있다면 쿠키 값을 반환하고, 쿠키가 없다면 예외를 던져주는 메서드다. 

 

public String resolveAccessToken(HttpServletRequest request) {
    Cookie[] cookies = request.getCookies();

    if(cookies != null) {
        for(Cookie c : cookies) {
            if(c.getName().equals(HEADER_ACCESS_TOKEN)) {
                return c.getValue();
            }
        }
    }
    else {
        throw new IllegalArgumentException("쿠키가 존재하지 않습니다.");
    }
    return null;
}

 

회원가입할 때는 당연히 쿠키가 없다. 로그인을 할 때도 당연히 쿠키가 없다. 로그인을 해야지 쿠키가 발급되기 때문이다. 그리고 댓글등록은 로그인을 해야지 사용할 수 있는 요청이면 요청에 쿠키가 담겨있어야 한다. 하지만 회원가입할 때 위 메서드가 돌아가니 쿠키가 없다고 판단하여 예외를 던진다. 그래서 제목과 같이 회원가입 요청과 로그인 요청은 Spring Security Filter들을 무시하고 바로 컨트롤러에 요청이 오게 만들어야 한다. 

 

 

해결

이 문제를 해결하는 방법은 회원가입 요청과 로그인 요청은 Spring Security Filter를 거치지 않고 바로 컨트롤러로 넘어가면 된다. 한마디로 필터 적용에서 제외시키면 된다. 아래 사진은 스프링에서 지원하는 antMatchers에 명시된 url을 필터 적용에서 제외시키는 인터페이스다. 

 

 

코드로 구현해보자. 

 

@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
    return (web) -> web.ignoring()
            .antMatchers("회원가입url", "로그인url");
}

 

 

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday