본문 바로가기
프로젝트/개인 프로젝트) 요만큼

JWT 인증 방법을 수정했다!

by 휴일이 2024. 3. 15.

 

 

여러분은 아는가?

HTTP 요청 Header 길이가 길수록 보내야 할 데이터 양이 늘어나니 패킷 길이가 길어져 요청이 무거워지고 서버에 부하가 간다는 사실을?

 

 

기존 요만큼에서 인증 과정은 이랬다.

1. Authorization 헤더에서 엑세스 토큰 확인

2. 유효하지 않을 경우 쿠키에 리프레쉬 토큰 확인

 

 

그래서 인증이 필요한 요청"마다"

Authorization 헤더에 엑세스 토큰

Cookie 에는 리프레쉬 토큰을 보내주고 있었다.

 

 

그런데 사실, 엑세스 토큰이 유효하다면 리프레쉬 토큰은 확인도 하지 않기 때문에

엑세스 토큰이 유효한 요청에는 굳이 쿠키까지 보낼 필요가 없는 것...

 

 

 

그래서 HTTP 요청을 좀 더 가볍게 하고 서버에 부하를 덜 가게 하기 위해서 인증 방법을 바꾸기로 했다!

1. 기존 인증이 필요한 요청에는 Authorization 헤더에 엑세스 토큰만 보내주기.

2. 엑세스 토큰만 확인하고 요청이 실패하면 401 응답

3. 클라이언트 서버에서는 401이 오면 "리프레쉬 토큰"으로 토큰 재발급용 api 호출

4. 리프레쉬 토큰이 유효해서 토큰을 재발급받으면, 처음에 인증 요청했던 api를 재호출.

 

 

사실 이 때 고민했던 건.

- 쿠키 추가된 요청 길이를 요청마다 보내는 것

- 엑세스 토큰이 유효하지 않을 때 토큰 재발급용 api 를 호출하고 다시 기존 인증 요청을 보내는 것

뭐가 더 빠르고 서버에 부하가 덜 갈까 하는 거였다.

 

왜냐하면 후자의 방법은 엑세스 토큰이 유효할 땐 확실히 빠를 테지만,

엑세스 토큰이 유효하지 않을 경우

기존요청 -> 재발급요청 -> 기존요청(다시)

이렇게 요청을 총 3번을 해야하잖아~

 

 

-> 그래서 이 부분은 엑세스 토큰의 길이를 1시간 정도로 잡아, 엑세스 토큰을 충분히 사용하고 재요청하는 횟수가 과하지 않도록 조절.

-> 리프레쉬 토큰이 유효하면, 엑세스 토큰을 파싱해서 그 안의 정보들로 다시 토큰을 재발급해준다.

 

그리고 엑세스 / 리프레쉬 토큰을 인증을 위해 전부 같이 보내는 건 보안에도 위험할 것 같아, 따로 따로 요청하는 것이 낫다고 판단!

 

 

 

JwtFilter

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

        String authorizationToken = request.getHeader(HttpHeaders.AUTHORIZATION);

        if (!StringUtils.hasText(authorizationToken)) {
            filterChain.doFilter(request, response);
            return;
        }

         boolean accessTokenValid = tokenService.tokenValid(authorizationToken);
        if (!accessTokenValid) {
            filterChain.doFilter(request, response);
        }

        String token = tokenService.reCreateToken(authorizationToken);
        String refreshToken = tokenService.createRefreshToken();

        setAuthenticationToSecurityContextHolder(token);

        response.setHeader(HttpHeaders.AUTHORIZATION, TokenProp.BEARER.getName() + " " + token);
        setCookieInRefreshToken(response, refreshToken);

        filterChain.doFilter(request, response);

    }

 

헤더서"만" 토큰 유무를 확인하고

토큰이 있다면 토큰 유효를 확인하고

유효하다면 토큰을 재발급한 뒤

스프링 컨텍스트에 토큰을 집어넣고 인증을 성공시킨다 ^0^/

 

 

만약 엑세스 토큰이 없다면 바로 인증이 실패해 401 status 를 반환하는데....!

이럴 경우 클라이언트는 토큰 요청 api 에 accessToken, refreshToken 을 전부 반환해야한다.

 

 

@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/token")
@Tag(name = "TOKEN", description = "토큰 API 명세서")
public class TokenController {

    private final TokenService tokenService;

    @PostMapping
    @Operation(summary = "토큰 재발급", description = "리프레시 토큰으로 인증 후 토큰 재발급용 api")
    public TokenResDto tokenValid(@RequestBody RefreshTokenReqDto dto) {
        return tokenService.refreshTokenValid(dto);
    }
}

 

요 토큰 재발급 api를 요청해서 해당 컨트롤러를 타게 되는데,

 

@Builder
public record TokenResDto(
        String accessToken,
        String refreshToken
) {
}

 

엑세스 토큰과 리프레쉬 토큰을 요청 바디에 받게 된다.

리프레쉬 토큰은 토큰 유효를 확인하기 위함이며

토큰이 유효하다면 토큰을 다시 만듬

 

 

 

TokenService

    @Override
    public TokenResDto refreshTokenValid(RefreshTokenReqDto dto) {
        boolean tokenValid = tokenValid(dto.refreshToken());
        if (!tokenValid) {
            throw new BadRequestException(Exception.TOKEN_NOT_VALID);
        }

        return TokenResDto.builder()
                .accessToken(reCreateToken(dto.accessToken()))
                .refreshToken(createRefreshToken())
                .build();
    }

 

리프레쉬 토큰이 유효하면

엑세스 토큰, 리프레쉬 토큰을 재발급 ^0^ /

 

 

 

이렇게 변경하여

우리 프로젝트 서버에 부하를 더 줄이고

보안에 더 유리하게 업그레이드 되었!!!다면 좋겠다 ^0^/ 헤헤..

728x90