쿠키를 통한 인증 및 검증
바로 전 게시글에서 만든 검증 방식은 Authorization 헤더에 accessToken 을 담아 주는 방식이었다.
이번엔 웹사이트에서 많이들 사용하는 쿠키를 사용하는 방식으로 바꿔보자.
기존의 accessToken 을 발급하는 방식은 그대로 사용하고 컨트롤러와 ArgumentResolver 를 변경한다.
인증 과정은 다음과 같다.
- 로그인 성공 시 응답으로 세션 토큰 발급
- 인증이 필요한 페이지에 요청 시 쿠키에 담긴 세션 토큰 정보로 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 정보와 일치하지 않으면 예외가 발생한다.
'API 만들어 보기 > 게시판 API' 카테고리의 다른 글
[API 인증] JWT 를 이용한 인증 2 (0) | 2023.10.07 |
---|---|
[API 인증] JWT 를 이용한 인증 1 (0) | 2023.10.07 |
[API 인증] DB를 통한 토큰 발급 및 검증 2 (0) | 2023.10.02 |
[API 인증] DB를 통한 토큰 발급 및 검증 1 (0) | 2023.10.01 |
[API 인증] ArgumentResolver 사용해보기 (0) | 2023.09.30 |