[Spring] JwtFilter - ArgumentResolver

์กฐ๋ฏผ๊ฒฝยท2025๋…„ 3์›” 12์ผ
0

Spring

๋ชฉ๋ก ๋ณด๊ธฐ
5/13

Chapter 1 : ๐Ÿ”’ย ์ธ์ฆ๊ณผ ์ธ๊ฐ€


๐Ÿ“Œ Spring์—์„œ ์ธ์ฆ(Authentication)๊ณผ ์ธ๊ฐ€(Authorization)๋Š” ๋ณด์•ˆ์˜ ํ•ต์‹ฌ ๊ฐœ๋…์œผ๋กœ, ์‚ฌ์šฉ์ž์˜ ์‹ ์› ํ™•์ธ๊ณผ ๊ถŒํ•œ ๊ด€๋ฆฌ๋ฅผ ๋‹ด๋‹นํ•œ๋‹ค.

๐Ÿ”‘ ์ธ์ฆ: ๋„ˆ ๋ˆ„๊ตฌ์•ผ?
๐Ÿ”’ ์ธ๊ฐ€: ๋„ˆ ์ด๊ฑฐ ํ•  ์ˆ˜ ์žˆ์–ด?



๐Ÿ”‘ ์ธ์ฆ(Authentication)

๐Ÿ“Œ ์ธ์ฆ์€ ์‚ฌ์šฉ์ž๊ฐ€ ๋ˆ„๊ตฌ์ธ์ง€ ํ™•์ธํ•˜๋Š” ๊ณผ์ •์ด๋‹ค. -> ๋กœ๊ทธ์ธ

  • ์ผ๋ฐ˜์ ์œผ๋กœ ์•„์ด๋””(ID)์™€ ๋น„๋ฐ€๋ฒˆํ˜ธ(Password)๋ฅผ ์ž…๋ ฅํ•˜์—ฌ ๋ณธ์ธ์ด ๋ˆ„๊ตฌ์ธ์ง€ ์ฆ๋ช…ํ•œ๋‹ค.

  • JWT, OAuth2, Session/Cookie ๋“ฑ์„ ์ด์šฉํ•ด ์ธ์ฆํ•  ์ˆ˜ ์žˆ๋‹ค.


โœ… Spring์—์„œ ์ธ์ฆ ๋ฐฉ๋ฒ•

  • Spring Security: ๊ฐ€์žฅ ๋งŽ์ด ์‚ฌ์šฉ๋˜๋Š” ์ธ์ฆ ํ”„๋ ˆ์ž„์›Œํฌ

  • JWT (JSON Web Token): ํ† ํฐ ๊ธฐ๋ฐ˜ ์ธ์ฆ ๋ฐฉ์‹

  • Session & Cookie: ์„œ๋ฒ„์—์„œ ์„ธ์…˜์„ ๊ด€๋ฆฌํ•˜๋Š” ๋ฐฉ์‹

  • OAuth 2.0: Google, Facebook ๋“ฑ์˜ ์™ธ๋ถ€ ์„œ๋น„์Šค ๋กœ๊ทธ์ธ ์—ฐ๋™


โœ… Spring Security์—์„œ ์ธ์ฆ ํ๋ฆ„

  1. ์‚ฌ์šฉ์ž๊ฐ€ ๋กœ๊ทธ์ธ ์š”์ฒญ (ID, PW)

  2. AuthenticationManager๊ฐ€ UserDetailsService๋ฅผ ํ†ตํ•ด ์‚ฌ์šฉ์ž ์ •๋ณด ์กฐํšŒ

  3. ๋น„๋ฐ€๋ฒˆํ˜ธ ๊ฒ€์ฆ ํ›„, ์„ฑ๊ณตํ•˜๋ฉด SecurityContextHolder์— Authentication ๊ฐ์ฒด ์ €์žฅ

  4. ์ธ์ฆ ์„ฑ๊ณต ์‹œ, JWT ํ† ํฐ ๋˜๋Š” ์„ธ์…˜์„ ์ƒ์„ฑํ•˜์—ฌ ๋ฐ˜ํ™˜




๐Ÿ”“ ์ธ๊ฐ€(Authorization)

๐Ÿ“Œ ์ธ๊ฐ€(๊ถŒํ•œ ๋ถ€์—ฌ)๋Š” ํŠน์ • ์‚ฌ์šฉ์ž๊ฐ€ ์–ด๋–ค ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ๊ฒฐ์ •ํ•˜๋Š” ๊ณผ์ •์ด๋‹ค. -> ๊ถŒํ•œ

  • ์˜ˆ๋ฅผ ๋“ค์–ด, ์ผ๋ฐ˜ ์‚ฌ์šฉ์ž๋Š” ์ฃผ๋ฌธ์„ ํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, ๊ฐ€๊ฒŒ ์‚ฌ์žฅ๋‹˜์€ ๊ฐ€๊ฒŒ ์ •๋ณด๋ฅผ ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.

  • ์ฆ‰, ์ธ์ฆ๋œ ์‚ฌ์šฉ์ž๊ฐ€ ํŠน์ • ๋ฆฌ์†Œ์Šค(URI, ๋ฉ”์„œ๋“œ, ๋ฐ์ดํ„ฐ)์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ํ™•์ธํ•˜๋Š” ๊ณผ์ •์ด๋‹ค.


โœ… Spring์—์„œ ์ธ๊ฐ€ ๋ฐฉ๋ฒ•

1. Spring Security์˜ ๊ถŒํ•œ(Role) ๊ธฐ๋ฐ˜ ์ธ๊ฐ€

  • @PreAuthorize("hasRole('ADMIN')") โ†’ ํŠน์ • ์—ญํ• ์„ ๊ฐ€์ง„ ์‚ฌ์šฉ์ž๋งŒ ์ ‘๊ทผ ํ—ˆ์šฉ

  • @Secured("ROLE_USER") โ†’ ํŠน์ • ์—ญํ• ์„ ๊ฐ€์ง„ ์‚ฌ์šฉ์ž๋งŒ ๋ฉ”์„œ๋“œ ์‹คํ–‰ ๊ฐ€๋Šฅ

  • @RolesAllowed("ROLE_OWNER") โ†’ ์ง€์ •๋œ ์—ญํ• ์ด ์žˆ์–ด์•ผ ์ ‘๊ทผ ๊ฐ€๋Šฅ


2. URL ๊ธฐ๋ฐ˜ ์ ‘๊ทผ ์ œ์–ด

http
    .authorizeRequests()
    .antMatchers("/admin/**").hasRole("ADMIN")
    .antMatchers("/user/**").hasRole("USER")
    .anyRequest().authenticated();

3. Method ๊ธฐ๋ฐ˜ ์ ‘๊ทผ ์ œ์–ด

@PreAuthorize("hasRole('OWNER')")
public void updateStoreInfo() { ... }




Chapter 2 : ๐Ÿ›ก๏ธย JwtFilter


๐Ÿ“Œ JwtFilter๋Š” Spring Security์˜ Filter ์ค‘ ํ•˜๋‚˜๋กœ, ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๋ณด๋‚ธ ์š”์ฒญ์—์„œ JWT(ํ† ํฐ)๋ฅผ ์ถ”์ถœํ•˜๊ณ  ๊ฒ€์ฆํ•˜๋Š” ์—ญํ• ์„ ํ•œ๋‹ค.



๐Ÿš€ JwtFilter์˜ ์—ญํ• 

๐Ÿ” ํด๋ผ์ด์–ธํŠธ ์š”์ฒญ์—์„œ JWT ์ถ”์ถœ

  • ์š”์ฒญ ํ—ค๋”(Authorization: Bearer <token>)์—์„œ JWT๋ฅผ ๊ฐ€์ ธ์˜ด.

๐Ÿ”‘ JWT ๊ฒ€์ฆ ๋ฐ ํŒŒ์‹ฑ

  • ์„œ๋ช…(Signature) ๊ฒ€์ฆ, ๋งŒ๋ฃŒ ์‹œ๊ฐ„ ํ™•์ธ, ํ† ํฐ ๋‚ด๋ถ€์˜ ์‚ฌ์šฉ์ž ์ •๋ณด ํŒŒ์‹ฑ.

๐Ÿ‘ค ์œ ์ € ์ •๋ณด๋ฅผ SecurityContext์— ์ €์žฅ

  • ๊ฒ€์ฆ์ด ์™„๋ฃŒ๋˜๋ฉด ํ•ด๋‹น ์‚ฌ์šฉ์ž์˜ ์ธ์ฆ ๊ฐ์ฒด๋ฅผ SecurityContextHolder์— ๋“ฑ๋ก.




๐Ÿ“œ Filter์—์„œ JWT ์œ ์ € ๋ฐ์ดํ„ฐ ์ถ”์ถœ

โœ… JWT์—์„œ ์ถ”์ถœ ๊ฐ€๋Šฅํ•œ ์ •๋ณด

  • User ID

  • Email

  • Role

  • ๊ธฐํƒ€ ์‚ฌ์šฉ์ž ์ •๋ณด (ํ•„์š” ์‹œ ์ถ”๊ฐ€ ๊ฐ€๋Šฅ)

String token = request.getHeader("Authorization").substring(7); // "Bearer " ์ œ๊ฑฐ
Claims claims = jwtUtil.parseToken(token);
String userId = claims.getSubject();
String role = claims.get("role", String.class);




๐Ÿ“ค Filter์—์„œ ์œ ์ € ๋ฐ์ดํ„ฐ๋ฅผ ArgumentResolver๋กœ ์ „๋‹ฌํ•˜๋Š” ๋ฐฉ๋ฒ•

๐Ÿš€ ๋ฌธ์ œ:

JwtFilter์—์„œ JWT ์ •๋ณด๋ฅผ ์ถ”์ถœํ–ˆ์ง€๋งŒ, ์ปจํŠธ๋กค๋Ÿฌ์—์„œ ํŽธ๋ฆฌํ•˜๊ฒŒ ์‚ฌ์šฉํ•˜๋ ค๋ฉด?


โœ… ํ•ด๊ฒฐ์ฑ…:

๐Ÿ“ Request Attribute ์‚ฌ์šฉ

๐Ÿงต ThreadLocal ์‚ฌ์šฉ

โœจ Custom ArgumentResolver ์‚ฌ์šฉ




๐Ÿ”„ ArgumentResolver๋ฅผ ํ™œ์šฉํ•œ ์œ ์ € ๋ฐ์ดํ„ฐ ์ „๋‹ฌ

๐Ÿ“Œ ์ปจํŠธ๋กค๋Ÿฌ์—์„œ @LoginUser ๊ฐ™์€ ์–ด๋…ธํ…Œ์ด์…˜์„ ๋ถ™์—ฌ ๋ฐ”๋กœ ์œ ์ € ์ •๋ณด๋ฅผ ๋ฐ›๋„๋ก ์ฒ˜๋ฆฌ ๊ฐ€๋Šฅ!


โœ… ์œ ์ € ์ •๋ณด๋ฅผ ์ €์žฅํ•˜๋Š” DTO

@Getter
@AllArgsConstructor
public class LoginUser {
    private Long id;
    private String email;
    private String role;
}

โœ… JwtFilter์—์„œ Request์— ์œ ์ € ์ •๋ณด ์ €์žฅ

LoginUser loginUser = new LoginUser(userId, email, role);
request.setAttribute("loginUser", loginUser); // Request์— ์ €์žฅ

โœ… ArgumentResolver ๊ตฌํ˜„

@Component
public class LoginUserArgumentResolver implements HandlerMethodArgumentResolver {

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.getParameterType().equals(LoginUser.class);
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
                                  NativeWebRequest webRequest, WebDataBinderFactory binderFactory) {
        return webRequest.getAttribute("loginUser", RequestAttributes.SCOPE_REQUEST);
    }
}

โœ… ์ปจํŠธ๋กค๋Ÿฌ์—์„œ ์‚ฌ์šฉ ์˜ˆ์‹œ

@RestController
@RequestMapping("/api")
public class UserController {

    @GetMapping("/profile")
    public ResponseEntity<?> getUserProfile(@LoginUser LoginUser loginUser) {
        return ResponseEntity.ok("Hello, " + loginUser.getEmail());
    }
}




๐Ÿ“ ์ •๋ฆฌ

โœ… JwtFilter ํ๋ฆ„ ์ •๋ฆฌ

1๏ธโƒฃ JwtFilter๊ฐ€ ์กด์žฌํ•ด์•ผ ํ•œ๋‹ค.

2๏ธโƒฃ Filter์˜ Authorization ํ—ค๋”์—์„œ JWT ์ถ”์ถœ ๋ฐ ๊ฒ€์ฆ

  • ๐Ÿ”‘ JWT ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ (์„œ๋ช… ํ™•์ธ, ๋งŒ๋ฃŒ ์‹œ๊ฐ„ ์ฒดํฌ ๋“ฑ)
  • ๐Ÿ‘ค JWT์—์„œ ์œ ์ € ๋ฐ์ดํ„ฐ ์ถ”์ถœ (ID, Email, Role ๋“ฑ)

3๏ธโƒฃ ํ•„ํ„ฐ์—์„œ ์ถ”์ถœํ•œ ์œ ์ € ๋ฐ์ดํ„ฐ๋ฅผ controller์— ์ „๋‹ฌํ•˜๊ธฐ ์œ„ํ•ด ArgumentResolver๋กœ ์ „๋‹ฌ

  • ๐Ÿšจ ์ด ๋ฐ์ดํ„ฐ๋ฅผ ArgumentResolver(ํ˜น์€ ๋‹ค๋ฅธ Filter)๋กœ ์ „๋‹ฌํ•  ๋ฐฉ๋ฒ• ํ•„์š”

4๏ธโƒฃ ์œ ์ € ์ •๋ณด๋ฅผ Request Attribute์— ์ €์žฅ

5๏ธโƒฃ ArgumentResolver์—์„œ Request Attribute์˜ ๋ฐ์ดํ„ฐ๋ฅผ controller์— ์ „๋‹ฌ

6๏ธโƒฃ controller์—์„œ @LoginUser๋ฅผ ์‚ฌ์šฉํ•ด ๊ฐ„ํŽธํ•˜๊ฒŒ ์œ ์ € ์ •๋ณด ์ ‘๊ทผ ๊ฐ€๋Šฅ



๐Ÿ’ก JwtFilter ์žฅ์ 

  • ์ปจํŠธ๋กค๋Ÿฌ๊ฐ€ JwtFilter์˜ ๊ตฌํ˜„ ๋ฐฉ์‹์— ์˜์กดํ•˜์ง€ ์•Š์Œ

  • ํ…Œ์ŠคํŠธ ๋ฐ ์œ ์ง€๋ณด์ˆ˜๊ฐ€ ์‰ฌ์›€

  • @LoginUser ๊ฐ™์€ ์–ด๋…ธํ…Œ์ด์…˜์„ ํ†ตํ•ด ์ฝ”๋“œ๊ฐ€ ๊ฐ„๊ฒฐํ•ด์ง

0๊ฐœ์˜ ๋Œ“๊ธ€