์ด์ง€๋ฏผ์˜ ๊ฐœ๋ฐœ ๋…ธํŠธ

๋ฐฑ์—”๋“œ ๊ฐœ๋ฐœ์„ ๊ณต๋ถ€ํ•˜๋ฉฐ Java์™€ Spring์„ ์ค‘์‹ฌ์œผ๋กœ ํ•™์Šต ๊ธฐ๋ก์„ ์ •๋ฆฌํ•˜๋Š” ๊ณต๊ฐ„์ž…๋‹ˆ๋‹ค.

๐Ÿ”ฅ ๋‚ด์ผ๋ฐฐ์›€์บ ํ”„/๐Ÿ“Œ ๊ณผ์ œ

[๋‚ด์ผ๋ฐฐ์›€์บ ํ”„ Spring 3๊ธฐ] CH3 ์ฝ”๋“œ ๊ฐœ์„  ๊ณผ์ œ - Lv 6

jiiimni 2026. 3. 10. 12:45

Lv6. Config ๊ตฌ์กฐ ๊ฐœ์„  ๋ฐ ArgumentResolver Bean ๋“ฑ๋ก ๋ฌธ์ œ ํ•ด๊ฒฐ

1. ๋ฌธ์ œ ์ธ์‹ ๋ฐ ์ •์˜

Lv5์—์„œ ๊ด€๋ฆฌ์ž API ๋กœ๊น… ๊ธฐ๋Šฅ(Interceptor + AOP)์„ ๊ตฌํ˜„ํ•˜๋ฉด์„œ WebConfig๋ฅผ ์ˆ˜์ •ํ•˜์˜€๋‹ค.
์ด ๊ณผ์ •์—์„œ AuthUserArgumentResolver๋ฅผ ์ƒ์„ฑ์ž ์ฃผ์ž… ๋ฐฉ์‹์œผ๋กœ ๊ด€๋ฆฌํ•˜๋„๋ก ๊ตฌ์กฐ๋ฅผ ์ •๋ฆฌํ•˜์˜€๋‹ค.

๊ธฐ์กด ์ฝ”๋“œ์—์„œ๋Š” HandlerMethodArgumentResolver๋ฅผ ๋“ฑ๋กํ•  ๋•Œ ์•„๋ž˜์ฒ˜๋Ÿผ ์ง์ ‘ ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜๋„ ์žˆ์—ˆ๋‹ค.

resolvers.add(new AuthUserArgumentResolver());

ํ•˜์ง€๋งŒ Config ๊ตฌ์กฐ๋ฅผ ์ •๋ฆฌํ•˜๋ฉด์„œ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์˜์กด์„ฑ ์ฃผ์ž…(DI) ๋ฐฉ์‹์œผ๋กœ ๋ณ€๊ฒฝํ•˜์˜€๋‹ค.

private final AuthUserArgumentResolver authUserArgumentResolver;

๊ทธ๋Ÿฐ๋ฐ ์‹คํ–‰ ๊ณผ์ •์—์„œ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ๋‹ค.

Could not autowire. No beans of 'AuthUserArgumentResolver' type found

์ฆ‰, WebConfig์—์„œ AuthUserArgumentResolver๋ฅผ ์ฃผ์ž…๋ฐ›์œผ๋ ค๊ณ  ํ–ˆ์ง€๋งŒ
์Šคํ”„๋ง ์ปจํ…Œ์ด๋„ˆ์— ํ•ด๋‹น ํด๋ž˜์Šค๊ฐ€ Bean์œผ๋กœ ๋“ฑ๋ก๋˜์–ด ์žˆ์ง€ ์•Š์•„ ๊ฐ์ฒด๋ฅผ ์ฐพ์ง€ ๋ชปํ•˜๋Š” ๋ฌธ์ œ์˜€๋‹ค.

์ด ๋ฌธ์ œ์˜ ์›์ธ์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

  • AuthUserArgumentResolver๊ฐ€ ์Šคํ”„๋ง Bean์œผ๋กœ ๋“ฑ๋ก๋˜์–ด ์žˆ์ง€ ์•Š์Œ
  • WebMvcConfigurer์—์„œ Resolver๋ฅผ ๋“ฑ๋กํ•˜๋ ค๋ฉด Bean ๋“ฑ๋ก + Config ๋“ฑ๋ก ๋‘ ๋‹จ๊ณ„๊ฐ€ ํ•„์š”

๊ฒฐ๊ตญ Config ํด๋ž˜์Šค์™€ Bean ๋“ฑ๋ก ๊ตฌ์กฐ๋ฅผ ๋ช…ํ™•ํ•˜๊ฒŒ ์ •๋ฆฌํ•  ํ•„์š”๊ฐ€ ์žˆ์—ˆ๋‹ค.


2. ํ•ด๊ฒฐ ๋ฐฉ์•ˆ

2-1. ์˜์‚ฌ๊ฒฐ์ • ๊ณผ์ •

AuthUserArgumentResolver๋ฅผ ์Šคํ”„๋ง Bean์œผ๋กœ ๋“ฑ๋กํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ํฌ๊ฒŒ ๋‘ ๊ฐ€์ง€๊ฐ€ ์žˆ์—ˆ๋‹ค.

๋ฐฉ๋ฒ• 1. @Component๋ฅผ ์‚ฌ์šฉํ•œ Bean ๋“ฑ๋ก

@Component
public class AuthUserArgumentResolver implements HandlerMethodArgumentResolver

๋ฐฉ๋ฒ• 2. @Configuration์—์„œ @Bean์œผ๋กœ ์ง์ ‘ ๋“ฑ๋ก

@Bean
public AuthUserArgumentResolver authUserArgumentResolver() {
    return new AuthUserArgumentResolver();
}

์ด๋ฒˆ ํ”„๋กœ์ ํŠธ์—์„œ๋Š” ๋ฐฉ๋ฒ• 1 (@Component)๋ฅผ ์„ ํƒํ•˜์˜€๋‹ค.

์„ ํƒ ์ด์œ ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

  • ArgumentResolver๋Š” ์—ฌ๋Ÿฌ ์ปจํŠธ๋กค๋Ÿฌ์—์„œ ์žฌ์‚ฌ์šฉ๋˜๋Š” ์ปดํฌ๋„ŒํŠธ
  • ์Šคํ”„๋ง์ด ์ง์ ‘ ๊ด€๋ฆฌํ•˜๋Š” Bean์œผ๋กœ ๋“ฑ๋กํ•˜๋Š” ๊ฒƒ์ด ๊ตฌ์กฐ์ ์œผ๋กœ ๋” ์ž์—ฐ์Šค๋Ÿฌ์›€
  • Config ํด๋ž˜์Šค์˜ ์ฑ…์ž„์„ ์ตœ์†Œํ™”ํ•  ์ˆ˜ ์žˆ์Œ

2-2. ํ•ด๊ฒฐ ๊ณผ์ •

1) AuthUserArgumentResolver๋ฅผ Bean์œผ๋กœ ๋“ฑ๋ก

@Component
public class AuthUserArgumentResolver implements HandlerMethodArgumentResolver {

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์Šคํ”„๋ง ์ปจํ…Œ์ด๋„ˆ๊ฐ€ ์ž๋™์œผ๋กœ Bean์œผ๋กœ ๋“ฑ๋กํ•œ๋‹ค.


2) WebConfig์—์„œ ArgumentResolver ๋“ฑ๋ก

@Configuration
@RequiredArgsConstructor
public class WebConfig implements WebMvcConfigurer {

    private final AuthUserArgumentResolver authUserArgumentResolver;
    private final AdminApiInterceptor adminApiInterceptor;

    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
        resolvers.add(authUserArgumentResolver);
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(adminApiInterceptor)
                .addPathPatterns("/admin/comments/*")
                .addPathPatterns("/admin/users/*");
    }
}

์ด ๊ณผ์ •์„ ํ†ตํ•ด

  • AuthUserArgumentResolver๋Š” ์Šคํ”„๋ง Bean
  • WebConfig๋Š” Resolver๋ฅผ Spring MVC์— ๋“ฑ๋กํ•˜๋Š” ์—ญํ• 

๋กœ ์ฑ…์ž„์ด ๋ช…ํ™•ํ•˜๊ฒŒ ๋ถ„๋ฆฌ๋˜์—ˆ๋‹ค.


3. ํ•ด๊ฒฐ ์™„๋ฃŒ

3-1. ํšŒ๊ณ 

์ด๋ฒˆ ๋ฌธ์ œ๋ฅผ ํ†ตํ•ด Spring MVC์˜ ArgumentResolver ๋™์ž‘ ๊ตฌ์กฐ๋ฅผ ๋ช…ํ™•ํžˆ ์ดํ•ดํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.

ํŠนํžˆ ๋‹ค์Œ ๋‘ ๊ฐ€์ง€ ํฌ์ธํŠธ๊ฐ€ ์ค‘์š”ํ–ˆ๋‹ค.

1) ArgumentResolver๋Š” ์ž๋™ ๋“ฑ๋ก๋˜์ง€ ์•Š๋Š”๋‹ค

๋‹จ์ˆœํžˆ ํด๋ž˜์Šค๋ฅผ ๋งŒ๋“œ๋Š” ๊ฒƒ๋งŒ์œผ๋กœ๋Š” ๋™์ž‘ํ•˜์ง€ ์•Š๋Š”๋‹ค.

๋‹ค์Œ ๋‘ ๋‹จ๊ณ„๊ฐ€ ํ•„์š”ํ•˜๋‹ค.

1. Bean ๋“ฑ๋ก
2. WebMvcConfigurer.addArgumentResolvers ๋“ฑ๋ก

2) Config ๊ตฌ์กฐ๊ฐ€ ๋ช…ํ™•ํ•ด์•ผ ์˜ค๋ฅ˜๋ฅผ ์ค„์ผ ์ˆ˜ ์žˆ๋‹ค

Config ํด๋ž˜์Šค๊ฐ€ ์—ฌ๋Ÿฌ ๊ฐœ๋กœ ๋‚˜๋‰˜๊ฑฐ๋‚˜ Bean ๋“ฑ๋ก์ด ๋ˆ„๋ฝ๋˜๋ฉด

Could not autowire
No beans found

๊ฐ™์€ ์˜ค๋ฅ˜๊ฐ€ ์‰ฝ๊ฒŒ ๋ฐœ์ƒํ•œ๋‹ค.

์ด๋ฒˆ ๊ฒฝํ—˜์„ ํ†ตํ•ด

  • ์Šคํ”„๋ง Bean ๋“ฑ๋ก ๋ฐฉ์‹
  • WebMvcConfigurer์˜ ์—ญํ• 
  • ArgumentResolver ๋™์ž‘ ๋ฐฉ์‹

์„ ์‹ค์ œ ์˜ค๋ฅ˜ ํ•ด๊ฒฐ ๊ณผ์ •์„ ํ†ตํ•ด ์ดํ•ดํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.


3-2. ์ „ํ›„ ๋ฐ์ดํ„ฐ ๋น„๊ต

ํ•ญ๋ชฉ๊ฐœ์„  ์ „๊ฐœ์„  ํ›„

Resolver ์ƒ์„ฑ new๋กœ ์ง์ ‘ ์ƒ์„ฑ ๊ฐ€๋Šฅ ์Šคํ”„๋ง Bean์œผ๋กœ ๊ด€๋ฆฌ
Config ๊ตฌ์กฐ Bean ๋“ฑ๋ก ๋ˆ„๋ฝ ๊ฐ€๋Šฅ DI ๊ธฐ๋ฐ˜ ๊ตฌ์กฐ
์˜ค๋ฅ˜ ๋ฐœ์ƒ No beans found ํ•ด๊ฒฐ
์œ ์ง€๋ณด์ˆ˜ ์„ค์ • ์œ„์น˜ ๋ถˆ๋ช…ํ™• ์—ญํ•  ๋ถ„๋ฆฌ

Lv6. JwtFilter์—์„œ ์ธ์ฆ๊ณผ ์ธ๊ฐ€ ์ฑ…์ž„ ๋ถ„๋ฆฌ

1. ๋ฌธ์ œ ์ธ์‹ ๋ฐ ์ •์˜

ํ˜„์žฌ JwtFilter๋Š” ์ธ์ฆ, ์ธ๊ฐ€, ์‚ฌ์šฉ์ž ์ •๋ณด ์„ธํŒ…, ์—๋Ÿฌ ์‘๋‹ต ์ƒ์„ฑ๊นŒ์ง€ ๋ชจ๋‘ ๋‹ด๋‹นํ•˜๊ณ  ์žˆ์—ˆ๋‹ค.

ํ•˜๋‚˜์˜ ํด๋ž˜์Šค์— ๋„ˆ๋ฌด ๋งŽ์€ ์ฑ…์ž„์ด ๋ชฐ๋ ค ์žˆ์–ด ์œ ์ง€๋ณด์ˆ˜์„ฑ๊ณผ ๊ฐ€๋…์„ฑ์ด ๋–จ์–ด์ง„๋‹ค๊ณ  ํŒ๋‹จํ–ˆ๋‹ค.

1. JWT ํ† ํฐ ์กด์žฌ ์—ฌ๋ถ€ ํ™•์ธ  
2. JWT ์œ ํšจ์„ฑ ๊ฒ€์ฆ  
3. Claims ์ถ”์ถœ ๋ฐ ์‚ฌ์šฉ์ž ์ •๋ณด ์ €์žฅ  
4. ๊ด€๋ฆฌ์ž ๊ถŒํ•œ ๊ฒ€์ฆ  
5. ์—๋Ÿฌ ์‘๋‹ต ์ƒ์„ฑ

์ฆ‰ ํ•˜๋‚˜์˜ ํด๋ž˜์Šค๊ฐ€ ์ธ์ฆ ๊ณผ ์ธ๊ฐ€ ๊ทธ๋ฆฌ๊ณ  ์˜ˆ์™ธ ์ฒ˜๋ฆฌ๊นŒ์ง€ ๋‹ด๋‹นํ•˜๊ณ  ์žˆ์—ˆ๋‹ค.

  • ํด๋ž˜์Šค์˜ ์ฑ…์ž„์ด ๊ณผ๋„ํ•˜๊ฒŒ ์ปค์ง
  • ์œ ์ง€๋ณด์ˆ˜ ์–ด๋ ค์›€
  • ํ…Œ์ŠคํŠธ ์–ด๋ ค์›€
  • ์ฝ”๋“œ ๊ฐ€๋…์„ฑ ์ €ํ•˜

2. ํ•ด๊ฒฐ ๋ฐฉ์•ˆ

2-1. ์˜์‚ฌ๊ฒฐ์ • ๊ณผ์ •

์ฒ˜์Œ์—๋Š” Filter๋ฅผ ์ธ์ฆ์šฉ/์ธ๊ฐ€์šฉ ๋‘ ๊ฐœ๋กœ ๋ถ„๋ฆฌํ•˜๋Š” ๊ตฌ์กฐ๋„ ๊ณ ๋ฏผํ–ˆ๋‹ค.
ํ•˜์ง€๋งŒ ํ˜„์žฌ ํ”„๋กœ์ ํŠธ ๊ทœ๋ชจ์™€ ๊ณผ์ œ ๋ฒ”์œ„๋ฅผ ๊ณ ๋ คํ–ˆ์„ ๋•Œ, ๋จผ์ € JwtFilter๋ฅผ ์–‡๊ฒŒ ๋งŒ๋“ค๊ณ  JWT ์ฒ˜๋ฆฌ ๋กœ์ง์„ ๋ณ„๋„ helper๋กœ ์œ„์ž„ํ•˜๋Š” ๋ฐฉ์‹์ด ๊ฐ€์žฅ ์ ์ ˆํ•˜๋‹ค๊ณ  ํŒ๋‹จํ–ˆ๋‹ค.


๊ฐ ํด๋ž˜์Šค์˜ ์—ญํ• ์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

ํด๋ž˜์Šค์—ญํ• 

JwtFilter ์š”์ฒญ ํ•„ํ„ฐ๋ง
JwtValidator JWT ๊ฒ€์ฆ
JwtAuthorizationChecker ๊ถŒํ•œ ๊ฒ€์‚ฌ

2-2. ํ•ด๊ฒฐ ๊ณผ์ •

JwtAuthHelper ํด๋ž˜์Šค๋ฅผ ์ƒ์„ฑํ–ˆ๋‹ค.

claims ์ถ”์ถœ, request attribute ์ €์žฅ, admin ๊ถŒํ•œ ์ฒดํฌ๋ฅผ JwtAuthHelper๋กœ ์ด๋™ํ–ˆ๋‹ค.

JwtFilter๋Š” ์š”์ฒญ ํ๋ฆ„ ์ œ์–ด์™€ ์˜ˆ์™ธ ์‘๋‹ต๋งŒ ๋‹ด๋‹นํ•˜๋„๋ก ์ˆ˜์ •ํ–ˆ๋‹ค.

@RequiredArgsConstructor
public class JwtAuthHelper {

    private final JwtUtil jwtUtil;

    public Claims validateAndExtractClaims(HttpServletRequest request) {
        String bearerJwt = request.getHeader("Authorization");

        if (bearerJwt == null) {
            throw new IllegalArgumentException("์ธ์ฆ ํ—ค๋”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.");
        }

        String jwt = jwtUtil.substringToken(bearerJwt);
        Claims claims = jwtUtil.extractClaims(jwt);

        if (claims == null) {
            throw new IllegalArgumentException("์œ ํšจํ•˜์ง€ ์•Š์€ ํ† ํฐ์ž…๋‹ˆ๋‹ค.");
        }

        return claims;
    }

    public void setUserAttributes(HttpServletRequest request, Claims claims) {
        request.setAttribute("userId", Long.parseLong(claims.getSubject()));
        request.setAttribute("email", claims.get("email"));
        request.setAttribute("userRole", claims.get("userRole"));
    }

    public void validateAdminAccess(String url, Claims claims) {
        UserRole userRole = UserRole.valueOf(claims.get("userRole", String.class));

        if (url.startsWith("/admin") && !UserRole.ADMIN.equals(userRole)) {
            throw new IllegalArgumentException("์ ‘๊ทผ ๊ถŒํ•œ์ด ์—†์Šต๋‹ˆ๋‹ค.");
        }
    }
}

3. ํ•ด๊ฒฐ ์™„๋ฃŒ

3-1. ํšŒ๊ณ 

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

์™„์ „ํžˆ ๋ณ„๋„ Filter๋กœ ๋ถ„๋ฆฌํ•˜์ง€๋Š” ์•Š์•˜์ง€๋งŒ, ์ตœ์†Œํ•œ ํ•˜๋‚˜์˜ ํด๋ž˜์Šค๊ฐ€ ๋ชจ๋“  ์ฑ…์ž„์„ ๊ฐ€์ง€๋Š” ๊ตฌ์กฐ๋Š” ๊ฐœ์„ ํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.

ํŠนํžˆ ๋‹ค์Œ ๊ฐœ๋…์„ ๋ช…ํ™•ํžˆ ์ดํ•ดํ•˜๊ฒŒ ๋˜์—ˆ๋‹ค.

๊ฐœ๋…์„ค๋ช…

์ธ์ฆ(Authentication) ์‚ฌ์šฉ์ž๊ฐ€ ๋ˆ„๊ตฌ์ธ์ง€ ํ™•์ธ
์ธ๊ฐ€(Authorization) ํ•ด๋‹น ์‚ฌ์šฉ์ž๊ฐ€ ์ ‘๊ทผ ๊ถŒํ•œ์ด ์žˆ๋Š”์ง€ ํ™•์ธ

3-2. ์ „ํ›„ ๋ฐ์ดํ„ฐ ๋น„๊ต

ํ•ญ๋ชฉ๊ฐœ์„  ์ „๊ฐœ์„  ํ›„

JwtFilter ์ฑ…์ž„ ์ธ์ฆ + ์ธ๊ฐ€ + ์‚ฌ์šฉ์ž ์ •๋ณด ์„ธํŒ… + ์—๋Ÿฌ ์‘๋‹ต ์š”์ฒญ ํ๋ฆ„ ์ œ์–ด + ์˜ˆ์™ธ ์‘๋‹ต
JWT ์ฒ˜๋ฆฌ ๋กœ์ง JwtFilter ๋‚ด๋ถ€ JwtAuthHelper๋กœ ๋ถ„๋ฆฌ
๊ฐ€๋…์„ฑ ๋‚ฎ์Œ ๊ฐœ์„ 
์œ ์ง€๋ณด์ˆ˜์„ฑ ๋ณ€๊ฒฝ ์ง€์ ์ด ๋งŽ์Œ ์—ญํ•  ๋ถ„๋ฆฌ๋กœ ๊ฐœ์„