[250327] TIL
오늘 한 일
Spring boot 에서 JWT 토큰 쿠키로 관리하기 (HttpOnly)
CORS 설정
config.setAllowCredentials(true)
- 인증 정보(쿠키, HTTP인증, 클라이언트 측 SSL 인증서)를 포함한 크로스 도메인 요청을 허용함
- true 로 설정하면 쿠키와 같은 인증 정보를 포함한 요청이 가능해짐
@Bean
CorsConfigurationSource corsConfigurationSource() {
return request -> {
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.setAllowedHeaders(Collections.singletonList("*"));
config.setAllowedMethods(Collections.singletonList("*"));
config.setAllowedOriginPatterns(Arrays.asList("http://localhost:5500", "http://127.0.0.1:5500"));
return config;
};
}
쿠키 저장, 삭제, 읽기 함수 만들기 ( feat. jwt token )
- jwt 토큰 관련된 함수는 예시 코드에서 삭제했음
- SameSite 옵션이 Lax 이어야함 (중요)
- None 옵션은 Secure 속성과 함께 사용 되어야하기 때문에 쿠키 전송이 안되는 오류가 발생함
@Component
public class JwtUtil {
private static final String COOKIE_NAME = "auth_token";
public void addTokenToCookie(HttpServletResponse response, String token){
response.setHeader("Set-Cookie", String.format("%s=%s; Max-Age=%d; Path=/; HttpOnly; SameSite=Lax",
COOKIE_NAME, token, (int)(EXPIRATION_TIME / 1000)));
}
public String extractTokenFromCookie(HttpServletRequest request){
Cookie[] cookies = request.getCookies();
if(cookies != null){
for (Cookie cookie: cookies){
if(COOKIE_NAME.equals(cookie.getName())){
return cookie.getValue();
}
}
}
return null;
}
public void deleteCookie(HttpServletResponse response){
Cookie cookie = new Cookie(COOKIE_NAME, null);
cookie.setHttpOnly(true);
cookie.setPath("/");
cookie.setMaxAge(0);
response.addCookie(cookie);
System.out.println("쿠키 삭제 완료");
}
}
Jwt 필터에서 쿠키 읽고 인증 여부 구분하기
@Component
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtUtil jwtUtil;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String authorizationHeader = request.getHeader("Authorization");
String token = null;
if(authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
token = authorizationHeader.substring(7);
} else {
token = jwtUtil.extractTokenFromCookie(request);
}
if(token != null) {
try {
Long userId = jwtUtil.extractUserId(token);
request.setAttribute("userId", userId);
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
userId, null, Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER"))
);
SecurityContextHolder.getContext().setAuthentication(authentication);
} catch (Exception e) {
System.out.println("JWT 인증 실패: " + e.getMessage());
}
}
filterChain.doFilter(request, response);
}
}
서비스, 컨트롤러에서 함수 소비
HttpServletResponse
- 클라이언트에서 HTTP 요청을 보내면 서버는 요청을 처리하고 HttpServletResponse 객체를 사용하여 응답을 구성함
- HTTP 응답 헤더 설정, 상태 코드 설정, 쿠키 추가, 본문 작성, 리다이렉트 처리등 기능을 제공함
public LoginResponseDto login(LoginRequestDto requestDto, HttpServletResponse response){
User user = userRepository.findByEmail(requestDto.getEmail())
.orElseThrow(()->new CustomException(ErrorCode.USER_NOT_FOUND));
if(!user.getPassword().equals(requestDto.getPassword())){
throw new CustomException(ErrorCode.INVALID_PASSWORD);
}
String token = jwtUtil.generateToken(user.getId());
jwtUtil.addTokenToCookie(response, token);
return new LoginResponseDto(user.getId(), user.getEmail(), user.getNickname(), user.getProfile());
}
public void logout(HttpServletResponse response){
jwtUtil.deleteCookie(response);
SecurityContextHolder.clearContext();
}
public boolean checkLogin(HttpServletRequest request){
String token = jwtUtil.extractTokenFromCookie(request);
if(token == null){
return false;
}
try{
Long userId = jwtUtil.extractUserId(token);
return !jwtUtil.isTokenExpired(token) && userRepository.existsById(userId);
} catch (Exception e) {
return false;
}
}
+ 프론트엔드에서 요청할 때
credentials: “include”
- 위 옵션 넣어줘야한다.
- 크로스 도메인 요청에서 쿠키와 인증 정보를 함께 전송할지 결정하는 것
const response = await fetch(`${API_CONFIG.BASE_URL}${endpoint}`, {
...defaultOptions,
...options,
headers: {
...defaultOptions.headers,
...options.headers,
},
credentials: "include"
});
크로스 도메인이란?
- 크로스 도메인이란 현재 보고 있는 웹페이지의 도메인과 다른 도메인으로 리소스를 요청하는 것을 의미
아래 요소들이 모두 같을 때만 같은 도메인으로 간주
프로토콜: http, https
호스트명: example.com, api.example.com
포트: 80(http), 443(https), 3000 등
다른 도메인 (Cross-Domain/Cross-Origin)
- https://example.com → https://api.example.com (서브도메인 다름)
- https://example.com → https://example.org (최상위 도메인 다름)
- https://example.com → http://example.com (프로토콜 다름)
- https://example.com → https://example.com:8080 (포트 다름)
Leave a comment