๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
๊ณต๋ถ€..์Šคํ„ฐ๋””.../์ˆ˜์ค€๋ณ„ ํ•™์Šต๋ฐ˜

์ˆ˜์ค€๋ณ„ ํ•™์Šต๋ฐ˜_๋ฒ ์ด์ง๋ฐ˜ 7ํšŒ์ฐจ ์„ธ์…˜

by carrot0911 2024. 12. 28.

๋ฒ ์ด์ง๋ฐ˜

์ธ์ฆ์ธ๊ฐ€ ํ™œ์šฉ(ํ† ํฐ์ธ์ฆ๋ฐฉ์‹: JWT)

12/26 11:00 ~ 12:10 (์•ฝ 1์‹œ๊ฐ„  10๋ถ„ ์ง„ํ–‰)

 

JWT

https://jwt.io/

 

JWT.IO

JSON Web Tokens are an open, industry standard RFC 7519 method for representing claims securely between two parties.

jwt.io

  • ์•”ํ˜ธํ™”๊ฐ€ ์•„๋‹ˆ๋‹ค!
  • ๊ทธ๋ƒฅ ๋‹จ์ง€ ๊ธฐ์กด ํ† ํฐ์˜ ๋ณ€์กฐ๋ฅผ ๋ง‰๊ธฐ ์œ„ํ•ด์„œ JWT์˜ Signature๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด๋‹ค. → ๋ณ€์กฐ๋ฅผ ๋ง‰๊ธฐ ์œ„ํ•ด์„œ ๋“ค์–ด๊ฐ„๋‹ค.
  • ์ฒซ ๋ฒˆ์งธ ์˜์—ญ์€ HEADER๋ผ๋Š” ๊ณณ์ด๋‹ค.
    • ์„œ๋ช…์ด ์‚ฌ์šฉ๋œ ์•Œ๊ณ ๋ฆฌ์ฆ˜์ด ๋ฌด์—‡์ธ์ง€, ํ† ํฐ์˜ ํƒ€์ž…์ด ๋ฌด์—‡์ธ์ง€ ์•Œ๋ ค์ฃผ๋Š” ๊ตฌ์—ญ์ด๋‹ค.
    • ์—ฐ์‚ฐํ•  ๋•Œ ์‚ฌ์šฉ๋˜๋Š” ์•Œ๊ณ ๋ฆฌ์ฆ˜์„ ๋œปํ•œ๋‹ค.
  • ๋‘ ๋ฒˆ์งธ ์˜์—ญ์€ PAYLOAD๋ผ๋Š” ๊ณณ์ด๋‹ค. → ๊ธฐ์กด ํ† ํฐ์€ ์ด๊ฒƒ๋งŒ ์žˆ์œผ๋ฉด ๋œ๋‹ค.
    • ๋ฐ์ดํ„ฐ์ด๋‹ค. ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๊ฐ€ ๋‹ด๊ฒจ์žˆ๋Š” ๊ตฌ์—ญ์ด๋‹ค.
  • ์„ธ ๋ฒˆ์งธ ์˜์—ญ์€ SIGNATUREํ•˜๋Š” ๊ณณ์ด๋‹ค.(์„œ๋ช…) → HEADER + PAYLOAD + ๋น„๋ฐ€ํ‚ค = SIGNATURE
    • ๋ณ€์กฐ๋ฅผ ๋ง‰๊ธฐ ์œ„ํ•ด์„œ ์ƒ๊ฒผ๋‹ค.
    • ๋น„๋ฐ€ํ‚ค๋Š” ๋ณ€์กฐ๊ฐ€ ๋˜์—ˆ๋Š”์ง€ ์•„๋‹Œ์ง€๋ฅผ ํ™•์ธํ•˜๋Š” ๋ฐ ์“ฐ์ด๋Š” ๊ธฐ์ˆ ์ด๋‹ค.

 

ํ๋ฆ„ & ๊ตฌ์„ฑ๋„

1. ๊ธฐ๋ณธ ์Šคํ”„๋ง ์š”์ฒญ ์‘๋‹ต ์ฒ˜๋ฆฌ ํ๋ฆ„๋„

2. ์ธ์ฆ์ธ๊ฐ€ ํ๋ฆ„๋„

  • ์˜๋ฌธ์˜ ํด๋ผ์ด์–ธํŠธ๊ฐ€ Controller์— ์ ‘๊ทผํ•œ๋‹ค.
    • ๋กœ๊ทธ์ธ์— ์ ‘๊ทผํ•ด์„œ ํ† ํฐ์„ ๋ฐœ๊ธ‰ ๋ฐ›๊ณ  ์ธ์ฆ์ด ํ•„์š”ํ•œ API์— ์ ‘๊ทผ์„ ํ•˜๊ฒŒ ๋˜๋ฉด ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

3. ์‹ค์Šต: ํ† ํฐ์—์„œ ํ™œ์šฉํ•  ๊ฐ์ฒด (JwtUtils)

public class JwtUtils {

	public String createToken() {
		// 1. ํ† ํฐ ์ƒ์„ฑ
		
		// 2. ํ† ํฐ ๋ฐ˜ํ™˜
	}
	

	public Long extractStudentIdFromToken(String token) {
	
		// 1. ํ† ํฐ ํ•ด๋…
		
		// 2. ์ •๋ณด ์ถ”์ถœ
		
		// 3. ํ•™์ƒ ์‹๋ณ„์ž ๋ฐ˜ํ™˜
	}
}

 

ํ† ํฐ ๋ฐœ๊ธ‰

1. ํ† ํฐ ๋ฐœ๊ธ‰ ๋กœ์ง: createToken()

@Component
public class JwtUtils {

    // SECRET ์˜ ๊ฒฝ์šฐ ์Šคํ”„๋ง์„ค์ •ํŒŒ์ผ(application.properties) ์—์„œ ์ฃผ์ž…๋ฐ›์•„ ํ™œ์šฉ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.
    private static final String SECRET = "my-cat-is-flying";  // ์•”๊ตฌํ˜ธ(์šฐ๋ฆฌ๋งŒ ์•„๋Š” ์•”ํ˜ธ๋ผ๊ณ  ์ƒ๊ฐํ•˜๋ฉด ๋œ๋‹ค.)
    ...(์ƒ๋žต)...

    /**
     * jwt ํ† ํฐ ๋ฐœ๊ธ‰
     *
     * ํ† ํฐ ์ƒ์„ฑ์— ํ•„์š”ํ•œ ์ •๋ณด๋ฅผ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ๋ฐ›์•„ JWT ํ† ํฐ์„ ์ƒ์„ฑํ•˜๊ณ  ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
     * @param studentId ํ•™์ƒ ์‹๋ณ„์ž
     * @param role ํ•™์ƒ ๊ถŒํ•œ
     * @param dataYouWannaPut ํ† ํฐ์— ๋„ฃ๊ณ  ์‹ถ์€ ๋ฐ์ดํ„ฐ
     * @return token
     * @throws UnsupportedEncodingException
     */
    public String createToken(Long studentId, String role, String dataYouWannaPut) throws UnsupportedEncodingException {
        // 1. ํ† ํฐ ์„œ๋ช…์— ํ™œ์šฉ๋  ์•Œ๊ณ ๋ฆฌ์ฆ˜ ์„ค์ •
        Algorithm algorithm = Algorithm.HMAC256(SECRET);  // Header

        // 2. ํ† ํฐ ์ƒ์„ฑ
        String token = JWT.create()
                .withIssuer("sparta.basic.com")
                .withSubject(studentId.toString())  // "Key": "value" -> "์ง€์ •": "์กฐ์ž‘ ๊ฐ€๋Šฅ"
                .withClaim("role", role)  // Key๋„ ๋‚ด ๋งˆ์Œ๋Œ€๋กœ ์กฐ์ž‘ ๊ฐ€๋Šฅํ•˜๋‹ค.
                .withClaim("customField1", dataYouWannaPut)
                .withClaim("customField2", "customFieldValue2")
                .withIssuedAt(new Date())
                .withExpiresAt(new Date(System.currentTimeMillis() + 3600 * 1000))
                .sign(algorithm); // ์„œ๋ช…
        
        // 3. ํ† ํฐ ๋ฐ˜ํ™˜
        return token;
    }
    
    ...(์ƒ๋žต)...
}

2. ์ƒ์„ฑ๋œ ํ† ํฐ ๋‚ด์šฉ ์˜ˆ์‹œ(Json)

{
  "sub": "student1",
  "role": "admin",
  "customField1": "customFieldValue1",
  "customField2": "customFieldValue2",
  "iss": "example.com",
  "exp": 1735129767,
  "iat": 1735126167
}
๋ฉ”์„œ๋“œ ์˜ˆ์‹œ ์„ค๋ช…
withIssuer( ) www.example.com ๋ฐœ๊ธ‰์ž: JWT๋ฅผ ์ƒ์„ฑํ•œ ์‹œ์Šคํ…œ ํ˜น์€ ์„œ๋ฒ„
withSubject( ) user1 ์‚ฌ์šฉ์ž: JWT์™€ ์—ฐ๊ด€๋œ ์‚ฌ์šฉ์ž ID, ๋ˆ„๊ตฌ๋ฅผ ์œ„ํ•ด ์ƒ์„ฑ๋˜์—ˆ๋Š”์ง€ ํ‘œ์‹œ
withClaim( ) admin ์ปค์Šคํ…€ ํ•„๋“œ: ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ํ•„์š”ํ•œ ์ถ”๊ฐ€ ์ •๋ณด๋ฅผ ์ œ๊ณตํ•˜๊ธฐ ์œ„ํ•ด ํ™œ์šฉ
withIssuedAt( ) 175123152 ๋ฐœ๊ธ‰ ์‹œ๊ฐ„: ํ† ํฐ์˜ ์ƒ์„ฑ ์‹œ๊ฐ„์„ ํ‘œ์‹œ
withExpiresAt( ) 1735119552 ๋งŒ๋ฃŒ ์‹œ๊ฐ„: ํ† ํฐ์˜ ๋งŒ๋ฃŒ ์‹œ๊ฐ„์„ ํ‘œ์‹œ
  • ์•”ํ˜ธํ™”๊ฐ€ ์•„๋‹ˆ๊ธฐ ๋•Œ๋ฌธ์— ๋…ธ์ถœ๋˜๋ฉด ์•ˆ๋˜๋Š” ์ •๋ณด(Password)๋Š” ๋„ฃ์œผ๋ฉด ์•ˆ๋œ๋‹ค!!

 

ํ† ํฐ ํ•ด๋…

ํ† ํฐ์„ ํ™œ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ๋จผ์ € ์„œ๋ฒ„๋กœ ํ† ํฐ์„ ์ „์†กํ•ด์•ผ ํ•œ๋‹ค.
๋ณดํ†ต Request Headers์— ํ† ํฐ์„ ๋‹ด์•„ ์ „์†กํ•œ๋‹ค.

1. ํด๋ผ์ด์–ธํŠธ์—์„œ ์„œ๋ฒ„๋กœ ํ† ํฐ์„ ๋ณด๋‚ด๋Š” ๋‹ค์–‘ํ•œ ๋ฐฉ๋ฒ•

a. ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ ํ™œ์šฉ - Query Params

GET /api/resource?token=<JWT ํ† ํฐ>

b. ์š”์ฒญ ๋ณธ๋ฌธ์œผ๋กœ ์ „๋‹ฌ - Request Body

{
  "token": "<JWT ํ† ํฐ>"
}

c. ๊ธฐ๋ณธ ํ—ค๋” ํ™œ์šฉ - Request Header → ๋งŽ์ด ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ์‹์ด๋‹ค.

Authorization: <JWT ํ† ํฐ>

d. OAuth 2.0 ํ‘œ์ค€ → ๋งŽ์ด ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ์‹์ด๋‹ค.

Authorization: Bearer <JWT ํ† ํฐ>

2. ํ† ํฐ ํ•ด๋… (๊ธฐ๋ณธ ํ—ค๋”๋ฅผ ํ™œ์šฉ)

@Component
public class JwtUtils {

    // SECRET ์˜ ๊ฒฝ์šฐ ์Šคํ”„๋ง์„ค์ •ํŒŒ์ผ(application.properties) ์—์„œ ์ฃผ์ž…๋ฐ›์•„ ํ™œ์šฉ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.
    private static final String SECRET = "my-cat-is-flying";
    private static final String BEARER_PREFIX = "Bearer";
    
    ...(์ƒ๋žต)...
    

    /**
     * ํ† ํฐ์—์„œ ํ•™์ƒ ์‹๋ณ„์ž๋ฅผ ์ถ”์ถœ ํ•ฉ๋‹ˆ๋‹ค.
     * @param token ์ผ๋ฐ˜ ํ† ํฐ
     * @return studentId(Long) ํ•™์ƒ ์‹๋ณ„์ž
     * @throws UnsupportedEncodingException
     */
    public Long extractStudentIdFromToken(String token) throws UnsupportedEncodingException {
        // 1. ํ† ํฐ์— ํ™œ์šฉํ•  ์•Œ๊ณ ๋ฆฌ์ฆ˜ ์„ค์ •
        Algorithm algorithm = Algorithm.HMAC256(SECRET);

        // 2. ํ† ํฐ ๊ฒ€์ฆ
        DecodedJWT decodedToken = JWT.require(algorithm)
                .withIssuer("sparta.basic.com")
                .build()
                .verify(token);

        // 3. ํ† ํฐ์—์„œ ์›ํ•˜๋Š” ์ •๋ณด ์ถ”์ถœ
        System.out.println("subject: " + decodedToken.getSubject());
        System.out.println("role: " + decodedToken.getClaim("role").asString());
        System.out.println("customField1: " + decodedToken.getClaim("customField1").asString());
        System.out.println("customField2: " + decodedToken.getClaim("customField2").asString());
        System.out.println("issued At: " + decodedToken.getIssuedAt());
        System.out.println("expires At: " + decodedToken.getExpiresAt());

        // 4. ํ•™์ƒ ์‹๋ณ„์ž ๋ฐ˜ํ™˜
        String studentId = decodedToken.getSubject();
        return Long.parseLong(studentId);
    }
    
    ...(์ƒ๋žต)...
}

3. ํ† ํฐ ํ•ด๋…(OAuth 2.0 ํ‘œ์ค€: Bearer)

@Component
public class JwtUtils {

    // SECRET ์˜ ๊ฒฝ์šฐ ์Šคํ”„๋ง์„ค์ •ํŒŒ์ผ(application.properties) ์—์„œ ์ฃผ์ž…๋ฐ›์•„ ํ™œ์šฉ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.
    private static final String SECRET = "my-cat-is-flying";
    private static final String BEARER_PREFIX = "Bearer";
    
    ...(์ƒ๋žต)...


    /**
     * ๋ฒ ์–ด๋Ÿฌ ํ† ํฐ์—์„œ ํ•™์ƒ ์‹๋ณ„์ž๋ฅผ ์ถ”์ถœ ํ•ฉ๋‹ˆ๋‹ค.
     * @param bearerToken
     * @return studentId(Long) ํ•™์ƒ ์‹๋ณ„์ž
     * @throws UnsupportedEncodingException
     */
    public Long extractStudentIdFromBearerToken(String bearerToken) throws UnsupportedEncodingException {
        // 1. ํ† ํฐ์— ํ™œ์šฉํ•  ์•Œ๊ณ ๋ฆฌ์ฆ˜
        Algorithm algorithm = Algorithm.HMAC256(SECRET);

        // 2. Bearer ํ† ํฐ ์ถ”์ถœ
        String token = bearerToken.substring(BEARER_PREFIX.length()).trim();

        // 3. ํ† ํฐ ๊ฒ€์ฆ
        DecodedJWT decodedToken = JWT.require(algorithm)
                .withIssuer("sparta.basic.com")
                .build()
                .verify(token);

        // 4. ํ† ํฐ์—์„œ ์›ํ•˜๋Š” ์ •๋ณด ์ถ”์ถœ
        System.out.println("subject: " + decodedToken.getSubject());
        System.out.println("role: " + decodedToken.getClaim("role").asString());
        System.out.println("customField1: " + decodedToken.getClaim("customField1").asString());
        System.out.println("customField2: " + decodedToken.getClaim("customField2").asString());
        System.out.println("issued At: " + decodedToken.getIssuedAt());
        System.out.println("expires At: " + decodedToken.getExpiresAt());

        // 5. ํ•™์ƒ ์‹๋ณ„์ž ๋ฐ˜ํ™˜
        String studentId = decodedToken.getSubject();
        return Long.parseLong(studentId);
    }
}

 

ํ•„ํ„ฐ๋Š” ์™œ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ผ๊นŒ?!

  • ์ค‘๋ณต ์ฝ”๋“œ๋ฅผ ์ค„์ด๊ธฐ ์œ„ํ•ด์„œ ํ•„ํ„ฐ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.
  • ์‘๋‹ต ์†๋„๋„ ํ›จ์”ฌ ๋” ๋น ๋ฅด๋‹ค.
  • ํ•˜์ง€๋งŒ ํ•„ํ„ฐ๋ฅผ ์“ฐ์ง€ ์•Š๋Š”๋‹ค๊ณ  ํ•ด์„œ ํ‹€๋ฆฌ๊ฒŒ ๊ตฌํ˜„ํ•˜๋Š” ๊ฒƒ์€ ์•„๋‹ˆ๋‹ค.

 

์ „์ฒด ์ฝ”๋“œ

๋”๋ณด๊ธฐ

JwtUtils.java

@Component
public class JwtUtils {

    // SECRET ์˜ ๊ฒฝ์šฐ ์Šคํ”„๋ง์„ค์ •ํŒŒ์ผ(application.properties) ์—์„œ ์ฃผ์ž…๋ฐ›์•„ ํ™œ์šฉ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.
    private static final String SECRET = "my-cat-is-flying";  // ์šฐ๋ฆฌ๋งŒ ์•Œ๊ณ  ์žˆ๋Š” ์•”๊ตฌํ˜ธ
    private static final String BEARER_PREFIX = "Bearer";

    /**
     * jwt ํ† ํฐ ๋ฐœ๊ธ‰
     *
     * ํ† ํฐ ์ƒ์„ฑ์— ํ•„์š”ํ•œ ์ •๋ณด๋ฅผ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ๋ฐ›์•„ JWT ํ† ํฐ์„ ์ƒ์„ฑํ•˜๊ณ  ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
     * @param studentId ํ•™์ƒ ์‹๋ณ„์ž
     * @param role ํ•™์ƒ ๊ถŒํ•œ
     * @param dataYouWannaPut ํ† ํฐ์— ๋„ฃ๊ณ  ์‹ถ์€ ๋ฐ์ดํ„ฐ
     * @return token
     * @throws UnsupportedEncodingException
     */
    public String createToken(Long studentId, String role, String dataYouWannaPut) throws UnsupportedEncodingException {
        // 1. ํ† ํฐ ์„œ๋ช…์— ํ™œ์šฉ๋  ์•Œ๊ณ ๋ฆฌ์ฆ˜ ์„ค์ •
        Algorithm algorithm = Algorithm.HMAC256(SECRET);  // Header

        // 2. ํ† ํฐ ์ƒ์„ฑ
        String token = JWT.create()
                .withIssuer("sparta.basic.com")
                .withSubject(studentId.toString())
                .withClaim("role", role)
                .withClaim("customField1", dataYouWannaPut)
                .withClaim("customField2", "customFieldValue2")
                .withIssuedAt(new Date())
                .withExpiresAt(new Date(System.currentTimeMillis() + 3600 * 1000))
                .sign(algorithm); // ์„œ๋ช…
        return token;
    }

    /**
     * ํ† ํฐ์—์„œ ํ•™์ƒ ์‹๋ณ„์ž๋ฅผ ์ถ”์ถœ ํ•ฉ๋‹ˆ๋‹ค.
     * @param token ์ผ๋ฐ˜ ํ† ํฐ
     * @return studentId(Long) ํ•™์ƒ ์‹๋ณ„์ž
     * @throws UnsupportedEncodingException
     */
    public Long extractStudentIdFromToken(String token) throws UnsupportedEncodingException {
        // 1. ํ† ํฐ์— ํ™œ์šฉํ•  ์•Œ๊ณ ๋ฆฌ์ฆ˜ ์„ค์ •
        Algorithm algorithm = Algorithm.HMAC256(SECRET);

        // 2. ํ† ํฐ ๊ฒ€์ฆ
        DecodedJWT decodedToken = JWT.require(algorithm)
                .withIssuer("sparta.basic.com")
                .build()
                .verify(token);

        // 3. ํ† ํฐ์—์„œ ์›ํ•˜๋Š” ์ •๋ณด ์ถ”์ถœ
        System.out.println("subject: " + decodedToken.getSubject());
        System.out.println("role: " + decodedToken.getClaim("role").asString());
        System.out.println("customField1: " + decodedToken.getClaim("customField1").asString());
        System.out.println("customField2: " + decodedToken.getClaim("customField2").asString());
        System.out.println("issued At: " + decodedToken.getIssuedAt());
        System.out.println("expires At: " + decodedToken.getExpiresAt());

        // 4. ํ•™์ƒ ์‹๋ณ„์ž ๋ฐ˜ํ™˜
        String studentId = decodedToken.getSubject();
        return Long.parseLong(studentId);
    }

    /**
     * ๋ฒ ์–ด๋Ÿฌ ํ† ํฐ์—์„œ ํ•™์ƒ ์‹๋ณ„์ž๋ฅผ ์ถ”์ถœ ํ•ฉ๋‹ˆ๋‹ค.
     * @param bearerToken
     * @return studentId(Long) ํ•™์ƒ ์‹๋ณ„์ž
     * @throws UnsupportedEncodingException
     */
    public Long extractStudentIdFromBearerToken(String bearerToken) throws UnsupportedEncodingException {
        // 1. ํ† ํฐ์— ํ™œ์šฉํ•  ์•Œ๊ณ ๋ฆฌ์ฆ˜
        Algorithm algorithm = Algorithm.HMAC256(SECRET);

        // 2. Bearer ํ† ํฐ ์ถ”์ถœ
        String token = bearerToken.substring(BEARER_PREFIX.length()).trim();

        // 3. ํ† ํฐ ๊ฒ€์ฆ
        DecodedJWT decodedToken = JWT.require(algorithm)
                .withIssuer("sparta.basic.com")
                .build()
                .verify(token);

        // 4. ํ† ํฐ์—์„œ ์›ํ•˜๋Š” ์ •๋ณด ์ถ”์ถœ
        System.out.println("subject: " + decodedToken.getSubject());
        System.out.println("role: " + decodedToken.getClaim("role").asString());
        System.out.println("customField1: " + decodedToken.getClaim("customField1").asString());
        System.out.println("customField2: " + decodedToken.getClaim("customField2").asString());
        System.out.println("issued At: " + decodedToken.getIssuedAt());
        System.out.println("expires At: " + decodedToken.getExpiresAt());

        // 5. ํ•™์ƒ ์‹๋ณ„์ž ๋ฐ˜ํ™˜
        String studentId = decodedToken.getSubject();
        return Long.parseLong(studentId);
    }
}

AuthController.java

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

    private final JwtUtils jwtUtils;

    /**
     * ๋กœ๊ทธ์ธ API
     * ํ•™์ƒ์˜ email, password ๋ฅผ ๋ฐ›์•„ ๋กœ๊ทธ์ธ ์‹œ๋„๋ฅผํ•ฉ๋‹ˆ๋‹ค.
     * ๋กœ๊ทธ์ธ ์„ฑ๊ณต์‹œ JWT ํ† ํฐ์„ ์ƒ์„ฑํ•ด ํด๋ผ์ด์–ธํŠธ๋กœ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
     * @return token
     * @throws UnsupportedEncodingException
     */
    @GetMapping("/login")
    public String loginAPI() throws UnsupportedEncodingException {
        // 1. ๋กœ๊ทธ์ธ์„ ์„ฑ๊ณตํ–ˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•ฉ๋‹ˆ๋‹ค.
        // ๋กœ๊ทธ์ธ ๋กœ์ง ์ž‘

        // 2. ํ† ํฐ์— ๋‹ด์„ ์ •๋ณด ์ค€๋น„
        Long studentId = 1L;     // ํ•™์ƒ ์‹๋ณ„์ž
        String role = "student"; //
        String dataThatYouWannaPut = "๋‚ด ๋งˆ์Œ๋Œ€๋กœ ๋ฐ์ดํ„ฐ";

        // 3. ํ† ํฐ ๋ฐœ๊ธ‰
        String token = jwtUtils.createToken(studentId, role, dataThatYouWannaPut);
        return token;
    }

    /**
     * ์ธ์ฆ์ด ํ•„์š”ํ•œ API
     * ์ด API๋Š” ๋กœ๊ทธ์ธํ•œ ์‚ฌ์šฉ์ž๋งŒ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋Š”  ์˜ˆ์ œ์ž…๋‹ˆ๋‹ค.
     * Authorization ํ—ค๋”์—์„œ JWT๋ฅผ ์ถ”์ถœํ•˜์—ฌ ์‚ฌ์šฉ์ž๋ฅผ ์ธ์ฆํ•ฉ๋‹ˆ๋‹ค.
     * ์ฃผ์„์„ ํ™œ์šฉํ•ด์„œ ์ผ๋ฐ˜ ํ† ํฐ Bearer ํ† ํฐ์„ Authorization ํ—ค๋”์—์„œ ์ถ”์ถœํ•ด์„œ ํ™œ์šฉํ•ฉ๋‹ˆ๋‹ค.
     *
     * @param request Authorization ํ—ค๋”๋ฅผ ํฌํ•จํ•œ ์š”์ฒญ ์ •๋ณด๋ฅผ ์ „๋‹ฌ ๋ฐ›์Šต๋‹ˆ๋‹ค.
     * @throws UnsupportedEncodingException
     */
    @GetMapping("/api")
    public void authRequiredAPI(HttpServletRequest request) throws UnsupportedEncodingException {
        
        // 1. requestHeader ์—์„œ ํ† ํฐ ์ถ”์ถœ
        String token = request.getHeader("Authorization");

        // 2. ํ† ํฐ์—์„œ ์ •๋ณด ์ถ”์ถœ
        // 2-1. ์ผ๋ฐ˜ ํ† ํฐ ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ
        Long studentId = jwtUtils.extractStudentIdFromToken(token);

        // 2-2.๋ฒ ์–ด๋Ÿฌ ํ† ํฐ ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ
        // Long studentId = jwtUtils.extractStudentIdFromBearerToken(token);

        // 3. ํ•™์ƒ ์•„์ด๋”” ํ™œ์šฉ
        log.info("ํ•™์ƒ ์•„์ด๋””๋Š”: {}", studentId);
    }
}