๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
๐Ÿš€ ํŒ (๊ธฐ์ˆ  ์ ์šฉ ๋ฐฉ๋ฒ• ๋“ฑ)/๐ŸŒฑ Spring

[Spring MVC ๐ŸŒ] ํšŒ์› ์‹๋ณ„์„ ํ•ด ๋ณด์ž! (1) - ์ฟ ํ‚ค ์ ์šฉ ๋ฐฉ๋ฒ• ๐Ÿช

by dev_writer 2024. 2. 8.

๊ฐœ์š”

์•ˆ๋…•ํ•˜์„ธ์š”. ์ด๋ฒˆ์—๋Š” ํ”„๋กœ์ ํŠธ๋ฅผ ํ•˜์‹œ๋ฉด์„œ ๋กœ๊ทธ์ธ, ํšŒ์›๊ฐ€์ž…, ๋กœ๊ทธ์ธ ํ•œ ํšŒ์› ์‹๋ณ„์„ ์Šคํ”„๋ง์—์„œ ์–ด๋–ป๊ฒŒ ํ•  ์ˆ˜ ์žˆ๋Š”์ง€์— ๋Œ€ํ•ด ๊ณต๋ถ€ํ•œ ๊ณผ์ •์„ ๊ณต์œ ํ•˜๊ณ ์ž ํ•ฉ๋‹ˆ๋‹ค!

 

๋ณดํ†ต ์œ„ ์ž‘์—…๋“ค์„ ํ•˜๊ธฐ ์œ„ํ•ด ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ๋ฅผ ์‚ฌ์šฉํ•˜์…จ๋˜ ๊ฒฝํ—˜์ด ์žˆ์œผ์‹ค ํ…๋ฐ์š”, ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ๋Š” ๋ณ„๋„์˜ ํ”„๋ ˆ์ž„์›Œํฌ๋‹ค ๋ณด๋‹ˆ ๊นŠ์ด ์žˆ๊ฒŒ ํ•™์Šตํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ  ์Šคํ”„๋ง๋งŒ์œผ๋กœ ํ•ด๋ณด๋Š” ๊ฒŒ ์ข‹๊ฒ ๋‹ค๊ณ  ์ƒ๊ฐ์ด ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค. (๋ฉ˜ํ† ๋งํ•ด ์ฃผ์‹œ๋Š” ์„ ๋ฐฐ๋‹˜๊ป˜์„œ๋„ ๊ฐ™์€ ์ƒ๊ฐ์„ ๊ฐ€์ง€๊ณ  ๊ณ„์…จ์Šต๋‹ˆ๋‹ค.)

 

ํฌ๊ฒŒ ์ฟ ํ‚ค, ์„ธ์…˜, JWT ๋ฐฉ์‹์œผ๋กœ ํšŒ์›๊ฐ€์ž… & ๋กœ๊ทธ์ธ์„ ํ•ด ๋ณด๊ณ , ์ด๊ฒƒ์„ ์–ด๋…ธํ…Œ์ด์…˜์œผ๋กœ ํŽธ๋ฆฌํ•˜๊ฒŒ ์‚ฌ์šฉํ•˜๋Š” ๋“ฑ์˜ ๋ฐฉ์‹์œผ๋กœ ๊ฐœ์„ ์‹œํ‚ค๋Š” ๋‚ด์šฉ์„ ์ž‘์„ฑํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

 

๊ทธ๋Ÿผ ์ฒซ ๋ฒˆ์งธ ๊ธ€์—์„œ๋Š” ์ฟ ํ‚ค ๋ฐฉ์‹์„ ์ด์šฉํ•œ ํšŒ์›๊ฐ€์ž… & ๋กœ๊ทธ์ธ์— ๋Œ€ํ•ด ์•Œ์•„๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค!

 

๋”ฐ๋ผ ํ•  ๋‚ด์šฉ์ด ๋งŽ์„ ๊ฒƒ ๊ฐ™์•„ ์ฝ”๋“œ๋ฅผ ์ œ๊ณต๋“œ๋ฆฌ๊ฒ ์Šต๋‹ˆ๋‹ค.
์ด๊ณณ์—์„œ ๋ฐ›์œผ์‹ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค!
๋น ๋ฅธ ๊ตฌํ˜„ ์„ค๋ช…์„ ์œ„ํ•ด ํ…Œ์ŠคํŠธ๋Š” ์ž‘์„ฑํ•˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค :)

 

์ฟ ํ‚ค

๋จผ์ € ์ฟ ํ‚ค๊ฐ€ ๋ฌด์—‡์ผ๊นŒ์š”? ์ฟ ํ‚ค๋Š” ์„œ๋ฒ„์—์„œ ์‚ฌ์šฉ์ž ๋ธŒ๋ผ์šฐ์ €์—๊ฒŒ ์ „๋‹ฌํ•˜๋Š” ์ž‘์€ ๋ฐ์ดํ„ฐ์ž…๋‹ˆ๋‹ค.

 

์›๋ž˜ HTTP๋Š” ๋ฌด์ƒํƒœ (Stateless) ํŠน์„ฑ์„ ๊ฐ€์ง€๊ณ  ์žˆ์–ด์„œ ์„œ๋ฒ„๊ฐ€ ํด๋ผ์ด์–ธํŠธ์˜ ์ƒํƒœ๋ฅผ ๋ณด์กดํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

 

์ฆ‰, ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์„œ๋ฒ„์— ์ ‘๊ทผํ•  ๋•Œ๋งˆ๋‹ค ์šฐ๋ฆฌ๊ฐ€ ๋ˆ„๊ตฌ์ธ์ง€๋ฅผ ์•Œ๋ ค์ฃผ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด๋•Œ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ• ์ค‘ ์ฟ ํ‚ค๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

 

์ตœ์ดˆ ์‹œ์ ์— ํšŒ์›์ด ๋กœ๊ทธ์ธํ•  ์‹œ ์„œ๋ฒ„๊ฐ€ ๋ธŒ๋ผ์šฐ์ €์—๊ฒŒ ์ฟ ํ‚ค๋ฅผ ์ฃผ๋ฉด, ์•ž์œผ๋กœ ๋กœ๊ทธ์ธ ํ•œ ํšŒ์›์ด ๋‹ค๋ฅธ ํŽ˜์ด์ง€๋“ค์„ ํƒ์ƒ‰ํ•  ๋•Œ๋งˆ๋‹ค ๋ธŒ๋ผ์šฐ์ €๋Š” ํ•ด๋‹น ์ฟ ํ‚ค๋ฅผ ๊ฐ€์ง€๊ณ  ๋Œ์•„๋‹ค๋‹ˆ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿด ๋•Œ๋งˆ๋‹ค ์„œ๋ฒ„๋Š” ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ๊ฐ€์ง„ ์ฟ ํ‚ค ๋‚ด์šฉ์„ ํ† ๋Œ€๋กœ ๋ˆ„๊ตฌ์ธ์ง€๋ฅผ ์‹๋ณ„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฒฐ๊ณผ์ ์œผ๋กœ ์ด๋ฅผ ํ†ตํ•ด ์‚ฌ์šฉ์ž๋Š” ๋งค๋ฒˆ ์ธ์ฆํ•˜์ง€ ์•Š๊ณ ๋„ ์ƒํƒœ๋ฅผ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. (= Stateless๋ฅผ Stateful"์Šค๋Ÿฝ๊ฒŒ" ์ด์šฉํ•˜๋„๋ก ๋ณด์™„, ์—ฌ์ „ํžˆ ์„œ๋ฒ„๋Š” ๋ธŒ๋ผ์šฐ์ €์— ๋Œ€ํ•œ ์ƒํƒœ๋ฅผ ์ €์žฅํ•˜์ง€ ์•Š์Œ - ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ •๋ณด๋ฅผ ์ œ๊ณต)

 

๋งˆ์น˜ ์—ฌํ–‰๊ฐ์ด ์ž…๊ตญ ์‹œ ์—ฌ๊ถŒ์— ์ธ์ฆ์„ ๋ฐ›๊ณ , (๋‹ค์†Œ ๊น๊นํ•œ ๋‚˜๋ผ์ผ ์‹œ) ๋Œ์•„๋‹ค๋‹ ๋•Œ๋งˆ๋‹ค ์‹ ๋ถ„์ฆ์„ ๊ฒ€์‚ฌ๋ฐ›๋Š” ๊ฒƒ๊ณผ ๋น„์Šทํ•˜๋‹ค๊ณ  ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค!

์ž…๊ตญ ์ ˆ์ฐจ์— ๋น„์œ ํ•ด ๋ดค์Šต๋‹ˆ๋‹ค!

 

๋Œ€๋žต์ ์œผ๋กœ ์ฟ ํ‚ค์— ๋Œ€ํ•ด ๊ณต๋ถ€ํ•ด ๋ดค์œผ๋‹ˆ, ์‹ค์ œ ๊ตฌํ˜„์„ ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. ๊ณตํ†ต์ ์ธ ๋‚ด์šฉ (Member ๋“ฑ)์€ ์˜ฌ๋ ค์ง„ ์ฝ”๋“œ๋ฅผ ์ฐธ๊ณ ํ•ด ์ฃผ์„ธ์š”!

AuthController

AuthController๋Š” ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค. (๋กœ๊ทธ์ธ/ํšŒ์›๊ฐ€์ž…์€ AuthController์—์„œ, ํšŒ์› ์‹๋ณ„ ๋“ฑ ํšŒ์› ๊ธฐ๋Šฅ์—์„œ๋Š” MemberController์—์„œ ๋‹ค๋ฃจ๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.)

// import ํ‘œํ˜„์€ ์ƒ๋žต

@RequiredArgsConstructor
@RequestMapping("/auth")
@RestController
public class AuthController {

    private static final String COOKIE_NAME = "AUTH";
    private static final int EXPIRATION_SECONDS = 60 * 60;

    private final AuthService authService;

    @PostMapping("/signup")
    public ResponseEntity<MemberCreateResponse> createMember(@RequestBody @Valid final MemberCreateRequest request) {
        Long memberId = authService.signup(request);
        return ResponseEntity.ok()
                .body(new MemberCreateResponse(memberId));
    }

    @PostMapping("/login/cookie")
    public ResponseEntity<Void> loginWithCookie(@RequestBody @Valid final LoginRequest request,
                                         final HttpServletResponse response) {
        Member loginMember = authService.loginWithCookieAndSession(request);
        Cookie loginCookie = generateCookieByMember(loginMember);
        response.addCookie(loginCookie);

        return ResponseEntity.ok()
                .build();
    }

    private Cookie generateCookieByMember(final Member member) {
        String cookieValue = generateCookieValueByMember(member);
        Cookie cookie = new Cookie(COOKIE_NAME, cookieValue);

        cookie.setPath("/");
        cookie.setSecure(true);
        cookie.setMaxAge(EXPIRATION_SECONDS);
        cookie.setHttpOnly(true);

        return cookie;
    }

    private String generateCookieValueByMember(final Member member) {
        return member.getNickname() + "." + member.getPassword();
    }
}
  • ํšŒ์›๊ฐ€์ž… (/auth/signup) ์‹œ MemberCreateRequest (username, nickname, password)๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํšŒ์›์„ ๋งŒ๋“ค๊ณ , ๋งŒ๋“  ํšŒ์›์˜ id๋งŒ์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
  • ์ฟ ํ‚ค ๋ฐฉ์‹ ๋กœ๊ทธ์ธ (/auth/login/cookie) ์‹œ LoginRequest (nickname, password)๋ฅผ ์„œ๋น„์Šค์— ์ „๋‹ฌํ•˜์—ฌ ๊ด€๋ จ๋œ ํšŒ์›์„ ์ฐพ๊ณ , ํšŒ์›์˜ ์†์„ฑ์„ ์ด์šฉํ•ด ์ฟ ํ‚ค๋ฅผ ๋งŒ๋“ญ๋‹ˆ๋‹ค.
    • ์ฟ ํ‚ค๋ฅผ ๋งŒ๋“ค ๋•Œ Path (์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๊ฒฝ๋กœ), Secure (SSL ํ™˜๊ฒฝ ์•”ํ˜ธํ™”), MaxAge (์œ ํšจ ๊ธฐ๊ฐ„), HttpOnly (์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ์—์„œ์˜ ์‚ฌ์šฉ ๋ฐฉ์ง€) ๋“ฑ ์ถ”๊ฐ€์ ์ธ ๋ณด์•ˆ ๋ฐ ์„ค์ • ๋“ฑ์„ ๋ถ™์—ฌ์ค๋‹ˆ๋‹ค.
    • ์ฟ ํ‚ค๋Š” ํ‚ค-๊ฐ’ ๊ตฌ์กฐ๋กœ ์ด๋ฃจ์–ด์ ธ ์žˆ์Šต๋‹ˆ๋‹ค. ํ‚ค ์ด๋ฆ„์„ AUTH, ๊ฐ’์„ ํšŒ์›์˜ ๋‹‰๋„ค์ž„ + "." + ํšŒ์›์˜ ๋น„๋ฐ€๋ฒˆํ˜ธ๋กœ ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. (์›๋ž˜๋Š” ๋น„๋ฐ€๋ฒˆํ˜ธ๋„ ์•”ํ˜ธํ™” ์ฒ˜๋ฆฌ๋ฅผ ํ•ด์•ผ ํ•˜์ง€๋งŒ, ํ•ด๋‹น ๊ธ€์—์„œ์˜ ์ฃผ์ œ๋ฅผ ๋ฒ—์–ด๋‚œ ๊ฒƒ ๊ฐ™์•„ ๋„˜๊ธฐ๊ฒ ์Šต๋‹ˆ๋‹ค.)
  • HttpServletResponse๋Š” ์Šคํ”„๋ง ์„œ๋ธ”๋ฆฟ ํ™˜๊ฒฝ์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋ธŒ๋ผ์šฐ์ €์™€ ์„œ๋ฒ„ ๊ฐ„์˜ ์š”์ฒญ-์‘๋‹ต ์‹œ ํ™œ์šฉ๋œ ๊ฐ์ฒด๋ฅผ ๋œปํ•ฉ๋‹ˆ๋‹ค. (๋ฐ˜๋Œ€๋กœ ์š”์ฒญ์„ ์˜๋ฏธํ•˜๋Š” HttpServletRequest๋„ ์žˆ์Šต๋‹ˆ๋‹ค.) ์ด ๊ฐ์ฒด์— ๋งŒ๋“  ์ฟ ํ‚ค๋ฅผ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค.

AuthService

๋‹ค์Œ์€ ์„œ๋น„์Šค ์ฝ”๋“œ๋ฅผ ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

// import ํ‘œํ˜„์€ ์ƒ๋žต

@RequiredArgsConstructor
@Transactional(readOnly = true)
@Service
public class AuthService {

    private final MemberRepository memberRepository;

    @Transactional
    public Long signup(final MemberCreateRequest request) {
        validateIsNotUsedNickname(request.nickname());
        MemberAuth memberAuth = new MemberAuth(request.nickname(), request.password());
        Member newMember = new Member(request.username(), memberAuth);
        Member createMember = memberRepository.save(newMember);

        return createMember.getId();
    }

    private void validateIsNotUsedNickname(final String nickname) {
        memberRepository.findByNickname(nickname)
                .ifPresent(member -> {
                    throw new AlreadyUsedNicknameException();
                });
    }

    private Member findMemberByNickname(final String nickname) {
        return memberRepository.findByNickname(nickname)
                .orElseThrow(MemberNotFoundException::new);
    }

    public Member loginWithCookieAndSession(final LoginRequest request) {
        return findMemberByNickname(request.nickname());
    }
}
  • ์ €ํฌ๋Š” ์ด๋ฆ„ (username)์€ ๋™๋ช…์ด์ธ์„ ๊ณ ๋ คํ•˜์—ฌ ์ค‘๋ณต์„ ํ—ˆ์šฉํ•˜๊ณ , ๋‹‰๋„ค์ž„ (nickname)์€ ์ค‘๋ณต ์ฒ˜๋ฆฌํ•˜์ง€ ์•Š์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๋‹‰๋„ค์ž„์ด ๊ณ ์œ ํ•˜๋‹ค๋Š” ์ ์„ ์ด์šฉํ•˜์—ฌ ์ฟ ํ‚ค๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์ฟ ํ‚ค์™€ ํ–ฅํ›„ ๋‹ค๋ฃฐ ์„ธ์…˜์—์„œ๋Š” ํšŒ์› ์ •๋ณด๊ฐ€ ํ•„์š”ํ•˜๊ธฐ์— Member ์ž์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋„๋ก ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค.
  • Member๋Š” ์ด๋ฆ„ (username)๊ณผ MemberAuth (๋ฐธ๋ฅ˜ ํƒ€์ž…, nickname๊ณผ password ๋‚ด์žฅ)๋ฅผ ๊ฐ€์ง€๋Š” ํ˜•ํƒœ์ž…๋‹ˆ๋‹ค.

์‹คํ–‰ ๊ฒฐ๊ณผ

ํšŒ์›๊ฐ€์ž…

ํšŒ์›๊ฐ€์ž…์„ ํฌ์ŠคํŠธ๋งจ์œผ๋กœ ์‹คํ–‰ํ•ด ๋ณด๋ฉด ์•„๋ž˜์™€ ๊ฐ™์ด ์ƒ์„ฑ๋œ memberId๊ฐ€ ๋‚˜์˜ต๋‹ˆ๋‹ค. (๋‹‰๋„ค์ž„์ด ๋“ฑ๋ก๋˜์ง€ ์•Š์•˜๋˜ ๊ฒƒ์ผ ๊ฒฝ์šฐ)

์ƒ์„ฑ๋œ ํšŒ์› id๊ฐ€ ๋‚˜์˜ต๋‹ˆ๋‹ค.

๋กœ๊ทธ์ธ

์ค‘์š”ํ•œ ๊ฒƒ์€ ๋กœ๊ทธ์ธ์ด๊ฒ ์ฃ ! ๋กœ๊ทธ์ธ ์‹œ์—๋Š” ์ฟ ํ‚ค๊ฐ€ ์ €์žฅ๋จ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด์ œ ์—ฌํ–‰๊ฐ (๋ธŒ๋ผ์šฐ์ €)์ด ์ธ์ฆ ํ‹ฐ์ผ“ (์ฟ ํ‚ค)์„ ์–ป์—ˆ์Šต๋‹ˆ๋‹ค.

์ฟ ํ‚ค ๊ฐ’์œผ๋กœ ๋‹‰๋„ค์ž„ + "." + ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์–ป์—ˆ์Šต๋‹ˆ๋‹ค. ์›๋ž˜๋Š” ๋น„๋ฐ€๋ฒˆํ˜ธ ์•”ํ˜ธํ™”๋„ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค!

 

์ •๋ฆฌ

์–ด๋– ์…จ๋‚˜์š”? ์•„์ง์€ ๋กœ๊ทธ์ธ ํ•œ ํšŒ์›์„ ์‹๋ณ„ํ•˜๋Š” ๋กœ์ง์ด ์ž‘์„ฑ๋˜์ง€ ์•Š์•„ ์™€๋‹ฟ์ง€ ์•Š์œผ์‹ค ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ํ—ท๊ฐˆ๋ฆฌ์‹ ๋‹ค๋ฉด ์—ฌํ–‰๊ฐ์ด ์ธ์ฆ ํ‹ฐ์ผ“์„ ๋ฐ›๋Š” ๊ณผ์ •์„ ์ƒ๊ฐํ•ด ์ฃผ์„ธ์š”!

 

๋‹ค์Œ ๊ธ€์—์„œ๋Š” ์„ธ์…˜์„ ์ด์šฉํ•œ ๋กœ๊ทธ์ธ ๋ฐฉ์‹์„ ์•Œ์•„๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค!

Reference