세션 방식
클라이언트가 서버로 로그인 요청 → 서버 session 생성/유지 → client에게 정보를 응답
서비스 요청 → 서버에서 인증/인가 확인을 위해 세션을 읽음 → 인증/인가 확인 후 응답
문제점
엔터프라이즈 환경에서는 다수의 서버가 존재하고, 각 서버의 세션이 동기화 되어야함 → 데이터베이스에게 부담
쿠키와 세션을 주로 같이 사용하는데 이 떄 CORS(Cross Origin Resource Sharing) 문제가 발생한다. 위 문제들을 해결하기 위한 방안이 Token 이다.
JWT
Spring은 stateless 서비스이다. token을 사용하면 연결을 하지 않은 채로 서비스 제공이 가능하다.
- token을 사용하면 서버가 늘어나도 인증 방식만 알고있으면 상관없다. → 확장성
- 쿠키를 전달하지 않기 때문에 취약점이 줄어든다.
- OAuth 를 통해 다른 서비스에서 로그인 기능을 제공 가능
- 서버 측의 부담이 덜하다
- 만료시간을 제대로 설정하면 보안적으로 잘 관리 가능
클라이언트가 로그인 요청 → 서버가 토큰 생성 → 응답 + 토큰 → 클라이언트는 토큰을 가지게 됨 → 서비스 요청 + 토큰 → 서버는 토큰을 기반으로 검증 → 맞으면 서비스 제공
취약점
- 토큰을 HTTP 헤더에 담아서 전송하기 때문에 스푸닝에 취약
- 쿠키 방식보다는 보안성이 우수
- CORS도 해결된다.
토큰의 구성요소
header.
payload. (claims)
signuture
eyJhbGciOiJIUzI1NiJ9. eyJzdWIiOiJ0ZXN0IiwiZXhwIjoxNjYyMzEzMjIxfQ. o-WzLG53-2K8_KsS-JTaJqoBPjhowWUNpJme5HS8_S4
예제 코드
private static final String SECRET_KEY = "asdasdasdsadfasfasdfsadasdasdasdsadasdasdsadasdasdasdsa";
//로그인 서비스 던질 때 같이
public String createToken(String subject, long expiredTime) {
if (expiredTime <= 0) {
throw new RuntimeException("만료시간이 0보다 커야된다.");
}
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
byte[] secretKeyBytes = DatatypeConverter.parseBase64Binary(SECRET_KEY);
Key signingKey = new SecretKeySpec(secretKeyBytes, signatureAlgorithm.getJcaName());
return Jwts.builder()
.setSubject(subject)
.signWith(signingKey, signatureAlgorithm)
.setExpiration(new Date(System.currentTimeMillis() + expiredTime))
.compact();
}
//토큰검증하는 메서드를 만들어서 boolean 리턴
public String getSubject(String token) {
Claims claims = Jwts.parserBuilder()
.setSigningKey(DatatypeConverter.parseBase64Binary(SECRET_KEY))
.build()
.parseClaimsJws(token)
.getBody();
return claims.getSubject();
}