(Json Web Token)
๋จผ์ ํ ํฐ์ ๋ํด์ ์์๋ณด์!
์ด ์
์ฅ๊ถ์ ํตํด์ ์ ๊ทผ์ด ํ์ฉ๋ ์๋ฒ์ ์์์ ์ ๊ทผํ ์ ์๋ ๊ฒ์ด๋ค.
๐ก access token : ๋ก๊ทธ์ธ ์ ๊ทผ ์นด๋ํค
ย ย ย ย ย refresh token : ์นด๋ํค ์ฌ๋ฐ๊ธ
์ ๊ทผ
ํ ๋ ์ฌ์ฉํ๋ค.์์: ๋ก๊ทธ์ธ -> Access token ๋ฐ๊ธ ๋ฐ์ -> Access token์ ํค๋์ ์ถ๊ฐํ ํ ์๋ฒ์ api์์ฒญ -> ์๋ฒ๊ฐ Access token์ validation -> validation์ ํต๊ณผํ๋ค๋ฉด ์์ฒญ์ ๋ง๊ฒ ์๋ต
๊ธฐ๋ณธ์ ์ผ๋ก Access Token์ ์ธ๋ถ ์ ์ถ ๋ฌธ์ ๋ก ์ธํด ์ ํจ๊ธฐ๊ฐ์ ์งง๊ฒ ์ค์ ํ๋๋ฐ, ์ ์์ ์ธ ํด๋ผ์ด์ธํธ๋ ์ ํจ๊ธฐ๊ฐ์ด ๋๋ Access Token์ ๋ํด Refresh Token์ ์ฌ์ฉํ์ฌ ์๋ก์ด Access Token์ ๋ฐ๊ธ๋ฐ์ ์ ์๋ค.
๋ง์ฝ Refresh Token์ด ์ ์ถ๋์ด์ ๋ค๋ฅธ ์ฌ์ฉ์๊ฐ ์ด๋ฅผ ํตํด ์๋ก์ด Access Token์ ๋ฐ๊ธ๋ฐ์๋ค๋ฉด? Access Token์ ์ถฉ๋์ด ๋ฐ์ํ๊ธฐ ๋๋ฌธ์, ์๋ฒ์ธก์์๋ ๋ ํ ํฐ์ ๋ชจ๋ ํ๊ธฐ์์ผ์ผ ํ๋ค.
โ ๏ธ ๋ฐ๊ธ ํ ์์ ์ด ๋ถ๊ฐ๋ฅ, ํ ํฐ์ ์์ ํ๊ฒ ๋ณด๊ดํด์ผ ํ๊ณ , ํ์ํ ์ ๋ณด๋ง ํฌํจํด์ผํ๋ค!
๐๐ป ๋ก๊ทธ์ธ : ์ฌ์ฉ์๊ฐ ID/PW๋ฅผ ํตํด ๋ก๊ทธ์ธ์ ์๋
secret key
๋ฅผ ํตํด Access token
์ ๋ฐ๊ธํ๋ค.๐๐ป ๋ณดํธ๋ ์๋ํฌ์ธํธ : API ์ํธํฌ์ธํธ์ ์์ฒญ์ ๋ณด๋ผ ๋, ํด๋ผ์ด์ธํธ๊ฐ Authorization header(or ๋งค๊ฐ๋ณ์..)์ Access token์ ๋ด์์ ๋ณด๋ธ๋ค.
๐ฉ๐ปโ๐ฆฐ ์ด๋ฅผ ๊ตฌํํ๊ธฐ ์ํด, ๐Spring Security์ JWT ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์กฐํฉํ์ฌ ๊ตฌํํด๋ณด์.
build.gradle
๋ค์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ถ๊ฐํ๋ค.
dependencies {
...
//security
implementation 'org.springframework.boot:spring-boot-starter-security'
// jwt
implementation 'io.jsonwebtoken:jjwt-api'
implementation 'io.jsonwebtoken:jjwt-impl'
implementation 'io.jsonwebtoken:jjwt-jackson'
}
TokenInfo
- jwt
์์ฑ์ ์ํ ์ ๋ณดํด๋ผ์ด์ธํธ์ ํ ํฐ ๋ณด๋ด๊ธฐ์ํด DTO ์์ฑ
@Builder
@Data
@AllArgsConstructor
@ConfigurationProperties("jwt")
public class TokenInfo {
// private String grantType;
// private String accessToken;
// private String refreshToken;
private String issuer;
private String secretKey;
}
grantType์ JWT ๋ํ ์ธ์ฆ ํ์ ์ผ๋ก, ์ฌ๊ธฐ์๋ Bearer๋ฅผ ์ฌ์ฉํ๋ค. ์ดํ HTTP ํค๋์ prefix๋ก ๋ถ์ฌ์ฃผ๋ ํ์ ์ด๊ธฐ๋ ํ๋ค.
@ConfigurationProperties
ํ๋กํผํฐ ํ์ผ์ด๋ ํ๊ฒฝ ๋ณ์์ ๊ฐ์ ์ธ๋ถ ์ค์ ์์ค์์ ๊ฐ์ ๊ฐ์ ธ์ Java ๊ฐ์ฒด์ ๋งคํํ ๋ ์ฌ์ฉํ๋ค.
TokenProvider
- ์ํธํ, ๋ณตํธํJWT ํ ํฐ ์์ฑ, ํ ํฐ ๋ณตํธํ ๋ฐ ์ ๋ณด ์ถ์ถ, ํ ํฐ ์ ํจ์ฑ ๊ฒ์ฆ์ ๊ธฐ๋ฅ์ด ๊ตฌํ๋ ํด๋์ค์ด๋ค.
๋จผ์ applicatoin.yml์ ๋ค์ ์ค์ ์ ์ถ๊ฐํ๋ค.
jwt:
issuer: (๋ฐํ์ธ)
secret_key: secretKey
ํ ํฐ์ ์ํธํ ๋ณตํธํ๋ฅผ ์ํ secret key๋ก์ ์ดํ HS256 ์๊ณ ๋ฆฌ์ฆ์ ์ฌ์ฉํ๊ธฐ ์ํด, 256๋นํธ๋ณด๋ค ์ปค์ผํ๋ค.
์ํ๋ฒณ์ ํ๋จ์ด ๋น 8bit ์ด๋ฏ๋ก 32๊ธ์ ์ด์์ด๋ฉด ๋๋ค.
@Service
@RequiredArgsConstructor
@Slf4j
public class TokenProvider {
private final TokenInfo tokenInfo;
/**
* JWT ํ ํฐ ์์ฑ ๋ฉ์๋
* @param user - ํ ํฐ์ ํด๋ ์(๋ด์ฉ)์ ํฌํจ๋ ๋ก๊ทธ์ธ ์ ์ ์ ๋ณด
* @return - ์์ฑ๋ JSON ์ ์ํธํํ ํ ํฐ ๊ฐ
*/
public String createToken(User user) {
log.info("CREATING TOKEN...");
// ํ ํฐ ๋ง๋ฃ ์๊ฐ : 24H
Date expiryDate = Date.from( // `Instant`๋ฅผ `Date` ๋ก ๋ณํ
Instant.now() // ํ์ฌ ์๊ฐ ์ ๋ณด๋ฅผ `Instant`๋ก ๊ฐ์ ธ์จ๋ค
.plus(1, ChronoUnit.DAYS) // ํ์ฌ ์๊ฐ + 1์ผ
);
System.out.println("expiryDate = " + expiryDate);
// ์ถ๊ฐ ํด๋ ์ ์ ์
Map<String, Object> claims = new HashMap<>();
claims.put("email", user.getEmail());
return Jwts.builder()
// token header์ ๋ค์ด๊ฐ ์๋ช
: ๋น๋ฐ๊ฐ๊ณผ ํจ๊ป ํด์๊ฐ์ HS256 ๋ฐฉ์์ผ๋ก ์ํธํ
.signWith(
tokenInfo.getSecretKey()
, SignatureAlgorithm.HS256
)
.setHeaderParam(Header.TYPE, Header.JWT_TYPE) // ํค๋ type : JWT
.setClaims(claims) // ์ถ๊ฐํด๋ ์์ ๋จผ์ ์ค์ ํด์ผ ํจ
// ์ถ๊ฐํ๋ ํด๋ ์์ด ์ด๋ฏธ ํ ํฐ์ ํฌํจ๋ ๋ค๋ฅธ ์ ๋ณด๋ค๊ณผ ์ถฉ๋ํ์ง ์๋๋ก ํด์ผํ๊ธฐ ๋๋ฌธ
.setIssuer(tokenInfo.getIssuer()) // ๋ด์ฉ iss
.setIssuedAt(new Date()) // ๋ด์ฉ iat : ํ ํฐ ๋ฐ๊ธ ์๊ฐ
.setExpiration(expiryDate) // ๋ด์ฉ exp : ์ ํจ ์๊ฐ
.setSubject(user.getId()) // sub: ํ ํฐ์ ์๋ณํ ์ ์๋ ์ฃผ์๋ฐ์ดํฐ
.compact();
}
๐(Header + Payload + Signature)
/**
* ํด๋ผ์ด์ธํธ๊ฐ ์ ์กํ ํ ํฐ์ ๋์ฝ๋ฉํ์ฌ ํ ํฐ์ ์์กฐ์ฌ๋ถ๋ฅผ ํ์ธ
* ํ ํฐ์ json์ผ๋ก ํ์ฑํด์ ํด๋ ์(ํ ํฐ์ ๋ณด)๋ฅผ ๋ฆฌํด
* @param token
* @return - ํ ํฐ ์์์๋ ์ธ์ฆ๋ ์ ์ ์ ๋ณด๋ฅผ ๋ฐํ
*/
public TokenUserInfo validateAndGetTokenUserInfo(String token) {
Claims claims = Jwts.parserBuilder()
// ํ ํฐ ๋ฐ๊ธ์์ ๋ฐ๊ธ ๋น์์ ์๋ช
์ ๋ฃ์ด์ค
.setSigningKey(tokenInfo.getSecretKey())
// ์๋ช
์์กฐ ๊ฒ์ฌ: ์์กฐ๋๊ฒฝ์ฐ ์์ธ๊ฐ ๋ฐ์ํฉ๋๋ค.
// ์์กฐ๊ฐ ๋์ง ์์ ๊ฒฝ์ฐ ํ์ด๋ก๋๋ฅผ ๋ฆฌํด
.build()
.parseClaimsJws(token)
.getBody();
log.info("claims: {}", claims);
return TokenUserInfo.builder()
.userId(claims.getSubject())
.email(claims.get("email", String.class))
.build();
}