JWT 의 의미
- JSON Web Token
- 권한을 체크하기 위한 토큰
- 직렬화 가능한 데이터인 JSON 을 이용한다
JWT 구조
- JOSE Header
유형과 알고리즘을 작성한다(JWS 인지, JWE 인지 알 수 있다) 현재 설명되는 내역은 JWS 로, 간단한 검증을 위함이다{ "typ":"JWT", "alg":"HS256" } - ex) JWT 이면서, Mac 검증 알고리즘은 HMAC Sha 256 Algorithm 으로 작성된 내역
- JWT Claim Set
권한 세트
여러가지의 타입이 있지만 간략하게 ...
{
"iss"[optional]: "[발급자]",
"exp"[optional]:"Its value MUST be a number containing a NumericDate value[만료]",
"iat"[optional]:"Its value MUST be a number containing a NumericDate value[발급]",
...
}
JWT 의 구조는 다음과 같다 ( jwt.io 참조 )
JOSE Header 에 있는 alg 으로 vaild 하는 algorithm 에 대해 sign하도록 한다
BASE64URL(UTF8(JWS Protected Header)) || ’.’ ||
BASE64URL(JWS Payload) || ’.’ ||
BASE64URL(JWS Signature)
간단히 실습을 위한 JWT 를 만들어주는 모델을 하나 구현해보았다.
이제 이 모델을 점점 고도화 하는 작업을 진행 하여보자
package mark.personal.simplejwt.model;
import lombok.Data;
import mark.personal.simplejwt.util.Base64UrlEncoder;
import java.nio.charset.StandardCharsets;
@Data
public class Header implements JWTSerializable{
private String type;
private String alg;
public Header() {
this.type = "JWT";
this.alg = "HS256";
}
public Header(String type, String alg) {
this.type = type;
this.alg = alg;
}
@Override
public String serialize() {
return Base64UrlEncoder.encoding(String.format("{\"typ\":\"%s\",\"alg\":\"%s\"}", type, alg).getBytes(StandardCharsets.UTF_8));
}
}
package mark.personal.simplejwt.model;
import lombok.Data;
import mark.personal.simplejwt.util.Base64UrlEncoder;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.List;
@Data
public class Payload implements JWTSerializable {
private LocalDateTime issueAt;
private LocalDateTime expiredAt;
private String issue;
private List<String> usableList;
public Payload() {
this.issue = "mark";
this.issueAt = LocalDateTime.now();
this.expiredAt = issueAt.plusMinutes(5);
this.usableList = new ArrayList<>(10);
}
public Payload(LocalDateTime issueAt, LocalDateTime expiredAt, String issue, List<String> usableList) {
this.issueAt = issueAt;
this.expiredAt = expiredAt;
this.issue = issue;
this.usableList = usableList;
}
public Payload(LocalDateTime issueAt, String issue, List<String> usableList) {
this.issueAt = issueAt;
this.issue = issue;
this.usableList = usableList;
this.expiredAt = issueAt.plusMinutes(5);
}
@Override
public String serialize() {
Long iat = this.issueAt.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
String iss = this.issue;
Long exp = this.expiredAt.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
StringBuilder builder = new StringBuilder();
for (String usable : usableList) {
builder.append(",\"")
.append(usable)
.append("\":true");
}
return Base64UrlEncoder.encoding(String.format("{\"iss\":\"%s\",\"iat\":%d,\"exp\":%d%s}", iss, iat, exp, builder).getBytes());
}
}
package mark.personal.simplejwt.model;
import lombok.Getter;
import mark.personal.simplejwt.util.Base64UrlEncoder;
import mark.personal.simplejwt.util.JWTSignatureAlgorithm;
@Getter
public class Signature implements JWTSerializable {
private Header header;
private Payload payload;
private String key;
private JWTSignatureAlgorithm jwtSignatureAlgorithm;
public Signature(Header header, Payload payload, String key) {
this.header = header;
this.payload = payload;
this.key = key;
this.jwtSignatureAlgorithm = JWTSignatureAlgorithm.getSignatureAlgorithm(key, header);
}
@Override
public String serialize() {
String signData = String.format("%s.%s", header.serialize(), payload.serialize());
return Base64UrlEncoder.encoding(jwtSignatureAlgorithm.getDataSignature(signData));
}
}
package mark.personal.simplejwt.model;
public interface JWTSerializable {
String serialize();
}
package mark.personal.simplejwt.util;
import mark.personal.simplejwt.model.Header;
import mark.personal.simplejwt.model.HeaderAlgorithm;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
public class JWTSignatureAlgorithm {
private Mac mac;
private SecretKeySpec secretKeySpec;
private JWTSignatureAlgorithm() {
}
private JWTSignatureAlgorithm(Mac mac, SecretKeySpec secretKeySpec) {
this.mac = mac;
this.secretKeySpec = secretKeySpec;
}
public static JWTSignatureAlgorithm getSignatureAlgorithm(String key, Header header) {
try {
Mac mac = Mac.getInstance(HeaderAlgorithm.valueOf(header.getAlg()).getName());
SecretKeySpec secret_key = new SecretKeySpec(
key.getBytes(StandardCharsets.UTF_8),
HeaderAlgorithm.valueOf(header.getAlg()).getName()
);
mac.init(secret_key);
return new JWTSignatureAlgorithm(
mac,
secret_key
);
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
throw new RuntimeException(e);
}
}
private String byteToHexString(byte[] byteArray) {
return new BigInteger(byteArray).toString(16);
}
public byte[] getDataSignature(String data) {
return mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
}
}
@AllArgsConstructor
@Getter
public enum HeaderAlgorithm {
HS256("HmacSHA256");
private final String name;
}
package mark.personal.simplejwt.util;
import java.util.Base64;
public class Base64UrlEncoder {
public static String encoding(byte[] data){
String result = Base64.getUrlEncoder().encodeToString(data);
return result.replaceAll("=","");
}
}'Spring' 카테고리의 다른 글
| [번외] [JSP] Include & Forward 방식의 차이 (0) | 2022.01.11 |
|---|---|
| [Spring] RequestMapping & Request Method Mapping (1) | 2022.01.10 |
| [Spring] Controller & RestController (0) | 2022.01.09 |