1. JWT 구성


- Header
- 토큰이 어떤 타입인지 (JWT)와 서명에 사용할 알고리즘(HS256)을 정의
- 위의 사진에서는
“alg” : “HS2556”
, 즉 HMAC-SHA256 알고리즘을 사용
- Payload
- 토큰에 담길 실제 데이터 포함
- 보통 유저에 대한 정보, 만료 시간, 발행자등을 포함
- 이 정보는 누구나 읽을 수 있지만 서명을 통해 위조 여부 검증 가능
- 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 참조 사이트
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);
}
}

- 유저 객체(
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());
}

- 미리 생성된 토큰을
varify
메소드에 전달, 토큰에서 추출한 유저 정보(id
,username
)가 정확히 복원되는지 확인
Share article