스프링 시큐리티 기본 설정(JwtExceptionFilter, JwtAuthenticationFilter) - 시큐리티 4편

JaeYeop·2022년 2월 12일
0

전체적인 흐름도는 이전 글에서 꼭 확인

https://velog.io/@jjy5349/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%8B%9C%ED%81%90%EB%A6%AC%ED%8B%B0-%EA%B8%B0%EB%B3%B8-%EC%84%A4%EC%A0%95SecurityConfig.class-JwtTokenProvider.class-%EC%8B%9C%ED%81%90%EB%A6%AC%ED%8B%B0-3%ED%8E%B8

JwtExceptionFilter

@Slf4j
@RequiredArgsConstructor
public class JwtExceptionFilter extends OncePerRequestFilter {

    private final ObjectMapper objectMapper;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        response.setCharacterEncoding("utf-8");

        try{
            filterChain.doFilter(request, response);
        } catch (IncorrectDeleteUserRequestException e){
            Map<String, String> map = new HashMap<>();

            map.put("errortype", "Forbidden");
            map.put("code", "402");
            map.put("message", "잘못된 토큰 또는 이미 삭제된 회원입니다.");

            log.error("삭제된 유저의 토큰으로 접근");
            response.getWriter().write(objectMapper.writeValueAsString(map));
        }
    }
}

JwtExceptionFilter는 우리가 '인증' 단계에서 throw new Exception()을 하게되면 다시 흐름도에 있는 체인들을 역행하며 타게된다.

이때 각 예외별로 예외처리를 하면 된다. 나 같은 경우에는 IncorrectDeleteUserRequestException라는 예외를 직접 커스텀하여 만들고 사용했다(커스텀 예외처리는 이 글에서 다루지는 않겠다).

부가설명

내가 지금까지 프로젝트를 진행하면서 JwtExceptionFilter에 예외를 하나만 등록 해놓은 이유는 후에 JwtAuthenticationFilter에서 JwtProvider.validate 메소드를 불러올텐데 여기서 예외를 던저버리면 전체 흐름도에서 나타난 CustomEntryPoint를 타지 못하고 다시 필터를 역행버린다. 그렇기 때문에 이 부분은 개발자의 커스터마이징이 필요한 부분이다.

나 같은 경우에는 IncorrectDeleteUserRequestException라는 예외를 통해서 이미 삭제된 유저를 다시 삭제요청 했을 때 처리를 JwtExceptionFilter통해서 해야했기 때문에 등록 해놓았다.

JwtAuthenticationFilter

@RequiredArgsConstructor
public class JwtAuthenticationFilter extends GenericFilterBean {

    private final JwtTokenProvider jwtTokenProvider;

    /**
     * methodName : doFilter
     * author : Jaeyeop Jung
     * description : AccessToken을 검증하고, 해당 유저를 찾아 권한처리를 위해 Context에 담는다.
     *
     * @param request  the request
     * @param response the response
     * @param chain    the chain
     * @throws IOException      the io exception
     * @throws ServletException the servlet exception
     */
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;

        String token = null;
        if(httpRequest.getHeader("Authorization") != null && httpRequest.getHeader("Authorization").startsWith("Bearer ")){
            token = httpRequest.getHeader("Authorization").split(" ")[1];
        }

        if( token != null && jwtTokenProvider.validateToken(token) ) {
            Authentication authentication = jwtTokenProvider.getAuthentication(token);
            SecurityContextHolder.getContext().setAuthentication(authentication);
        }

        chain.doFilter(httpRequest, httpResponse);
    }
}

자 이제 중요한 JwtAuthenticationFilter이다.

이 필터에서는 HTTP Request에 담긴 AccessToken을 검증하고 검증이 완료되면

        if( token != null && jwtTokenProvider.validateToken(token) ) {
            Authentication authentication = jwtTokenProvider.getAuthentication(token);
            SecurityContextHolder.getContext().setAuthentication(authentication);
        }

이 코드에서 SecurityContextHolder에 담았다.

만약 담기지않고 chain.doFilter를 통해서 다음 필터로 넘어간다면 CustomEntryPoint로 넘어가 버릴 것이다.

CustomEntryPoint

@Component
@RequiredArgsConstructor
@Slf4j
public class CustomEntryPoint implements AuthenticationEntryPoint {

    private final ObjectMapper objectMapper;

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        response.setCharacterEncoding("utf-8");
        log.warn("CustomEntryPoint : 잘못된 토큰으로 페이지 요청");

        response.setStatus(HttpServletResponse.SC_FORBIDDEN);

        Map<String, String> map = new HashMap<>();
        map.put("errortype", "Forbidden");
        map.put("code", "403");
        map.put("message", "잘못된 토큰으로 접근하였습니다. 다시 로그인 해주세요");

        response.getWriter().write(objectMapper.writeValueAsString(map));
    }
}

다음은 CustomEntryPoint이다. 여기는 우리가 잘못된 토큰으로 접근했다는 사실을 알리며 HTTP Response에 담길 내용을 작성하면 된다.

CustomAccessDeniedHandler

@Component
@RequiredArgsConstructor
@Slf4j
public class CustomAccessDeniedHandler implements AccessDeniedHandler {

    private final ObjectMapper objectMapper;

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
        response.setCharacterEncoding("utf-8");
        log.warn("CustomAccessDeniedHandler : User가 ADMIN 권한에 접근 시도");

        response.setStatus(HttpServletResponse.SC_FORBIDDEN);

        Map<String, String> map = new HashMap<>();
        map.put("errortype", "Forbidden");
        map.put("code", "403");
        map.put("message", "허용하지 않는 권한에 접근하였습니다");

        response.getWriter().write(objectMapper.writeValueAsString(map));
    }
}

CustomAccessDeniedHandler에서는 다른 권한에 접근했다는 내용을 담은 HTTP Response를 작성하면된다.

다음 글에서

내용이 더 길어지기 전에 잠시 쉬었다 가야겠다

profile
이게 왜 틀리지... (나의 메모용 블로그)

0개의 댓글