๐ง ์ํฉ
์ธ์ฆ ์ JWT๋ฅผ ์ฌ์ฉํ์ฌ ์ธ์ฆ์ ์งํํ๋ฉฐ, ํ์๊ฐ์ ๊ณผ ๋ก๊ทธ์ธ์ ์ ์ธํ ๋ชจ๋ URL์ ๋ํด์ ์ธ์ฆ ๊ด๋ จ Interceptor๋ฅผ ๊ตฌํํ์ฌ ๋ฑ๋กํด ๋ ์ํ์์ต๋๋ค.
ํด๋น ์ธํฐ์ ํฐ์์๋ AccessToken์ด ์๋ ๊ฒฝ์ฐ ์์ธ๋ฅผ throw ํ๋๋ก ๊ตฌํํ์๋๋ฐ, ๋ค์๊ณผ ๊ฐ์ ์ฝ๋์์ต๋๋ค.
@Component
public class LogInInterceptor implements HandlerInterceptor {
// ์๋ต
@Override
public boolean preHandle(final HttpServletRequest request,
final HttpServletResponse response,
final Object handler) {
AccessToken token = extractAccessTokenUseCase.command(
new ExtractAccessTokenUseCase.Command(request.getHeader(AUTHORIZATION_HEADER))
);
Claims claims = extractClaimsUseCase.command(
new ExtractClaimsUseCase.Command(token)
);
validateClaims(claims);
authenticationContext.setPrincipal(parseLong(claims.get(MEMBER_ID_CLAIM)));
return true;
}
private void validateClaims(final Claims claims) {
// ํด๋ ์์ด ์๋์ง ๊ฒ์ฌ
if (claims.get(MEMBER_ID_CLAIM) == null) {
throw new AuthenticationException(INVALID_ACCESS_TOKEN);
}
// ํด๋ ์์ด Long์ผ๋ก ํ์ฑ๋ ์ ์๋์ง ๊ฒ์ฌ
try {
parseLong(claims.get(MEMBER_ID_CLAIM));
} catch (NumberFormatException e) {
throw new AuthenticationException(INVALID_ACCESS_TOKEN);
}
}
}
๊ทธ๋ฆฌ๊ณ ์ด๋ฅผ ๋ค์๊ณผ ๊ฐ์ด ๋ฑ๋กํ์์ต๋๋ค.
@Override
public void addInterceptors(final InterceptorRegistry registry) {
registry.addInterceptor(logInInterceptor)
.addPathPatterns("/api/**")
.excludePathPatterns(SIGN_UP_URL, LOGIN_URL);
}
๋ฌธ์ ๋ ์ด๋ ๊ฒ ๋ฑ๋กํ๋ค ๋ณด๋, ๋ค์๊ณผ ๊ฐ์ ๋ฌธ์ ๊ฐ ๋ฐ์ํ์์ต๋๋ค.
์ ์๋์ง ์๋ url ์ค, /api/~~ ์ผ๋ก ์์ฒญ์ด ๋ค์ด์ค๋ ๊ฒฝ์ฐ(addPathPatterns์ ์ผ์นํ๋ ๊ฒฝ์ฐ),
AccessToken์ด ์๋ค๋ฉด 404 ์๋ฌ๊ฐ ์๋ 500 ์์ธ๊ฐ ๋ฐ์ํฉ๋๋ค.
์กฐ๊ธ ๋ ์ ๊ธฐํ ์ ์ ์ด์ธ์ url๋ก ๋ค์ด์ค๋ ๊ฒฝ์ฐ์๋ AccessToken์ด ์์ด๋ 404๊ฐ ์ ๋๋ก ๋ฐํ๋๋ค๋ ์ ์ด์์ต๋๋ค.
์ด๋ฌํ ์ด์ ๋ฅผ ์์๋ณด๋๋ก ํ๊ฒ ์ต๋๋ค.
๐ง ์์ธ
์คํ๋ง์ DispatcherServlet์ ์ฝ๋๋ฅผ ์ดํด๋ณผ ํ์์ฑ์ด ์์ต๋๋ค.
์ค์ํ ๋ถ๋ถ์ ๋๋ฒ๊น ํ์๋ฅผ ๋ฃ์ด ๋นจ๊ฐ์์ผ๋ก ํ์๋๊ฒ ํ์์ต๋๋ค.
๋จผ์ ์์ฒญ์ ๋ํ์ฌ Handler๋ฅผ ์กฐํํฉ๋๋ค.
๋ง์ฝ Handler๋ฅผ ์ฐพ์ง ๋ชปํ๋ค๋ฉด mappedHandler == null ์ด๋ฏ๋ก noHandlerFound๋ฅผ ์ํํ ํ, returnํฉ๋๋ค.
(์ต์ข ์ ์ผ๋ก 404 ๋ฐํ)
๊ทธ๋ฌ๋ Handler๋ฅผ ์ฐพ์ ๊ฒฝ์ฐ Handler๋ฅผ ์ฒ๋ฆฌํ ์ ์๋ HandlerAdapter๋ฅผ ์กฐํํ ํ
handler.applyPreHandle()์ ์ํํ๋๋ฐ, ์ด๊ณณ์์ Interceptor์ preHandle()์ด ํธ์ถ๋ฉ๋๋ค.
์ฐธ๊ณ ๋ก ์ด ๊ฒฝ์ฐ Interceptor์์ ์์ธ๊ฐ ๋ฐ์ํ๋ ๊ฒฝ์ฐ ๋๊ฐ์ง ์ํฉ์ด ๋ฐ์ํ ์ ์์ต๋๋ค.
1. ์์ฒญ url์ ๋์๋๋ url์ด @Controller ํด๋์ค ๋ด์์ @RequestMapping์ ํตํด ์กด์ฌํ๋ ๊ฒฝ์ฐ
-> @ControllerAdvice์ ์ํด ์ ์์ ์ผ๋ก ์ฒ๋ฆฌ๋ฉ๋๋ค.
2. ๊ทธ๋ ์ง ์์ ๊ฒฝ์ฐ
@ControllerAdvice ์ ์ํ์ฌ ์ฒ๋ฆฌ๋์ง ์์ต๋๋ค.
(์ด๋ AbstractHandlerMethodExceptionResolver์ shouldApplyTo() ๋ฉ์๋ ๋๋ฌธ์ ๋๋ค.)
ํฐ ํ๋ฆ์ ์ ๋ฆฌํ์๋ฉด ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
์์ฒญ์ ๋ํ Handler๋ฅผ ์ฐพ์ ๊ฒฝ์ฐ -> HandlerAdapter ์กฐํ -> Interceptor์ preHandle() ์ํ -> ํธ๋ค๋ฌ ์คํ
์์ฒญ์ ๋ํ Handler๋ฅผ ์ฐพ์ง ๋ชปํ ๊ฒฝ์ฐ -> noHandlerFound() ํธ์ถ ํ return (์ต์ข ์ ์ผ๋ก NOT FOUND ๋ฐํ)
๐ง ํธ๋ค๋ฌ๊ฐ ์ ์ฐพ์์ ธ์ผ ํ ํ ๋ฐ...
์์ ๊ณผ์ ์ ์ดํดํ๋ค๋ฉด, ์กฐ๊ธ ์ด์ํ ๋ถ๋ถ์ด ์กด์ฌํฉ๋๋ค.
์ฐ์ ์ ์ ์ํฉ์ ๋ค์ ์ ๋ฆฌํด๋ณด๊ฒ ์ต๋๋ค.
์ ์๋์ง ์๋ url ์ค, /api/~~ ์ผ๋ก ์์ฒญ์ด ๋ค์ด์ค๋ ๊ฒฝ์ฐ, AccessToken์ด ์๋ค๋ฉด 404 ์๋ฌ๊ฐ ์๋ 500 ์์ธ๊ฐ ๋ฐ์ํฉ๋๋ค.
/api/~~๊ฐ ์๋ /xxx ๋ฑ์ url๋ก ๋ค์ด์ค๋ ๊ฒฝ์ฐ์๋, AccessToken์ด ์์ด๋ 404๊ฐ ์ ๋๋ก ๋ฐํ๋ฉ๋๋ค.
๋ถ๋ช ์์ ์ฝ๋๋๋ก๋ผ๋ฉด ์ ์๋์ง ์์ url์ ๋ํด์๋ ํธ๋ค๋ฌ๋ฅผ ์ฐพ์ ์ ์์ผ๋ฏ๋ก 404๊ฐ ๋ฐํ๋์ด์ผ ํ๋ ๊ฒ์ด ๋ง์ต๋๋ค.
๊ทธ๋ผ ๋ฌด์์ด ๋ฌธ์ ์ผ๊น์??
๋๋ฒ๊น ์ ํด๋ณด๋ฉด ๋ค์๊ณผ ๊ฐ์ ์ฌ์ค์ ์ ์ ์์ต๋๋ค.
์ ์๋์ง ์์ URL์ ์์ฒญ์ ๋ณด๋ด๋ ๊ฒฝ์ฐ์๋ Handler๊ฐ ์ฐพ์์ง๋ค๋ ๊ฒ์ด์์ต๋๋ค.
๊ทธ ํธ๋ค๋ฌ๋ ๋ฐ๋ก ResourceHttpRequestHandler ์์ต๋๋ค.
ํด๋น ํธ๋ค๋ฌ๊ฐ ์กฐํ๋๊ธฐ ๋๋ฌธ์ Interceptor์ preHandle()์ด ํธ์ถ๋๋ ๊ฒ์ด์๊ณ , ๋ฐ๋ผ์ 404 NOT FOUND ์์ธ๊ฐ ๋ฐํ๋์ง ์๋ ๊ฒ์ด์์ต๋๋ค.
๐ง ํด๊ฒฐ๋ฐฉ๋ฒ
Application.properties์ ๋ค์๊ณผ ๊ฐ์ ์์ฑ์ ์ถ๊ฐํฉ๋๋ค.
spring.web.resources.add-mappings=false
์ด๋ฅผ ํตํด ResourceHttpRequestHandler๊ฐ ๋งคํ๋๋ ๊ฒ์ ๋ฐฉ์งํ ์ ์์ต๋๋ค.
๋ง์ฝ Resource์ ๋ํ ์ ๊ทผ์ ์ฌ์ฉํ๊ณ ์๋ ๊ฒฝ์ฐ๋ผ๋ฉด, ์๋์ ๊ฐ์ ์ค์ ์ ์ถ๊ฐํด ์ฃผ์ด์ผ ํฉ๋๋ค.
@Configuration
public class AuthenticationConfig implements WebMvcConfigurer {
// ์๋ต
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/docs/**")
.addResourceLocations("classpath:/static/docs/");
}
}
๐ง Reference
https://tecoble.techcourse.co.kr/post/2021-11-24-spring-customize-unhandled-api/
https://dev-monkey-dugi.tistory.com/136