쿠키를 통한 인증 및 검증

바로 전 게시글에서 만든 검증 방식은 Authorization 헤더에 accessToken 을 담아 주는 방식이었다.

이번엔 웹사이트에서 많이들 사용하는 쿠키를 사용하는 방식으로 바꿔보자.

기존의 accessToken 을 발급하는 방식은 그대로 사용하고 컨트롤러와 ArgumentResolver 를 변경한다.

인증 과정은 다음과 같다.

  1. 로그인 성공 시 응답으로 세션 토큰 발급
  2. 인증이 필요한 페이지에 요청 시 쿠키에 담긴 세션 토큰 정보로 DB 에서 확인

 

AuthController

AuthService 에서 만든 accessToken 을 쿠키에 담아 전달하는 컨트롤러를 만든다.

@PostMapping("/auth/login")
public ResponseEntity login(@RequestBody LoginRequest request) {
    // DB 에서 조회
    String accessToken = authService.signin(request);
    // accessToken 을 쿠키에 담아 전달
    ResponseCookie cookie = ResponseCookie.from("SESSION", accessToken)
        .domain("localhost")   // todo 서버 환경에 따라 설정파일로 분리해서 관리하자.
        .path("/")
        .httpOnly(true)
        .secure(false)
        .maxAge(Duration.ofDays(30))  // 한달이 국룰
        .sameSite("Strict")
        .build();
    return ResponseEntity.ok()
        .header(HttpHeaders.SET_COOKIE, cookie.toString())
        .build();
}

ㄴ ResponseCookie

accessToken 을 담아 줄 쿠키를 설정할 수 있는 클래스이다. 쿠키는 key & value 의 형태로 담긴다.

여기서는 SESSION 이라는 이름의 쿠키에 accessToken 값을 담았다.

domain 은 서버 환경에 따라 localhost 로 할 지, 실제 서버 도메인을 할 지 필요에 따라 설정 파일로 분리해서 관리한다.

httpOnly, secure, sameSite 와 관련된 정보는 중요하니 ReponseCookie 에 관한건 찾아보고 공부해두도록 하자.

maxAge 로 쿠키의 수명을 초 단위로 설정할 수도 있고 일 단위로 할 수도 있다. 한달이 국룰이라고 한다.

ㄴ ResponseEntity

응답으로 보내줄 상태 코드, 상태 메시지, 응답 바디 등을 설정할 수 있는 클래스이다.

여기서는 요청 성공 시 200 의 상태코드를 주기로 한다.

 

AuthResolver

기존의 헤더를 확인하던 검증 로직을 쿠키를 확인하는걸로 변경한다.

@Slf4j
@RequiredArgsConstructor
public class AuthResolver implements HandlerMethodArgumentResolver {

    private final SessionRepository sessionRepository;

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.hasParameterAnnotation(Login.class);
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        HttpServletRequest nativeRequest = webRequest.getNativeRequest(HttpServletRequest.class); // nullable 이므로 체크해줌
        if (nativeRequest == null) {
            log.error("servletRequest null");
            throw new UnauthorizedException();
        }

        Cookie[] cookies = nativeRequest.getCookies();
        if (cookies.length == 0) {
            log.error("쿠키가 없음");
            throw new UnauthorizedException();
        }
        // 데이터베이스로 사용자 확인작업
        String accessToken = cookies[0].getValue();
        Session session = sessionRepository.findByAccessToken(accessToken)
            .orElseThrow(() -> new UnauthorizedException());

        return new UserSession(session.getUser().getName());
    }
}

ㄴgetNativeRequest()

이 메서드를 이용하여  HttpServletRequest 를 가져와서 쿠키를 확인한다.

HttpServletRequest 는 nullable 이므로 null 체크를 해주었다.

 


테스트

AuthControllerTest

AuthResolver 의 로직이 바뀌었기 때문에 컨트롤러 테스트도 바꿔줘야한다.

권한이 필요한 페이지에 요청 시 SESSION 이라는 쿠키에  accessToken 이 담겼는지 확인하고

올바른 accessToken 일 시 접속을 허용하는 테스트를 작성한다.

@DisplayName("검증된 세션 값으로 권한이 필요한 페이지에 요청시 접속에 성공한다.")
@Test
@Transactional
void access() throws Exception {
    // given
    User user = userRepository.findByEmailAndPassword("hyukkind@naver.com", "1234")
        .orElseThrow(() -> new InvalidSigninInformationException());
    Session session = user.addSession();

    // expected
    mockMvc.perform(
            get("/auth")
                .cookie(new Cookie("SESSION", session.getAccessToken()))
                .contentType(APPLICATION_JSON)
        )
        .andDo(print())
        .andExpect(status().isOk());
}

@DisplayName("검증되지 않은 세션값으로 권한이 필요한 페이지에 요청시 실패한다.")
@Test
@Transactional
void access2() throws Exception {
    // given
    User user = userRepository.findByEmailAndPassword("hyukkind@naver.com", "1234")
        .orElseThrow(() -> new InvalidSigninInformationException());
    Session session = user.addSession();

    // expected
    mockMvc.perform(
            get("/auth")
                .cookie(new Cookie("SESSION", "1"))
                .contentType(APPLICATION_JSON)
        )
        .andDo(print())
        .andExpect(status().isUnauthorized());
}

 

테스트는 성공하고 쿠키값에 담긴 accessToken 이 DB 정보와 일치하지 않으면 예외가 발생한다.

+ Recent posts