네이버 토큰을 가져오는 OAuth2.0 방식 실습

네이버도 카카오처럼 Spring에서 지원해주지는 않으므로 등록시 authorization-grant-type, client-authentication-method, redirect-uri등을 추가로 설정해줘야 한다.
그리고 별도로 provider를 만들고 authorization-uri, user-name-attribute, token-uri, user-info-uri을 넣어 주어야 한다.
카카오와 다소 다른점은 위 사진처럼 response 객체로 받고 response 객체 하위에 접근하여 유저정보를 받아보는 식으로 진행된다. 그래서 OAuth2.0 방식으로 유저에 관한 데이터를 받아오는 경우 공식 API문서를 참조를 하는 편이 좋을 것 같다.
스웨거 Swagger
스웨거 에디터 링크
이렇게 외부 프로그램과 연결하거나 프로그램에 관해 작성할 때 API문서 형식으로 작성을 하게 된다. 이럴 때 자주사용하는 것으로는 swagger API가 있는데 REST API를 문서화하고 테스트할 수 있게 도와주는 도구라고 볼 수가 있다.
- 사용 이유
- 코드를 기반으로 API 문서 생성 가능
- 브라우저에 직접 요청하여 API 테스트 가능
- Request/Response 확인
- 프론트엔드와 백엔드 간의 협업에서 효율이 증가

implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.6'
위 의존성을 추가하면 spring이 Controller을 분석하여 API를 읽어드려 자동으로 문서를 생성한다.
그리고 Swagger Annotation을 붙여 자세한 문서를 만드는 것이 가능하다.
- @Operation : API 설명
- @Parameter : 파라미터 설명
- @Schema : DTO 설명
JWT 토큰 다루기
기존의 Spring MVC + Thymeleaf 환경에서는 주로 세션(Session) 기반 인증을 사용한다.
사용자가 로그인하면 서버는 세션을 생성하고, 브라우저는 JSESSIONID 쿠키를 저장하여 이후 요청마다 함께 전달한다. 하지만 분리된 프론트엔드 환경에서 사용할 때는 CORS 설정문제, HTTPS 인정서 문제, 등 해야 할 것이 많기 때문에 다른 인증방식이 필요하다.
이러한 환경에서 많이 사용하는 방식이 JWT 기반 인증이다.
JWT는 Json Web Token의 약자로 사용자 인증 정보를 담고 있는 문자열 토큰이다. 로그인 성공시 서버는 JWT를 발급하고 클라이언트는 요청마다 헤더에 토큰을 포함하여 전달을 하게 된다. 다만 JWT는 문자열로 이루어져 있기 때문에 JWT토큰을 갖고있는 클라이언트가 있으면 보안상 문제가 된다.
이를 때문에 사용해주는 것이 Access Token과 Refresh Token 전략이다.
Access Token과 Refresh Token 전략
Access Token은 실제 인증에 사용하는 토큰으로 수명이 짧고 요청 시 함께 전달한다. 보통 몇 분 ~ 몇 시간 사용하게 된다.
Refresh Token은 Access Token을 재발급하기 위한 토큰으로 Access Token보다 상대적으로 수명이 길고 보안적으로 더 안전하게 관리하는 것이 가능하다. 보통 DB 또는 Redis 등에 저장하는 경우가 많다.
보통 JWT 토큰을 만들때 2개를 만들고 하나를 엑세스 토큰, 다른 하나를 리프레시 토큰으로 한다. 그리고 엑세스 토큰은 로그인하는 유저에게 맡기고 리프레시 토큰을 운영자가 관리를 하고 이 두가지 토큰을 보고 갱신할지 말지를 판단을 하게 된다.
토큰 재발급 흐름은 다음과 같다.
- Access Token 만료
- 클라이언트가 Refresh Token과 함께 재발급 요청
- 서버가 Refresh Token 검증
- 새로운 Access Token 발급
프론트에는 보통 Axios Interceptor을 사용하여 토큰 만료를 감지하고 갱신하고 이를 로그인에 사용하는 것이 가능하다.
public KeyPair issueKeyPair(Long id, Role role){
String accessToken = issueAccessToken(id, role);
String refreshToken = issueRefreshToken(id, role);
return new KeyPair(accessToken, refreshToken);
}
하나의 메서드에서 토큰 쌍을 생성되도록 하여 일관된 보안정책 유지, 생성 방식 오염 방지를 하도록 한다. 요즘은 next.js의 발전으로 자체 서버에 토큰을 관리하는 것도 가능하다.
블랙리스트 전략과 화이트리스트 전략
-
블랙리스트 전략
사용 금지된 토큰 목록을 관리하는 방식이다. 로그아웃시 해당 JWT를 블랙리스트에 등록하고 이후 요청시 블랙리스트에 존재하면 인증을 거부한다. 구현이 비교적 간단하고 기존의 JWT 구조를 유지가능한 장점이 있다. -
화이트리스트 전략
허용된 토큰만 저장하는 방식이다. DB or Redis에 저장된 토큰만 인증을 허용한다. 기본적으로 모든 토큰을 차단하고 저장된 토큰인지를 확인하는 방식이다.
실무에서는 Redis를 사용한 화이트리스트 전략이 많이 사용된다. 빠른 조회속도, 토큰 만료 자동 처리기능, TTL지원으로 Refresh Token 관리에 매우 적합하기 때문이다. 하지만 대기업의 경우 대용량 세선의 크기를 감당가능하고 보안을 더 중요시 여기서 블랙리스트를 사용하는 곳도 있다고 한다.
JJWT 라이브러리 사용하기
JWT를 쉽게 다루기 위해 JJWT를 사용해준다. 이는 서드파티이기 때문에 gradle에 끌고 오기 위해서는 버전을 기재해줄 필요가 있다.
JWT는 크게 3부분으로 구성되는데 이는 Header, Payload, Signature이다. 그중 Payload는 사용자의 영역을 담는다. 그리고 위변조를 방지하기 위해 Signature를 사용한다. 이를 다루는데 Secret Key와 암호화 알고리즘을 사용한다. 이 Signature에 대한 분석을 위해 사용 알고리즘, 토큰 타입,등이 포함된다.
{
"sub": "1234",
"name": "Jone doe",
"iat": 15160423
}
위는 payload에 들어간 토큰을 decode 시켜준 것이다. 이 값들은 아래 메서드를 통해 들어가게 된다.
//토큰 주제
.subject("eddie")
//토큰 발급 시간
.issuedAt(new Date())
//토큰 만료 시간
.expiration(exp)
@Test
void it_will_issue_a_token(){
Date cur = new Date();
Date exp = new Date(cur.getTime() + 30_000L);
JwtBuilder jwtBuilder = Jwts.builder()
.subject("Hello World!")
.claim("name","eddie")
.issuedAt(new Date())
.expiration(exp);
}
위는 JWT Payload 생성 예시이다. 이를 사용하기 위해서는 signature이 필요하다.
.requestMatchers(CorsUtils::isPreFlightRequest)
.permitAll()
CORS 정책을 적용하는 환경에서는 브라우저가 실제 요청 전에 사전 검사를 하는 preflight 요청을 보낼 수가 있다. 브라우저가 먼저 서버에 허용여부를 물어보게 된다. 위 코드는 브라우저의 사전 검사 요청은 인증없이 허용하겠다는 것을 의미한다.
JWT 전달 방식
로그인 성공 후 JWT를 클라이언트에 전달하는 방식은 여러가지가 존재한다.
- 응답 Body로 전달
- Header에 넣어서 전달
- Cookie로 전달
이벤트 기반 처리
Spring Security에서는 인증 성공/실패 시 이벤트가 발생하게 된다.
성공하면 oauthSuccessEvent가 발생한다. 실패 시 oauthFailureEvent가 발생하게 된다.
멤버 서비스에 있는 것이 끝나고 끝난 시점을 캐치해낸 핸들러가 있는데 이를 구현하는 것이 필요하다. 실패했다면 실패한 이벤트를 알려주는것을 구현해줘야 한다.
JWT Parsing 시 발생가능한 예외
- MalformedJwtException : JWT 형식 자체가 깨진 경우, 예시: abc.def처럼 정상 JWT 형식이 아닌 경우
- ExpiredJwtException : 유효기간이 만료된 경우, exp 시간이 지난 JWT라고 볼 수가 있다.
- SignatureException : 서명이 일치하지 않는 경우, 위변조 가능성 존재한 JWT이다.
- JwtException : 위 JWT 관련 예외들의 상위 예외 클래스, JWT 검증 실패를 포괄적으로 처리 가능한다.
- IllegalArgumentException : 토큰 문자열 자체가 비어있거나 잘못된 경우 발생 가능