항해99 TIL 4주차 - 25

강민범·2023년 11월 17일
0

JWT

JWT란 JSON 포맷을 이용하여 사용자의 정보를 저장하는 Claim기반의 Web Token이다. JWT를 사용하는 가장 큰 특징은 사용자의 정보를 Server에 저장하지 않기 때문에 서버에 부하가 낮아지게된다. 하지만 쿠키/세션에 비해 구현을 하는 과정이 복잡해지고 JWT에 담겨있는 내용이 많아질수록 네트워크 비용이 증가한다.

JWT구조

JWT는 Header

{
  "alg": "HS256",
  "typ": "JWT"
}

alg:토큰의 타입 지정
typ:알고리즘 방식 지정, 서명 및 토큰 검증에 사용

Payload

{
  "sub": "1234567890",
  "username": "카즈하",
  "admin": true
}

Payload에는 실제 유저에 대한 정보가 담겨져 있다.
iss: 토근 발급자
sub: 토큰 제목
aud: 토큰 대상자
exp: 토큰 만료 시간
nbf: 토큰 활성 날짜, 이 날이 지나기전에 활성화되지 않음
iat: 토큰 발급 시간, 토큰 발급 이후 경과 시간을 알 수 있음
jti: JWT 토큰 식별자, 중복 방지 위해 사용, 일회용 토큰 등에 사용

Signature

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret)

Signature는 토큰을 인코딩하거나 암호화할때 사용하는 유효한 코드이다.Header와 Payload의 값을 BASE64Url을 이용해 인코딩하고 인코딩한 값을 비밀키를 이용하여 해싱을하고 다시 BASE64Url로 인코딩한다.

JWTUtils

@Component
public class JwtUtil {
    public static final String AUTHORIZATION_HEADER = "Authorization";
    public static final String AUTHORIZATION_KEY = "auth";
    public static final String BEARER_PREFIX = "Bearer ";
    private final long TOKEN_TIME = 360 * 60 * 1000L;

    @Value("${jwt.secret.key}")
    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 email, AdminRoleEnum role) {
        Date date = new Date();

        return BEARER_PREFIX +
            Jwts.builder()
                .setSubject(email)
                .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) {
        token = URLEncoder.encode(token, StandardCharsets.UTF_8).replaceAll("\\+", "%20"); // Cookie Value 에는 공백이 불가능해서 encoding 진행

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

        res.addCookie(cookie);
    }

    // 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 getAdminInfoFromToken(String token) {
        return Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody();
    }


    // HttpServletRequest 에서 Cookie Value : JWT 가져오기
    public String getTokenFromRequest(HttpServletRequest req) {
        Cookie[] cookies = req.getCookies();
        if(cookies != null) {
            for (Cookie cookie : cookies) {
                if (cookie.getName().equals(AUTHORIZATION_HEADER)) {
                    try {
                        return URLDecoder.decode(cookie.getValue(), "UTF-8"); // Encode 되어 넘어간 Value 다시 Decode
                    } catch (UnsupportedEncodingException e) {
                        return null;
                    }
                }
            }
        }
        return null;
    }
}

Enum

Enumerated Type의 줄임말로 열거형이라고도 불리며 요소,멤버라 불리는 명명된 집합을 이루는 자료형이다. 상수 역할을 하는 식별자라고 생각하면 편하다.

public enum AdminRoleEnum {
    MANAGER(Authority.MANAGER),  // 매니저 권한
    STAFF(Authority.STAFF);  // 스태프 권한

    private final String authority;

    AdminRoleEnum(String authority) {
        this.authority = authority;
    }

    public static class Authority {
        public static final String MANAGER = "MANAGER";
        public static final String STAFF = "STAFF";
    }
}

이 표현 방식을

public enum AdminRoleEnum {
    MANAGER(Authority.MANAGER),  // 매니저 권한
    STAFF(Authority.STAFF);  // 스태프 권한

}

이렇게 줄여도 똑같은 의미이다.

profile
개발자 성장일기

0개의 댓글