Spring ์ฌํ ๋์ ๊ธฐ๋ฅ (Lv5 ~ Lv7)
์ด๋ฒ ๋จ๊ณ์์๋ ๊ธฐ์กด ํ์ ๊ธฐ๋ฅ ์์ ๋ค์ ์์ ์ ์งํํ๋ค.
- JWT ์ธ์ฆ ๊ตฌ์กฐ ๋ฆฌํฉํ ๋ง
- ์ธ์ฆ ์ฑ ์ ๋ถ๋ฆฌ
- ArgumentResolver ํ์ฉ
- ๋จ์ ํ ์คํธ ์์ฑ
- ํ ์คํธ ์ปค๋ฒ๋ฆฌ์ง ์ธก์
๋จ์ํ ๊ธฐ๋ฅ์ ์ถ๊ฐํ๋ ๊ฒ์ด ์๋๋ผ ์ฝ๋ ๊ตฌ์กฐ ๊ฐ์ ๊ณผ ํ ์คํธ ๊ฐ๋ฅํ ๊ตฌ์กฐ๋ฅผ ๋ง๋๋ ๊ฒ์ ์ง์คํ๋ค.
Lv5 JWT ์ธ์ฆ ๊ตฌ์กฐ ๊ฐ์
๋ฌธ์ ์ํฉ
JWT ์ธ์ฆ ๋ก์ง์ ๊ตฌํํ ๋ ์ฒ์์๋ ๋๋ถ๋ถ์ ๋ก์ง์ด JwtFilter์ ์ง์ค๋์ด ์์๋ค.
๊ธฐ์กด JwtFilter๋ ๋ค์ ์ญํ ์ ๋ชจ๋ ์ํํ๊ณ ์์๋ค.
JwtFilter
- ํ ํฐ ๊ฒ์ฆ
- Claims ์ถ์ถ
- ์ฌ์ฉ์ ์ ๋ณด request ์ ์ฅ
- ๊ด๋ฆฌ์ ๊ถํ ๊ฒ์ฌ
์ฆ ํ๋์ ํด๋์ค๊ฐ ๋๋ฌด ๋ง์ ์ญํ ์ ๊ฐ์ง๊ณ ์์๋ค.
์ด ๊ตฌ์กฐ๋ ๋ค์ ๋ฌธ์ ๋ฅผ ๋ง๋ค ์ ์๋ค.
- ํด๋์ค์ ์ฑ ์์ด ๋๋ฌด ๋ง์์ง
- ํ ์คํธ๊ฐ ์ด๋ ค์์ง
- ์ฝ๋ ์ ์ง๋ณด์๊ฐ ์ด๋ ค์์ง
์ด ๋ฌธ์ ๋ ๊ฐ์ฒด์งํฅ ์ค๊ณ ์์น ์ค ํ๋์ธ SRP(Single Responsibility Principle) ๋ฅผ ์๋ฐํ๋ ๊ตฌ์กฐ์๋ค.
SRP๋ ํ๋์ ํด๋์ค๋ ํ๋์ ์ฑ ์๋ง ๊ฐ์ ธ์ผ ํ๋ค๋ ์์น์ด๋ค.
ํด๊ฒฐ ๋ฐฉ๋ฒ
JWT ์ธ์ฆ ๊ด๋ จ ๋ก์ง์ JwtAuthHelper ํด๋์ค๋ก ๋ถ๋ฆฌํ์๋ค.
๊ตฌ์กฐ๋ ๋ค์๊ณผ ๊ฐ์ด ๋ณ๊ฒฝํ์๋ค.
๊ธฐ์กด ๊ตฌ์กฐ
JwtFilter
-> validate token
-> extract claims
-> set request attribute
-> check admin role
๊ฐ์ ๊ตฌ์กฐ
JwtFilter
-> JwtAuthHelper
JwtAuthHelper
validateAndExtractClaims()
setUserAttributes()
validateAdminAccess()
์ด๋ ๊ฒ ์ธ์ฆ ๊ด๋ จ ๋ก์ง์ Helper ํด๋์ค๋ก ์ด๋์์ผฐ๋ค.
JwtAuthHelper
ํ ํฐ ๊ฒ์ฆ๊ณผ Claims ์ถ์ถ์ ๋ด๋นํ๋ ๋ฉ์๋์ด๋ค.
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;
}
์ด ๋ฉ์๋๋ ๋ค์ ์์๋ก ๋์ํ๋ค.
Authorization Header ํ์ธ
- Bearer ํ ํฐ ๋ถ๋ฆฌ
- JWT Claims ์ถ์ถ
JWT์์ Claims๋ ํ ํฐ์ ๋ด๊ธด ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ์๋ฏธํ๋ค.
์๋ฅผ ๋ค์ด
userId
email
userRole
๊ฐ์ ์ ๋ณด๋ค์ด Claims์ ํฌํจ๋๋ค.
์ฌ์ฉ์ ์ ๋ณด request ์ ์ฅ
JWT์์ ์ถ์ถํ ์ ๋ณด๋ฅผ request attribute๋ก ์ ์ฅํ์๋ค.
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"));
}
์ด๋ ๊ฒ ์ ์ฅํ ์ด์ ๋ ์ดํ ๋จ๊ณ์์ ๋ค์ ๊ตฌ์กฐ๋ก ์ฌ์ฉํ๊ธฐ ์ํด์์ด๋ค.
JwtFilter
-> request attribute ์ ์ฅ
-> ArgumentResolver
-> Controller
์ฆ Controller์์๋ request๋ฅผ ์ง์ ์ฌ์ฉํ์ง ์๊ณ AuthUser ๊ฐ์ฒด๋ก ๋ฐ์ ์ ์๊ฒ ๋๋ค.
๊ด๋ฆฌ์ ๊ถํ ๊ฒ์ฌ
๊ด๋ฆฌ์ API ์ ๊ทผ ์ ๊ถํ์ ๊ฒ์ฌํ๋ ๋ก์ง์ด๋ค.
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("์ ๊ทผ ๊ถํ์ด ์์ต๋๋ค.");
}
}
์ฌ๊ธฐ์ ์ค์ํ ํฌ์ธํธ๋ ๋ค์์ด๋ค.
url.startsWith("/admin")
์ฆ ๊ด๋ฆฌ์ API ๊ฒฝ๋ก๋
/admin/comments
/admin/users
๊ฐ์ด ์์ํ๋๋ก ์ค๊ณ๋์ด ์๊ธฐ ๋๋ฌธ์
์ด ๊ฒฝ๋ก๋ฅผ ๊ธฐ์ค์ผ๋ก ๊ถํ ๊ฒ์ฌ๋ฅผ ์ํํ๋๋ก ๊ตฌํํ๋ค.
๊ตฌ์กฐ ๊ฐ์ ๊ฒฐ๊ณผ
๊ธฐ์กด ๊ตฌ์กฐ
JwtFilter
-> ์ธ์ฆ
-> ์ธ๊ฐ
-> ์ฌ์ฉ์ ์ ๋ณด ์ฒ๋ฆฌ
-> ํ ํฐ ์ฒ๋ฆฌ
๊ฐ์ ๊ตฌ์กฐ
JwtFilter
-> JwtAuthHelper
์ฆ
Filter -> ์์ฒญ ํ๋ฆ ์ ์ด
Helper -> ์ธ์ฆ ๋ก์ง ์ฒ๋ฆฌ
๋ก ์ญํ ์ ๋ถ๋ฆฌํ์๋ค.
Lv6 ์ฝ๋ ๊ตฌ์กฐ ๊ฐ์ ๋ฐ ๋ฆฌํฉํ ๋ง
๋ฌธ์ ์ธ์
๊ธฐ๋ฅ ๊ตฌํ์ด ์๋ฃ๋ ํ ์ฝ๋๋ฅผ ๋ค์ ํ์ธํ๋ฉด์ ๋ค์ ๋ฌธ์ ๋ฅผ ๋ฐ๊ฒฌํ๋ค.
- ์ธ์ฆ ์ ๋ณด๋ฅผ Controller์์ ์ง์ ๊บผ๋ด๋ ์ฝ๋ ์กด์ฌ
- request attribute ์ ๊ทผ ์ฝ๋ ๋ฐ๋ณต
- ํ ์คํธ ์์ฑ์ด ์ด๋ ค์ด ๊ตฌ์กฐ
์๋ฅผ ๋ค์ด ๊ธฐ์กด ๋ฐฉ์์ ๋ค์๊ณผ ๊ฐ์๋ค.
Long userId = (Long) request.getAttribute("userId");
์ด ๋ฐฉ์์ ๋ค์ ๋ฌธ์ ๋ฅผ ๊ฐ์ง๋ค.
- Controller๊ฐ HttpServletRequest์ ์์กด
- ์ธ์ฆ ์ ๋ณด ์ฒ๋ฆฌ ์ฝ๋๊ฐ ๋ฐ๋ณต๋จ
- ํ ์คํธ ์์ฑ์ด ์ด๋ ค์
ํด๊ฒฐ ๋ฐฉ๋ฒ
Spring์์ ์ ๊ณตํ๋ HandlerMethodArgumentResolver ๋ฅผ ํ์ฉํ์๋ค.
ArgumentResolver๋ Controller์ ํ๋ผ๋ฏธํฐ๋ฅผ ์๋์ผ๋ก ์์ฑํด์ฃผ๋ ๊ธฐ๋ฅ์ด๋ค.
์ฆ Controller์์๋ request๋ฅผ ์ง์ ๋ค๋ฃจ์ง ์์๋ ๋๋ค.
AuthUserArgumentResolver
Long userId = (Long) request.getAttribute("userId");
String email = (String) request.getAttribute("email");
UserRole userRole = UserRole.of((String) request.getAttribute("userRole"));
return new AuthUser(userId, email, userRole);
์ด ํด๋์ค์ ์ญํ ์ ๋ค์๊ณผ ๊ฐ๋ค.
request attribute์์ ์ธ์ฆ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์จ๋ค
-> AuthUser ๊ฐ์ฒด ์์ฑ
-> Controller ํ๋ผ๋ฏธํฐ๋ก ์ ๋ฌ
Controller ์ฝ๋ ๊ฐ์
๊ธฐ์กด ๋ฐฉ์
Long userId = (Long) request.getAttribute("userId");
๊ฐ์ ๋ฐฉ์
public ResponseEntity<CommentSaveResponse> saveComment(
@Auth AuthUser authUser,
...
)
Controller์์๋ AuthUser ๊ฐ์ฒด๋ง ์ฌ์ฉํ๋ฉด ๋๋ค.
๊ตฌ์กฐ ๊ฐ์ ํจ๊ณผ
Controller -> ์ธ์ฆ ๋ก์ง ์ ๊ฑฐ
ArgumentResolver -> ์ธ์ฆ ๊ฐ์ฒด ์์ฑ
Filter -> request attribute ์ ์ฅ
์ฆ ๋ค์ ๊ตฌ์กฐ๊ฐ ๋ง๋ค์ด์ก๋ค.
JwtFilter
-> JwtAuthHelper
-> request attribute ์ ์ฅ
ArgumentResolver
-> AuthUser ๊ฐ์ฒด ์์ฑ
Controller
-> AuthUser ์ฌ์ฉ
์ด ๊ตฌ์กฐ๋ ์ค์ Spring Security์ ์ธ์ฆ ํ๋ฆ๊ณผ๋ ์ ์ฌํ๋ค.
Lv6 ๋ฒจ๋ก๊ทธ
Lv 6. ์ ์ ์๋ ๊ธฐ๋ฅ ์ด์ธ ‘๋ด’๊ฐ ์ ์ํ ๋ฌธ์ ์ ํด๊ฒฐ ๊ณผ์ - jiiimni ๋ฒจ๋ก๊ทธ
Lv7 ํ ์คํธ ์ฝ๋ ์์ฑ ๋ฐ ์ปค๋ฒ๋ฆฌ์ง ์ธก์
์ด๋ฒ ๋จ๊ณ์์๋ ์๋น์ค ๋ก์ง๊ณผ ์ธ์ฆ ๋ก์ง์ ๋ํ ๋จ์ ํ ์คํธ๋ฅผ ์์ฑํ์๋ค.
ํ ์คํธ ์์ฑ ๋ชฉ์ ์ ๋ค์๊ณผ ๊ฐ๋ค.
- ๋น์ฆ๋์ค ๋ก์ง ๊ฒ์ฆ
- ๋ฆฌํฉํ ๋ง ์์ ์ฑ ํ๋ณด
- ์ฝ๋ ํ์ง ํฅ์
์์ฑํ ํ ์คํธ
Service ํ ์คํธ
CommentServiceTest
ManagerServiceTest
AuthServiceTest
UserServiceTest
Config ํ ์คํธ
JwtAuthHelperTest
GlobalExceptionHandlerTest
WebConfigTest
FilterConfigTest
ํ ์คํธ ์ปค๋ฒ๋ฆฌ์ง
IntelliJ Coverage ๊ธฐ์ค
์ ์ฒด ํด๋์ค ์ปค๋ฒ๋ฆฌ์ง 77%
๋ฉ์๋ ์ปค๋ฒ๋ฆฌ์ง 73%
๋ผ์ธ ์ปค๋ฒ๋ฆฌ์ง 76%
Domain ํจํค์ง 82%
Config ํจํค์ง 81%


ํ ์คํธ ์์ฑ ์์
@Test
void ๋๊ธ_์์ฑ์_์ฑ๊ณตํ๋ค() {
given(todoRepository.findById(anyLong()))
.willReturn(Optional.of(todo));
CommentSaveResponse response =
commentService.saveComment(authUser, 1L, request);
assertEquals("๋๊ธ", response.getContents());
}
์ด ํ ์คํธ๋ ๋ค์ ํ๋ฆ์ ๊ฒ์ฆํ๋ค.
Todo ์กด์ฌ ์ฌ๋ถ ํ์ธ
-> Comment ์์ฑ
-> Response ๋ฐํ
ํ ์คํธ ์์ฑํ๋ฉด์ ์๊ฒ ๋ ์
ํ ์คํธ ์ฝ๋๋ฅผ ์์ฑํ๋ฉด์ ๋ค์์ ๋ง์ด ๋๊ผ๋ค.
- ํ ์คํธ ๊ฐ๋ฅํ ๊ตฌ์กฐ๊ฐ ์ค์ํ๋ค
- ์ฑ ์ ๋ถ๋ฆฌ๊ฐ ๋์ด ์์ด์ผ ํ ์คํธ๊ฐ ์ฝ๋ค
- ํ ์คํธ ์ฝ๋๊ฐ ๋ฆฌํฉํ ๋ง ์์ ์ฑ์ ๋์ฌ์ค๋ค
ํนํ JwtFilter ๊ตฌ์กฐ๋ฅผ ๋ฆฌํฉํ ๋งํ๋ฉด์ ํ ์คํธ ์ฝ๋ ์์ฑ์ด ํจ์ฌ ์ฌ์์ก๋ค.
ํธ๋ฌ๋ธ์ํ
JWT Claims ํ ์คํธ ์ค๋ฅ
ํ ์คํธ ์ฝ๋ ์์ฑ ์ค ๋ค์ ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ค.
Cannot resolve symbol Claims
์์ธ์ build.gradle ์ค์ ์ด์๋ค.
๊ธฐ์กด ์ค์
compileOnly 'io.jsonwebtoken:jjwt-api'
compileOnly๋ ํ ์คํธ ํด๋์คํจ์ค์ ํฌํจ๋์ง ์๋๋ค.
๊ทธ๋์ ํ ์คํธ ์ฝ๋์์ Claims๋ฅผ ์ฌ์ฉํ ์ ์์๋ค.
ํด๊ฒฐ ๋ฐฉ๋ฒ
๋ค์๊ณผ ๊ฐ์ด ์์ ํ์๋ค.
implementation 'io.jsonwebtoken:jjwt-api'
์ดํ ํ ์คํธ ์ฝ๋์์ Claims๋ฅผ ์ ์์ ์ผ๋ก ์ฌ์ฉํ ์ ์์๋ค.
ํ๊ณ
์ด๋ฒ ๋์ ๊ธฐ๋ฅ์์๋ ๋จ์ํ ๊ธฐ๋ฅ ๊ตฌํ๋ณด๋ค ์ฝ๋ ๊ตฌ์กฐ ๊ฐ์ ๊ณผ ํ ์คํธ ์ฝ๋ ์์ฑ ๊ฒฝํ์ ๋ง์ด ํ ์ ์์๋ค.
JWT ์ธ์ฆ ๊ตฌ์กฐ ๋ฆฌํฉํ ๋ง
-> ์ฑ
์ ๋ถ๋ฆฌ
-> ArgumentResolver ํ์ฉ
-> ํ
์คํธ ์ฝ๋ ์์ฑ
์ด ๊ณผ์ ์ ํตํด Spring ์ ํ๋ฆฌ์ผ์ด์ ๊ตฌ์กฐ๋ฅผ ๋ ๊น์ด ์ดํดํ ์ ์์๋ค.
๋ํ Filter -> Helper -> Resolver -> Controller ๊ตฌ์กฐ๋
Spring Security์ ์ธ์ฆ ํ๋ฆ๊ณผ๋ ์ ์ฌํ๋ค๋ ์ ์์ ์๋ฏธ ์๋ ๊ฒฝํ์ด์๋ค.
ํ ์คํธ ์ฝ๋ ์์ฑ ๊ณผ์ ์์๋ AI๋ฅผ ์ฐธ๊ณ ํ์ฌ ํ ์คํธ ๊ตฌ์กฐ์ ์์ฑ ๋ฐฉ๋ฒ์ ํ์ตํ์๋ค.
๋จ์ํ ์ฝ๋๋ฅผ ๊ทธ๋๋ก ์ฌ์ฉํ๋ ๊ฒ์ด ์๋๋ผ ํ ์คํธ ๋์ ์๋ฆฌ๋ฅผ ์ดํดํ๊ณ ํ๋ก์ ํธ ๊ตฌ์กฐ์ ๋ง๊ฒ ์์ ํ์ฌ ์ ์ฉํ์๋ค.
์ด๋ฅผ ํตํด ๋จ์ ํ ์คํธ ์์ฑ ๋ฐฉ์๊ณผ ํ ์คํธ ์ปค๋ฒ๋ฆฌ์ง ์ธก์ ์ ๋ํ ์ดํด๋๋ฅผ ๋์ผ ์ ์์๋ค.