SpringSecurity, JWT에 대해
이번에 JWT에 대해 다시 공부를 해보았다. 매번 인터넷에서만 가져다 붙이다보니 상세하게 공부해보았다
혼자 기억하기위해서 메모했어요.
SpringSecurity
제일중요한 SpringSecuriry는 Authentication객체를 확인하다는것이다.
JWT
JWT는 3가지 파트로 나누어짐
- Header : 헤더는 토큰의 타입과 서명 알고리즘이 들어간다
- Payload : 서버에서 넣은 정보가 들어간다
- Signature : Header + Payload + SecretKey
핵심은 시그니처는 Header + Payload + SedretKey합을 조합해서 넣은 값이다
구현
구현은 2가지다
- 토큰을 생성해주는 JWT토큰 생성자
- 토큰을 검증하는 JWT필터 SpringSecurity가 필터기반이기때문에
JWT필터 인증절차
- 필터 커스텀 인증처리할거 말거 정해주기
- 토큰발급
- 토큰을 검사 그리고 토큰을 꺼내서 Authentication만들기
JWT토큰 생성
토큰생성시에 필요한것은 언제 발행했는지, 시크릿키는 무엇인지 어떤정보를 넣을지 이렇게인거같다
구현코드는 아래와 같다.
헷갈리는게 있따 payload와 Claims는 머가다른건가
Payload: JWT의 두 번째 부분. 사용자 데이터(정보)가 들어가는 부분
Claims: Payload 안에 들어가는 키-값 형태의 데이터
key-value쌍하나를 하나의 claim이라고 한다
// 토큰 생성 JWT를 자바에서 만들 수 있도록 이렇게 코드를 짠거임
public String createToken(String email, String role){
Claims claims = Jwts.claims().setSubject(email); // payload라고 생각
claims.put("role", role);
Date now = new Date();
String token = Jwts.builder()
.setClaims(claims)
.setIssuedAt(now)
.setExpiration(new Date(now.getTime()+expiration*60*1000L))
.signWith(SECRET_KEY) // signWith()는 JWT의 서명(Signature) 을 만드는 부분이야.
.compact();
return token;
}
subject랑 put차이점은?
subject는 표준 Claim 중 하나인 보통 사용자의 고유 식별자, 이메일, 사용자 ID 같은 것을 넣는다고한다.
putd은 커스텀 Claim이다.
signWith()머지 왜 SECRET_KEY를 넣는건가
JWT의 서명(Signature) 을 만드는 부분이다
signature만들 때 내부적으로 하는 일 (대충 흐름)
- Payload(Claims) + Header 를 문자열로 만든다.
- 그 문자열에 SECRET_KEY 를 가지고 서명(Signature) 을 생성한다.
- 최종적으로 JWT는 이렇게 만든다
인증하기
토큰이 비어있는경우와 아닌경우 비어있는경우는 다음필터로 시큐리티에서 알아서 판단
아닌경우
- 헤더에서 원본토큰을 뺀다
- Bearer형식인지 검사 아니면 예외 왜냐하면 우리서버는 기대하는 인증 방식이 "Bearer 토큰 인증"이라고 약속했기 떄문에
- Bearer파싱 7자리 지우기
직접 토큰을 다시만들면서 검증
Claims claims = Jwts.parserBuilder().setSigningKey(secretKey) // 서명부분을 똑같이 넣어서 검증하는거임 token을 보면 시그니처 = header + payload + secretKey 이렇기때문에 같은것을 넣어서 검증 token에 이미 서명이있으니까
.build()
.parseClaimsJws(jwtToken) // 여기까지는 우리가 토큰을 다시한번 만듬 (signedKey는 = header + payload + secretKey 이기떄문에 라이브러리안에 파싱하면서 다시암호화시키는게 들어가있음 (parseClaimsJws, parseClaimsJwt 다르다)
.getBody(); // Claims객체가 나온다 Header는 잘안보는 이유 GPT에 나옴 알고리즘, 타입명시는 잘 안바뀌기때문에
parseClaimsJws(jwtToken)가 하는 일
- 먼저 토큰을 . 기준으로 쪼갬 [헤더, 페이로드, 서명]
- 서명 검증 헤더 + 페이로드 + secretKey로 다시 서명을 만들기.
- 그리고 토큰에 들어있던 서명이랑 비교해. 같으면 → "유효한 토큰이네!" 하고 계속 진행. 다르면 → 예외 발생 (서명 위조 가능성)
- 검증 통과하면 → Claims(페이로드) 뽑아서 리턴
parseClaimsJws, parseClaimsJwt
- parseClaimsJwt() 서명 없는 JWT 파싱 (JWT만 해석) → 요즘 거의 안씀
- parseClaimsJws() 서명 있는 JWT 파싱 + 서명 검증 → 이걸 주로 씀
왜 signature부분만 비교할까?
처음에 말했듯이 signature = header + payload + secretKey해서 알고리즘을 사용해서 암호화한것이다.
그래서 signature부분만 검증하는것이다
왜 setSigningKey만 하는걸까?
헤더(Header)와 페이로드(Payload)는 외부에서 쉽게 변경할 수 있기 때문에, 서명(Signature)만을 사용해 위변조를 방지하는 것이다.
그래서 서명은 이를통해 바뀌고 비밀키는 보여지지않아서 적들이 바꾸지 못하기때문이다
그래서 JWT의 서명은 토큰을 발급한 쪽에서만 알 수 있는 비밀키(secretKey)를 사용하여 생성되기 때문에, 서명을 검증하려면 그 비밀키를 알아야 해.
즉 우리쪽에서만 아는것이기때문에 헤더와 페이로드가 조작되지 않았음을 보장하려면 우리의 비밀키를 통해 다시검증하는것이다.
만약 저기서 헤더 페이로드바꾸면 시그니처가 바뀌는데 비밀키를 우리것으로 사용하기때문에
그런데 서버는 이전 토큰을 기억못하는데어찌 위조됬는지 판단하지?
서명값이 변경되지않기때문에 이전이랑 적이 헤더와 페이로드를 변경해도 서명값이 똑같은데
서버에서는 헤더와 페이로드를 빼서 다시 만들어본 값이랑 서명값이 다르기때문에 위조됬는지 판단한다
서명 값은 서버에서 처음 내려준 값이 변경되지 않는다!!