Spring Boot에서는 /signup, /login 등 인증 정보 없이 접근 가능한 경로를 WhiteListUri로 설정했다.
Spring Security의 인증이 로직이 적용되도록 React 클라이언트에서 사용자 인증 상태를 확인하는 로직을 구현하려고 한다.
간단하게 생각하면 인증이 필요한 경로로 리디렉션 될때마다 인증 검증 API를 호출시키면 되는거 아닌가?
/member/verify 로 인증 검증 API 분리/member/verify API로 별도 분리하여 재사용성을 높이고, 코드 유지보수를 간소화했다. /member/verify를 일관된 진입점으로 활용할 수 있다.React 클라이언트
const useAuth = () => {
// useNavigate를 통한 리다이렉션 처리
const navigate = useNavigate();
useEffect(() => {
const verifyAuth = async () => {
try {
const response = await fetch('http://localhost:8080/member/verify', {
method: 'GET',
credentials: 'include', // 쿠키 포함
});
if (!response.ok) {
navigate('/');
}
} catch (error) {
console.error('인증 확인 실패:', error);
navigate('/');
}
};
verifyAuth();
}, [navigate]);
};
credentials: 'include'로 쿠키 기반 인증/member/verify 호출로 인증 상태를 즉시 확인하고 검증 결과 반환/으로 리다이렉션시킴Springboot
@RequestMapping("/member")
public class MemberController {
@GetMapping("/verify")
public BaseResponse<MemberResponseDto> verifyMember(
Principal principal
){
String email = principal.getName();
return memberService.verifyMember(email);
}
}
useAuth 훅이 /member/verify API 호출을 통해 인증 상태 확인./)으로 리다이렉션.
로그인 성공 후, dashboard(whiteListUri로 지정하지 않은 경로)로 리디렉션 되는 즉시 /member/verify API가 호출되는 것을 확인할 수 있다.
/member/verify API로 인증 검증 로직을 분리한 이유는 단순하고 직관적인 기능 구현과 신속한 개발이 주요 목적이었다. 마감기한을 준수하면서도 동작하는 코드를 작성하기 위한, 당시에는 최선의 선택이었다.
하지만, 조금 더
sexy
할 순 없을까?
클라이언트와 서버간 인증 검증 로직 플로우를 추가적으로 학습하다가 다음과 같은 방법론들을 알게 되었다. 추후 배포까지 완료된 후, 리팩토링 시기에 적용해보려고 한다.
AOP(Aspect-Oriented Programming)를 통해 인증 및 인가 로직을 메서드 호출 전에 자동으로 수행하면 검증 API 호출이 필요 없어지고, 인증 로직이 컨트롤러에 노출되지 않는다.
@Before 로직을 삽입.@Aspect
@Component
public class AuthorizationAspect {
@Before("@annotation(com.diary.musicinmydiaryspring.common.annotation.Authorized)")
public void authorizeUser(JoinPoint joinPoint) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null || !authentication.isAuthenticated()) {
throw new CustomRuntimeException(BaseResponseStatus.UNAUTHORIZED);
}
}
}
Filter나 Interceptor는 HTTP 요청 단계에서 전역적인 인증 검증을 처리할 수 있다. 이를 통해, 클라이언트가 /member/verify를 호출하지 않아도 인증 상태를 확인할 수 있다.
OncePerRequestFilter를 확장해 구현.@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) {
if (isWhiteListed(request.getRequestURI())) {
filterChain.doFilter(request, response);
return;
}
String jwtToken = getCookieValueFromToken(request, "accessToken");
if (jwtToken != null && jwtProvider.validateToken(jwtToken)) {
Authentication authentication = jwtProvider.getAuthentication(jwtToken);
SecurityContextHolder.getContext().setAuthentication(authentication);
String email = jwtProvider.getClaims(jwtToken).getSubject();
request.setAttribute("email", email);
}
filterChain.doFilter(request, response);
}
private String getCookieValueFromToken(HttpServletRequest request, String cookieName) {
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
if (cookieName.equals(cookie.getName())) {
return cookie.getValue();
}
}
}
return null;
}
}