Spring

TIL Spring #3-1

๋ฆฐ์˜ˆ์กฐ 2023. 12. 14. 21:25

[์ธ์ฆ๊ณผ ์ธ๊ฐ€]

์ฟ ํ‚ค์™€ ์„ธ์…˜

JWT


 

<์งค๋ง‰ํ•œ bean>

๋”๋ณด๊ธฐ

ํ˜น์‹œ, bean์„ ์ˆ˜๋™์œผ๋กœ ๋“ฑ๋กํ•ด์•ผ๋˜๋Š” ๊ฒฝ์šฐ๋„ ์žˆ์„๊นŒ? 

๊ธฐ์ˆ ์ ์ธ ๋ฌธ์ œ๋‚˜ ๊ณตํ†ต ๊ด€์‹ฌ์‚ฌ๋ฅผ ์ฒ˜๋ฆฌํ•  ๋•Œ ์‚ฌ์šฉํ•˜๋Š” ๊ฐ์ฒด๋Š” ์ˆ˜๋™์œผ๋กœ bean์„ ๋“ฑ๋กํ•ด์ฃผ๋Š” ๊ฒƒ์ด ์ข‹๋‹ค. (๋น„๊ต์  ๋ถ€๊ฐ€์ ์ธ ๊ฒƒ)

- @Component ๋ง๊ณ  ๋ฉ”์„œ๋“œ์— @Bean ์• ๋„ˆํ…Œ์ด์…˜ ์ถ”๊ฐ€ํ•ด์ฃผ๋ฉด ๋œ๋‹ค~!


์ธ์ฆ/์ธ๊ฐ€

์ธ์ฆ: ํ•ด๋‹น ์œ ์ €๊ฐ€ ์‹ค์ œ ์œ ์ €์ธ์ง€ ์ธ์ฆํ•˜๋Š” ๊ฒƒ ex)๋กœ๊ทธ์ธ

์ธ๊ฐ€: ํ•ด๋‹น ์œ ์ €๊ฐ€ ํŠน์ • ๋ฆฌ์†Œ์Šค์— ์ ‘๊ทผ์ด ๊ฐ€๋Šฅํ•œ์ง€ ํ—ˆ๊ฐ€๋ฅผ ํ™•์ธํ•˜๋Š” ๊ฒƒ ex)๋น„ํšŒ์›๋กœ๊ทธ์ธ

 

<์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ธ์ฆ>

- ์ผ๋ฐ˜์ „์œผ๋กœ ์„œ๋ฒ„-ํด๋ผ์ด์–ธํŠธ ๊ตฌ์กฐ๋กœ ๋˜์–ด์žˆ๊ณ , ์‹ค์ œ๋กœ ์ด ๋‘๊ฐ€์ง€๋Š” ๋ฉ€๋ฆฌ ๋–จ์–ด์ ธ์žˆ๋‹ค.

- HTTP ํ”„๋กœํ† ์ฝœ์„ ์ด์šฉํ•˜์—ฌ ํ†ต์‹ ํ•˜๋Š”๋ฐ, ๊ทธ ํ†ต์‹ ์€ ๋น„์—ฐ๊ฒฐ์„ฑ๊ณผ ๋ฌด์ƒํƒœ๋กœ ์ด๋ฃจ์–ด์ง„๋‹ค.

 

์‰ฝ๊ฒŒ ๋งํ•ด, ๋ช‡๊ฐ€์ง€ ๊ฒฝ์šฐ๋ฅผ ์ œ์™ธํ•˜๊ณค ์„œ๋ฒ„์™€ ํด๋ผ์ด์–ธํŠธ๋Š” ์‹ค์ œ๋กœ ์—ฐ๊ฒฐ๋˜์–ด ์žˆ๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋‹ค. (์„œ๋ฒ„ ๋น„์šฉ ๋ฌธ์ œ)

ํ•˜์ง€๋งŒ ์šฐ๋ฆฌ๊ฐ€ ์‚ฌ์šฉํ–ˆ์„ ๋•Œ๋Š” ์ผ๋ จ์˜ ์ฒ˜๋ฆฌ ๊ณผ์ •์ด ์—ฐ๊ฒฐ๋˜์–ด ์žˆ๋‹ค๊ณ  ๋Š๊ปด์ง„๋‹ค. ์–ด๋–ป๊ฒŒ ๊ฐ€๋Šฅํ• ๊นŒ?

 

# ์œ ์ €๊ฐ€ ์ธ์ฆํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์˜ˆ๋กœ ๋“ค์–ด๋ณด์ž

 

์ธ์ฆ ๋ฐฉ์‹์—๋Š” ํฌ๊ฒŒ ๋‘๊ฐ€์ง€๊ฐ€ ์žˆ๋‹ค.

 

1. ์ฟ ํ‚ค-์„ธ์…˜ ๋ฐฉ์‹

 

:'์„ธ์…˜์ €์žฅ์†Œ' ๋ผ๋Š” ๊ฐœ๋…์ด ์กด์žฌํ•˜๊ณ  ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๋กœ๊ทธ์ธ์„ ํ•ด์„œ ํ†ต๊ณผํ–ˆ์„ ๋•Œ ์„œ๋ฒ„๋กœ๋ถ€ํ„ฐ ์„ธ์…˜์•„์ด๋””๋ฅผ ๋ฐœ๊ธ‰๋ฐ›๊ฒŒ ๋˜๊ณ 

ํด๋ผ์ด์–ธํŠธ๋Š” ํ•ด๋‹น sessin-id ๋ฅผ '์ฟ ํ‚ค'๋ผ๋Š” ์ €์žฅ์†Œ์— ๋ณด๊ด€ํ•˜๊ณ  ๋‹ค์Œ ์š”์ฒญ์„ ํ•  ๋•Œ๋งˆ๋‹ค session-id๋ฅผ ๊ฐ™์ด ๋ณด๋‚ธ๋‹ค.(http header)

 

์• ์ดˆ์— ์„ธ์…˜์•„์ด๋”” ๋ฐœ๊ธ‰ํ•ด์ค„ ๋•Œ ๊ทธ๊ฑธ ์ฟ ํ‚ค์— ๋‹ด์•„์„œ ์ฃผ๋Š”๊ฑด๊ฐ€? ๊ทธ ๋‹ค์Œ์— ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๊ณ„์†ํ•ด์„œ ๊ทธ๊ฑธ ์“ฐ๊ณ ?

์•„๋‹Œ๋ฐ? ์„ธ์…˜์•„์ด๋”” ์—†์ด ์• ์ดˆ์— ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์ฟ ํ‚ค ์ž์ฒด๋ฅผ ๊ฐ–๊ณ  ์žˆ๋Š”๊ฑด๊ฐ€?

 

<์ฟ ํ‚ค>

- ํด๋ผ์ด์–ธํŠธ์— ์ €์žฅ๋  ๋ชฉ์ ์œผ๋กœ ์ƒ์„ฑํ•œ ์ •๋ณด๋ฅผ ๋‹ด์€ ํŒŒ์ผ

- ์—ฌ๋Ÿฌ ๊ตฌ์„ฑ ์š”์†Œ๊ฐ€ ์žˆ๋Š”๋ฐ ๊ฐ€์žฅ ์ค‘์š”ํ•œ ๋ถ€๋ถ„์€ name, value ์ด๋‹ค.

  •  name: ์ฟ ํ‚ค๋ฅผ ์‹๋ณ„ํ•˜๋Š”๋ฐ ์‚ฌ์šฉ๋˜๋Š” ํ‚ค (์ค‘๋ณต x)
  •  value: ์ฟ ํ‚ค์˜ ๊ฐ’

 

<์„ธ์…˜>

  • ์„œ๋ฒ„์—์„œ ์ผ์ •์‹œ๊ฐ„๋™์•ˆ ํด๋ผ์ด์–ธํŠธ ์ƒํƒœ๋ฅผ ์œ ์ง€ํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉํ•œ๋‹ค.
  • ์„œ๋ฒ„์—์„œ ํด๋ผ์ด์–ธํŠธ๋ณ„๋กœ ์œ ์ผ๋ฌด์ดํ•œ '์„ธ์…˜ID'๋ฅผ ๋ถ€์—ฌํ•œ ํ›„ ํด๋ผ์ด์–ธํŠธ ๋ณ„ ํ•„์š”ํ•œ ์ •๋ณด๋ฅผ ์„œ๋ฒ„์— ์ €์žฅ!
  • ํ•ด๋‹น ID๋Š” ํด๋ผ์ด์–ธํŠธ์˜ ์ฟ ํ‚ค๊ฐ’(์„ธ์…˜์ฟ ํ‚ค)์œผ๋กœ ์ €์žฅ๋˜์–ด ํด๋ผ์ด์–ธํŠธ ์‹๋ณ„์— ์‚ฌ์šฉ๋œ๋‹ค.

 

2. JWT (JSON Web Token)

: ์ธ์ฆ์— ํ•„์š”ํ•œ ์ •๋ณด๋ฅผ ์•”ํ˜ธํ™”์‹œํ‚จ ํ† ํฐ์ด๋‹ค. 1๋ฒˆ ๋ฐฉ์‹๊ณผ ์œ ์‚ฌํ•˜๊ฒŒ ํ•ด๋‹น ํ† ํฐ(JSON ํ˜•ํƒœ์˜ claim ๊ธฐ๋ฐ˜ ํ† ํฐ์ž„)์œผ๋กœ ์„œ๋ฒ„๊ฐ€ ํด๋ผ์ด์–ธํŠธ๋ฅผ ์‹๋ณ„ํ•œ๋‹ค.

 

-์„œ๋ฒ„์—์„œ ๋กœ๊ทธ์ธ ์ •๋ณด๋ฅผ ์ €์žฅํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹Œ ๋กœ๊ทธ์ธ ์ •๋ณด๋ฅผ secret key๋ฅผ ์‚ฌ์šฉํ•ด JWT๋กœ ์•”ํ˜ธํ™”ํ•œ๋‹ค. ํ•ด๋‹น JWT ํ† ํฐ์„ ํ†ตํ•ด ์ธ์ฆ/์ธ๊ฐ€๋ฅผ ์ฒ˜๋ฆฌํ•œ๋‹ค.

JWT ํ† ํฐ ์˜ˆ

 

<์‚ฌ์šฉ ํ๋ฆ„>

๋”๋ณด๊ธฐ

1. ํด๋ผ์ด์–ธํŠธ ๋กœ๊ทธ์ธ ์„ฑ๊ณต ์‹œ

- ์„œ๋ฒ„์—์„œ ๋กœ๊ทธ์ธ ์ •๋ณด๋ฅผ JWT๋กœ ์•”ํ˜ธํ™”ํ•œ๋‹ค.

- ๊ทธ๋ฆฌ๊ณ  ์ง์ ‘ ์ฟ ํ‚ค๋ฅผ ์ƒ์„ฑ, JWT๋ฅผ ๋‹ด์•„ Client ์‘๋‹ต์— ์ „๋‹ฌํ•œ๋‹ค.

- ๋ธŒ๋ผ์šฐ์ € ์ฟ ํ‚ค ์ €์žฅ์†Œ์— ์ž๋™์œผ๋กœ ํ•ด๋‹น JWT ์ €์žฅ๋œ๋‹ค.

 

2. Client์—์„œ JWT๋ฅผ ํ†ตํ•ด ์ธ์ฆํ•˜๋Š” ๋ฐฉ์‹์€?

- ํด๋ผ์ด์–ธํŠธ๊ฐ€ API ์š”์ฒญ ์‹œ ์„œ๋ฒ„์—์„œ๋Š” ๋งค ์š”์ฒญ๋งˆ๋‹ค ์ฟ ํ‚ค์— ํฌํ•จ๋œ JWT๋ฅผ ์ฐพ๋Š”๋‹ค. (์ฟ ํ‚ค์—๋Š” ์—ฌ๋Ÿฌ ์ •๋ณด๊ฐ€ ๋‹ด๊ฒจ ์žˆ๋‹ค. ๊ทธ ์ค‘ JWT๊ฐ€ ๋‹ด๊ธด ์ฟ ํ‚ค์™€ ์ด๋ฆ„์ด ๋™์ผํ•œ์ง€ ํ™•์ธ ํ›„ ๊ฐ€์ ธ์˜จ๋‹ค.)

 

3.  ์„œ๋ฒ„?

- ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์ „๋‹ฌํ•œ JWT secret key ์‚ฌ์šฉํ•ด ์œ„์กฐ ์—ฌ๋ถ€๋ฅผ ๊ฒ€์ฆ ๋ฐ ํ•ด๋‹น ํ† ํฐ์˜ ์œ ํšจ๊ธฐ๊ฐ„์„ ๊ฒ€์ฆํ•œ๋‹ค.

- ๊ฒ€์ฆ ์„ฑ๊ณต ์‹œ ํ•ด๋‹น ํ† ํฐ์—์„œ user ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์™€ ํ™•์ธํ•œ๋‹ค.

 

JWT

: ๋ˆ„๊ตฌ๋‚˜ ํ‰๋ฌธ์œผ๋กœ ๋ณตํ˜ธํ™”๊ฐ€ ๊ฐ€๋Šฅํ•˜๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ secret key๊ฐ€ ์—†์œผ๋ฉด JWT ์ˆ˜์ •์ด ๋ถˆ๊ฐ€ํ•˜๋‹ค.

๊ฒฐ๊ตญ JWT๋Š” Read only ๋ฐ์ดํ„ฐ๋‹ค.

 

 

JWT ๋‹ค๋ฃจ๊ธฐ

dependency ์ถ”๊ฐ€ ๋ฐ application properties์— jwt secret key ๋ถ€๋ถ„์„ ์ถ”๊ฐ€ํ•ด์ฃผ์–ด์•ผ ํ•œ๋‹ค.

 

JWT ์‚ฌ์šฉ ์˜ˆ)

//๊ฐ€๋…์„ฑ์„ ์œ„ํ•ด import ๋‚ ๋ฆผ

@Component
public class JwtUtil {
    // Header KEY ๊ฐ’
    public static final String AUTHORIZATION_HEADER = "Authorization";
    // ์‚ฌ์šฉ์ž ๊ถŒํ•œ ๊ฐ’์˜ KEY
    public static final String AUTHORIZATION_KEY = "auth";
    // Token ์‹๋ณ„์ž
    public static final String BEARER_PREFIX = "Bearer ";
    // ํ† ํฐ ๋งŒ๋ฃŒ์‹œ๊ฐ„
    private final long TOKEN_TIME = 60 * 60 * 1000L; // 60๋ถ„

    @Value("${jwt.secret.key}") // Base64 Encode ํ•œ SecretKey
    private String secretKey;
    private Key key;
    private final SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;

    // ๋กœ๊ทธ ์„ค์ •
    public static final Logger logger = LoggerFactory.getLogger("JWT ๊ด€๋ จ ๋กœ๊ทธ");

    @PostConstruct
    public void init() {
        byte[] bytes = Base64.getDecoder().decode(secretKey);
        key = Keys.hmacShaKeyFor(bytes);
    }

    // ํ† ํฐ ์ƒ์„ฑ
    public String createToken(String username, UserRoleEnum role) {
        Date date = new Date();

        return BEARER_PREFIX +
                Jwts.builder()
                        .setSubject(username) // ์‚ฌ์šฉ์ž ์‹๋ณ„์ž๊ฐ’(ID)
                        .claim(AUTHORIZATION_KEY, role) // ์‚ฌ์šฉ์ž ๊ถŒํ•œ
                        .setExpiration(new Date(date.getTime() + TOKEN_TIME)) // ๋งŒ๋ฃŒ ์‹œ๊ฐ„
                        .setIssuedAt(date) // ๋ฐœ๊ธ‰์ผ
                        .signWith(key, signatureAlgorithm) // ์•”ํ˜ธํ™” ์•Œ๊ณ ๋ฆฌ์ฆ˜
                        .compact();
    }

    // JWT Cookie ์— ์ €์žฅ
    public void addJwtToCookie(String token, HttpServletResponse res) {
        try {
            token = URLEncoder.encode(token, "utf-8").replaceAll("\\+", "%20"); // Cookie Value ์—๋Š” ๊ณต๋ฐฑ์ด ๋ถˆ๊ฐ€๋Šฅํ•ด์„œ encoding ์ง„ํ–‰

            Cookie cookie = new Cookie(AUTHORIZATION_HEADER, token); // Name-Value
            cookie.setPath("/");

            // Response ๊ฐ์ฒด์— Cookie ์ถ”๊ฐ€
            res.addCookie(cookie);
        } catch (UnsupportedEncodingException e) {
            logger.error(e.getMessage());
        }
    }

    // JWT ํ† ํฐ substring
    public String substringToken(String tokenValue) {
        if (StringUtils.hasText(tokenValue) && tokenValue.startsWith(BEARER_PREFIX)) {
            return tokenValue.substring(7);
        }
        logger.error("Not Found Token");
        throw new NullPointerException("Not Found Token");
    }

    // ํ† ํฐ ๊ฒ€์ฆ
    public boolean validateToken(String token) {
        try {
            Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token);
            return true;
        } catch (SecurityException | MalformedJwtException | SignatureException e) {
            logger.error("Invalid JWT signature, ์œ ํšจํ•˜์ง€ ์•Š๋Š” JWT ์„œ๋ช… ์ž…๋‹ˆ๋‹ค.");
        } catch (ExpiredJwtException e) {
            logger.error("Expired JWT token, ๋งŒ๋ฃŒ๋œ JWT token ์ž…๋‹ˆ๋‹ค.");
        } catch (UnsupportedJwtException e) {
            logger.error("Unsupported JWT token, ์ง€์›๋˜์ง€ ์•Š๋Š” JWT ํ† ํฐ ์ž…๋‹ˆ๋‹ค.");
        } catch (IllegalArgumentException e) {
            logger.error("JWT claims is empty, ์ž˜๋ชป๋œ JWT ํ† ํฐ ์ž…๋‹ˆ๋‹ค.");
        }
        return false;
    }

    // ํ† ํฐ์—์„œ ์‚ฌ์šฉ์ž ์ •๋ณด ๊ฐ€์ ธ์˜ค๊ธฐ
    public Claims getUserInfoFromToken(String token) {
        return Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody();
    }
}

'Spring' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€

TIL Spring #3-3  (0) 2023.12.18
TIL Spring #3-2  (0) 2023.12.15
TIL Spring #2-5  (0) 2023.12.13
TIL Spring #2-4  (0) 2023.12.12
TIL Spring #2-3  (0) 2023.12.12