SecutiryContextHolder
SecurityContext
Authentication
GrantAuthority
//login service로직 일부
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(dto.getEmail(), dto.getPassword());
Authentication authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken);
String authorities = authentication.getAuthorities()
.stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.joining(","));
여기서 authenticationManager는 실제 인증 로직이 담긴 AuthenticationProvider를 관리하며, authenticate()메소드를 통해 아직 인증되지 않은 Authentication객체로부터 인증완료된 Authentication을 반환한다.
OncePerRequestFilter vs GenericFilterBean
인증/인가를 구현하기 위해 여러 레포를 참고하였는데, 대체로 filer클래스가OncePerRequestFilter를 상속받는 경우와 GenericFilterBean을 상속받는 경우로 나뉘었다.
둘 다 기본적인 인증 필터 기능에는 문제가 없지만 어떤 차이가 있는지 찾아봤다.
스프링에서, 디스패처 서블릿이 서블릿 컨테이너 앞에서 모든 요청을 컨트롤러에 전달한다.
서블릿은 요청마다 서블릿을 생성하여 메모리에 저장한 뒤 같은 클라이언트의 요청이 들어올 경우 생성해둔 서블릿 객체를 재활용한다.
그런데 만약 서블릿이 다른 서블릿으로 dispatch하게 되면, 다른 서블릿 앞단에서 filter chain을 한번 더 거치게 된다.
이 차이때문에 OncePerRequestFilter를 사용한다.
쉽게 말해, 내부적으로 프로젝트의 다른 API에 요청할 때마다 모든 API가 동일한 보안 필터를 갖기 때문에 동일한 인증이 다시 발생하게 된다.
이를 막기 위해서 OncePerRequest를 상속받아 AuthenticationFilter를 구현하는 것이 더 나은 선택!
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {
public static final String AUTHORIZATION_HEADER = "Authorization";
public int tokenPrefixLength = TokenProvider.TOKEN_PREFIX.length();
private final TokenProvider tokenProvider;
private String resolveToken(HttpServletRequest request) {
String bearerToken = request.getHeader(AUTHORIZATION_HEADER);
if (bearerToken != null && bearerToken.startsWith(TokenProvider.TOKEN_PREFIX)) {
return bearerToken.substring(tokenPrefixLength);
}
return null;
}
// 인증/인가 filter처리 전 토큰을 통해 인증 정보 저장
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String jwt = resolveToken(request);
if (StringUtils.hasText(jwt)) {
Authentication authentication = tokenProvider.getAuthentication(jwt);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
filterChain.doFilter(request, response);
}
}
OnePerRequestFilter도 GenericFilterBean을 상속받은 클래스인데, 어떤 부분이 해당 차이를 결정짓는지 코드로 확인해보자.
@Override
public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
if (!((request instanceof HttpServletRequest httpRequest) && (response instanceof HttpServletResponse httpResponse))) {
throw new ServletException("OncePerRequestFilter only supports HTTP requests");
}
String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName();
boolean hasAlreadyFilteredAttribute = request.getAttribute(alreadyFilteredAttributeName) != null;
if (skipDispatch(httpRequest) || shouldNotFilter(httpRequest)) {
// Proceed without invoking this filter...
filterChain.doFilter(request, response);
}
else if (hasAlreadyFilteredAttribute) {
if (DispatcherType.ERROR.equals(request.getDispatcherType())) {
doFilterNestedErrorDispatch(httpRequest, httpResponse, filterChain);
return;
}
// Proceed without invoking this filter...
filterChain.doFilter(request, response);
}
else {
// Do invoke this filter...
request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);
try {
doFilterInternal(httpRequest, httpResponse, filterChain);
}
finally {
// Remove the "already filtered" request attribute for this request.
request.removeAttribute(alreadyFilteredAttributeName);
}
}
}
alreadyFilteredAttributeName 속성을 통해 요청이 이미 필터링되었는지 확인하고, 이미 필터링되었으면 다시 실행하지 않음으로서 요청 당 한 번만 실행을 보장한다.
Spring Security 설정 후 Swagger가 막힌다?
.requestMatchers(
"/api/v1/auth/**",
"/swagger-ui/**",
"/swagger-resources/**",
"/v3/api-docs/**"
).permitAll()
swagger관련 엔드포인트를 permitAll해줌으로서 인증 필터에서 막히지 않도록 설정해주기
오타가 많아요ㅠ