처음에 비회원으로 진행하려던 사이트가 설계상 회원이 필수인 것을 뒤늦게 깨닫고 개발에 들어갔다. 거슬러 가는 행위가 좋거나 효율적이지 않지만, 설계상 헷갈렸거나 더러웠던 코드들을 쳐내는 계기가 되었다.
이번 글은 스프링 부트로 카카오 로그인을 따라하면서 + 에러 모음을 적었다.
스프링으로 카카오 로그인을 따라 구현해 본 경험은 있어서 애플리케이션 키값 정보들의 사용법은 아는 상태이다.
차이점은 스프링으로 할 때는 세션
에 사용자 정보를 추가했지만, 이번에는 JWT 토큰
을 써서 서버 부담을 줄이고자 했다.
Jwt는 간략하게 말하자면 사용자 식별 정보를 포함하는 accessToken
과 accessToken이 만료되었을 경우 재발급을 요청하는 refreshToken
으로 이루어진 토큰 시스템이다.
보안을 위해서 시큐리티와 같이 사용하는 경우도 있다.
하지만 말하고 싶은 것은 필수가 아니라는 점이다.
Jwt는 구현 방식에 대해서 의견도 각각 다르기 때문에 일단 해보는 걸 추천.
또 하나 저번 소셜 로그인과 차이점은 프론트가 다른 포트를 가진 리액트라는 점이다.
그래서 카카오 애플리케이션에서
redirct_uri
를localhost:3000~
으로 시작하는 프론트 url을 적어야 한다.
참고 https://su-vin25.tistory.com/entry/React-SpringBoot-카카오-소셜-로그인-구현-REST-API
위의 참고 사이트 가서 로직 그림 한번 보면 이해가 간다. 설정 부분도 참고 많이 함.
카카오 로그인 페이지로 이동하려면 button
, a
태그를 사용해서 로그인 페이지 링크를 삽입한다.
https://kauth.kakao.com/oauth/authorize?client_id={클라이언트 아이디}
&redirect_uri={리다이렉트 주소}&response_type=code&scope=talk_message&email
scope는 동의 항목 이야기다. talk_message
가 들어간 이유는 나에게 메세지 보내기 테스트용.
어쨌든 이 정보들을 그대로 추가하면 외부로 주요 개인정보가 노출된다. .env
를 사용해보자.
파일 위치는 폴더 최상단이어야 한다.
<button
className="kakao"
onClick={(e) => {
const clientId = process.env.REACT_APP_KAKAO_CLIENT_ID;
const redUrl = process.env.REACT_APP_KAKAO_REDIRECT_URL_LOCAL;
const url = `https://kauth.kakao.com/oauth/authorize?client_id=${clientId}&redirect_uri=${redUrl}&response_type=code&scope=talk_message&email`;
window.location.href = url;
}}
>
카카오로 로그인
</button>
분명히 env 파일 형식도 맞고 key=value
형태로 적었는데 값이 undefined가 뜨는 경우가 발생했다;;;
해결 : 모든 변수 앞에 REACT_APP 을 접두사로 추가해야 한다.
인증을 모든 요청마다 각각의 Controller
에서 처리하는 것은 비효율적이다.
따라서 인증이 필요한 경로를 /auth
로 시작하게 처리한 후 해당 경로를 interceptor의 url 패턴에 등록하기로 했다.
인증이 필요한 페이지와 그렇지 않은 페이지를 미리 분리해 두었다면 일이 훨씬 수월했을 것이다.
아래는 내가 작성한 인터셉터의 일부이다.
AuthInterceptor.java
@Slf4j
@Component
@RequiredArgsConstructor
public class AuthInterceptor implements HandlerInterceptor {
private final JwtTokenProvider jwtTokenProvider;
private final AuthTokensGenerator authTokensGenerator;
private final MemberService memberService;
... 생략
}
롬복을 사용해서 주입을 했는데 왜 jwtTokenProvider
는 null일까?
원인은 인터셉터가 아니라 인터셉터를 등록하는 WebMvcConfig
에 있다.
WebMvcConfig.java
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
private final AuthInterceptor authInterceptor;
public WebMvcConfig (AuthInterceptor authInterceptor) {
this.authInterceptor = authInterceptor;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
List<String> URL_PATTERNS = Arrays.asList("/auth/**");
registry.addInterceptor(authInterceptor)
.addPathPatterns(URL_PATTERNS)
.excludePathPatterns("/css/**", "/images/**", "/js/**");
}
}
registry.addInterceptor(new AuthInterceptor())
위와 같이 new
로 생성해서 인터셉터 객체를 등록하지 말고, 여기서 생성자 주입 등으로 인터셉터를 주입해야만 null을 방지할 수 있다.
private final AuthInterceptor authInterceptor;
public WebMvcConfig (AuthInterceptor authInterceptor) {
this.authInterceptor = authInterceptor; //미리 주입해주자.
}
해당 컴포넌트를 사용하려면 빈 주입을 해주어야 하는데, 생성자 주입은 코드가 갈 수록 길어지고, 이름 변경 등이 일어나면 수정이 타 주입에 비해서 번거롭다.
하지만 먼저 롬복의 @RequiredArgsConstructor
를 사용해서 개발을 일단 해놓은 다음에, 어노테이션을 주석처리한다.
그러면 에러가 뜨면서 hover시 Add constructor parameters 옵션을 선택하면 자동으로 생성자 주입으로 바꾸어 줘서 더 편리하다. 실제로는 생성자 주입이 가장 좋은 방법이라고 한다.
카카오로 로그아웃을 한다면 로그아웃 url을 통해 카카오 로그아웃을 하고
백단에 리프레시 토큰을 전달해서 디비 내 사용자의 리프레쉬 토큰을 삭제하기로 했다.
axios.post()
를 통해서 전달할 것인데 headers
에 담으면 참 쉽다.
하지만 @RequestParam
, @RequestBody
는 더 골치아프고 시간을 먹는다.
후자의 경우 인코딩/디코딩 처리가 들어가는데 그럴바에 헤더에 담고 말지;;
실제로 위 두 방법을 써봤지만 어찌 해도 값이 잘 안 들어오다가 헤더 처리로 단번에 해결했다.