※ 인증은 사용자가 누구냐에 관한 것이고, 인가는 사용자가 사용할 수 있는 자원을 말한다.
Basic 인증
- 상태가 없는 웹 애플리케이션에서 인증을 구현하는 가장 간단한 방법으로, 모든 HTTP 요청에 아이디와 비밀번호를 같이 보낸다.
- 최초 로그인한 후 HTTP 요청 헤더의 Authorization: 부분에 Basic <ID>:<Password> 처럼 아이디와 비밀번호를 콜론으로 이어붙인 Base64로 인코딩한 문자열을 함께 보낸다.
- HTTP 요청을 수신한 서버는 인코딩된 문자열을 디코딩해 아이디와 비밀번호를 찾아낸 후 사용자 정보가 저장된 데이터베이스 또는 인증 서버의 레코드와 비교한다.
- 만약 데이터베이스의 레코드와 아이디와 비밀번호가 일치하면 요청받은 일을 수행하고, 아니면 거부한다.
문제점
- Basic 인증 방법은 아이디와 비밀번호를 노출하기 때문에 HTTP와 사용하기에 보안에 취약하다.
- 따라서 중간에 누군가 HTTP 요청을 가로채 문자열을 디코딩하면 아아이디와 비밀번호를 알아낼 수 있기 때문에, 반드시 HTTPS와 사용해야 한다.
- 모든 요청이 일종의 로그인 요청이기 때문에 사용자를 로그아웃 시킬 수 없다.
- 사용자의 계정 정보가 있는 저장장소의 경우, 인증 서버와 인증 DB에 과부하가 걸릴 확률이 높다.
- 인증 서버가 단일 장애점이 된다. 즉, 인증 서버에 오류가 나는 경우 전체 시스템이 가동 불가
토큰 기반 인증
- 토큰은 사용자를 구별할 수 있는 문자열로, 최초 로그인 시 서버가 만들어 준다.
- 서버가 토큰을 만들어 반환하면 클라이언트는 이후 요청에 아이디와 비밀번호 대신 토큰을 계속 넘겨 자신이 인증된 사용자임을 알린다.
- 토큰을 기반으로 한 요청은 헤더에 Authorization: Bearer <TOKEN> 을 명시해야 하고, 서버는 이 토큰을 받아 인증한다.
- 아이디와 비밀번호를 매번 네트워크를 통해 전송해야 할 필요가 없으므로 보안 측면에서 좀 더 안전하다.
- 서버가 토큰을 마음대로 생성할 수 있으므로, 사용자의 인가 정보 또는 유효시간을 정해 관리할 수 있다.
- But, 스케일 문제는 해결하지 못한다.
JSON 웹 토큰 (JWT)
- 서버에서 전자 서명된 토큰을 이용하면 인증에 따른 스케일 문제 해결 가능하며, 전자 서명된 토큰 중 하나가 JSON WEB TOKEN
- JWT는 JSON 형태로 된 토큰으로, {header}.{payload}.{signature}로 구성된다.
Header | typ | 토큰의 타입 |
alg | 토큰의 서명을 발행하는 데 사용된 해시 알고리즘 종류 | |
Payload | sub | 토큰의 주인으로, ID처럼 유일한 식별자 |
iss | 토큰을 발행한 주체 | |
iat | 토큰이 발행된 날짜와 시간 | |
exp | 토큰이 만료되는 시간 | |
Signature | 토큰을 발행한 주체 Issuer가 발행한 서명으로, 토큰의 유효성 검사에 사용 |
※ JWT도 토큰 기반 인증이므로 서버가 생성하지만, 토큰 기반 인증과의 차이점은 서버가 헤더와 페이로드를 생성한 후 전자 서명을 한다는 점
JWT 토큰 생성과 인증
- 브라우저에서 최초 로그인 시, 서버는 사용자의 아이디와 비밀번호를 서버에 저장된 아이디와 비밀번호와 비교해 인증한다.
- 사용자 정보를 바탕으로 {헤더}.{페이로드} 부분을 작성하고,자신의 secret 키로 {헤더}.{페이로드} 전자 서명한다.
- 전자 서명이란 {헤더}.{페이로드}와 시크릿키를 이용해 해시 함수에 돌려 암호화한 결과값
- 시크릿키란 나만 알고있는 문자열, 비밀번호 같은 것
- 전자 서명의 결과로 나온 값을 {헤더}.{페이로드}.{서명}으로 이어 붙이고 Base64로 인코딩 후 반환한다.
- 누군가 이 토큰으로 리소스 접근을 요청하면, 서버는 일단 이 토큰을 Base64로 디코딩한다.
- 디코딩해서 얻은 JSON을 {헤더}.{페이로드}와 {서명} 부분으로 나눈다.
- 서버는 {헤더}.{페이로드}와 자신이 갖고 있는 secret으로 전자 서명을 만든 후 방금 만든 전자 서명을 HTTP 요청이 갖고 온 {서명} 부분과 비교해 토큰의 유효성을 검사한다.
특징
- 인증 서버에 토큰의 유효성에 대해 물어볼 필요가 없고, 이는 인증 서버에 부하를 일이키지 않는다는 뜻으로 더 이상 인증 서버가 단일 장애점이 아니다.
- 누군가 토큰을 훔쳐가면 해당 계정의 리소스에 접근할 수 있기 때문에 반드시 HTTPS를 통해 통신해야 한다.
- 스프링 시큐리티를 이용하면 요청을 인증하는 코드를 한 번만 짜고, 이 코드가 모든 API를 수행하기 바로 전에 실행되도록 설정할 수 있다.
토큰 생성 코드
@Slf4j
@Service
public class TokenProvider {
private static final String SECRET_KEY = "NMA8JPctFuna59f5";
public String create(UserEntity userEntity) {
// 만료기한 1일
Date expiryDate = Date.from(Instant.now()
.plus(1, ChronoUnit.DAYS));
// JWT Token 생성
return Jwts.builder()
// header, secret_key
.signWith(SignatureAlgorithm.HS512, SECRET_KEY)
// payload
.setSubject(userEntity.getId())
.setIssuer("My app")
.setIssuedAt(new Date())
.setExpiration(expiryDate)
.compact();
}
public String validateAndGetUserId(String token) {
// parseClaimsJws: Base64로 디코딩 및 파싱
// 헤더와 페이로드를 setSigningKey로 넘어온 시크릿을 이용해 서명한 후 token의 서명과 비교
// 위조되지 않았다면 페이로드 리턴, 위조라면 예외 날림
// userId가 필요하므로 getBody 호출
Claims claims = Jwts.parser()
.setSigningKey(SECRET_KEY)
.parseClaimsJws(token)
.getBody();
return claims.getSubject();
}
}
- create()는 JWT 라이브러리를 이용해 JWT 토큰을 생성한다.
- validateAndGetUserId()는 토큰을 디코딩 및 파싱하고 토큰의 위조 여부를 확인하고, 사용자의 아이디를 리턴한다.
- 라이브러리를 이용하면 JSON을 생성, 서명, 인코딩, 디코딩, 파싱하는 작업을 하지 않아도 된다.
'Spring > Rest API' 카테고리의 다른 글
REST 서비스 사용 (0) | 2022.01.30 |
---|---|
스프링 데이터 REST (0) | 2022.01.30 |
REST 엔드포인트 정의 (0) | 2022.01.30 |
RestContoller 요청과 응답 방법 (0) | 2022.01.20 |
REST 아키텍처 패턴 (0) | 2022.01.20 |