# [Security] JWT ๊ตฌํ˜„ (4) - JWT ํ•„ํ„ฐ & JWT ํ•ธ๋“ค๋Ÿฌ ๊ตฌํ˜„
Study Repository

[Security] JWT ๊ตฌํ˜„ (4) - JWT ํ•„ํ„ฐ & JWT ํ•ธ๋“ค๋Ÿฌ ๊ตฌํ˜„

by rlaehddnd0422

์ด๋ฒˆ์—๋Š” ์ธ์ฆ ์‹œ์ ์— ์ž๋™์œผ๋กœ ํ˜ธ์ถœ๋  ์ˆ˜ ์žˆ๋Š” JWT ํ•„ํ„ฐ๋ฅผ ๊ตฌํ˜„ํ•ด๋ณด๊ณ , JWT ํ•„ํ„ฐ๋ฅผ ํ†ต๊ณผํ•˜์ง€ ๋ชปํ•œ ๊ฒฝ์šฐ (๊ถŒํ•œ ๋ฏธ๋‹ฌ, ํ† ํฐ Validate ์‹คํŒจ) ์ฒ˜๋ฆฌํ•˜๋Š” JWT ํ•ธ๋“ค๋Ÿฌ๋ฅผ ๊ตฌํ˜„ํ•ด๋ด…์‹œ๋‹ค.

 

๐ŸšฉJwtFilter ํ•„ํ„ฐ ๊ตฌํ˜„ extends GenericFilterBean

@Slf4j
public class JwtFilter extends GenericFilterBean {

    public static final String AUTHORIZATION_HEADER = "Authorization";
    
    private TokenProvider tokenProvider;
    
    public JwtFilter(TokenProvider tokenProvider) {
        this.tokenProvider = tokenProvider;
    }


    // ํ† ํฐ์˜ ์ธ์ฆ์ •๋ณด SecurityContext์— ์ €์žฅ
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
        String jwt = resolveToken(httpServletRequest);
        String requestURI = httpServletRequest.getRequestURI();

        if (StringUtils.hasText(jwt) && tokenProvider.validateToken(jwt)) {
            Authentication authentication = tokenProvider.getAuthentication(jwt);
            SecurityContextHolder.getContext().setAuthentication(authentication);
            log.debug("Security Context์— '{}' ์ธ์ฆ ์ •๋ณด๋ฅผ ์ €์žฅํ–ˆ์Šต๋‹ˆ๋‹ค, uri: {}", authentication.getName(), requestURI);
        } else {
            log.debug("์œ ํšจํ•œ JWT ํ† ํฐ์ด ์—†์Šต๋‹ˆ๋‹ค, uri: {}", requestURI);
        }

        filterChain.doFilter(servletRequest, servletResponse);
    }

    private String resolveToken(HttpServletRequest request) {
        String bearerToken = request.getHeader(AUTHORIZATION_HEADER);

        if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
            return bearerToken.substring(7);
        }

        return null;
    }
}
  • GenericFilterBean์€ Spring Security์™€ ํ•จ๊ป˜ ๋งŽ์ด ์‚ฌ์šฉ๋˜๋ฉฐ, ์‚ฌ์šฉ์ž ์ธ์ฆ, ๊ถŒํ•œ ๋ถ€์—ฌ, ๋กœ๊น… ๋“ฑ๊ณผ ๊ฐ™์€ ๊ณตํ†ต๋œ ์ž‘์—…์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐ ์œ ์šฉํ•˜๊ฒŒ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.
  • ๋”ฐ๋ผ์„œ UsernamePasswordAuthenticationFilter๋Š” ์ฃผ๋กœ Spring Security์—์„œ ์‚ฌ์šฉ์ž ์ธ์ฆ์— ํŠนํ™”๋œ ๋ชฉ์ ์œผ๋กœ ์‚ฌ์šฉ๋˜๋Š” ๋ฐ˜๋ฉด, GenericFilterBean์€ Spring Framework์—์„œ ์ผ๋ฐ˜์ ์ธ ํ•„ํ„ฐ๋ง ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๊ณ ์ž ํ•  ๋•Œ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. GenericFilterBean์€ ๋‹ค์–‘ํ•œ ํ•„ํ„ฐ๋ง ์ž‘์—…์„ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋Š” ์œ ์—ฐ์„ฑ์„ ์ œ๊ณตํ•˜๋ฉฐ, Spring์˜ ๋‹ค๋ฅธ ๊ธฐ๋Šฅ๊ณผ ํ†ตํ•ฉํ•˜์—ฌ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

JwtFilter - doFilter()

// ํ† ํฐ์˜ ์ธ์ฆ์ •๋ณด SecurityContext์— ์ €์žฅ
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
    HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
    String jwt = resolveToken(httpServletRequest); // 0
    String requestURI = httpServletRequest.getRequestURI();
	
    if (StringUtils.hasText(jwt) && tokenProvider.validateToken(jwt)) 
        Authentication authentication = tokenProvider.getAuthentication(jwt); // 1
        SecurityContextHolder.getContext().setAuthentication(authentication); // 2
        log.debug("Security Context์— '{}' ์ธ์ฆ ์ •๋ณด๋ฅผ ์ €์žฅํ–ˆ์Šต๋‹ˆ๋‹ค, uri: {}", authentication.getName(), requestURI);
    } else {
        log.debug("์œ ํšจํ•œ JWT ํ† ํฐ์ด ์—†์Šต๋‹ˆ๋‹ค, uri: {}", requestURI);
    }

    filterChain.doFilter(servletRequest, servletResponse); // 3
}

 

resolveToken method

private String resolveToken(HttpServletRequest request) {
    String bearerToken = request.getHeader(AUTHORIZATION_HEADER);

    if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
        return bearerToken.substring(7);
    }

    return null;
}

0) resolveToken๋ฉ”์†Œ๋“œ๋ฅผ ํ†ตํ•ด request๋กœ๋ถ€ํ„ฐ Authorizaiton ํ—ค๋” ์ •๋ณด๋ฅผ ๊บผ๋‚ด์™€ ๋ฒ ๋ฆฌ์–ด๋ฅผ ํ•ด์ œํ•˜๊ณ  ๋ฆฌํ„ดํ•ฉ๋‹ˆ๋‹ค.

 

1) ์š”์ฒญ ์ •๋ณด์— ํ† ํฐ์ด ์žˆ๊ณ  ์ด ์ „ ํฌ์ŠคํŒ…์—์„œ ์ƒ์„ฑํ•œ TokenProvider๋กœ ํ† ํฐ ๊ฒ€์ฆ์— ํ†ต๊ณผํ•˜๋ฉด ํ† ํฐ์œผ๋กœ๋ถ€ํ„ฐ Authentication ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ญ๋‹ˆ๋‹ค.

 

2) Authentication ๊ฐ์ฒด๋ฅผ Security Context์— ๋‹ด์Šต๋‹ˆ๋‹ค. ( for Authentication & Authorization )

 

3) ๋‹ค์Œ ํ•„ํ„ฐ๋กœ ๋„˜์–ด๊ฐ‘๋‹ˆ๋‹ค.

 

๐Ÿšฉ์ด ํ•„ํ„ฐ๋Š” ์ด๋ ‡๊ฒŒ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค!

์ธ์ฆ or ์ธ๊ฐ€๊ฐ€ ํ•„์š”ํ•œ ์š”์ฒญ 

โžก๏ธ JwtFilter์—์„œ ์š”์ฒญ ์ธํ„ฐ์…‰ํŠธ

โžก๏ธ ์š”์ฒญ ํ—ค๋” ์ •๋ณด์—์„œ ํ† ํฐ์„ ๊บผ๋‚ด์™€ TokenProvider๋กœ ํ† ํฐ ๊ฒ€์ฆ

โžก๏ธ ๊ฒ€์ฆ ํ†ต๊ณผ ์‹œ Authentication ๊ฐ์ฒด ์ƒ์„ฑ

โžก๏ธ Session์˜์—ญ์˜ SecurityContext์— Authentication ๊ฐ์ฒด๋ฅผ ๋‹ด๊ณ  ํ•„ํ„ฐ ํ†ต๊ณผ 

 

 

์ด์ œ, JWT ํ•„ํ„ฐ ๊ตฌํ˜„์— ์ด์–ด ์ธ์ฆ, ์ธ๊ฐ€์— ์‹คํŒจํ•˜์—ฌ ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•œ ๊ฒฝ์šฐ ์ด ์˜ˆ์™ธ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ํ•ธ๋“ค๋Ÿฌ๋ฅผ ๋งŒ๋“ค์–ด ๋ด…์‹œ๋‹ค.

 

๐Ÿšฉ AuthenticationEntryPoint ๊ตฌํ˜„ : ์ธ์ฆ ์‹คํŒจ ์ฒ˜๋ฆฌ ํ•ธ๋“ค๋Ÿฌ

AuthenticationEntryPoint๋Š” ์ธ์ฆ ์ฒ˜๋ฆฌ ๊ณผ์ •์—์„œ ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” ๊ฒฝ์šฐ ์˜ˆ์™ธ๋ฅผ ์ฒ˜๋ฆฌํ•ด์ฃผ๋Š” ํ•ธ๋“ค๋Ÿฌ์ž…๋‹ˆ๋‹ค.

@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
    
    @Override
    public void commence(HttpServletRequest request,
                         HttpServletResponse response,
                         AuthenticationException authException) throws IOException {
        // ์œ ํšจํ•œ ์ž๊ฒฉ์ฆ๋ช…์„ ์ œ๊ณตํ•˜์ง€ ์•Š๊ณ  ์ ‘๊ทผํ•˜๋ ค ํ• ๋•Œ 401
        response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
    }
}
  • ์ธ์ฆ์— ์‹คํŒจํ•˜์˜€์„ ๊ฒฝ์šฐ 401 ์‘๋‹ต์„ ๋ฆฌํ„ดํ• ์ˆ˜ ์žˆ๋„๋ก EntryPoint๋ฅผ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค.

 

๐Ÿšฉ AccessDeniedHandler ๊ตฌํ˜„ : ๊ถŒํ•œ ๋ฏธ๋‹ฌ ์ฒ˜๋ฆฌ ํ•ธ๋“ค๋Ÿฌ

AccessDeniedHandler๋Š” ์ธ๊ฐ€ ์ฒ˜๋ฆฌ ๊ณผ์ •์—์„œ ์—์™ธ๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” ๊ฒฝ์šฐ ์ฒ˜๋ฆฌํ•ด์ฃผ๋Š” ํ•ธ๋“ค๋Ÿฌ์ž…๋‹ˆ๋‹ค.

@Component
public class JwtAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException {
        //ํ•„์š”ํ•œ ๊ถŒํ•œ์ด ์—†์ด ์ ‘๊ทผํ•˜๋ ค ํ• ๋•Œ 403
        response.sendError(HttpServletResponse.SC_FORBIDDEN);
    }
}
  • ํ•„์š”ํ•œ ๊ถŒํ•œ์ด ์—†๋Š” ๊ฒฝ์šฐ 403 ์‘๋‹ต์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ํ•ธ๋“ค๋Ÿฌ๋ฅผ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค.

 

๐ŸšฉJwtFilter & Handler ๋“ฑ๋ก

JwtFilter โžก๏ธ JwtSecurityConfig ๋“ฑ๋ก 

JwtSecurityConfig ์ƒ์„ฑ ๋ฐ ๋“ฑ๋ก

public class JwtSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
    private TokenProvider tokenProvider;
    public JwtSecurityConfig(TokenProvider tokenProvider) {
        this.tokenProvider = tokenProvider;
    }

    @Override
    public void configure(HttpSecurity http) {
        http.addFilterBefore(
                new JwtFilter(tokenProvider),
                UsernamePasswordAuthenticationFilter.class
        );
    }
}
  • ๊ธฐ์กด์˜ ๋กœ๊ทธ์ธ ์‹œ์ ์—์„œ ํ˜ธ์ถœ๋˜๋˜ UsernamePasswordAuthenticationFilter๋ฅผ JwtFilter๋กœ Override ํ•ด์ค๋‹ˆ๋‹ค.
  • addFilterBefore() : ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ ํ•„ํ„ฐ๋ง์— ๋“ฑ๋กํ•ด์ฃผ์–ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— addFilterBefore์— ๋“ฑ๋กํ•ด์ค๋‹ˆ๋‹ค.

 

JwtSecurityConfig & AuthenticationEndPoint & AccessDeniedHandler โžก๏ธ SecurityConfig ๋“ฑ๋ก

@Configuration
@EnableWebSecurity
@EnableMethodSecurity
@RequiredArgsConstructor
public class SecurityConfig {

    private final TokenProvider tokenProvider;
    private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
    private final JwtAccessDeniedHandler jwtAccessDeniedHandler;

    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.   // ํ† ํฐ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ์‹์ด๋ฏ€๋กœ csrf disable
                csrf().disable()

                .exceptionHandling()  // ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ํ•ธ๋“ค๋Ÿฌ ๋“ฑ๋ก 
                .authenticationEntryPoint(jwtAuthenticationEntryPoint)
                .accessDeniedHandler(jwtAccessDeniedHandler)

                .and()
                .headers()
                .frameOptions()
                .sameOrigin()

                // ์„ธ์…˜ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ธฐ ์œ„ํ•ด stateless๋กœ ์„ค์ •
                .and()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)

                .and()
                .authorizeRequests()
                .antMatchers("/api/hello").permitAll()
                .antMatchers("/api/authenticate").permitAll() // ๋กœ๊ทธ์ธ
                .antMatchers("/api/signup").permitAll() // ํšŒ์›๊ฐ€์ž…
                .anyRequest().authenticated()

                .and()
                .apply(new JwtSecurityConfig(tokenProvider)); // JWT ํ•„ํ„ฐ ๋“ฑ๋ก

        return http.build();
    }
}

 

 

<์ฐธ๊ณ ์ž๋ฃŒ>
๋ณธ ํฌ์ŠคํŒ…์€ ์ •์€๊ตฌ๋‹˜์˜ Spring Boot JWT Tutorial ๊ฐ•์˜๋ฅผ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•˜์˜€์Šต๋‹ˆ๋‹ค.

 

[๋ฌด๋ฃŒ] Spring Boot JWT Tutorial - ์ธํ”„๋Ÿฐ | ๊ฐ•์˜

Spring Boot, Spring Security, JWT๋ฅผ ์ด์šฉํ•œ ํŠœํ† ๋ฆฌ์–ผ์„ ํ†ตํ•ด ์ธ์ฆ๊ณผ ์ธ๊ฐ€์— ๋Œ€ํ•œ ๊ธฐ์ดˆ ์ง€์‹์„ ์‰ฝ๊ณ  ๋น ๋ฅด๊ฒŒ ํ•™์Šตํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค., - ๊ฐ•์˜ ์†Œ๊ฐœ | ์ธํ”„๋Ÿฐ

www.inflearn.com

 

๋ธ”๋กœ๊ทธ์˜ ์ •๋ณด

Study Repository

rlaehddnd0422

ํ™œ๋™ํ•˜๊ธฐ