티스토리 뷰
기본 개념, 큰 그림은 여러 책, 여러 블로그 많이 나올꺼임.
여기서는 실제 구현 시, 고려할 점들 몇가지 적을꺼임.
token에 어디까지 담을 수 있음? 내가 넣고 싶은거 넣을 수 있음?
결론은 맞음. Map<String, Object> 객체도 담을 수 있음.
token에 보통 사용자ID를 담음.
token을 만들 때, String값을 담아서 만들꺼임.
다음은 내가 참고한 책의 예제 소스임.
private String makeToken(Date expiry, User user) {
Date now = new Date();
return Jwts.builder()
.setHeaderParam(Header.TYPE, Header.JWT_TYPE)
.setIssuer(jwtProperties.getIssuer())
.setIssuedAt(now)
.setExpiration(expiry)
.setSubject(user.getEmail())
.claim("id", user.getId())
.signWith(SignatureAlgorithm.HS256, jwtProperties.getSecretKey())
.compact();
}
setSubject()에 user.getEmail()을 담고 있음.
token으로 부터 다시 user.getEmail()을 가져다 쓰려면
다음 예제 코드 처럼 불러다 쓰면 됌.
private Claims getClaims(String token) {
return Jwts.parser()
.setSigningKey(jwtProperties.getSecretKey())
.parseClaimsJws(token)
.getBody();
}
public Authentication getAuthentication(String token) {
Claims claims = getClaims(token);
Set<SimpleGrantedAuthority> authorities = Collections.singleton(new SimpleGrantedAuthority("ROLE_USER"));
return new UsernamePasswordAuthenticationToken(new org.springframework.security.core.userdetails.User(claims.getSubject(), "", authorities), token, authorities);
}
token으로 부터 Claims 라는 객체를 만들고,
claims.getSubject()를 통해 token만들 때 넣었던 user.getEmail()을 쓸 수 있다.
눈치 챘을지도 모르지만, token만들 때 예제 코드를 다시 보면,
...
.claim.("id", user.getId())
...
라는 부분이 있다.
이 부분에서 Map<String, Object>처럼 쓸 수 있다.
이렇게 추가적으로 넣은 값들은 다음과 같이 불러다 쓸 수 있다.
public Long getUserId(String token) {
Claims claims = getClaims(token);
return claims.get("id", Long.class);
}
private Claims getClaims(String token) {
return Jwts.parser()
.setSigningKey(jwtProperties.getSecretKey())
.parseClaimsJws(token)
.getBody();
}
그럼 token에 email말고도 자주쓰는 값들, 예를들면 이름, 나이, 주민번호,,,,,, 등등 담아두면
email 들고 DB가서 조회하지 않아도 가져다 쓸 수 있을꺼임.
근데 문제는 token을 도둑맞으면 token에 담아둔 정보를 볼 수 있음.
예시로 만든 token임
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJnb3N0YmFkdWNraW5nMUBuYXRlLmNvbSIsImlhdCI6MTY5NTY1NzIxNSwiZXhwIjoxNjk1NjU3MjIwLCJzdWIiOiIxIiwia2V5MSI6InZhbHVlMSIsImtleTIiOjEsImtleTMiOjIsImtleTQiOjMuMH0.AuanqgtM7OVMMsZovm81KMm2eOQHFHqOLjix_i8ADJE
뭐가 있는지 이렇겐 안보이지만,
jwt.io 접속 후에 왼쪽에 붙여넣으면 오른쪽에 내용이 다 보임
sub(subject)랑 key1~4 까지 다 보임..
객체도 넣을 수 있음? ㅇㅇ user객체 넣어보면 다음처럼 다 보임.
token에 데이터를 담아두면, DB 가지 않아도 알 수 있지만,
탈취 당했을 때 그 데이터들이 다 보이기 때문에, 최소한의 데이터만 담는게 맞지 않을까 싶음.
secret key 는 어디에 쓰나요?
token만들 때 secret key 사용함. 이전 예제 코드에서도 secretKey가 있음,(jwtProperties.getSecretKey())
token을 복호화 할때 쓴다고도 함.
보통 책에는 이 정도로 써있음. 사실 더 얘기할거도 없긴함.
나도 jwt 처음 접했을땐 '아,, jwt 쓰려면 필요한건가 보다'했음.
jwt.io로 돌아가서,, 위 화면에서 오른쪽 데이터가 수정 가능함.
수정함에 따라 왼쪽 token도 실시간으로 바뀜.
대박. 유효기간 10년짜리 만들어서 두고두고 쓸 수 있을지도?
username이나 authorities도 바꿔서 다른 사용자 정보로 로그인도 가능할듯?
바꿔서 해보자.
다음과 같은 Exception이 발생함.
io.jsonwebtoken.SignatureException: JWT signature does not match locally computed signature.
JWT validity cannot be asserted and should not be trusted.
........
signature가 맞지 않는다 함.
무지성으로 token만들어서 하면 안되나봄.
jwt.io 오른쪽 하단에 'VERIFY SIGNATURE' 라는 입력한이 있음.
'your-256-bit-secret'이라는 입력란에,
spring에서 설정한 secreyKey를 넣어보자.
여길 수정하면 역시, 실시간으로 token값이 바뀜.
이 token으로 다시 요청해보자.
아.... 해봤는데 계속 위와 같은 로그만 찍힘........... 됄줄 알았는데..
(나는 token에 secretKey를 탈취해도 못써먹음)
이건 더 공부해봐야 겠음.
refresh token
이거도 구글링 하면 이론은 많이 나옴.
실제 구현에 관한 내용은 있긴 있음.
두가지가 있다고 함.
로그인 하면 token, refresh token 발급 후, refresh는 email과 함께 DB에 넣는거 까지는 같은데,
1. 매요청마다 token, refresh token을 headers에 태워 보냄. token이 만료돼면, 같이 들고온 refresh token으로 email찾아서 다시 token만들어서 response headers에 담아 주고 filter 통과 시킴
2. 매 요청은 token만 보내는데, 만료됄경우, callback에서 refresh token을 headers에 태워서 다시 요청을 보냄.
2번 방법이 좋아 보임. 매번 필요없는 refresh token을 보내지 않아도 되니깐.
결론은, 실제 구현은 1번으로 했음.
처음에 2번으로 해봤는데, 총 요청을 3번 보내야 함.
①만료된 token으로 요청, ②refresh token보내고 새로운 token받아오는 요청, ③새 token으로 원래 요청
특징1. callback에 callback에 callback으로 구현 해야함. (혹시 다른 방법이 있을까요??)
특징2. ①에서 보낸 요청 정보 그대로, token만 바꿔서 ③에서 보내야 함.
근데 이건 뭐,, fetch 공통함수로 돌려두면 그렇게 큰 단점은 아니라고 봄. 구현은 가능함.
특징3. 진짜 인증실패 했을때와, 만료 됐을 때 exception을 따로 분리해서 화면에서 분기 태워야 함.
아마 status를 다르게 exception을 내던가, 417로 BusinessException을 내서 return 객체에서 구분을 해야 함.
구현할 생각 전에는 좋아 보였는데, 구현하려니까 관리가 너무 힘들어 보임.
1번으로 구현한다면
1-1. filter에서 token만료 일때는 같이 들고왔던 refresh token으로 새 token 발급 후, response headers에 넣어서 보내준다.
1-2. 화면에서 response에 headers에 token이 있다면 새로 갈아 끼워준다.
simple한데, 해야 할 일이 있음.
해야할일. 요청의 callback에서 response에 headers에 접근은 하는데, 내가 filter에서 보낸 headers 키엔 접근이 안됌.
근데 브라우저 Network에서 response보면 (떡하니) 있음.
cors 설정에서 내가 접근하려는 response headers를 허용시켜놔야 함.
단점. 비동기 요청이 한번에 여러번 발생한다면, 새 token을 여러번 발급해버릴 수 있음.
총 3번의 비동기 요청이 있다 치면, ①번 비동기 요청시 만료로 인식해서 새 token을 뱉을 텐데,
새 token을 받기 전에 ②번 비동기 요청이 가면, 요청했으니까 또 새 token을 만듬. ...
그래서 비동기 요청이 여러번 있다면, 새 token을 여러번 딸 지도....(현재 프로젝트에선 그렇게 돌아가고 있음..)
1번, 2번 둘다 해보고 나니, 다시 2번이 좋아 보임..
구현방법이 복잡하고 코드 가독성이 떨어질진 몰라도,,
1번 방법의 단점은 좀 치명적인거 같음.
-- 아니지.. 다시 생각해보니까 2번으로 했어도 비동기 여러번 호출되면 여러번 새로 발급하는건 똑같음.
글을 잘 적는거도 아닌데 서술만 되어 있어서 이해하기 어려운 부분이 있을 수 있으니,
조만간 github에 올려놔야 겠음.
claim의 subject에 숫자(int, long)은 넣을 수 없음?
네 없어요. 다음 그림 보면, parameter로 String을 받게 되어 있음.
user 구분값이 userId, email 등등이면 그걸로 token만들면 됌.
'근데 우리는 userId가 있긴한데, user pk를 long타입으로 잡았음.'
'token에 userId를 담아도 되긴 한데, long 타입 id로 하고 싶음.'
그때는 String.valueOf를 통해 문자열로 바꿔서 할 수 있긴 함.
token으로 부터 subject를 뽑을 때도 String값이 나왔을 테니, Long.valueOf로 long 타입으로 바꿔줘야 함.
'spring(boot)' 카테고리의 다른 글
[jpa]query method, projection (0) | 2023.10.11 |
---|---|
[security]CORS preflight 요청 (0) | 2023.10.09 |
[security]JPA로 구현 시, Role table 없애기 (0) | 2023.09.08 |
[message] 2.DB 방식 (0) | 2023.06.06 |
[message] 1.properties방식 (0) | 2023.06.06 |