코드스테이츠 프리프로젝트 진행중 내가 새로배우거나 해결한 에러를 정리해보려 합니다.
1. CORS 에러 해결
포스트맨으로는 정상작동 되었으나, 로컬 환경에서 ngrok 이라는 도구를 통해 react 기반의 프론트 서버랑 통신하는데 있어서 cors 에러가 나왔었습니다.
@Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration config = new CorsConfiguration();
// 기존코드
// config.setAllowedOrigins(Arrays.asList("*"));
// config.setAllowedMethods(Arrays.asList("GET", "POST", "PATCH", "DELETE"));
// 바뀐코드
config.setAllowedOrigins(Arrays.asList("*"));
config.setAllowedMethods(Arrays.asList("*"));
config.setAllowedHeaders(Arrays.asList("*"));
UrlBasedCorsConfigurationSource source =
new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return source;
}
오리진만 열어두면 되는줄 알았는데 헤더도 열어둬야 하는것 같았습니다. 추가로 ngrok 로그를 보니까 스프링 시큐리티 관련해서인지 OPTION 메서드 요청도 들어오고 있길래 문제가 생길까봐 매서드도 전부 열어두었습니다.
이후 AWS 클라우드 환경에서 고정된 도메인을 가지고 오리진을 제한하면 될것같습니다만, 헤더부분은 열어두는게 좋을지, 닫으면 어떤부분을 닫아야하는지 잘 모르겠네요.
해당 내용은 프로젝트 끝나고 좀더 공부해보면 좋을것 같습니다.
2. JWT 토큰을 쿠키에 담아 반환 / 로그인 후 유저정보 반환
코드스테이츠 학습과정을 통해 JWT 토큰을 생성하고 리스폰스 헤더에 담아서 전달하는 부분은 배웠습니다만,
프론트쪽에서 토큰을 쿠키에 담아주는것을 원하셨습니다.
추가적으로 로그인 인증 후 일부 유저정보도 body에 담아 보내줘야 했습니다.
@Override
protected void successfulAuthentication(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain,
Authentication authResult) throws ServletException, IOException {
User user = (User) authResult.getPrincipal();
String accessToken = delegateAccessToken(user);
String refreshToken = delegateRefreshToken(user);
// header
response.setHeader("Authorization", "Bearer " + accessToken);
response.setHeader("Refresh", refreshToken);
// cookie
Cookie cookie = new Cookie("Authorization", "Bearer" + accessToken);
cookie.setMaxAge(jwtTokenizer.getAccessTokenExpirationMinutes());
// cookie.setHttpOnly(true);
response.addCookie(cookie);
Cookie refresh = new Cookie("Refresh", refreshToken);
refresh.setMaxAge(jwtTokenizer.getRefreshTokenExpirationMinutes());
// refresh.setHttpOnly(true);
response.addCookie(refresh);
// body
UserResponseSimple dto = new UserResponseSimple();
dto.setUserId(user.getUserId());
dto.setEmail(user.getEmail());
dto.setDisplayName(user.getDisplayName());
//dto.setCreatedAt(user.getCreatedAt());
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setStatus(HttpStatus.OK.value());
// response.getWriter().write(gson.toJson(dto));
response.getWriter().write(objectMapper
// .registerModule(new JavaTimeModule()) // LocalDateTime 값은 안보내기로 함
.writeValueAsString(dto));
this.getSuccessHandler().onAuthenticationSuccess(request, response, authResult);
}
현재 UserNamePasswordAuthenticationFilter 를 상속받는 커스텀 필터를 사용중이고,
인증 성공시 호출되는 successfulAuthentication 메서드를 통해 기존에 헤더에 토큰을 넣어 반환하는 것에 추가로 쿠키와 유저정보를 담은 오브젝트를 직렬화 시켜서 response 에 담아 보내주는 형식으로 만들어 보았습니다.
뭔가 기존에 header만 보낼때는 못느꼈지만 이렇게 기능이 잡다하게 많아지니 책임이 난잡해보여서 다른 서비스 객체를 만들어서 메서드를 빼야하나 고민하고 있습니다.
추가로 원래는 gson이라는 외부 라이브러리를 통해 json 형태의 문자열로 만들어줬는데,
이러한 경고문이 떠서 알아보니 Java 버전 호환문제라는것 같은데, (현재 프로젝트는 Java 11버전을 사용하고 있습니다.)
단순히 gson의 문제인것 같아서 gson 대신 ObjectMapper를 사용하니 해결되었습니다.
경고문과는 별개로 gson을 사용했을때도 값은 알맞게 출력이 되었습니다.
3. expose header
앞서 JWT 토큰을 헤더와 쿠키 양방향으로 넘기도록 만들었는데, (이후 사용하지 않는쪽은 폐기할까 생각중입니다.)
헤더값이 위의 이미지처럼 포스트맨 환경에서는 정상적으로 뱉어주는데, 프론트랑 통신하면은 헤더값이 제대로 넘어가질 않는 문제가 있었습니다. 해당부분은 expose header 를 열어서 해결했습니다.
@Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOrigins(Arrays.asList("*"));
config.setAllowedMethods(Arrays.asList("*"));
config.setAllowedHeaders(Arrays.asList("*"));
// 위의 코드에서 추가된 부분
config.addExposedHeader("Authorization");
config.addExposedHeader("Refresh");
UrlBasedCorsConfigurationSource source =
new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return source;
}
1번에서 본 코드와 추가된 부분 이외에는 동일한 구성입니다.
4. 페이징 처리를 위한 커스텀 쿼리
해당 블로그 포스팅에서도 확인이 가능하시겠지만, 제가 예전에 페이징 관련해서 JPA를 통한 네이티브 쿼리를 작성해본 적이 있습니다. 해당 경험을 토대로 이번 프로젝트에서도 비슷하게 적용시켜보았습니다.
이번에는 QueryDsl 을 사용했습니다.
@Override
public List<QuestionResponseSimple> getQuestionsByQuestionPage(QuestionPage questionPage) {
QQuestion question = QQuestion.question;
QUser user = QUser.user;
QAnswer answer = QAnswer.answer;
List<QuestionResponseSimple> result = jpaQueryFactory
.select(Projections.fields(QuestionResponseSimple.class,
question.questionId,
question.title,
question.content,
user.displayName.as("user"),
question.createdAt,
question.votes,
question.view,
answer.count().as("answers")))
.from(question)
.join(question.user, user)
.on(question.user.userId.eq(user.userId))
.leftJoin(question.answers, answer)
.on(question.questionId.eq(answer.question.questionId))
.groupBy(question.questionId)
.limit(30)
.offset((long) (questionPage.getPage() - 1) * 30)
.orderBy(question.questionId.desc())
.fetch();
return result;
}
QueryDsl 이 익숙하지가 않아 문제가 없을까 싶긴 하지만 일단은 정상작동 하고 있습니다.
limit 나 offset 에 쓰이는 갯수(30개)는 다른곳에서 상수값으로 받아오거나 QuestionPage 오브젝트에 필드값을 받아서 사용하는 식으로 바꿀 예정입니다.
sql 쿼리도 한번만 나가고, 결과값도 원하는 결과값을 받을 수 있었습니다.
// 사족
이제 프리프로젝트가 1주정도 남았고, 이후 메인프로젝트를 진행하는데, 지금처럼 프로젝트에 관한 경험은 꾸준히 블로깅 할 예정입니다.
현재 당장 해야할 일은 익셉션 핸들링과 AWS로 클라우드 환경 구성 정도가 떠오르고, 이후 시간이 좀 남으면 코드 리팩토링까지 마치고 프리프로젝트를 깔끔하게 끝낼 수 있을 것 같습니다.
사실 제가 코드스테이츠 섹션 4 후기를 빼먹었는데, 사실 섹션 3 후기를 섹션 4 도중에 작성해서 그런지 뭔가 마음가짐도 비슷했고, 딱히 더 작성할만한 내용이 없었기에 빼먹게 되었습니다만, 섹션 4 후기는 섹션 5 후기와 함께 작성하지 않을까 싶습니다.
'프로젝트' 카테고리의 다른 글
CodeStates 메인프로젝트 - 웹소켓을 이용한 실시간 채팅 구현 / 코드 파해치기 (0) | 2023.03.19 |
---|---|
CodeStates / StackOverflow 프리프로젝트 3주차 (0) | 2023.03.04 |
토이프로젝트 / NodeToSpring - 페이징 기능 개선 (SqlResultSetMapping ) (0) | 2022.12.25 |
토이프로젝트 - NodeToSpring 4. 리팩토링 약간... (0) | 2022.12.13 |
토이프로젝트 - NodeToSpring 3. controller (0) | 2022.12.04 |