이번에는 받아온 토큰을 바탕으로 accessToken을 검증해보도록 하겠다.
클라이언트는 요청을 보낼 때 마다 accessToken을 전달한다고 했다. 클라이언트는 이런 형태로 accessToken을 전달한다.
Authorization: bearer accessToken
서버에서는 filter를 돌 때 마다 accessToken을 검증하는 로직을 수행한다. 나는 이를 security filterchain에 customfilter를 추가해 구현하였다.
import java.io.IOException;
import java.util.Arrays;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import com.project.bookforeast.common.security.error.TokenErrorResult;
import com.project.bookforeast.common.security.error.TokenException;
import com.project.bookforeast.common.security.service.JwtUtil;
import com.project.bookforeast.common.security.service.SecurityService;
import com.project.bookforeast.user.dto.UserDTO;
import io.jsonwebtoken.Claims;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
@Component
public class JwtAuthorizationFilter extends OncePerRequestFilter {
private final JwtUtil jwtUtil;
private final SecurityService securityService;
@Autowired
public JwtAuthorizationFilter(JwtUtil jwtUtil, SecurityService securityService) {
this.jwtUtil = jwtUtil;
this.securityService = securityService;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
if(checkAccessTokenValid(request)) {
filterChain.doFilter(request, response);
} else {
throw new RuntimeException("알 수 없는 오류가 발생했습니다.");
}
}
private boolean checkAccessTokenValid(HttpServletRequest request) {
String accessToken = jwtUtil.extractTokenFromHeader(request);
if(!jwtUtil.validateAccessToken(accessToken)) {
securityService.saveUserInSecurityContext(accessToken);
}
return true;
}
@Override
protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
String[] excludePath = {
"/api-docs/json",
"/api-docs",
"/api/u/v1/social-login",
"/swagger-ui/",
"/swagger-config",
"/swagger.yaml",
"/requestBodies",
"/swagger-",
"/error"
};
String path = request.getRequestURI();
System.out.println(path);
System.out.println(Arrays.toString(excludePath));
boolean shouldNotFilter = Arrays.stream(excludePath).anyMatch(path::startsWith);
return shouldNotFilter;
}
}
주의 해야할 부분은 accessToken이 검증되고 나면 saveUserInSecurityContext로직을 호출시켜 유저 정보를 db에서 불러와 SecurityContext에 넣어줘야한다는 것이다. 그 이유는 이 글에서 참고해 보면 될 것 같다.
public void saveUserInSecurityContext(String accessToken) {
String socialId = jwtUtil.extractClaim(accessToken, Claims::getSubject);
String socialProvider = jwtUtil.extractClaim(accessToken, Claims::getIssuer);
saveUserInSecurityContext(socialId, socialProvider);
}
private void saveUserInSecurityContext(String socialId, String socialProvider) {
UserDetails userDetails = loadUserBySocialIdAndSocialProvider(socialId, socialProvider);
Collection<? extends GrantedAuthority> authorities = userDetails.getAuthorities();
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, authorities);
if(authentication != null) {
SecurityContext context = SecurityContextHolder.createEmptyContext();
context.setAuthentication(authentication);
SecurityContextHolder.setContext(context);
}
}
아무튼 이렇게 코드를 작성해서 filter를 돌 때마다 accessToken을 검증하고 유저정보를 securityContext에 넣어주는 것까지 성공했다.
하지만 만약에 에러가 발생한다면? 특히 accessToken검증하는 부분에서 token이 만료될 경우에는 클라이언트에서 refreshToken을 보내주어야 한다. 이는 다음 글에서 알아보겠다.