inblog logo
|
SHIN
    JSONSpringBootJPA

    JWT V2

    암호화 (with Test)
    SHIN's avatar
    SHIN
    Sep 20, 2024
    JWT V2
     

    1. JWT 구성

    notion image
     
    notion image
     
    💡
    1. Header
        • 토큰이 어떤 타입인지 (JWT)와 서명에 사용할 알고리즘(HS256)을 정의
        • 위의 사진에서는 “alg” : “HS2556”, 즉 HMAC-SHA256 알고리즘을 사용
    1. Payload
        • 토큰에 담길 실제 데이터 포함
        • 보통 유저에 대한 정보, 만료 시간, 발행자등을 포함
        • 이 정보는 누구나 읽을 수 있지만 서명을 통해 위조 여부 검증 가능
    1. Signature
        • 토큰의 위조 방지 부분
        • 서버는 서명을 통해 토큰이 변조되지 않았는지 확인
        • 서명은 header와 payload를 연결 후, secret key로 서명하여 생성
        • 위의 사진에서는 HMAC-SHA256 알고리즘을 통해 생성
     
    1-1. Hash, HS256, HMAC 연관성
    💡
    • Hash
      • 입력 데이터를 고정된 크기의 고유한 출력 값으로 변환하는 함수
      • Ex) SHA256 = 임의의 길이의 데이터를 256bit의 고정된 크기의 hash 값으로 변환하는 hash 알고리즘
      • 이 변환은 단방향으로 원래의 데이터를 hash 값으로부터 되돌리는 것은 불가능
    • HS256
      • HMAC을 사용한 hash 알고리즘인 “HMAC-SHA256”의 줄임말
      • 이는 JWT에서 서명 생성 시 사용하는 알고리즘
      • HS256 = SHA256 hash 함수와 secret key를 결합한 HMAC 알고리즘을 통해 서명 생성
    • HMAC (hash 기반 메시지 인증 코드)
      • hash함수와 secret key를 사용하여 메시지의 무결성 확인 및 메시지가 변조되지 않았는지 확인하는 방법
      • 즉 단순한 hash 함수에 secret key를 추가해서 더 안전한 인증 방식 제공
     
    정리
    💡
    • JWT는 header, payload, signature 로 구성되며, 이를 통해 데이터가 변조되지 않았는지 확인 가능
    • signature 부분에서 HS256 알고리즘은 HMAC을 사용해 SHA256 hash 함수로 생성
    • hash는 데이터를 고정된 길이의 값으로 변환하며 HMAC은 hash에 secret key를 추가해서 더 안전한 signature 제공
     
    HMAC 참조 사이트
    https://sunphiz.me/wp/archives/1104
     

     

    2. 암호화

     
    단방향
    💡
    • 복호화 안됨 → 잠금만 가능 (Encoding만 가능)
    • 암호화 했을 때 풀 수 없을 때 사용 → 서명, 위조 검증
     
    양방향
    💡
    • 암호화 & 복호화 가능 → Encoding, Decoding 가능
    • 대칭키 (열쇠 1개) : 생성과 검증을 한 명이 할 때.
    • 공개키 (열쇠가 쌍으로 2개) : 생성과 검증을 다른 사람이 할 때.
     
    Base64 (양방향 = 복호화 가능)
    💡
    • 아스키 코드 : 8bit → 2의 8승 → 경우의 수 : 256
    • Base64 : 6bit → 2의 6승 → 경우의 수 : 64
    • 파일, 사진들을 문자화해서 전송하기 위해서 사용.
    • 왜 사용? → JSON { } 에 심어서 함께 전송가능 = 통신 체계 심플
     

     

    1. Controller (login 메소드)

    @PostMapping("/login") public ResponseEntity<?> login(@RequestBody UserRequest.LoginDTO loginDTO, Errors errors) { String acessToken = userService.로그인(loginDTO); return ResponseEntity.ok() .header("Ahthorization", "Bearer " + acessToken) // header를 넣으면 body도 넣어야 함 -> 문법 .body(Resp.ok(null)); // "Bearer " = access token (protocol) }
    • 유저가 로그인 요청 시 loginDTO 객체에 유저이름과 비밀번호 정보가 들어오고, 이를 userService.로그인() 메소드를 호출해서 검증
    • 검증 성공 시 JWT Access Token 발급, 이를 HTTP 응답 헤더에 Authorization: Bearer <토큰> 형식으로 담아서 반환
    • 웅답 본문(body)에는 추가적인 데이터가 없고, Resp.ok(null)로 응답
     

    2. Service (로그인 메소드)

    public String 로그인(UserRequest.LoginDTO loginDTO) { // 1. 해당 유저 존재 유무 조회 User user = userRepository.findByUsernameAndPassword(loginDTO.getUsername(), loginDTO.getPassword()) .orElseThrow(() -> new Exception401("인증되지 않았습니다")); // 2. 조회되면 JWT 만들고 응답 String accessToken = JwtUtil.create(user); return accessToken; }
    • DB에서 loginDTO로 받은 유저이름과 비밀번호를 조회, 해당 유저 존재 시 JWT 생성
    • JwtUtil.create(user) 는 JWT를 생성하는 메소드, 해당 JWT는 추후 클라이언트에게 응답으로 제공
     

    3. JwtUtil (create, verify 메소드)

    public static String create(User user){ String accessToken = JWT.create() // 이 정보로 단방향 hash .withSubject("TEST") .withExpiresAt(new Date(System.currentTimeMillis() + 1000*60*60*24*7)) // 토큰 만료 시간 .withClaim("id", user.getId()) // payload (keymap) .withClaim("username", user.getUsername()) .sign(Algorithm.HMAC512("shin")); // secret 키 같은 개념 return accessToken; } public static User verify(String jwt){ DecodedJWT decodedJWT = JWT.require(Algorithm.HMAC512("shin")).build().verify(jwt); int id = decodedJWT.getClaim("id").asInt(); String username = decodedJWT.getClaim("username").asString(); return User.builder() .id(id) .username(username) .build(); }
    • create(User user)
      • JWT 생성 메소드
      • 유저의 ID, username 정보를 담고, 만료 기간은 현재 시점에서 7일로 설정
      • 최종적으로 HMAC512 알고리즘을 사용해 서명(sign)하여 토큰 반환
    • verify(String jwt)
      • JWT 검증 메소드
      • 서명된 토큰 검증, 토큰에서 id와 username 클레임을 추출해서 새로운 User 객체로 반환
     
     

    4. JwtUtilTest

     
    4-1. JWT 생성 Test
    @Test public void create_test() { User user = User.builder().id(1).username("shin").build(); String accessToken = JwtUtil.create(user); System.out.println(accessToken); } }
    notion image
    • 유저 객체(User)를 만들어 JwtUtil.create() 메소드 호출, 결과로 반환된 JWT 토큰 출력
     
     
    4-2. JWT 검증 Test
    @Test public void verify_test() { String accessToken = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJzdWIiOiLslYjrhZUiLCJpZCI6MSwiZXhwIjoxNzI3NDAyMDg5LCJ1c2VybmFtZSI6InNoaW4ifQ.dnmveRXQrp0dU99Y9NQtswPTsy11hNuCw5CvcY47d9ATgLj2aLo0wNviCjNJh_Nik7ISZN-9XgvHXrb1r7OS3Q"; User user = JwtUtil.verify(accessToken); System.out.println(user.getId()); System.out.println(user.getUsername()); }
    notion image
    • 미리 생성된 토큰을 varify 메소드에 전달, 토큰에서 추출한 유저 정보(id, username)가 정확히 복원되는지 확인
     
    Share article

    SHIN

    RSS·Powered by Inblog