거의 몇주에 걸쳐서 MSA 구조의 인증와 로그인 유지 작업을 진행했다.
사실 jwt filter 적용에 문제가 있었고,spring cloud gateway를 통한 클라이언트 요청 헤더에 token을 어떻게 처리할지에 고민이 많았다.
- 기본적으로 스프링 클라우드 게이트웨이를 사용하지 않고 요청 헤더로 넘어오는 토큰을 인증할 수 있는 방법은 없을까? 라는 고민을 했다.
- 1번을 충족시키기 위해서는 다른 서버에서 저장된 context를 공유해야했다.
- OnceperRequestFilter를 적용
addFilterAfter
or addFilterBefore
등..
- Cookie에 회원의 정보를 저장하는 방식 대신 회원용 UUID를 랜덤 발급해서 해당 값을 이용한 redis 접근, 데이터 이용을 진행
- 요청 헤더로 넘어오는 Authorization: Bearer은 어떻게 매번 유효한지 확인 작업을 진행할 예정인가?
import com.yaloostore.front.auth.jwt.meta.AuthInformation;
import com.yaloostore.front.common.utils.CookieUtils;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import static com.yalooStore.security_utils.util.AuthUtil.HEADER_UUID;
import static com.yalooStore.security_utils.util.AuthUtil.JWT;
@RequiredArgsConstructor
@Slf4j
public class CustomJwtAuthenticationFilter extends OncePerRequestFilter {
private final CookieUtils cookieUtils;
private final RedisTemplate redisTemplate;
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
String member = cookieUtils.getUuidFromCookie(request.getCookies(), HEADER_UUID.getValue());
if (Objects.isNull(member)){
filterChain.doFilter(request,response);
return;
}
if (!(SecurityContextHolder.getContext().getAuthentication() instanceof AnonymousAuthenticationToken)){
AuthInformation authInformation = (AuthInformation) redisTemplate.opsForHash().get(member, JWT.getValue());
log.info("auth loginId= ==== == {} ", authInformation.getLoginId());
Collection<? extends GrantedAuthority> authorities = getGrantAuthority(authInformation.getAuthorities());
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(authInformation.getLoginId(), authInformation.getAccessToken(),authorities);
SecurityContext context = SecurityContextHolder.getContext();
context.setAuthentication(authenticationToken);
filterChain.doFilter(request,response);
}
}
private Collection<? extends GrantedAuthority> getGrantAuthority(List<String> authorities) {
if (Objects.isNull(authorities)){
return null;
}
return authorities.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());
}
}
HEADER_UUID
라는 이름의 쿠키가 있다면 인증에 성공한 것이기 때문에 이를 가지고 redis에 저장한 값들을 가져와 작업을 진행한다.
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
//세션 대신 jwt 사용으로 해당 작업에서 사용하지 않음을 명세
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.addFilterAt(customLoginAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
http.addFilterAfter(customJwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
http.addFilterAfter(customJwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);