[๋„์ „ ๊ธฐ์ˆ !] ํ† ํฐ ์ „๋žต, ๊ถŒํ•œ ์ธ์ฆ

2021. 12. 21. 11:43ใ†Back-End ์ž‘์—…์‹ค/๊ธฐํƒ€ ์ฐธ๊ณ  ์ž๋ฃŒ

728x90
๋ฐ˜์‘ํ˜•

๐Ÿš€ ํ† ํฐ์„ ์‚ฌ์šฉํ•œ ์ด์œ ?

๊ฐ€์žฅ ํฐ ์ด์œ ๋Š” HTTP์˜ ํŠน์ง• ์ค‘ Connectionless ์™€ Stateless ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

HTTP ํ”„๋กœํ† ์ฝœ์€ ํ•œ๋ฒˆ์˜ ์š”์ฒญ - ์‘๋‹ต ์‚ฌ์ดํด์ด ์™„๋ฃŒ๋˜๋ฉด ์—ฐ๊ฒฐ์„ ์ข…๋ฃŒํ•˜๊ธฐ ๋•Œ๋ฌธ์—, ๋™์ผํ•œ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์—ฌ๋Ÿฌ๋ฒˆ ์š”์ฒญ์„ ํ•ด์˜ค๋”๋ผ๋„ ์„œ๋ฒ„๋Š” ํ•ด๋‹น ํด๋ผ์ด์–ธํŠธ์— ๋Œ€ํ•œ ๊ถŒํ•œ์„ ์ธ์ง€ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.

ํด๋ผ์ด์–ธํŠธ์˜ ์ƒํƒœ๋ฅผ ์œ ์ง€ํ•˜๊ธฐ ์œ„ํ•œ ๋ฐฉ๋ฒ•์œผ๋กœ๋Š” ๋Œ€ํ‘œ์ ์œผ๋กœ ์„ธ์…˜๊ณผ ์ฟ ํ‚ค ๋ฐฉ์‹์ด ์žˆ์Šต๋‹ˆ๋‹ค.

Cookie ๋ฐฉ์‹ : ๊ฐœ๋ณ„ Client ์ƒํƒœ ์ •๋ณด๋ฅผ HTTP Request / Response Header์— ๋‹ด์•„ ์ „๋‹ฌํ•˜๋Š” Infomation / Data๋ฅผ ์ด์•ผ๊ธฐ ํ•ฉ๋‹ˆ๋‹ค.

 

<์ถœ์ฒ˜ :&nbsp; https://ledgku.tistory.com/72 >

Cookie๋Š” ์น˜๋ช…์ ์ธ ๋‹จ์ ์ด ์žˆ๋Š”๋ฐ, ๊ทธ๊ฒƒ์€ ๋ฐ”๋กœ ๋ณด์•ˆ ์ทจ์•ฝ์  ์ž…๋‹ˆ๋‹ค.

Cookie ๋ฐฉ์‹์€ Client ์ƒํƒœ ์ •๋ณด๋ฅผ Client์— ์ €์žฅํ•˜๊ณ , HTTP Request / Response Header์— ๋‹ด์•„ ์ „๋‹ฌํ•˜๊ธฐ ๋•Œ๋ฌธ์— ํ•ดํ‚น ๋ฐ ์Šค๋‹ˆํ•‘ ๊ณต๊ฒฉ์— ์˜ํ•œ ๋ณ€์กฐ์™€ ์™ธ๋ถ€ ๋…ธ์ถœ์— ์ทจ์•ฝํ•œ ๋‹จ์ ์ด ์žˆ์Šต๋‹ˆ๋‹ค.

์œ„ ์‚ฌ์ง„์ฒ˜๋Ÿผ ์•”ํ˜ธํ™” ๋˜์ง€ ์•Š์€ Cookie๋Š” ํ†ต์‹  ๊ฐ„ ์ •๋ณด๊ฐ€ ๋ชจ๋‘ ๋…ธ์ถœ๋ฉ๋‹ˆ๋‹ค.

Session ๋ฐฉ์‹ : ๊ฐœ๋ณ„ Client ์ƒํƒœ ์ •๋ณด๋ฅผ Server์— ์ €์žฅํ•˜๋Š” ๊ธฐ์ˆ  ์ž…๋‹ˆ๋‹ค.

Server๋Š” ๊ฐœ๋ณ„ Client Session ์‹๋ณ„ํ•˜๊ธฐ ์œ„ํ•ด "Session ID"๋ฅผ ๋ถ€์—ฌํ•˜๊ณ , ์ด๊ฒƒ์€ "Session Cookie"๋ฅผ ์ด์šฉํ•˜์—ฌ Client์™€ Server ๊ฐ„์— ์ฃผ๊ณ  ๋ฐ›๋Š” ํ˜•์‹์œผ๋กœ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค.

Session๋ฐฉ์‹์€ Client ์ƒํƒœ ์ •๋ณด๋ฅผ Server์— ์ €์žฅํ•˜๊ธฐ ๋•Œ๋ฌธ์— Cookie์— ๋น„ํ•ด ๋ณด์•ˆ์ƒ ์•ˆ์ „ํ•˜๋‹ค๋Š” ์žฅ์ ์ด ์žˆ์œผ๋‚˜, ์ด๊ฒƒ ์—ญ์‹œ ๋ณด์•ˆ์ƒ ์ทจ์•ฝ์ ์ด ์žˆ๋Š”๋ฐ, ๊ณต๊ฒฉ์ž๊ฐ€ ์ •์ƒ์ ์ธ ์‚ฌ์šฉ์ž์˜ Session ID ์ •๋ณด๋ฅผ ํƒˆ์ทจํ•œ๋‹ค๋ฉด ์ •์ƒ ์‚ฌ์šฉ์ž๋กœ ์œ„์žฅ ์ ‘๊ทผ์ด ๊ฐ€๋Šฅํ•œ HTTP Session Hijecking ๊ณต๊ฒฉ์ด ๊ฐ€๋Šฅํ•˜๋‹ค๋Š” ์ทจ์•ฝ์ ์ด ์žˆ๊ณ , Session ์ €์žฅ์†Œ๋ฅผ Server์—์„œ ๊ด€๋ฆฌํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ด์šฉ์ž๊ฐ€ ๋งŽ์•„์ง€๋ฉด ๋งŽ์•„์งˆ์ˆ˜๋ก Server์— ๊ฑธ๋ฆฌ๋Š” ๋ถ€ํ•˜๊ฐ€ ์ฆ๊ฐ€ํ•œ๋‹ค๋Š” ๋‹จ์ ์ด ์žˆ์Šต๋‹ˆ๋‹ค.

์œ„์˜ ๋‘๊ฐ€์ง€ ๋ฐฉ์‹์ด ๋Œ€ํ‘œ์ ์ธ ์ธ์ฆ ๋ฐฉ์‹์ด๋‚˜, ์—ด๊ฑฐํ•œ ๋‹จ์ ๋“ค์„ ๊ณ ๋ คํ•˜์—ฌ ์ €ํฌ๋Š” JWT(Json Web Token)๋ผ๋Š” ๋ฐฉ์‹์„ ์ฑ„ํƒํ•˜์—ฌ ์ธ์ฆ์„ ๊ตฌํ˜„ ํ•ด ๋ณด์•˜์Šต๋‹ˆ๋‹ค.

ํ† ํฐ ๊ธฐ๋ฐ˜ ์ธ์ฆ์‹œ์Šคํ…œ์€ ์œ ์ €์˜ ์ธ์ฆ ์ •๋ณด๋ฅผ ์„œ๋ฒ„๋‚˜ ์„ธ์…˜์— ๋‹ด์•„๋‘์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ํด๋ผ์ด์–ธํŠธ๋Š” API ์š”์ฒญํ• ๋•Œ๋งˆ๋‹ค ํ† ํฐ์„ HTTP Authorization header์— ์ธ์ฆ ์ •๋ณด๋ฅผ ๋‹ด์•„ ์„œ๋ฒ„์—๊ฒŒ ๋ณด๋ƒ…๋‹ˆ๋‹ค. ์„œ๋ฒ„๋Š” ์ด ํ† ํฐ์„ ํ†ตํ•˜์—ฌ ํด๋ผ์ด์–ธํŠธ๋“ค์„ ์‹๋ณ„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

JWT Token ๋ฐฉ์‹์€ ํฌ๊ฒŒ 3๊ฐ€์ง€ ์š”์†Œ๋กœ ๊ตฌ์„ฑ์ด ๋ฉ๋‹ˆ๋‹ค.

  • Header : 3๊ฐ€์ง€ ์š”์†Œ๋ฅผ ์•”ํ˜ธํ™”ํ•  ์•Œ๊ณ ๋ฆฌ์ฆ˜ ๋“ฑ์˜ Option Value
  • Payload : User์˜ ๊ณ ์œ  ID ๋“ฑ ์ธ์ฆ์— ํ•„์š”ํ•œ ์ •๋ณด
  • Verify Signatuer : Header, Paload์™€ Secret Key๊ฐ€ ๋”ํ•ด์ ธ ์•”ํ˜ธํ™”

์ €ํฌ๋Š” ์•”ํ˜ธํ™” ์•Œ๊ณ ๋ฆฌ์ฆ˜์œผ๋กœ HMAC SHA256์„ ์‚ฌ์šฉํ•˜์˜€์Šต๋‹ˆ๋‹ค.

์—ฌ๊ธฐ์„œ HMAC์€ ๋ฉ”์‹œ์ง€ ์ธ์ฆ ์ฝ”๋“œ๋ฅผ ํ•ด์‰ฌ ํ•จ์ˆ˜๋ฅผ ์ด์šฉํ•˜์—ฌ ์•”ํ˜ธํ™” ํ•˜๋Š” ๋ฐฉ์‹์ด๊ณ , SHA256์€ ๊ธธ์ด๊ฐ€ 256bit ์ธ Message Disast๋ฅผ 64๋ฒˆ์˜ Round๋ฅผ๋Œ๋ ค ์ผ๋ฐฉํ–ฅ(๋ณตํ˜ธํ™” ๋ถˆ๊ฐ€ ๋ฐฉ์‹)์œผ๋กœ ์•”ํ˜ธํ™” ํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ, ์•ˆ์ „ํ•˜๋‹ค๊ณ  ํŒ๋‹จ๋˜์–ด ์ด ์•”ํ˜ธํ™” ๋ฐฉ์‹์„ ์„ ํƒํ•˜๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

๋˜ํ•œ, ์„œ๋ฒ„์˜ ๋ถ€๋‹ด์ด ๊ฐ์†Œํ• ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ, ์—ฌ๋Ÿฌ ํ”Œ๋žซํผ ๋ฐ ๋„๋ฉ”์ธ์—์„œ ์ ์šฉ๊ฐ€๋Šฅ ํ•˜๋‹ค๋Š” ์žฅ์ ์ด ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ์ด์œ ๋กœ, ๋Œ€ํ‘œ์ ์ธ ํ† ํฐ ๊ธฐ๋ฐ˜ ์ธ์ฆ ์‹œ์Šคํ…œ์ธ JWT์„ ์‚ฌ์šฉํ•˜์˜€์Šต๋‹ˆ๋‹ค.

 

๐Ÿš€ ์—‘์„ธ์Šคํ† ํฐ๊ณผ ๋ฆฌํ”„๋ ˆ์‹œํ† ํฐ ์ „๋žต

 

๋จผ์ € Access Token์€ Client๊ฐ€ Server์— ์ธ์ฆ์„ ์œ„ํ•ด ์‚ฌ์šฉ๋˜๋Š” JSON Web Token์ž…๋‹ˆ๋‹ค.

๋ณด์•ˆ์—์„œ๋Š” 100% ์•ˆ์ „์€ ์—†๋‹ค๋ผ๋Š” ๋ง์ด ์žˆ์Šต๋‹ˆ๋‹ค. JWT ์—ญ์‹œ 100% ๋ณด์•ˆ์— ์•ˆ์ „ํ•  ์ˆ˜๋Š” ์—†์Šต๋‹ˆ๋‹ค.

์•…์˜์ ์ธ ์ด์šฉ์ž๊ฐ€ Access Token์„ ํƒˆ์ทจ ํ–ˆ๋‹ค๋ฉด Session ๋ฐฉ์‹์—์„œ Session ID๋ฅผ ํƒˆ์ทจํ•œ ๊ฒƒ๊ณผ ๊ฐ™์ด ํŠน์ • ์ด์šฉ์ž์— ๊ถŒํ•œ์œผ๋กœ Server์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ์œ„ํ—˜์„ฑ์ด ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ๋ฌธ์ œ๋กœ ์ธํ•ด ์ €ํฌ๋Š” ์•„๋ž˜ ๊ทธ๋ฆผ๊ณผ ๊ฐ™์ด Access Token ์œ ํšจ ์‹œ๊ฐ„์„ 5๋ถ„์œผ๋กœ ์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค.

    //์—‘์„ธ์Šค ํ† ํฐ ์œ ํšจ์‹œ๊ฐ„ 5๋ถ„
    public static Long ACCESS_TOKEN_VALID_TIME =  5 * 60 * 1000L;

    public static String ACCESS_TOKEN_NAME = "ACCESS TOKEN";

    public static String REFRESH_TOKEN_NAME = "REFRESH TOKEN";

    public JwtUtil(String secret) {
        key = Keys.hmacShaKeyFor(secret.getBytes());
    }

์ด๋ ‡๊ฒŒ Access Token์— ์‹œ๊ฐ„์„ 5๋ถ„์œผ๋กœ๋งŒ ์ฃผ์—ˆ๋‹ค๋ฉด ์„ ๋Ÿ‰ํ•œ ์ด์šฉ์ž๋“ค์€ 5๋ถ„์ด ์ง€๋‚˜๋ฉด ๋‹ค์‹œ Login์„ ํ•ด์•ผ ํ•˜๋Š” ๋ถˆํŽธํ•จ์ด ์ƒ๊ธฐ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ๋ฌธ์ œ๋ฅผ ๊ทน๋ณตํ•˜๊ธฐ ์œ„ํ•ด์„œ ์ €ํฌ๋Š” Refrash Token ๊ธฐ๋ฒ•์„ ์ถ”๊ฐ€๋กœ ์‚ฌ์šฉํ•˜์˜€์Šต๋‹ˆ๋‹ค.

//๋ฆฌํ”„๋ ˆ์‹œ ํ† ํฐ ์œ ํšจ์‹œ๊ฐ„ 24์‹œ๊ฐ„(ms๋‹จ์œ„)
    public static Long REFRESH_TOKEN_VALID_TIME = 1440 * 60 * 1000L;

Refrash Token์˜ ๊ฒฝ์šฐ ์œ ํšจ ๊ธฐ๊ฐ„์„ 24์‹œ๊ฐ„์œผ๋กœ ์ฃผ์–ด ์„ ๋Ÿ‰ํ•œ ์ด์šฉ์ž๊ฐ€ ์ง€์†์ ์ธ Login์„ ํ†ตํ•ด ๋ถˆํŽธํ•จ์ด ์ƒ๊ธฐ์ง€ ์•Š๋„๋ก ๊ตฌํ˜„์„ ํ•˜์˜€์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ, Refrash Token ์—ญ์‹œ ์•…์˜์ ์ธ ์ด์šฉ์ž์—๊ฒŒ ํƒˆ์ทจ ๋‹นํ•  ์‹œ ๋ณด์•ˆ ์‚ฌ๊ณ ๋กœ ์ด์–ด์งˆ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์ด ์ •๋ณด๋ฅผ ์™ธ๋ถ€์— ๋…ธ์ถœ ์‹œํ‚ค์ง€ ์•Š๊ธฐ ์œ„ํ•ด Data Base์— ์ €์žฅํ•˜๋„๋ก ์„ค๊ณ„ ํ•˜์˜€์Šต๋‹ˆ๋‹ค.

 

๋ฐ˜์‘ํ˜•

 

@Transactional
    public DefaultRes<UserSignInResponseDto> signIn(UserSignInRequestDto requestDto){

        Optional<String> optionalEmail = userRepository.findByEmail(requestDto.getEmail());

        return optionalEmail.map(email ->{

            Optional<User> optionalUser = userRepository.findByUser(email, requestDto.getPassword());

            return optionalUser.map(user -> {
                String accessToken = JwtUtil.createAccessToken(user.getId(), user.getGrade());
                String refreshToken = JwtUtil.createRefreshToken(user.getId(), user.getGrade());
                user.setRefreshToken(refreshToken);                                                 // Dirty Checking์„ ์ด์šฉํ•œ Data Base ๊ฐ’ ์ €์žฅ

                return DefaultRes.response(HttpStatus.OK.value(),"์„ฑ๊ณต", new UserSignInResponseDto(accessToken,refreshToken, user.getId(), user.getGrade()));

            }).orElseGet(()-> DefaultRes.response(HttpStatus.OK.value(), "๋น„๋ฐ€๋ฒˆํ˜ธ๋ถˆ์ผ์น˜"));

        }).orElseGet(()->DefaultRes.response(HttpStatus.OK.value(), "์•„์ด๋””๋ถˆ์ผ์น˜"));

    }

 

 

๐Ÿš€ ์„œ๋ฒ„์™€ ํด๋ผ์ด์–ธํŠธ๊ฐ„์˜ ํ†ต์‹ 

 

 

๐Ÿ“Œ ๊ถŒํ•œ ์ฒ˜๋ฆฌ

ํด๋ผ์ด์–ธํŠธ ์š”์ฒญ์— ๋Œ€ํ•œ ๊ถŒํ•œ ์ฒ˜๋ฆฌ๋Š” ์Šคํ”„๋ง ์ธํ„ฐ์…‰ํ„ฐ๋ฅผ ํ™œ์šฉํ•˜์—ฌ ๊ตฌํ˜„ํ•˜์˜€์Šต๋‹ˆ๋‹ค.

Client๊ฐ€ Request๋ฅผ ๋ณด๋‚ด๊ฒŒ ๋˜๋ฉด Dispatcher Servlet์—์„œ Request์— ํ•ด๋‹นํ•˜๋Š” Controller๋กœ ๊ฐ€๊ธฐ ์ „์— ํ•ด๋‹น ๋‚ด์šฉ์„ Interceptor๊ฐ€ ๊ฐ€๋กœ์ฑ„๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค.

์ด๋ฅผ ํ†ตํ•ด ์ €ํฌ๊ฐ€ ๊ตฌํ˜„ํ•œ User Grade (Admin, End User, Black User)๋ฅผ ํ™•์ธํ•˜๊ณ , ํ•ด๋‹น Grade์— ๋งž๋Š” ํ–‰๋™๋งŒ์„ ํ•  ์ˆ˜ ์žˆ๊ฒŒ ๊ตฌํ˜„์„ ํ–ˆ์Šต๋‹ˆ๋‹ค.

}else if(tokenName.equals(JwtUtil.REFRESH_TOKEN_NAME)){
            Long userPk = claims.get("user_pk", Long.class);
            Boolean check = sessionService.refreshTokenCheck(userPk, jwtToken);
            if(check){
                String accessToken;
                if(userGrade.equals(UserGrade.ADMIN)){
                    accessToken = JwtUtil.createAccessToken(userPk, UserGrade.ADMIN);
                }else{
                    accessToken = JwtUtil.createAccessToken(userPk, UserGrade.USER);
                }

์œ„์™€ ๊ฐ™์ด ๋จผ์ € ์ €ํฌ๋Š” Loginํ•œ ์ด์šฉ์ž๊ฐ€ ์ •์ƒ์ ์ธ Token์„ ๊ฐ€์ง€๊ณ  ์žˆ๋Š”์ง€๋ฅผ ํ™•์ธํ•œ ๋’ค ๊ทธ ์ด์šฉ์ž ํšŒ์›์ •๋ณด์˜ Grade ๊ฐ’์„ ํŒ๋‹จํ•˜๊ฒŒ ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค.

์ฆ‰, ์ €ํฌ๋Š” Interceptor๋ฅผ ํ†ตํ•ด ์ด์šฉ์ž์˜ Token Value๋ฅผ ํŒ๋‹จํ•˜๊ณ , User Grade์„ ํŒ๋‹จํ•˜์—ฌ ๊ฑฐ๊ธฐ์— ๋งž๋Š” ๊ถŒํ•œ์„ ๊ฐ€์ง€๊ณ , ์ €ํฌ๊ฐ€ ๊ตฌํ˜„ํ•œ Service๋ฅผ ์ด์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ๊ตฌํ˜„ํ•˜์˜€์Šต๋‹ˆ๋‹ค.

 

728x90
๋ฐ˜์‘ํ˜•