Spring Security ์ธ์ฆ ํ•„ํ„ฐ ์˜ˆ์™ธ ์ฒ˜๋ฆฌํ•˜๊ธฐ

2024. 11. 27. 04:35ยทํ”„๋กœ์ ํŠธ/NolGoat

์ธ์ฆ ํ•„ํ„ฐ์—์„œ ๋ฐœ์ƒํ•œ ์˜ˆ์™ธ์˜ ๊ณตํ†ต ์ฒ˜๋ฆฌ

โš ๏ธ๋ฌธ์ œ

์ธ์ฆ ํ•„ํ„ฐ๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ Spring Security ํ•„ํ„ฐ๋กœ์„œ ๋™์ž‘ํ•˜๋ฉฐ, `DispatcherServlet` ์ด์ „์— ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. ์ด๋กœ ์ธํ•ด `@RestControllerAdvice`์˜ `@ExceptionHandler`๋ฅผ ํ†ตํ•œ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ๋ฐฉ์‹์ด ์ ์šฉ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
(@ExceptionHandler๋Š” DispatcherServlet ๋‚ด์—์„œ ๋ฐœ์ƒํ•œ ์˜ˆ์™ธ๋งŒ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.)

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(ApiException.class)
    public ResponseEntity<ErrorResponse> handleApiException(ApiException e) {
        ErrorCode errorCode = e.getErrorCode();

        return ResponseEntity
                .status(errorCode.getHttpStatus())
                .body(new ErrorResponse(errorCode));
    }
}

๋”ฐ๋ผ์„œ ์ด ๋ฐฉ์‹์œผ๋กœ๋Š” ์ธ์ฆ ํ•„ํ„ฐ์—์„œ ๋ฐœ์ƒํ•œ ์˜ˆ์™ธ๋ฅผ ์›ํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ๊ณตํ†ต ์‘๋‹ต ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.

 

โœ…ํ•ด๊ฒฐ

Spring Security๊ฐ€ ์ œ๊ณตํ•˜๋Š” `AuthenticationEntryPoint`๋ฅผ ๋„์ž…ํ•˜์—ฌ ์ธ์ฆ ๊ณผ์ •์—์„œ ๋ฐœ์ƒํ•˜๋Š” ์˜ˆ์™ธ๋ฅผ ๋ณ„๋„๋กœ ์ฒ˜๋ฆฌํ•˜๋„๋ก ๊ตฌ์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ์ธ์ฆ ํ•„ํ„ฐ์—์„œ ๋ฐœ์ƒํ•˜๋Š” ์˜ˆ์™ธ๋Š” AuthenticationEntryPoint์—์„œ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

  • AuthenticationEntryPointImpl
@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
        response.setStatus(HttpStatus.UNAUTHORIZED.value());
        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().write("{\"error\": \"AUTHENTICATION_FAIL\", \"message\": \"์ž˜๋ชป๋œ ์ธ์ฆ ์ •๋ณด์ž…๋‹ˆ๋‹ค.\"}");
    }
}

 

  • SecurityConfig
private final AuthenticationEntryPointImpl authenticationEntryPoint;

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    ...
    http.exceptionHandling(exceptions -> exceptions
            .authenticationEntryPoint(authenticationEntryPoint)
    );
    ...
    return http.build();
}

 

์ธ์ฆ ์˜ˆ์™ธ์˜ ์„ธ๋ถ€ ์ •๋ณด ์ฒ˜๋ฆฌ

โš ๏ธ๋ฌธ์ œ 

Spring Security๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ์ธ์ฆ ๊ด€๋ จ ์˜ˆ์™ธ๋ฅผ `AuthenticationException`์œผ๋กœ ๊ฐ์‹ธ์„œ AuthenticationEntryPoint๋กœ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค. ์ด๋กœ ์ธํ•ด, ๋„˜์–ด์˜จ ์˜ˆ์™ธ์˜ ์„ธ๋ถ€ ์ •๋ณด๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์—†๊ณ , ์˜ˆ์™ธ ์ข…๋ฅ˜์— ๋”ฐ๋ฅธ ์ ์ ˆํ•œ ์‘๋‹ต์„ ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.

@Slf4j
@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        log.info(authException.toString()); // InsufficientAuthenticationException
        
        response.setStatus(HttpStatus.UNAUTHORIZED.value());
        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().write("{\"error\": \"AUTHENTICATION_FAIL\", \"message\": \"์ž˜๋ชป๋œ ์ธ์ฆ ์ •๋ณด์ž…๋‹ˆ๋‹ค.\"}");
    }
}
org.springframework.security.authentication.InsufficientAuthenticationException: Full authentication is required to access this resource

 

โœ…ํ•ด๊ฒฐ 

์ธ์ฆ ํ•„ํ„ฐ์—์„œ ๋ฐœ์ƒํ•œ ์˜ˆ์™ธ๋ฅผ HttpServletRequest์˜ ์†์„ฑ์— ์ €์žฅํ•œ ๋’ค, AuthenticationEntryPoint์—์„œ ์ด๋ฅผ ์กฐํšŒํ•˜์—ฌ ์‘๋‹ตํ•  ์ˆ˜ ์žˆ๋„๋ก ํ–ˆ์Šต๋‹ˆ๋‹ค.

  • ์ธ์ฆ ํ•„ํ„ฐ์—์„œ ์˜ˆ์™ธ ์ €์žฅ
@RequiredArgsConstructor
public class AuthFilter extends OncePerRequestFilter {

    private final AuthService authService;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        try {
            String authorizationHeader = request.getHeader(AUTHORIZATION_HEADER);
            authService.verifyAuthorizationHeader(authorizationHeader);
            String accessToken = authorizationHeader.split(" ")[1];
            authService.verifyAccessToken(accessToken);
        } catch (ApiException e) {
            request.setAttribute("exception", e);
            filterChain.doFilter(request, response);
            return;
        }
        filterChain.doFilter(request, response);
    }
}

 

  • ์ €์žฅํ•œ ์˜ˆ์™ธ๋ฅผ AuthenticationEntryPoint์—์„œ ์กฐํšŒ ๋ฐ ์‘๋‹ต
@RequiredArgsConstructor
@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {

    private final ObjectMapper objectMapper;

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        Object exception = request.getAttribute("exception");
        response.setStatus(HttpStatus.UNAUTHORIZED.value());
        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().write(objectMapper.writeValueAsString(new ErrorResponse(((ApiException) exception).getErrorCode())));
    }
}

 

์ฐธ๊ณ ) AuthenticationException

AuthenticationException, ์ฆ‰ ์ธ์ฆ ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ๋‹ค์Œ ์„ธ ๊ณผ์ •์ด ์ง„ํ–‰๋ฉ๋‹ˆ๋‹ค.

  1. SecurityContext์—์„œ ์ธ์ฆ ์ •๋ณด ์‚ญ์ œ
    • ๊ธฐ์กด ์ธ์ฆ ์ •๋ณด๊ฐ€ ๋” ์ด์ƒ ์œ ํšจํ•˜์ง€ ์•Š๋‹ค๊ณ  ํŒ๋‹จํ•˜์—ฌ Authentication์„ ์ดˆ๊ธฐํ™”ํ•ฉ๋‹ˆ๋‹ค.
  2. AuthenticationEntryPoint ํ˜ธ์ถœ
    • AuthenticationException์ด ๋ฐœ์ƒํ•˜๋ฉด ํ•„ํ„ฐ๋Š” AuthenticationEntryPoint๋ฅผ ์‹คํ–‰ํ•˜์—ฌ ์ธ์ฆ ์‹คํŒจ๋ฅผ ๊ณตํ†ต์œผ๋กœ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
  3. ์ธ์ฆ ํ”„๋กœ์„ธ์Šค์˜ ์š”์ฒญ ์ •๋ณด ์ €์žฅ ๋ฐ ๊ฒ€์ƒ‰
    • ๊ธฐ๋ณธ ๊ตฌํ˜„์ฒด๋Š” HttpSessionRequestCache์ž…๋‹ˆ๋‹ค.
    • ex) ํŽ˜์ด์ง€ A์— ์ ‘๊ทผํ•˜๋ ค๋‹ค ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋กœ ๋ฆฌ๋‹ค์ด๋ ‰์…˜๋œ ๊ฒฝ์šฐ, ์ธ์ฆ์„ ์™„๋ฃŒํ•œ ํ›„, ๋‹ค์‹œ ํŽ˜์ด์ง€ A๋กœ ์ด๋™ํ•  ์ˆ˜ ์žˆ๋„๋ก ์ •๋ณด๋ฅผ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.

 

ํ† ํฐ ๊ฒ€์ฆ ๊ณผ์ •์—์„œ ๋ฐœ์ƒํ•˜๋Š” JwtException ์ฒ˜๋ฆฌ

โš ๏ธ๋ฌธ์ œ  

ํ† ํฐ ๋งŒ๋ฃŒ ์—ฌ๋ถ€๋ฅผ ํ™•์ธํ•˜๋Š” ์ฝ”๋“œ์—์„œ๋Š” ์ปค์Šคํ…€ ์˜ˆ์™ธ(ApiException)๋ฅผ ๋ฐœ์ƒ์‹œํ‚ค๋„๋ก ์„ค๊ณ„ํ–ˆ์ง€๋งŒ, JWT ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์˜ ๋‚ด๋ถ€ ๋กœ์ง์—์„œ ๋จผ์ € `ExpiredJwtException`์ด ๋ฐœ์ƒํ•˜์—ฌ ์˜๋„ํ•œ ๋Œ€๋กœ ์ฒ˜๋ฆฌ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. ์ด๋Š” `parseSignedClaims()` ํ˜ธ์ถœ ๊ณผ์ •์—์„œ ์„œ๋ช… ๊ฒ€์ฆ๊ณผ ๋™์‹œ์— ํ† ํฐ ๋งŒ๋ฃŒ ์—ฌ๋ถ€๋ฅผ ๊ฒ€์ฆํ•˜๊ธฐ ๋•Œ๋ฌธ์ด์—ˆ์Šต๋‹ˆ๋‹ค.

  • JwtProvider
public Boolean ieExpired(String token) {
    return Jwts.parser()
        .verifyWith(secretKey)
        .build()
        .parseSignedClaims(token) // ์—ฌ๊ธฐ์„œ JwtException์ด ๋ฐœ์ƒ
        .getPayload()
        .getExpiration()
        .before(new Date());
}

 

  • ํ† ํฐ ๋งŒ๋ฃŒ ๊ฒ€์ฆ ๋กœ์ง
String accessToken = authorization.split(" ")[1];
if (jwtProvider.ieExpired(accessToken)) {
    throw new ApiException(TOKEN_EXPIRED);
}

 

โœ…ํ•ด๊ฒฐ  

ํ† ํฐ ๋งŒ๋ฃŒ ๊ฒ€์ฆ์„ ์ง์ ‘ ํ•˜์ง€ ์•Š๊ณ , JWT ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์˜ ๊ธฐ๋ณธ ๊ฒ€์ฆ ๋กœ์ง์„ ํ™œ์šฉํ•˜์—ฌ ์˜ˆ์™ธ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ค๋„๋ก ์ฝ”๋“œ๋ฅผ ๋‹จ์ˆœํ™”ํ–ˆ์Šต๋‹ˆ๋‹ค. ์ดํ›„, JwtException๊ณผ ApiException์„ ๊ตฌ๋ถ„ํ•˜์—ฌ ์ฒ˜๋ฆฌํ•˜๋„๋ก ์ฝ”๋“œ๋ฅผ ์ˆ˜์ •ํ–ˆ์Šต๋‹ˆ๋‹ค.

  • ํ† ํฐ์˜ ํ˜•์‹, ๋งŒ๋ฃŒ์—ฌ๋ถ€ ๋“ฑ๊ณผ ๊ด€๋ จ๋œ JwtException์€ ์ง์ ‘ ์ฒ˜๋ฆฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
public void verifyAccessToken(String accessToken) {
    Claims payload = getPayload(accessToken);
    String type = payload.get(CLAIM_TYPE, String.class);
    if (!type.equals(CLAIM_TYPE_ACCESS)) {
        throw new ApiException(INVALID_TOKEN_TYPE);
    }
}
    
private Claims getPayload(String token) {
    return Jwts.parser()
            .verifyWith(secretKey)
            .build()
            .parseSignedClaims(token)
            .getPayload();
}

 

  • ์ฒ˜๋ฆฌ ๋Œ€์ƒ ์˜ˆ์™ธ์— JwtException๋„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.
// ํ—ค๋”, ํ† ํฐ ๊ฒ€์ฆ
String accessToken;
try {
    String authorizationHeader = request.getHeader(AUTHORIZATION_HEADER);
    authService.verifyAuthorizationHeader(authorizationHeader);
    accessToken = authorizationHeader.split(" ")[1];
    authService.verifyAccessToken(accessToken);
} catch (JwtException | ApiException e) {
    request.setAttribute("exception", e);
    filterChain.doFilter(request, response);
    return;
}

 

  • ์˜ˆ์™ธ์˜ ์ข…๋ฅ˜์— ๋”ฐ๋ผ ๋ถ„๊ธฐํ•˜์—ฌ ๊ฐ๊ฐ์— ๋งž๋Š” ์ ์ ˆํ•œ ์‘๋‹ต์„ ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ–ˆ์Šต๋‹ˆ๋‹ค.
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
    Object exception = request.getAttribute("exception");
    if (exception instanceof ExpiredJwtException) {
        response.setStatus(HttpStatus.UNAUTHORIZED.value());
        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().write(objectMapper.writeValueAsString(new ErrorResponse(TOKEN_EXPIRED)));
        return;
    }
    // ExpiredJwtException์„ ์ œ์™ธํ•œ ๋‚˜๋จธ์ง€ JwtException ์ฒ˜๋ฆฌ
    if (exception instanceof JwtException) {
        response.setStatus(HttpStatus.UNAUTHORIZED.value());
        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().write(objectMapper.writeValueAsString(new ErrorResponse(INVALID_TOKEN_FORMAT)));
        return;
    }
    if (exception instanceof ApiException) {
        response.setStatus(HttpStatus.UNAUTHORIZED.value());
        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().write(objectMapper.writeValueAsString(new ErrorResponse(((ApiException) exception).getErrorCode())));
    }
}

 

๊ฒฐ๊ณผ

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

'ํ”„๋กœ์ ํŠธ > NolGoat' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€

ํšจ์œจ์ ์ธ ํ† ํฐ ๊ด€๋ฆฌ๋ฅผ ์œ„ํ•œ Redis ๋„์ž…  (1) 2024.11.29
์ธ์ฆ ๋ณด์•ˆ ๊ฐ•ํ™”  (0) 2024.11.29
์ธ์ฆ์ด ํ•„์š”ํ•œ URL ๊ณตํ†ต ๊ด€๋ฆฌ  (0) 2024.11.27
์กฐํšŒ ๋ฐฉ์‹ ๊ฐœ์„  ๋ฐ ์ธ๋ฑ์Šค ์ˆ˜์ •  (0) 2024.11.26
MySQL ์กฐํšŒ ์„ฑ๋Šฅ ๊ฐœ์„   (0) 2024.11.26
'ํ”„๋กœ์ ํŠธ/NolGoat' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€
  • ์ธ์ฆ ๋ณด์•ˆ ๊ฐ•ํ™”
  • ์ธ์ฆ์ด ํ•„์š”ํ•œ URL ๊ณตํ†ต ๊ด€๋ฆฌ
  • ์กฐํšŒ ๋ฐฉ์‹ ๊ฐœ์„  ๋ฐ ์ธ๋ฑ์Šค ์ˆ˜์ •
  • MySQL ์กฐํšŒ ์„ฑ๋Šฅ ๊ฐœ์„ 
yongh๐Ÿ™‚
yongh๐Ÿ™‚
yongh-dev ๋‹˜์˜ ๋ธ”๋กœ๊ทธ ์ž…๋‹ˆ๋‹ค.
  • yongh๐Ÿ™‚
    ๊ฐœ๋ฐœ ๊ธฐ๋ก
    yongh๐Ÿ™‚
  • ์ „์ฒด
    ์˜ค๋Š˜
    ์–ด์ œ
    • ๋ถ„๋ฅ˜ ์ „์ฒด๋ณด๊ธฐ (9)
      • ํ”„๋กœ์ ํŠธ (9)
        • NolGoat (9)
  • ์ตœ๊ทผ ๊ธ€

  • ์ตœ๊ทผ ๋Œ“๊ธ€

  • hELLOยท Designed By์ •์ƒ์šฐ.v4.10.2
yongh๐Ÿ™‚
Spring Security ์ธ์ฆ ํ•„ํ„ฐ ์˜ˆ์™ธ ์ฒ˜๋ฆฌํ•˜๊ธฐ
์ƒ๋‹จ์œผ๋กœ

ํ‹ฐ์Šคํ† ๋ฆฌํˆด๋ฐ”