๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
๐Ÿ“• Spring Framework/Spring ๊ฐœ๋… ์ •๋ฆฌ

Spring Security [2] - ์˜ˆ์™ธ ์ฒ˜๋ฆฌ AuthenticationEntryPoint & AccessDeniedHandler

by GroovyArea 2022. 8. 21.
์„œํ๋ฆฌํ‹ฐ๋ฅผ ๋„์ž…ํ•˜๋ฉฐ ์ธ์ฆ, ์ธ๊ฐ€์˜ ๊ณผ์ •์„ ๋งˆ์ณค๋‹ค. 

์ด์ œ ์ธ์ฆ ๋ฐ ์ธ๊ฐ€ ์ž‘์—…์—์„œ ๋ฐœ์ƒํ•˜๋Š” ์˜ˆ์™ธ์— ๋Œ€ํ•ด์„œ ์ฒ˜๋ฆฌ๋ฅผ ํ•ด์ฃผ์–ด์•ผ ํ•˜๋Š”๋ฐ, ํ•œ ๊ฐ€์ง€ ์ƒ๊ฐํ•ด๋ด์•ผ ํ•˜๋Š” ๋ฌธ์ œ๊ฐ€ ์žˆ๋‹ค.

์Šคํ”„๋ง ์„œํ๋ฆฌํ‹ฐ๋Š” ํ•„ํ„ฐ์— ๊ธฐ๋ฐ˜ํ•œ ์ฒด์ด๋‹ ๊ตฌ์กฐ์ด๋ฏ€๋กœ, ์Šคํ”„๋ง ์ปจํ…Œ์ด๋„ˆ๊นŒ์ง€ ์š”์ฒญ์ด ๋„๋‹ฌํ•˜์ง€ ์•Š๋Š”๋‹ค.
๋”ฐ๋ผ์„œ, @ExceptionHandler๋ฅผ ํ†ตํ•œ ํŽธํ•œ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ๊ฐ€ ๋ถˆ๊ฐ€ํ•˜๋‹ค. ์ง์ ‘ Try - catch๋กœ ์žก์•„์„œ ์‘๋‹ต์„ ๋‚ด๋ ค์ฃผ๋Š” ๋ฐฉ๋ฒ• ๋ฐ–์—” ์—†๋‹ค. 

ํ•˜์ง€๋งŒ, ์Šคํ”„๋ง ์„œํ๋ฆฌํ‹ฐ๊ฐ€ ๊ทธ๋ ‡๊ฒŒ ํ—ˆ์ˆ ํ•˜์ง„ ์•Š๋‹ค. ์„œํ๋ฆฌํ‹ฐ ํ•„ํ„ฐ ์ฒด์ธ์˜ ๊ตฌ์กฐ๋ฅผ ๋ณด๋ฉด ๋งˆ์ง€๋ง‰ ์ฆˆ์Œ์— ์˜ˆ์™ธ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ํ•„ํ„ฐ๊ฐ€ ์žˆ๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค. 

์—ฌ๊ธฐ์„œ ์ฃผ๋กœ ์‚ฌ์šฉํ•˜๋Š” AuthenticationEntryPoint์™€ AccessDeniedHandler๋ฅผ ๊ตฌํ˜„ํ•ด ์ธ์ฆ ๋ฐ ์ธ๊ฐ€ ๊ณผ์ •์—์„œ ์ผ์–ด๋‚œ ์˜ˆ์™ธ์— ๋Œ€ํ•œ ์‘๋‹ต์„ ๋‚ด๋ ค์ค„ ์ˆ˜ ์žˆ๋‹ค.

ํ•„ํ„ฐ์—์„œ๋งŒ ๋™์ž‘ํ•˜๋Š”๊ฒŒ ์„œํ๋ฆฌํ‹ฐ์˜ ๊ตฌ์กฐ

 

์ธ์ฆ ๊ด€๋ จ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ AuthenticationEntryPoint

์ธ์ฆ ๊ด€๋ จ ์ž‘์—…์„ ํ•˜๋ฉฐ ์ˆ˜๋งŽ์€ ์˜ˆ์™ธ๋ฅผ ์ฒ˜๋ฆฌํ•˜๊ฑฐ๋‚˜ ๋˜์กŒ๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด,

  • UserDetails ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•˜๊ธฐ ์œ„ํ•ด DB์—์„œ find๋ฅผ ํ•  ๋•Œ์˜ ์˜ˆ์™ธ - RuntimeException ๋˜์ง
  • JWT ์ธ์ฆ ๊ณผ์ • ์ค‘ ํ† ํฐ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ์˜ ์ˆ˜๋งŽ์€ ์˜ˆ์™ธ - IllegalArgumentException ๋˜์ง

 

public boolean validateAccessToken(String token) {
    try {
        Jwts.parserBuilder().setSigningKey(secretKey.getBytes()).build().parseClaimsJws(token);
        return true;
    } catch (MalformedJwtException e) {
        throw new IllegalArgumentException(JwtErrorMessage.MALFORMED.getMessage());
    } catch (ExpiredJwtException e) {
        throw new IllegalArgumentException(JwtErrorMessage.EXPIRED.getMessage());
    } catch (UnsupportedJwtException e) {
        throw new IllegalArgumentException(JwtErrorMessage.UNSUPPORTED.getMessage());
    } catch (ClassCastException e) {
        throw new IllegalArgumentException(JwtErrorMessage.CLASS_CAST_FAIL.getMessage());
    } catch (SignatureException e) {
        throw new IllegalArgumentException(JwtErrorMessage.INVALID_SIGNATURE.getMessage());
    } catch (Exception e) {
        log.error("================================================ \n" +
                "JwtValidator - validateAccessToken() ์˜ค๋ฅ˜๋ฐœ์ƒ \n" +
                "token : " + token +
                "\n Exception Message : " + e.getMessage() + "\n " +
                "================================================");
        throw new IllegalArgumentException(e.getMessage());
    }
}

=> JWT ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ์—์„œ ์˜ˆ์™ธ ๋˜์ง€๊ธฐ

 

User dbUser = userRepository.findByLoginId(loginId).orElseThrow(() -> new UsernameNotFoundException(ResponseMessages.USER_NOT_EXISTS_MESSAGE.getMessage()));

=> UserDetails ๊ฐ์ฒด ์ƒ์„ฑ ๊ณผ์ • ์ค‘ ์ฝ”๋“œ -> ๋™์ผํ•˜๊ฒŒ ์˜ˆ์™ธ๋ฅผ ๋˜์ง„๋‹ค.

 

์ด๋Ÿฌํ•œ ์˜ˆ์™ธ๋ฅผ ์–ด๋””์„ ๊ฐ€ ๋ฐ›์•„์„œ ์‘๋‹ต์„ ๋‚ด๋ ค์ฃผ์–ด์•ผ ํ•œ๋‹ค.

์ด๋•Œ, AuthenticationEntryPoint ํด๋ž˜์Šค๋ฅผ ์ด์šฉํ•ด ์˜ˆ์™ธ ์ฒ˜๋ฆฌ๊ฐ€ ๊ฐ€๋Šฅํ•˜๋‹ค.

 

@Slf4j
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        String exceptionMessage = (String) request.getAttribute("exception");

        List<String> list = Arrays.stream(JwtErrorMessage.values())
                .map(JwtErrorMessage::getMessage)
                .collect(Collectors.toList());

        if (!list.contains(exceptionMessage)) {
            response.setContentType("application/json;charset=UTF-8");
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            response.getWriter().print(exceptionMessage);
        }

        if (exceptionMessage.equals(JwtErrorMessage.MALFORMED.getMessage())) {
            setResponse(response, JwtErrorMessage.MALFORMED);
        } else if (exceptionMessage.equals(JwtErrorMessage.CLASS_CAST_FAIL.getMessage())) {
            setResponse(response, JwtErrorMessage.CLASS_CAST_FAIL);
        } else if (exceptionMessage.equals(JwtErrorMessage.EXPIRED.getMessage())) {
            setResponse(response, JwtErrorMessage.EXPIRED);
        } else if (exceptionMessage.equals(JwtErrorMessage.INVALID_SIGNATURE.getMessage())) {
            setResponse(response, JwtErrorMessage.INVALID_SIGNATURE);
        } else {
            setResponse(response, JwtErrorMessage.UNSUPPORTED);
        }

    }

    private void setResponse(HttpServletResponse response, JwtErrorMessage jwtErrorMessage) throws IOException {
        response.setContentType("application/json;charset=UTF-8");
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        response.getWriter().print(jwtErrorMessage.getMessage());
    }
}

=> commence() ๋ฅผ ์˜ค๋ฒ„๋ผ์ด๋“œ ํ•ด ๊ตฌํ˜„ํ•˜๋ฉด ๋œ๋‹ค. ๋‚˜ ๊ฐ™์€ ๊ฒฝ์šฐ๋Š” JWT์— ๊ด€ํ•œ ์˜ˆ์™ธ ์ผ€์ด์Šค๊ฐ€ ๋งŽ๊ธฐ ๋•Œ๋ฌธ์— if๋ฌธ์œผ๋กœ ๋ถ„๊ธฐ ์ฒ˜๋ฆฌํ–ˆ๊ณ , ๋‚˜๋จธ์ง€ ์˜ˆ์™ธ์— ๊ด€ํ•ด์„œ๋Š” JWT ์˜ˆ์™ธ๋ฅผ ๋ชจ์•„๋‘” enum ํด๋ž˜์Šค์— ์—†๋Š” ๊ฐ์ฒด์ธ์ง€๋ฅผ ๊ฒ€์ฆํ•˜๊ณ  ๋”ฐ๋กœ response๋ฅผ ๋ฉ”์‹œ์ง€์™€ ํ•จ๊ป˜ ๋‚ ๋ ค์ฃผ๋Š” ํ˜•์‹์œผ๋กœ ๊ตฌํ˜„ํ–ˆ๋‹ค.

 

ํ† ํฐ ์œ ํšจ์‹œ๊ฐ„ ๋งŒ๋ฃŒ ์‹œ ์˜ˆ์™ธ ๋ฉ”์‹œ์ง€ ์‘๋‹ต
Repository ๋ฉ”์„œ๋“œ์˜ ์˜ˆ์™ธ ๋ฉ”์‹œ์ง€ ์‘๋‹ต

 

์ธ๊ฐ€ ๊ด€๋ จ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ AccessDeniedHandler

์ธ์ฆ ๊ด€๋ จ ์ž‘์—…์—์„œ ์„ฑ๊ณต์ ์œผ๋กœ ์˜ˆ์™ธ์— ๋Œ€ํ•œ ์ฒ˜๋ฆฌ ์‘๋‹ต์„ ๋งˆ์ณค๋‹ค.ํ•˜์ง€๋งŒ, ์ธ์ฆ์ด ์™„๋ฃŒ๋˜์–ด๋„ ์ธ๊ฐ€์—์„œ ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉฐ ๊ทธ ์ฆ‰์‹œ, ๊ถŒํ•œ์ด ์—†๋Š” ์‘๋‹ต๊ณผ ํ•จ๊ป˜ ์ ‘๊ทผ์„ ์ œํ•œํ•ด์•ผ ํ•œ๋‹ค. ์ด ๊ฒฝ์šฐ๋Š” AccessDeniedHandler ํด๋ž˜์Šค๋ฅผ ๊ตฌํ˜„ํ•˜์—ฌ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋‹ค.

 

https://sweeeetgoguma.tistory.com/entry/Spring-Security-1-JWT%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-REST-API-%EC%9D%B8%EC%A6%9D%EA%B3%BC-%EC%9D%B8%EA%B0%80

 

Spring Security [1] - JWT๋ฅผ ์ด์šฉํ•œ REST API ์ธ์ฆ๊ณผ ์ธ๊ฐ€

๊ธฐ์กด ์ธ์ฆ์€ JWT๋ฅผ ์ด์šฉํ•œ ํ•„ํ„ฐ๋กœ, ์ธ๊ฐ€๋Š” ์ธํ„ฐ์…‰ํ„ฐ๋กœ ์• ๋…ธํ…Œ์ด์…˜์„ ์ •์˜ํ•ด ์†์ˆ˜ ๊ตฌํ˜„ํ–ˆ์—ˆ๋‹ค. ์ด๋ฒˆ์— ๋ฆฌํŒฉํ„ฐ๋ง์„ ํ•˜๋ฉด์„œ, ์Šคํ”„๋ง์ด ์ œ๊ณตํ•˜๋Š” ๋ณด์•ˆ ๊ด€๋ จ ํ”„๋ ˆ์ž„์›Œํฌ์ธ ์„œํ๋ฆฌํ‹ฐ๋ฅผ ์‚ฌ์šฉํ•ด๋ณด์ž๋Š”

sweeeetgoguma.tistory.com

SecurityConfig์—์„œ ์„ค์ • ํ–ˆ๋“ฏ์ด, ๊ฐ url path๋งˆ๋‹ค role์„ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋‹ค. ์—ฌ๊ธฐ์„œ ๋ฐ˜ํ•˜๋Š” ๊ถŒํ•œ์˜ ์š”์ฒญ์ด ๋“ค์–ด์™”์„ ๋•Œ, ํ•ด๊ฒฐ ๊ฐ€๋Šฅํ•˜๋‹ค.

 

ublic class CustomAccessDeniedHandler implements AccessDeniedHandler {

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
        setResponse(response);
    }

    private void setResponse(HttpServletResponse response) throws IOException {
        response.setContentType("application/json;charset=UTF-8");
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        response.getWriter().print(SecurityMessage.PERMISSION_DENIED.getMessage());
    }
}

=> ๋น„๊ต์  ๊ฐ„๋‹จํ•˜๊ฒŒ ๊ถŒํ•œ์ด ์—†๋‹ค๋Š” ์‘๋‹ต ๋ฉ”์‹œ์ง€๋ฅผ ๋‚ด๋ ค์ฃผ๋Š” ๋ฐฉ์‹์œผ๋กœ ๊ตฌํ˜„ํ–ˆ๋‹ค.

 

์ด๋ ‡๊ฒŒ ์„œํ๋ฆฌํ‹ฐ๋ฅผ ์ด์šฉํ•ด ์ธ์ฆ, ์ธ๊ฐ€๋ฅผ ๊ตฌํ˜„ํ–ˆ๋‹ค. ์ข€ ์˜ค๋ž˜ ๊ฑธ๋ ธ๊ณ , ์ œ๋Œ€๋กœ ์•Œ๊ณ  ํ•˜๋ ค๋‹ค ๋ณด๋‹ˆ ์‹œ๊ฐ„๋„ ์˜ค๋ž˜ ๊ฑธ๋ ธ๋‹ค. ํ•˜์ง€๋งŒ ์ข‹์€ ์ž์–‘๋ถ„์ด ๋˜์—ˆ์Œ์ด ๋ถ„๋ช…ํ•˜๋‹ค๊ณ  ์ƒ๊ฐ์ด ๋œ๋‹ค.์ด์ œ ๋ณธ๊ฒฉ์ ์œผ๋กœ ์„œ๋น„์Šค ๋กœ์ง์„ ๋‹ค๋ฃจ๋ฉด์„œ ๋ฆฌํŒฉํ† ๋ง์„ ์ง„ํ–‰ํ•œ๋‹ค. ํŒŒ์ดํŒ…~

 

https://github.com/GroovyArea/My-ChickenBreast-Shop

 

GitHub - GroovyArea/My-ChickenBreast-Shop: shop api with spring boot

shop api with spring boot . Contribute to GroovyArea/My-ChickenBreast-Shop development by creating an account on GitHub.

github.com

 

๋ฐ˜์‘ํ˜•