[Security] ์˜ˆ์™ธ์ฒ˜๋ฆฌ

Updated:

Categories:

Tags: , ,

๐Ÿ“Œ ๊ฐœ์ธ์ ์ธ ๊ณต๊ฐ„์œผ๋กœ ๊ณต๋ถ€๋ฅผ ๊ธฐ๋กํ•˜๊ณ  ๋ณต์Šตํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉํ•˜๋Š” ๋ธ”๋กœ๊ทธ์ž…๋‹ˆ๋‹ค.
์ •ํ™•ํ•˜์ง€ ์•Š์€ ์ •๋ณด๊ฐ€ ์žˆ์„ ์ˆ˜ ์žˆ์œผ๋‹ˆ ์ฐธ๊ณ ๋ฐ”๋ž๋‹ˆ๋‹ค :๐Ÿ˜ธ
[ํ‹€๋ฆฐ ๋‚ด์šฉ์€ ๋Œ“๊ธ€๋กœ ๋‚จ๊ฒจ์ฃผ์‹œ๋ฉด ๋ณต๋ฐ›์œผ์‹ค๊ฑฐ์—์š”]

Spring Security์—์„œ ๋ฐœ์ƒํ•œ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ํ•„์š”์„ฑ

๋กœ๊ทธ์ธ์— ์ž˜๋ชป ์ •๋ณด๋ฅผ ๊ธฐ์ž…ํ–ˆ์„ ๋•Œ

๋งŒ๋ฃŒ๋œ ํ† ํฐ์„ ๋„ฃ์—ˆ์„๋•Œ

๊ถŒํ•œ์ด ๋ถ€์—ฌ๋˜์ง€ ์•Š์€ ๋ฆฌ์†Œ์Šค์— request๋ฅผ ์ „์†ก

(Admin์ด ์•„๋‹Œ User๊ฐ€ ๋‹ต๋ณ€์„ ์‹œ๋„)



Spring Security์—์„œ ์ฒ˜๋ฆฌ๋˜์ง€ ์•Š์€ ์˜ˆ์™ธ๋‚˜ ๊ทธ ์™ธ์˜ ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ,
์„œ๋ธ”๋ฆฟ ์ปจํ…Œ์ด๋„ˆ์ธ Tomcat์ด HTTP ์ƒํƒœ ์ฝ”๋“œ์— ๋”ฐ๋ผ ๊ธฐ๋ณธ ์˜ค๋ฅ˜ ํŽ˜์ด์ง€๋ฅผ ๋ฐ˜ํ™˜ํ•˜๊ฒŒ ๋œ๋‹ค.

MVC์—์„œ ๋ฐœ์ƒํ•˜๋Š” ์˜ˆ์™ธ์™€ security์˜ ์˜ˆ์™ธ๋Š” ๋‹ค๋ฅธ ๊ณณ์—์„œ ๋ฐœ์ƒํ•˜๊ธฐ ๋•Œ๋ฌธ์—
๋ณ„๋„๋กœ ๊ด€๋ฆฌํ•ด์ฃผ์–ด์•ผ ํ•œ๋‹ค!
Spring Security์˜ ์˜ˆ์™ธ๋Š” ํ•„ํ„ฐ ์ฒด์ธ ๋‚ด์—์„œ ๋ฐœ์ƒํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ด ๋ถ€๋ถ„์—์„œ ์žก์•„์ฃผ์–ด์•ผ ํ•œ๋‹ค.

์˜ˆ์™ธ์ฒ˜๋ฆฌ

1๏ธโƒฃ JwtVerificationFilter์— ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ๋กœ์ง ์ถ”๊ฐ€

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class JwtVerificationFilter extends OncePerRequestFilter {
    ...
    ...

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        // (1)
        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);
    }
  • try -catch๋ฌธ์ด ์ถ”๊ฐ€

    โ†’ ํŠน์ • ์˜ˆ์™ธ ํƒ€์ž…์˜ Exception์ด catch ๋˜๋ฉด ํ•ด๋‹น Exception์„ request.setAttribute("exception", Exception ๊ฐ์ฒด)์™€ ๊ฐ™์ด HttpServletRequest์˜ ์• ํŠธ๋ฆฌ๋ทฐํŠธ(Attribute)๋กœ ์ถ”๊ฐ€

  1. ๊ธฐ์กด ์˜ˆ์™ธ ์ฒ˜๋ฆฌ์™€๋Š” ๋‹ค๋ฅด๊ฒŒ Exception์„ catchํ•œ ํ›„์— Exception์„ ๋‹ค์‹œ throw ํ•œ๋‹ค๋“ ์ง€ ํ•˜๋Š” ์ฒ˜๋ฆฌ๋ฅผ ํ•˜์ง€ ์•Š๊ณ , ๋‹จ์ˆœํžˆ request.setAttribute()๋ฅผ ์„ค์ •
  2. SecurityContext์— ํด๋ผ์ด์–ธํŠธ์˜ ์ธ์ฆ ์ •๋ณด(Authentication ๊ฐ์ฒด)๊ฐ€ ์ €์žฅ๋˜์ง€ ์•Š๊ฒŒ ํ•˜๊ธฐ ์œ„ํ•จ
  3. SecurityContext์— ํด๋ผ์ด์–ธํŠธ์˜ ์ธ์ฆ ์ •๋ณด(Authentication ๊ฐ์ฒด)๊ฐ€ ์ €์žฅ๋˜์ง€ ์•Š์€ ์ƒํƒœ๋กœ ๋‹ค์Œ(next) Security Filter ๋กœ์ง์„ ์ˆ˜ํ–‰ํ•˜๋‹ค ๋ณด๋ฉด ๊ฒฐ๊ตญ์—๋Š” Filter ๋‚ด๋ถ€์—์„œ AuthenticationException์ด ๋ฐœ์ƒํ•˜๊ฒŒ ๋˜๊ณ , ์ด AuthenticationException์€ ๋ฐ”๋กœ ์•„๋ž˜์—์„œ ์„ค๋ช…ํ•˜๋Š” AuthenticationEntryPoint๊ฐ€ ์ฒ˜๋ฆฌํ•˜๊ฒŒ ๋œ๋‹ค.

2๏ธโƒฃ AuthenticationEntryPoint ๊ตฌํ˜„

AuthenticationEntryPoint๋Š” SignatureException, ExpiredJwtException ๋“ฑ Exception ๋ฐœ์ƒ์œผ๋กœ ์ธํ•ด SecurityContext์— Authentication์ด ์ €์žฅ๋˜์ง€ ์•Š์„ ๊ฒฝ์šฐ ๋“ฑ AuthenticationException์ด ๋ฐœ์ƒํ•  ๋•Œ ํ˜ธ์ถœ๋˜๋Š” ํ•ธ๋“ค๋Ÿฌ ๊ฐ™์€ ์—ญํ• ์„ ํ•œ๋‹ค.

  1. MemberAuthenticationEntryPoint : AuthenticationEntryPoint ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ํด๋ž˜์Šค

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    
     package com.springboot.auth.handler;
     ...
     ...
        
     @Slf4j
     @Component
     public class MemberAuthenticationEntryPoint implements AuthenticationEntryPoint {
         @Override
         public void commence(HttpServletRequest request, 
     										    HttpServletResponse response,
     											  AuthenticationException authException) 
     							throws IOException, ServletException {
        							
             Exception exception = (Exception) request.getAttribute("exception");
             ErrorResponder.sendErrorResponse(response, HttpStatus.UNAUTHORIZED);
        
             logExceptionMessage(authException, exception);
            
         }
        
         private void logExceptionMessage(AuthenticationException authException, 
     																	   Exception exception) {
        																	   
             String message = exception != null ? exception.getMessage() : authException.getMessage();
             log.warn("Unauthorized error happened: {}", message);
         }
     }
    
    • commence ๋ฉ”์„œ๋“œ : ์ธ์ฆ๋˜์ง€ ์•Š์€ ์‚ฌ์šฉ์ž๊ฐ€ ๋ฆฌ์†Œ์Šค์— ์ ‘๊ทผํ•˜๋ ค ํ•  ๋•Œ ํ˜ธ์ถœ
      • request์— ๋‹ด๊ฒจ์ ธ ์žˆ๋Š” ์˜ˆ์™ธ ๊ฐ์ฒด๋ฅผ ๊ฐ€์ ธ ์˜จ๋‹ค. (์œ„์˜ JwtVerificationFilter์˜ doFilterInternal ๋ฉ”์„œ๋“œ์—์„œ ์˜ˆ์™ธ ๋ฐœ์ƒ์‹œ request.setAttribute(โ€œexceptionโ€, se); ๋กœ ์˜ˆ์™ธ๋ฅผ ๋‹ด์•˜๊ธฐ ๋•Œ๋ฌธ์— ์ „์— ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋‹ค๋ฉด ๋‹ด๊ฒจ์ ธ ์žˆ๋‹ค.
      • ErrorResponder.sendErrorResponse(response, HttpStatus.UNAUTHORIZED) โ†’ ์—๋Ÿฌ ์‘๋‹ต์„ ์ „์†ก
    • logExceptionMessage ๋ฉ”์„œ๋“œ
      • authException์€ ์ธ์ฆ ์˜ˆ์™ธ, ๋‘๋ฒˆ์งธ ํŒŒ๋ผ๋ฏธํ„ฐ์˜ exception์€ ์ถ”๊ฐ€ ์˜ˆ์™ธ
      • String message = exception != null ? exception.getMessage() โ‡’ ์ถ”๊ฐ€ ์˜ˆ์™ธ๊ฐ€ ์žˆ์œผ๋ฉด ๊ทธ ๋ฉ”์„ธ์ง€๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  , ์ธ์ฆ ์˜ˆ์™ธ ๋ฉ”์„ธ์ง€๊ฐ€ ์žˆ๋‹ค๋ฉด ์ธ์ฆ ์˜ˆ์™ธ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค
  2. ErrorResponder

    AuthenticationException ๋ฐœ์ƒํ•˜๋ฉด ErrorResponse๋ฅผ ์ƒ์„ฑํ•ด์„œ ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ์ „์†ก

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    
     package com.springboot.auth.utils;
     ...
     ...
     public class ErrorResponder {
         public static void sendErrorResponse(HttpServletResponse response,
                                              HttpStatus httpStatus)
                 throws IOException {
        
             Gson gson = new Gson();
             ErrorResponse errorResponse = ErrorResponse.of(httpStatus);
             response.setContentType(MediaType.APPLICATION_JSON_VALUE);
             response.setStatus(httpStatus.value());
             response.getWriter().write(gson.toJson(errorResponse,ErrorResponse.class));
         }
    
    • HttpStatus๋ฅผ ๋ฐ›์•„ ErrorREpnose ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑ
    • response.setContentType(MediaType.APPLICATION_JSON_VALUE); โ‡’ ์‘๋‹ต์„ json type์œผ๋กœ ๋ณด๋‚ด์ค€๋‹ค.
    • HTTP ์‘๋‹ต์— JSON ํ˜•์‹์˜ ์—๋Ÿฌ ๋ฉ”์‹œ์ง€๋ฅผ ์ž‘์„ฑํ•˜์—ฌ ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ์ „์†กํ•˜๋Š” ์—ญํ• ์„ ํ•œ๋‹ค.

3๏ธโƒฃ AccessDeniedHandler ๊ตฌํ˜„

์ธ์ฆ์—๋Š” ์„ฑ๊ณตํ–ˆ์ง€๋งŒ ํ•ด๋‹น ๋ฆฌ์†Œ์Šค์— ๋Œ€ํ•œ ๊ถŒํ•œ์ด ์—†์œผ๋ฉด ํ˜ธ์ถœ๋˜๋Š” ํ•ธ๋“ค๋Ÿฌ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.springboot.auth.handler;
...

@Slf4j
@Component
public class MemberAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest request, 
                       HttpServletResponse response, 
                       AccessDeniedException accessDeniedException) 
            throws IOException, ServletException {

        ErrorResponder.sendErrorResponse(response, HttpStatus.FORBIDDEN);
        log.warn("Forbidden error happend: {}", accessDeniedException.getMessage());
    }
}
  • sendErrorResponse ๋ฉ”์†Œ๋“œ๋Š” HTTP ์‘๋‹ต์˜ ์ฝ˜ํ…์ธ  ํƒ€์ž…์„ application/json์œผ๋กœ ์„ค์ •ํ•˜๊ณ , ์ƒํƒœ ์ฝ”๋“œ๋ฅผ 403์œผ๋กœ ์„ค์ •ํ•˜๋ฉฐ, ErrorResponse ๊ฐ์ฒด๋ฅผ JSON ๋ฌธ์ž์—ด๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ ์‘๋‹ต ๋ณธ๋ฌธ์— ์ž‘์„ฑ
  • accessDeniedException.getMessage()๋Š” ์ ‘๊ทผ ๊ฑฐ๋ถ€ ์˜ˆ์™ธ์˜ ์ƒ์„ธ ๋ฉ”์‹œ์ง€๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

4๏ธโƒฃ SecurityConfiguration์— AuthenticationEntryPoint ๋ฐ AccessDeniedHandler ์ถ”๊ฐ€

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class SecurityConfiguration {
   ...
   ...

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .headers().frameOptions().sameOrigin()
            .and()
            .csrf().disable()
            .cors(withDefaults())
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()
            .formLogin().disable()
            .httpBasic().disable()
            .exceptionHandling()
            .authenticationEntryPoint(new MemberAuthenticationEntryPoint())  // (1) ์ถ”๊ฐ€
            .accessDeniedHandler(new MemberAccessDeniedHandler())            // (2) ์ถ”๊ฐ€
            .and()
            .apply(new CustomFilterConfigurer())
            .and()
            .authorizeHttpRequests(authorize -> authorize
                    .antMatchers(HttpMethod.POST, "/*/members").permitAll()
                    .antMatchers(HttpMethod.PATCH, "/*/members/**").hasRole("USER")
                    .antMatchers(HttpMethod.GET, "/*/members").hasRole("ADMIN")
                    .antMatchers(HttpMethod.GET, "/*/members/**").hasAnyRole("USER", "ADMIN")
                    .antMatchers(HttpMethod.DELETE, "/*/members/**").hasRole("USER")
                    .anyRequest().permitAll()
            );
        return http.build();
    }
    
    ...
    ...
    ...
    ...
    
    }
  • exceptionHandling(): ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ์„ค์ •์„ ๊ตฌ์„ฑ
  • authenticationEntryPoint(new MemberAuthenticationEntryPoint()): ์ธ์ฆ ์‹คํŒจ ์‹œ ์ฒ˜๋ฆฌํ•  ํ•ธ๋“ค๋Ÿฌ ์„ค์ •.
  • accessDeniedHandler(new MemberAccessDeniedHandler()): ๊ถŒํ•œ ๋ถ€์กฑ ์‹œ ์ฒ˜๋ฆฌํ•  ํ•ธ๋“ค๋Ÿฌ ์„ค์ •.

Spring ์นดํ…Œ๊ณ ๋ฆฌ ๋‚ด ๋‹ค๋ฅธ ๊ธ€ ๋ณด๋Ÿฌ๊ฐ€๊ธฐ

Leave a comment