Skip to content

Commit

Permalink
[REFACTOR] JWT 처리 방식 변경 및 리팩토링 (#223)
Browse files Browse the repository at this point in the history
* feat: JwtService에 인터페이스 적용

- JwtService에 interface 적용 및 구현 클래스 적용
- Cookie의 sameSite 옵션을 strict로 설정

* refactor: JWT 요청 API의 Request DTO 변경

- /api/auth JWT 요청 API의 Request DTO를 의도에 맞도록 수정(TokenRequest)

* refactor: 필요없는 구문 삭제

* refactor: JwtService의 의존 관계 변경

-  JwtService의 의존관계를 TokenRepository에서 TokenService로 변경
- TokenService에 필요한 메서드 추가

* refactor: tokenService를 활용하는 코드로 리팩토링

- tokenRepository를 활용하는 코드에서 tokenService를 활용하는 코드로 변경

* refactor: Facade 패턴 적용

* feat: Access token 처리 방식 변경

- Access token의 저장 위치를 Authorization header로 변경
- Access token의 생성/resolve 방식 변경
- Access token의 정상 작동 확인
- 불필요한 로직 제거 필요
- JWT 테스트 코드 변경 필요

* refactor: enum 이름 변경

- JWT 관련 enum 상수 이름 변경

* test: JWT 처리 방식 변경에 의한 테스트 코드 변경

- JWT 중 Access token을 Header에 저장하는 것으로 바꾸면서 Controller 및 JWT 관련  테스트 코드 정상 작동하도록 수정

* fix: JWT 에러 메세지 변경 및 버그 픽스

- JWT 관련 에러 메세지를 세부적으로 나눔
- Refresh token의 탈취 여부를 확인하는 로직 버그 픽스
- Access token 추출 시, 빈 문자열일 때에도 예외를 발생하도록 변경

* test: JwtFacade 테스트 코드에 DCI 패턴 적용

* test: TokenService 테스트 코드에 DCI 패턴 적용

- TokenService 테스트 코드에 DCI 패턴 적용
- JWT 관련 예외 메세지 추가

* feat: JWT 처리 도중 Exception 발생 시, logout하도록 처리

- JWT에서 예외 발생 시 처리하는 ExceptionHandlerFilter에서 JWT 관련 Exception 발생 시, Logout 로직이 실행되도록 처리

* fix: logout 처리 위치 변경

* fix: custom header가 전달되지 않는 버그 픽스

* fix: 브라우저에서 Authorization 헤더 접근이 안되는 버그 픽스

- CorsConfigurationSource에 .setExposedHeaders 옵션 설정

* feat: Access token 재발급 여부 관련 헤더 추가

- Access token 재발급 여부에 따라 token-reissued에 값 설정

* fix: 브라우저에서 token-reissued 헤더 접근이 안되는 버그 픽스

* refactor: 불필요한 로직 제거

- ExceptionHandlerFilter에서 불필요한 로직 제거

* refactor: Facade 구현체 이름 통일

- Facade 구현체 이름을 FacadeImpl에서 FacadeService로 통일
  • Loading branch information
SSung023 authored Jul 25, 2024
1 parent eefe604 commit c36bd15
Show file tree
Hide file tree
Showing 28 changed files with 584 additions and 453 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,10 @@ public long updatePoints(Long amount) {
return this.point;
}

public boolean isRegistered() {
return this.role != Role.NOT_REGISTERED;
}

@Override
public Optional<Files> getFiles() {
return Optional.ofNullable(this.files);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package com.genius.gitget.global.security.config;

import static com.genius.gitget.global.security.constants.JwtRule.ACCESS_HEADER;
import static com.genius.gitget.global.security.constants.JwtRule.ACCESS_REISSUED_HEADER;

import jakarta.servlet.http.HttpServletRequest;
import java.util.Collections;
import java.util.List;
Expand All @@ -24,6 +27,10 @@ public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
config.setAllowedMethods(ALLOWED_METHODS);
config.setAllowCredentials(true);
config.setAllowedHeaders(Collections.singletonList("*"));

config.setExposedHeaders(Collections.singletonList(ACCESS_HEADER.getValue()));
config.addExposedHeader(ACCESS_REISSUED_HEADER.getValue());

config.setMaxAge(3600L);
return config;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import com.genius.gitget.global.security.handler.OAuth2FailureHandler;
import com.genius.gitget.global.security.handler.OAuth2SuccessHandler;
import com.genius.gitget.global.security.service.CustomOAuth2UserService;
import com.genius.gitget.global.security.service.JwtService;
import com.genius.gitget.global.security.service.JwtFacade;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
Expand All @@ -32,7 +32,7 @@ public class SecurityConfig {
private static final String PERMITTED_ROLES[] = {"USER", "ADMIN"};
private final CustomCorsConfigurationSource customCorsConfigurationSource;
private final CustomOAuth2UserService customOAuthService;
private final JwtService jwtService;
private final JwtFacade jwtFacade;
private final UserService userService;
private final OAuth2SuccessHandler successHandler;
private final OAuth2FailureHandler failureHandler;
Expand All @@ -57,7 +57,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
.sessionCreationPolicy(SessionCreationPolicy.STATELESS))

// JWT 검증 필터 추가
.addFilterBefore(new JwtAuthenticationFilter(jwtService, userService),
.addFilterBefore(new JwtAuthenticationFilter(jwtFacade, userService),
UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(new ExceptionHandlerFilter(), JwtAuthenticationFilter.class)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,15 @@
@RequiredArgsConstructor
@Getter
public enum JwtRule {
JWT_ISSUE_HEADER("Set-Cookie"),
JWT_RESOLVE_HEADER("Cookie"),
ACCESS_PREFIX("access"),
REFRESH_PREFIX("refresh");

ACCESS_HEADER("Authorization"),
ACCESS_PREFIX("Bearer "),
ACCESS_REISSUED_HEADER("token-reissued"),

REFRESH_PREFIX("refresh"),

REFRESH_ISSUE("Set-Cookie"),
REFRESH_RESOLVE("Cookie");

private final String value;
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
package com.genius.gitget.global.security.controller;

import static com.genius.gitget.global.util.exception.ErrorCode.NOT_AUTHENTICATED_USER;
import static com.genius.gitget.global.util.exception.SuccessCode.SUCCESS;

import com.genius.gitget.challenge.user.domain.User;
import com.genius.gitget.challenge.user.service.UserService;
import com.genius.gitget.global.security.domain.UserPrincipal;
import com.genius.gitget.global.security.dto.AuthResponse;
import com.genius.gitget.global.security.dto.SignupResponse;
import com.genius.gitget.global.security.service.JwtService;
import com.genius.gitget.global.security.dto.TokenRequest;
import com.genius.gitget.global.security.service.JwtFacade;
import com.genius.gitget.global.util.exception.BusinessException;
import com.genius.gitget.global.util.response.dto.CommonResponse;
import com.genius.gitget.global.util.response.dto.SingleResponse;
import jakarta.servlet.http.HttpServletResponse;
Expand All @@ -27,18 +29,20 @@
@RequestMapping("/api")
public class AuthController {
private final UserService userService;
private final JwtService jwtService;
private final JwtFacade jwtFacade;

@PostMapping("/auth")
public ResponseEntity<SingleResponse<AuthResponse>> generateToken(HttpServletResponse response,
@RequestBody SignupResponse tokenRequest) {
User requestUser = userService.findUserByIdentifier(tokenRequest.identifier());
jwtService.validateUser(requestUser);
@RequestBody TokenRequest tokenRequest) {
User user = userService.findUserByIdentifier(tokenRequest.identifier());
if (!user.isRegistered()) {
throw new BusinessException(NOT_AUTHENTICATED_USER);
}

jwtService.generateAccessToken(response, requestUser);
jwtService.generateRefreshToken(response, requestUser);
jwtFacade.generateAccessToken(response, user);
jwtFacade.generateRefreshToken(response, user);

AuthResponse authResponse = userService.getUserAuthInfo(requestUser.getIdentifier());
AuthResponse authResponse = userService.getUserAuthInfo(user.getIdentifier());

return ResponseEntity.ok().body(
new SingleResponse<>(SUCCESS.getStatus(), SUCCESS.getMessage(), authResponse)
Expand All @@ -49,7 +53,7 @@ public ResponseEntity<SingleResponse<AuthResponse>> generateToken(HttpServletRes
public ResponseEntity<CommonResponse> logout(
@AuthenticationPrincipal UserPrincipal userPrincipal,
HttpServletResponse response) {
jwtService.logout(userPrincipal.getUser(), response);
jwtFacade.logout(response, userPrincipal.getUser().getIdentifier());

return ResponseEntity.ok().body(
new CommonResponse(SUCCESS.getStatus(), SUCCESS.getMessage())
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.genius.gitget.global.security.dto;

public record TokenRequest(
String identifier
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,20 @@
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.filter.OncePerRequestFilter;

@Slf4j
@RequiredArgsConstructor
public class ExceptionHandlerFilter extends OncePerRequestFilter {

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
try {
filterChain.doFilter(request, response);
} catch (BusinessException e) {
log.error("[ERROR]" + e.getMessage(), e);
setErrorResponse(response, e);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@

import static com.genius.gitget.global.security.config.SecurityConfig.PERMITTED_URI;

import com.genius.gitget.global.security.constants.JwtRule;
import com.genius.gitget.global.security.service.JwtService;
import com.genius.gitget.challenge.user.domain.User;
import com.genius.gitget.challenge.user.service.UserService;
import com.genius.gitget.global.security.service.JwtFacade;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
Expand All @@ -19,7 +18,7 @@

@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtService jwtService;
private final JwtFacade jwtFacade;
private final UserService userService;

@Override
Expand All @@ -32,26 +31,27 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse
return;
}

String accessToken = jwtService.resolveTokenFromCookie(request, JwtRule.ACCESS_PREFIX);
if (jwtService.validateAccessToken(accessToken)) {
String accessToken = jwtFacade.resolveAccessToken(request);
if (jwtFacade.validateAccessToken(accessToken)) {
setAuthenticationToContext(accessToken);
filterChain.doFilter(request, response);
return;
}

String refreshToken = jwtService.resolveTokenFromCookie(request, JwtRule.REFRESH_PREFIX);
String refreshToken = jwtFacade.resolveRefreshToken(request);
User user = findUserByRefreshToken(refreshToken);

if (jwtService.validateRefreshToken(refreshToken, user.getIdentifier())) {
String reissuedAccessToken = jwtService.generateAccessToken(response, user);
jwtService.generateRefreshToken(response, user);
if (jwtFacade.validateRefreshToken(refreshToken, user.getIdentifier())) {
String reissuedAccessToken = jwtFacade.generateAccessToken(response, user);
jwtFacade.generateRefreshToken(response, user);
jwtFacade.setReissuedHeader(response);

setAuthenticationToContext(reissuedAccessToken);
filterChain.doFilter(request, response);
return;
}

jwtService.logout(user, response);
jwtFacade.logout(response, user.getIdentifier());
}

private boolean isPermittedURI(String requestURI) {
Expand All @@ -63,12 +63,12 @@ private boolean isPermittedURI(String requestURI) {
}

private User findUserByRefreshToken(String refreshToken) {
String identifier = jwtService.getIdentifierFromRefresh(refreshToken);
String identifier = jwtFacade.getIdentifierFromRefresh(refreshToken);
return userService.findUserByIdentifier(identifier);
}

private void setAuthenticationToContext(String accessToken) {
Authentication authentication = jwtService.getAuthentication(accessToken);
Authentication authentication = jwtFacade.getAuthentication(accessToken);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,13 @@

import static com.genius.gitget.global.util.exception.ErrorCode.MEMBER_NOT_FOUND;

import com.genius.gitget.global.security.domain.UserPrincipal;
import com.genius.gitget.challenge.user.domain.User;
import com.genius.gitget.challenge.user.repository.UserRepository;
import com.genius.gitget.global.security.domain.UserPrincipal;
import com.genius.gitget.global.util.exception.BusinessException;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

@Service
Expand All @@ -18,7 +17,7 @@ public class CustomUserDetailsService implements UserDetailsService {
private final UserRepository userRepository;

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
public UserDetails loadUserByUsername(String username) {
User user = userRepository.findById(Long.valueOf(username))
.orElseThrow(() -> new BusinessException(MEMBER_NOT_FOUND));

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.genius.gitget.global.security.service;

import com.genius.gitget.challenge.user.domain.User;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.core.Authentication;

public interface JwtFacade {
String generateAccessToken(HttpServletResponse response, User user);

String generateRefreshToken(HttpServletResponse response, User user);

String resolveAccessToken(HttpServletRequest request);

String resolveRefreshToken(HttpServletRequest request);

String getIdentifierFromRefresh(String refreshToken);

boolean validateAccessToken(String accessToken);

boolean validateRefreshToken(String refreshToken, String identifier);

void setReissuedHeader(HttpServletResponse response);

void logout(HttpServletResponse response, String identifier);

Authentication getAuthentication(String accessToken);
}
Loading

0 comments on commit c36bd15

Please sign in to comment.