본문 바로가기

Spring

JWT 를 이용한 권한 검증 및 처리 # jws

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)

RFC 7519

간단히 실습을 위한 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("=","");
    }
}