
아
https://dev-dx2d2y-log.tistory.com/218
[백엔드] 로그인 시의 SecurityFilterChain의 동작과정
https://dev-dx2d2y-log.tistory.com/217 [백엔드] Spring Security 첫 걸음 - DelegatingFilterProxy, FilterChainProxy로 Web Context Filter에 Spring Bean Filter 등DelegatingFilterProxy그 전에 그냥 HTTP 요청이 어떻게 처리되냐면..클라
dev-dx2d2y-log.tistory.com
이번 겨울에 스프링 시큐리티를 공부하면서 몇 가지 연습개발도 해보고 개념도 익혔는데, 이걸 실제로 적용시켜보려니까 약간 막히는 부분이 있어서 최종적으로 정리를 하고 넘어가려한다. 레디스로 배워왔고, 기존에 구식으로 남아있던 JWT토큰 관련 지식들도 업데이트할 요령으로 글을 써본다.
로그인 / 로그아웃
https://dev-dx2d2y-log.tistory.com/218
[백엔드] 로그인 시의 SecurityFilterChain의 동작과정
https://dev-dx2d2y-log.tistory.com/217 [백엔드] Spring Security 첫 걸음 - DelegatingFilterProxy, FilterChainProxy로 Web Context Filter에 Spring Bean Filter 등DelegatingFilterProxy그 전에 그냥 HTTP 요청이 어떻게 처리되냐면..클라
dev-dx2d2y-log.tistory.com
원래는 스프링 기본 폼로그인을 사용한다면 UsernamePasswordAuthenticationFilter를 사용해도되지만, 사실 로그인 과정이라는 것이 레포지토리라던가, 아니면 다른 서비스계층이라던가하는, 필터 로직보다는 비즈니스 로직에 더 밀접하게 연관되어있다. 따라서 커스텀 로그인을 진행하려면 필터단에서 진행하는 것보다는 비즈니스 로직에서 진행하는 것이 더 좋다고.
이외에는 별거없다. 토큰을 발급하고, 저장하고.. 이런 용도
로그아웃도 마찬가지. 비즈니스 로직으로 로그아웃 요청을 받아서 레디스의 내용만 지우면 된다.
레디스를 통한 RTR
JWT토큰에 대해서도 내용을 덧붙여보자면, accessToken을 발급하고 refreshToken도 발급하는 등 기본 플로우는 바뀔게 없지만 refreshToken을 가지고 accessToken을 재발급하는 과정을 다뤄보자면..
0. 서버는 accessToken과 refreshToken을 발급하고, accessToken은 쿠키 또는 프론트엔드 메모리에, refreshToken은 쿠키와 레디스에 저장한다.
1. accessToken을 발급받고 서비스를 이용하던 사용자의 accessToken이 만료된다.
2. accessToken이 만료되면 서버는 401 에러를 반환한다.
3. 401 에러가 반환되면 클라이언트는 accessToken 재발급 요청을 보내게되고, 서버는 이 요청을 받아서 쿠키의 refreshToken을 확인 및 redis에 저장된 값과 비교한다.
4-1. 값이 일치하지 않으면 토큰이 탈취된 것으로 간주하고 토큰을 전부 제거한다.
4-2. 값이 일치하면 새 토큰을 발급하고, 기존토큰은 제거한다.
뭐 이런 형식으로 이어진다..
@Transactional(readOnly = true)
public TokenReissueResponseDTO reIssueToken(String refreshToken){
String email = tokenProvider.getEmailFromToken(refreshToken);
String refreshTokenInRedis = redisTemplate.opsForValue().get(
"refreshToken:" + email
);
if (refreshTokenInRedis == null){
throw new CustomException(ErrorCode.REFRESH_TOKEN_NOT_FOUND);
}
if (!refreshToken.equals(refreshTokenInRedis)){
redisTemplate.delete("refreshToken:"+email);
throw new CustomException(ErrorCode.REFRESH_TOKEN_REUSED);
}
User user = userFinder.findByEmail(email);
UserDetails userDetails = CustomUserDetails.of(user);
Authentication authentication = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities()
);
String accessToken = tokenProvider.createToken(email, authentication, JWTType.ACCESS);
String newRefreshToken = tokenProvider.createToken(email, authentication, JWTType.REFRESH);
redisTemplate.opsForValue().set(
"refreshToken:" + user.getEmail(),
newRefreshToken,
JWTType.REFRESH.getValidTime(),
TimeUnit.SECONDS
);
return new TokenReissueResponseDTO(accessToken, newRefreshToken);
}
예전에는 토큰을 DB에서 관리했기 때문에 TTL 설정이 다소 복잡한 감이 있었는데 레디스를 사용함으로써 TTL 관리가 더 쉬워짐에 따라 더 깔끔한 RTR이 가능해졌다. accessToken은 인메모리 또는 쿠키에, refreshToken은 쿠키와 레디스에 저장해 RTR 때마다 검증하는 방법으로 갈 수 있겠다. 쿠키에 저장할 때에는 Secure, HttpOnly, SameSite 설정을 사용해 보안취약점을 방지해야한다.
사실 새로 배운 내용은 없긴한데.. 레디스를 처음 써보기도했고, 지디지 플젝트랙 이후로 RTR도 오랜만에 구현해보는거라 적어보았다.
인증필터
https://dev-dx2d2y-log.tistory.com/219
[골든리포트!] 7) JWT토큰 인증과정을 Spring Security 필터체인에 태워보기
저번에 인증과정에서 SecurityFilterChain을 어느정도 알아보았다. 이번에는 저번에 만들어본 JWTAuthorizationFilter를 수정해보도록한다. https://dev-dx2d2y-log.tistory.com/216 [골든리포트!] 6) 필터체인에서 JWT필
dev-dx2d2y-log.tistory.com
예전에 스프링 시큐리티를 배울 때에는 AuthenticationFilter, AuthenticationManager, AuthenticationProvider 등등 다양한 객체들이 인증과정에 참여했지만, JWT토큰을 사용하면 STATELESS하기도하고, 생각보다 관리할 것도 적어서 그냥 AuthenticationFilter만 적용시키면 된다.
사실 필터 내용도 별 다른 차이점은 크게 없는데, 그냥 책임을 조금 조정한 것이다. 요청에서 토큰을 가져오는 것과, 토큰의 식별자를 통해 Authentication 객체를 만드는 것을 기존에는 필터의 책임이었다면 JWT토큰을 관리하는 객체의 책임으로 넘어갔다 정도?
@Override
protected void doFilterInternal(HttpServletRequest req,
HttpServletResponse res,
FilterChain filterChain) throws ServletException, IOException {
try {
String accessToken = tokenProvider.getTokenFromHeader(req);
if (!StringUtils.hasText(accessToken)) {
filterChain.doFilter(req, res);
return;
}
Authentication authentication = tokenProvider.getAuthentication(accessToken);
SecurityContextHolder.getContext().setAuthentication(authentication);
filterChain.doFilter(req, res);
} catch (CustomException ce){
ce.printStackTrace();
throw new CustomAuthenticationException(ce);
}
}
그래서 인증필터의 로직도 깔끔해졌다.
tokenProvider 객체가 요청에서 토큰 가져오기, Authentcation 객체 생성하기 등의 책임을 담당하게 되었다. 다만 그런만큼 tokenProvider 객체의 책임이 좀 늘어났는데, 그 책임이 원래 tokenProvider의 책임이기도하고ㅇㅇ..
암튼 그래서
1. 로그인/로그아웃은 필터단 말고 서비스단에서 처리하자
2. 레디스를 통해 더 간편한 RTR이 가능해졌다.
정도가 되겠다.
스프링 시큐리티.. 배울 때 정말 재밌게 배웠는데 오래 안쓰니까 까먹기도하고, 이걸 내 개인프로젝트가 아니라 실전 개발에 적용시켜본 것도 처음이기도하고, 암튼 그렇다. 세오스 스터디에서도 배웠는데 몇 개 또 까먹은 것 같기도하고.. 그리고 사실 스프링 시큐리티를 처음 배울 때 했던 프로젝트는 폼 로그인 없이 그냥 OAuth2에게 로그인을 맡겼던 것이기 때문에.. 뭔가 좀 다르기도했다.
그래도 스프링 시큐리티에 대해서는 확실하게 더 배울 수 있었다. 잘 쓰이진 않지만 AuthenticationFilter 이후의 필터들 (AnonymousAuthenticatilFilter, RememberMeAuthenticationFilter 등...)에 대한 지식도 조금 가지고 있으니, 이것들과 섞으면 또 다른 동아리나, 스터디를 진행할 때 하나의 장점으로 세울 수 있을 것 같다.
기초적인 로그인 및 인증인가기능은 그래도 좀 터득했다고 할 수 있으려나. 작년에 처음 로그인 로직 다루고 거의 1년 만에 여기까지 왔는데, 확실히 어렵다. 지디지 플젝트랙 때 코멤 분께서 로그인 어렵다고 경고 아닌 경고를하셨는데, 그 말이 맞습니다..
'CS > 백엔드' 카테고리의 다른 글
| [백엔드] 락을 통해 동시성 해결하기 - 모니터락, DB락, 분산락(레디스) (0) | 2026.04.21 |
|---|---|
| [백엔드] 스프링 컴포넌트 스캔과 Bean 등록하기 및 DI (0) | 2026.03.16 |
| [백엔드] 인증요청 시의 SecurityFilterChain의 동작과정 (0) | 2026.02.18 |
| [백엔드] 로그인 시의 SecurityFilterChain의 동작과정 (0) | 2026.02.16 |
| [백엔드] Spring Security 첫 걸음 - DelegatingFilterProxy, FilterChainProxy로 Web Context Filter에 Spring Bean Filter 등록하기 (0) | 2026.02.14 |
