로그인, 로그아웃 기능을 구현하면서
HttpServletResponse객체를 주로 사용했는데,HttpServletRequest객체가 있음을 알게 되었고 둘의 차이가 궁금해서 찾아보게 되었다.
getParameter(String name): 요청 파라미터를 가져옴getHeader(String name): 요청 헤더를 가져옴getMethod(): HTTP 메서드(GET, POST)를 반환getRequestURI(): 요청 URI를 가져옴String authToken = request.getHeader("Authorization");
String username = request.getParameter("username");
setStatus(int sc): 응답 상태 코드 설정addHeader(String name, String value): 응답 헤더 추가addCookie(Cookie cookie): 쿠키 추가getWriter(): 응답 본문에 내용을 작성할 수 있는 PrintWriter 객체 반환response.setStatus(HttpServletResponse.SC_OK); // 상태 코드 200 설정
response.addHeader("Content-Type", "application/json"); // 헤더 추가
// UserController
@PostMapping("/auth/login")
public ResponseDto<?> login(@RequestBody LoginRequestDto request, HttpServletResponse res) {
return userService.login(request, res);
}
// UserService
public ResponseDto<?> login(LoginRequestDto request, HttpServletResponse res) {
String email = request.getEmail();
String password = request.getPassword();
// 존재하는 유저인지 확인
Optional<P_user> userOptional = userRepository.findByEmail(email);
if (!userOptional.isPresent()) {
return new ResponseDto<>(-1, "존재하지 않는 사용자입니다", null);
}
P_user user = userOptional.get();
// 비밀번호 비교
if (!passwordEncoder.matches(password, user.getPassword())) {
return new ResponseDto<>(-1, "비밀번호가 일치하지 않습니다", null);
}
// JWT 생성, 쿠키 저장
String token = jwtUtil.createToken(user.getNickname(), user.getRole());
jwtUtil.addJwtToCookie(token, res);
Cookie jwtCookie = new Cookie(JwtUtil.AUTHORIZATION_HEADER, token);
jwtCookie.setPath("/");
jwtCookie.setHttpOnly(true); // HttpOnly 설정
res.addCookie(jwtCookie);
LoginResponseDto responseDto = LoginResponseDto.builder()
.nickname(user.getNickname())
.role(user.getRole().toString())
.build();
return new ResponseDto<>(1, "로그인이 성공적으로 완료되었습니다", responseDto);
}
HttpServletRequest를 받아 클라이언트가 보낸 토큰을 추출해 무효화(블랙리스트 처리 등), 세션 종료 등의 처리 할 수 있음HttpServletResponse를 통해 JWT가 담긴 쿠키를 삭제해 클라이언트 측에서도 토큰을 무효화 할 수 있음// UserController
@PostMapping("/auth/logout")
public ResponseDto<?> logout(HttpServletResponse res) {
return userService.logout(res);
}
// UserService
public ResponseDto<?> logout(HttpServletResponse res) {
Cookie cookie = new Cookie(JwtUtil.AUTHORIZATION_HEADER, null);
cookie.setPath("/");
cookie.setMaxAge(0);
res.addCookie(cookie);
return new ResponseDto<>(1, "로그아웃이 완료되었습니다", null);
}
정리해 본 결과
HttpServletResponse: 응답에 추가 정보(쿠키, 헤더 등)를 설정할 때 사용됨HttpServletRequest: 응답에서 추가 정보(쿠키, 헤더)를 추출하여 사용할 때 사용됨HttpServletRequest를에서 사용자 정보를 추출해 조회, 반환한다.// UserController
@GetMapping("/mypage")
public ResponseDto getMypage(HttpServletRequest req) {
return userService.getMypage(req);
}
// UserService
public ResponseDto getMypage(HttpServletRequest req) {
P_user user = validateTokenAndGetUser(req).orElse(null);
if (user == null) {
return new ResponseDto<>(-1, "유효하지 않은 토큰이거나 존재하지 않는 사용자입니다", null);
}
MypageResponseDto response = MypageResponseDto.builder()
.email(user.getEmail())
.nickname(user.getNickname())
.phone(user.getPhone())
.birth(user.getBirth())
.imageProfile(user.getImageProfile())
.address(user.getAddress())
.lat(user.getLatLng().getY())
.lng(user.getLatLng().getX())
.build();
return new ResponseDto<>(1, null, response);
}
오늘은 거의 절반을 인증 로직이랑 싸웠다.
현재 JWT 토큰 값을 쿠키에 담아 전달하는 방식을 사용하고 있는데, 쿠키에는 공백값이 허용되지 않는다.
망충한 나는 그걸 모르고 테스트를 하면서 쿠키 값에 Bearer 접두사와 공백을 포함했고, 그 결과 유효하지 않은 쿠키 에러가 계속 발생했다.
심지어 쿠키에는 접두사도 필요없다.. 그래서 좀 더 자세히 알아보고 정리할 필요가 있었다.
가장 일반적인 방식
클라이언트가 Authorization 헤더에 Bearer 토큰을 포함해 서버로 전송
쿠키 대신 요청 헤더를 사용해 전송
클라이언트가 요청마다 JWT를 Authorization 헤더에 추가해 서버에 전송
장점
단점
JWT 토큰을 서버 측 세션 저장소에 저장하여 관리하는 방식
JWT가 클라이언트가 아닌 서버에서 관리되므로 보안이 강화됨
JWT의 장점 중 하나인 무상태성을 활용하지 못하고, 세션 관리를 필요로 하게 됨
장점
단점
클라이언트에서 Local Storage나 Session Storage에 JWT를 저장
요청 시마다 Authorization 헤더에 토큰을 포함하는 방식
JWT를 클라이언트 측에서 직접 관리하므로 쿠키보다 XSS 공격에 취약할 수 있음
장점
Authorization 헤더에 토큰을 직접 추가하여 전송할 수 있음단점
JWT를 클라이언트의 쿠키에 저장하여 사용
서버로 요청을 보낼 때 토큰을 쿠키에 담아 전송
장점
HttpOnly와 Secure 설정을 통해 쿠키 접근을 제한하고 HTTPS에서만 전송하도록 할 수 있음SameSite 옵션을 설정하면 CSRF 공격을 방지할 수 있음단점
HttpOnly, Secure 옵션을 설정해 CSRF 및 XSS 방지 조치를 하는 것이 안전Authorization 헤더에 토큰을 포함하여 전송하는 방식이 적합Authorization 헤더에서 'Bearer ' 접두사를 사용하여 서버가 헤더에 포함된 값이 Bearer 토큰(JWT)임을 인식하게 함Bearer 접두사의 주요 목적
1. 토큰 유형 구분
Authorization 헤더는 다양한 인증 방식에서 사용됨Bearer는 서버가 Authorization 헤더에 담긴 값이 JWT와 같은 Bearer 토큰임을 알 수 있게 해주는 접두사임Basic, Digest, Bearer 등 다양한 인증 방식 중 Bearer 방식을 사용하고 있음을 알 수 있음Bearer 방식은 IETF(Internet Engineering Task Force)에서 정의한 표준 인증 방식으로 OAuth 2.0 인증 프로토콜에서도 사용됨Authorization: Bearer <token> 이라는 간결한 형식을 제공하여 인증에 일관성을 유지할 수 있게 함Authorization 헤더에서 토큰 인증 방식을 명시하기 위한 접두사이기 때문에 쿠키에서는 사용되지 않는다Authorization 헤더와 달리 특정한 인증 프로토콜을 나타내지 않기 때문에Authorization 헤더는 본 인증(Basic Authentication), 다이제스트 인증(Digest Authentication), OAuth 등의 여러 인증 방식에서 사용됨 -> 그래서 Bearer 접두사로 헤더의 값이 Bearer 토큰임을 명확히 해야 함이로서 오늘 반나절동안 바보짓을 한 댓가를 치루었다..
나머지 기능 구현하고 찬찬히 로직 수정해야겠다.