๐Ÿฐ [flowbit] #14. JWT๋ฅผ ์‹ค์ œ ์ธ์ฆ ํ๋ฆ„์— ์—ฐ๊ฒฐํ•˜๊ธฐ

bean8080๐Ÿซ›ยท์•ฝ 14์‹œ๊ฐ„ ์ „

flowbit ๐Ÿฐโ˜˜๏ธ

๋ชฉ๋ก ๋ณด๊ธฐ
15/15

โ˜˜๏ธ 1. ์˜ค๋Š˜ ๋ชฉํ‘œ

์ด์ „ ์ž‘์—…์—์„œ๋Š” ํšŒ์›๊ฐ€์ž…, ๋กœ๊ทธ์ธ, JWT ๋ฐœ๊ธ‰๊นŒ์ง€ ๊ตฌํ˜„ํ–ˆ๋‹ค.

๋กœ๊ทธ์ธ์— ์„ฑ๊ณตํ•˜๋ฉด accessToken์„ ๋ฐœ๊ธ‰๋ฐ›์„ ์ˆ˜ ์žˆ์—ˆ๊ณ , ์ธ์ฆ์˜ ์ฒซ ๋‹จ๊ณ„๋Š” ์™„์„ฑ๋œ ์ƒํƒœ์˜€๋‹ค.

ํ•˜์ง€๋งŒ ์•„์ง ํ•œ ๊ฐ€์ง€ ๋ฌธ์ œ๊ฐ€ ๋‚จ์•„ ์žˆ์—ˆ๋‹ค.

๋กœ๊ทธ์ธ์— ์„ฑ๊ณตํ–ˆ๋‹ค๋Š” ์‚ฌ์‹ค์„ ์„œ๋ฒ„๋Š” ์•Œ๊ณ  ์žˆ์—ˆ์ง€๋งŒ,
๊ทธ ํ† ํฐ์ด ์‹ค์ œ API ์š”์ฒญ์—์„œ๋Š” ์‚ฌ์šฉ๋˜์ง€ ์•Š๊ณ  ์žˆ์—ˆ๋‹ค.

์ฆ‰ ํ˜„์žฌ ์ƒํƒœ๋Š” ์ด๋Ÿฐ ๋А๋‚Œ์ด์—ˆ๋‹ค.

๋กœ๊ทธ์ธ ์„ฑ๊ณต
โ†’ JWT ๋ฐœ๊ธ‰
โ†’ ๋

์ถœ์ž…์ฆ์€ ๋ฐœ๊ธ‰ํ–ˆ๋Š”๋ฐ,
์•„์ง ๋ฌธ ์•ž ๊ฒฝ๋น„์›์ด ์—†๋Š” ์ƒํƒœ์˜€๋‹ค.

๊ทธ๋ž˜์„œ ์ด๋ฒˆ ์ž‘์—… ๋ชฉํ‘œ๋Š” ๋ช…ํ™•ํ–ˆ๋‹ค.

JWT๋ฅผ ์‹ค์ œ ์š”์ฒญ ์ธ์ฆ ํ๋ฆ„์— ์—ฐ๊ฒฐํ•œ๋‹ค.


โ˜˜๏ธ 2. JWT๋ฅผ ์™œ ์š”์ฒญ๋งˆ๋‹ค ๋‹ค์‹œ ๋ณด๋‚ด์•ผ ํ• ๊นŒ

์ฒ˜์Œ์—๋Š” ์ด๋Ÿฐ ์ƒ๊ฐ์ด ๋“ค์—ˆ๋‹ค.

๋กœ๊ทธ์ธ ์„ฑ๊ณตํ–ˆ๋Š”๋ฐ ์™œ ๋˜ ํ† ํฐ์„ ๋ณด๋‚ด์•ผ ํ•˜์ง€?

๊ทธ ์ด์œ ๋Š” JWT ๋ฐฉ์‹์€ ์„ธ์…˜ ๋ฐฉ์‹๊ณผ ๋‹ค๋ฅด๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

	์„ธ์…˜ ๋ฐฉ์‹:

		๋กœ๊ทธ์ธ
		โ†’ ์„œ๋ฒ„๊ฐ€ ์ƒํƒœ ๊ธฐ์–ต

	JWT ๋ฐฉ์‹:

		๋กœ๊ทธ์ธ
		โ†’ ํ† ํฐ ๋ฐœ๊ธ‰

		API ์š”์ฒญ
		โ†’ ํ† ํฐ ๊ฐ™์ด ์ „๋‹ฌ
		โ†’ ์„œ๋ฒ„๊ฐ€ ๋งค๋ฒˆ ๊ฒ€์ฆ

์ฆ‰ ์„œ๋ฒ„๋Š” ๋กœ๊ทธ์ธ ์ƒํƒœ๋ฅผ ์ €์žฅํ•˜์ง€ ์•Š๋Š”๋‹ค.

๋Œ€์‹  ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๋งค ์š”์ฒญ๋งˆ๋‹ค

Authorization: Bearer {accessToken}

ํ˜•ํƒœ๋กœ ์ถœ์ž…์ฆ์„ ๋“ค๊ณ  ์˜จ๋‹ค.


โ˜˜๏ธ 3. JWT Filter ์ถ”๊ฐ€

๊ทธ๋ž˜์„œ ์š”์ฒญ์ด Controller๊นŒ์ง€ ๊ฐ€๊ธฐ ์ „์—
๋จผ์ € ํ† ํฐ์„ ๊ฒ€์‚ฌํ•˜๋Š” ํ•„ํ„ฐ๋ฅผ ์ถ”๊ฐ€ํ–ˆ๋‹ค.

์š”์ฒญ ํ๋ฆ„์€ ๋‹ค์Œ์ฒ˜๋Ÿผ ๋ฐ”๋€Œ์—ˆ๋‹ค.

๋ธŒ๋ผ์šฐ์ €
โ†“
JWT Filter
โ†“
Controller
โ†“
Service

JWT Filter ๋‚ด๋ถ€ ํ๋ฆ„์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

Authorization ํ—ค๋” ํ™•์ธ
โ†“
Bearer ์ œ๊ฑฐ
โ†“
JWT ์ถ”์ถœ
โ†“
ํ† ํฐ ๊ฒ€์ฆ
โ†“
์‚ฌ์šฉ์ž ์กฐํšŒ
โ†“
SecurityContext ์ €์žฅ

JWT ๊ฒ€์ฆ ์ฑ…์ž„์€ ๊ธฐ์กด JwtTokenProvider๋ฅผ ํ™•์žฅํ•ด์„œ ์ฒ˜๋ฆฌํ–ˆ๋‹ค.

public boolean validateToken(String token) {
    try {
        parseClaims(token);
        return true;
    } catch (Exception e) {
        return false;
    }
}

ํ† ํฐ์„ ์ฝ๊ธฐ๋งŒ ํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ,
์„œ๋ช…๊ณผ ๋งŒ๋ฃŒ์‹œ๊ฐ„๊นŒ์ง€ ๊ฒ€์ฆํ•˜๋„๋ก ํ–ˆ๋‹ค.


โ˜˜๏ธ 4. Spring Security๊ฐ€ ํ˜„์žฌ ์‚ฌ์šฉ์ž๋ฅผ ๊ธฐ์–ตํ•˜๋Š” ๋ฐฉ์‹

JWT Filter๋ฅผ ๋ถ™์ด๊ณ  ๋‚˜์„œ๋„ ํ•œ ๊ฐ€์ง€ ๋ฌธ์ œ๊ฐ€ ์žˆ์—ˆ๋‹ค.

์ธ์ฆ์€ ๋˜์—ˆ๋Š”๋ฐ

๋ˆ„๊ฐ€ ๋กœ๊ทธ์ธํ–ˆ๋Š”์ง€

๋ฅผ ์ฝ”๋“œ ์•ˆ์—์„œ ์‰ฝ๊ฒŒ ๊บผ๋‚ผ ์ˆ˜ ์—†์—ˆ๋‹ค.

Spring Security๋Š” ๋‚ด๋ถ€์ ์œผ๋กœ ๋‹ค์Œ ๊ณต๊ฐ„์— ํ˜„์žฌ ์‚ฌ์šฉ์ž๋ฅผ ์ €์žฅํ•œ๋‹ค.

SecurityContext

๊ตฌ์กฐ๋Š” ์ด๋Ÿฐ ๋А๋‚Œ์ด๋‹ค.

SecurityContext

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ userId=3  โ”‚
โ”‚ email=... โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

ํ•˜์ง€๋งŒ ๋งค๋ฒˆ ๋‹ค์Œ์ฒ˜๋Ÿผ ๊บผ๋‚ด๋Š” ๊ฑด ๋„ˆ๋ฌด ๋ณต์žกํ–ˆ๋‹ค.

SecurityContextHolder
        .getContext()
        .getAuthentication()

๊ทธ๋ž˜์„œ ํ˜„์žฌ ๋กœ๊ทธ์ธ ์‚ฌ์šฉ์ž๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ์ „์šฉ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋งŒ๋“ค์—ˆ๋‹ค.

@Component
public class CurrentUserProvider {

    public Long getCurrentUserId() {

        Authentication authentication =
                SecurityContextHolder
                        .getContext()
                        .getAuthentication();

        CustomUserDetails userDetails =
                (CustomUserDetails)
                        authentication.getPrincipal();

        return userDetails
                .getUser()
                .getId();
    }
}

์ด์ œ๋Š” ์–ด๋””์„œ๋“  ๋‹ค์Œ์ฒ˜๋Ÿผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

Long userId = currentUserProvider.getCurrentUserId();

โ˜˜๏ธ 5. ํ˜„์žฌ ๋กœ๊ทธ์ธ ์‚ฌ์šฉ์ž ์กฐํšŒ API ์ถ”๊ฐ€

JWT๊ฐ€ ์‹ค์ œ๋กœ ๋™์ž‘ํ•˜๋Š”์ง€ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด
๊ฐ„๋‹จํ•œ API๋„ ์ถ”๊ฐ€ํ–ˆ๋‹ค.

GET /api/users/me

์š”์ฒญ: Authorization: Bearer {accessToken}

์‘๋‹ต: 3

ํ† ํฐ์ด ์—†์œผ๋ฉด: 403 Forbidden

ํ† ํฐ์ด ์žˆ์œผ๋ฉด: ํ˜„์žฌ ๋กœ๊ทธ์ธ ์‚ฌ์šฉ์ž ID ๋ฐ˜ํ™˜

์ด ์‹œ์ ๋ถ€ํ„ฐ JWT๋Š” ๋‹จ์ˆœ ๋ฌธ์ž์—ด์ด ์•„๋‹ˆ๋ผ
์‹ค์ œ ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ์—ฐ๊ฒฐํ•˜๋Š” ์—ญํ• ์„ ํ•˜๊ธฐ ์‹œ์ž‘ํ–ˆ๋‹ค.

test.http ์—์„œ /api/users/me ์„ฑ๊ณต ๊ฒฐ๊ณผ


โ˜˜๏ธ 6. ์˜ˆ์™ธ ์ฒ˜๋ฆฌ์™€ ์•ˆ์ •์„ฑ ๋ณด๊ฐ•

์ธ์ฆ ํ๋ฆ„์„ ๋ถ™์ด๋‹ค ๋ณด๋‹ˆ
๊ธฐ๋Šฅ ์ž์ฒด๋ณด๋‹ค ์‹คํŒจ ์ƒํ™ฉ์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ๋„ ์ค‘์š”ํ•˜๋‹ค๋Š” ๊ฑธ ๋А๊ผˆ๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด ํšŒ์›๊ฐ€์ž… ๋กœ์ง์€ ์ฒ˜์Œ์— ์ด๋ ‡๊ฒŒ ๋˜์–ด ์žˆ์—ˆ๋‹ค.

if (userRepository.existsByEmail(request.getEmail())) {
    throw new IllegalArgumentException("์ด๋ฏธ ์‚ฌ์šฉ ์ค‘์ธ ์ด๋ฉ”์ผ์ž…๋‹ˆ๋‹ค.");
}

์ผ๋ฐ˜์ ์ธ ๊ฒฝ์šฐ์—๋Š” ์ž˜ ๋™์ž‘ํ•œ๋‹ค.

ํ•˜์ง€๋งŒ ๋™์‹œ์— ๊ฐ™์€ ์ด๋ฉ”์ผ ๊ฐ€์ž… ์š”์ฒญ์ด ๋“ค์–ด์˜ค๋ฉด ์ด๋Ÿฐ ์ƒํ™ฉ์ด ๊ฐ€๋Šฅํ–ˆ๋‹ค.

์š”์ฒญ A
existsByEmail()
โ†’ false

์š”์ฒญ B
existsByEmail()
โ†’ false

์š”์ฒญ A
insert ์„ฑ๊ณต

์š”์ฒญ B
insert
โ†’ DB unique ์ถฉ๋Œ

๊ทธ๋ž˜์„œ ์ตœ์ข…์ ์œผ๋กœ๋Š” DB ์ œ์•ฝ ์กฐ๊ฑด ์˜ˆ์™ธ๊นŒ์ง€ ์ฒ˜๋ฆฌํ•˜๋„๋ก ๋ณด์™„ํ–ˆ๋‹ค.

๋˜ ๊ธฐ์กด GlobalExceptionHandler๋„ ํ™•์žฅํ–ˆ๋‹ค.

400 โ†’ ์ž˜๋ชป๋œ ์š”์ฒญ
401 โ†’ ์ธ์ฆ ์‹คํŒจ
409 โ†’ ์ค‘๋ณต ์š”์ฒญ
500 โ†’ ์„œ๋ฒ„ ๋‚ด๋ถ€ ์˜ค๋ฅ˜

๊ธฐ๋Šฅ์ด ์ •์ƒ ๋™์ž‘ํ•˜๋Š” ๊ฒƒ๋งŒํผ,
์‹คํŒจํ–ˆ์„ ๋•Œ ์˜๋ฏธ ์žˆ๋Š” ์‘๋‹ต์„ ์ฃผ๋Š” ๊ฒƒ๋„ ์ค‘์š”ํ•˜๋‹ค๊ณ  ๋А๊ผˆ๋‹ค.


โ˜˜๏ธ 7. ์ด๋ฒˆ ์ž‘์—…์—์„œ ์ •๋ฆฌ๋œ ์ธ์ฆ ํ๋ฆ„

์ด๋ฒˆ ์ž‘์—… ์ดํ›„ Flowbit ์ธ์ฆ ํ๋ฆ„์€ ๋‹ค์Œ์ฒ˜๋Ÿผ ์ •๋ฆฌ๋๋‹ค.

ํšŒ์›๊ฐ€์ž…
โ†’ BCrypt ์ธ์ฝ”๋”ฉ
โ†’ User ์ €์žฅ

๋กœ๊ทธ์ธ
โ†’ JWT ๋ฐœ๊ธ‰

API ์š”์ฒญ
โ†’ Authorization ํ—ค๋” ์ „๋‹ฌ

JWT Filter
โ†’ ํ† ํฐ ๊ฒ€์ฆ

SecurityContext
โ†’ ์‚ฌ์šฉ์ž ์ €์žฅ

CurrentUserProvider
โ†’ ํ˜„์žฌ ์‚ฌ์šฉ์ž ์กฐํšŒ

โ˜˜๏ธ 8. ๋งˆ๋ฌด๋ฆฌ

์ด์ „ ์ž‘์—…์—์„œ๋Š” JWT๋ฅผ ๋ฐœ๊ธ‰ํ•˜๋Š” ๋‹จ๊ณ„๊นŒ์ง€๋งŒ ๊ตฌํ˜„ํ–ˆ๋‹ค.

์ด๋ฒˆ์—๋Š” ๊ทธ ํ† ํฐ์„ ์‹ค์ œ API ์š”์ฒญ์— ์—ฐ๊ฒฐํ–ˆ๋‹ค.

์ด์ œ Flowbit์€ ๋‹จ์ˆœํžˆ ๋กœ๊ทธ์ธ ๊ธฐ๋Šฅ์ด ์žˆ๋Š” ์ƒํƒœ๊ฐ€ ์•„๋‹ˆ๋ผ,

๋ˆ„๊ฐ€ ์š”์ฒญํ–ˆ๋Š”์ง€

๋ฅผ ์•Œ ์ˆ˜ ์žˆ๋Š” ์ƒํƒœ๊ฐ€ ๋˜์—ˆ๋‹ค.

์ดํ›„์—๋Š” ์—ฌ๊ธฐ์„œ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ๋‹ค์Œ ๊ตฌ์กฐ๋กœ ์ด์–ด์งˆ ์ˆ˜ ์žˆ๋‹ค.

Task.createdBy
TaskEvent.actorId
WorkspaceMember.user
RBAC ๊ถŒํ•œ ์ œ์–ด

์˜ค๋Š˜ ์ž‘์—…์„ ํ•œ ๋ฌธ์žฅ์œผ๋กœ ์ •๋ฆฌํ•˜๋ฉด

๋ฐœ๊ธ‰๋œ JWT๋ฅผ ์‹ค์ œ ์ธ์ฆ ํ๋ฆ„์— ์—ฐ๊ฒฐํ•˜๊ณ , ํ˜„์žฌ ๋กœ๊ทธ์ธ ์‚ฌ์šฉ์ž์™€ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ๊ตฌ์กฐ๊นŒ์ง€ ํ™•์žฅํ–ˆ๋‹ค.

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