Skip to content

Commit

Permalink
[#3] chore: JWT 관련 필터 및 오류 제어 설정
Browse files Browse the repository at this point in the history
  • Loading branch information
dl-00-e8 committed Jan 28, 2024
1 parent 61c2137 commit ad024a5
Show file tree
Hide file tree
Showing 7 changed files with 115 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import com.gongjakso.server.global.security.kakao.KakaoClient;
import com.gongjakso.server.global.security.kakao.dto.KakaoProfile;
import com.gongjakso.server.global.security.kakao.dto.KakaoToken;
import com.gongjakso.server.global.util.redis.RedisClient;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
Expand All @@ -19,6 +20,7 @@
public class OauthService {

private final KakaoClient kakaoClient;
private final RedisClient redisClient;
private final TokenProvider tokenProvider;
private final MemberRepository memberRepository;

Expand Down Expand Up @@ -48,6 +50,10 @@ public LoginRes signIn(String code) {

TokenDto tokenDto = tokenProvider.createToken(member);

// Redis에 RefreshToken 저장
// TODO: timeout 관련되어 constant가 아닌 tokenProvider 내의 메소드로 관리할 수 있도록 수정 필요
redisClient.setValue(member.getEmail(), tokenDto.refreshToken(), 30 * 24 * 60 * 60 * 1000L);

// Response
return LoginRes.of(member, tokenDto);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package com.gongjakso.server.global.config;

import com.gongjakso.server.global.security.jwt.JwtAccessDeniedHandler;
import com.gongjakso.server.global.security.jwt.JwtAuthenticationEntryPoint;
import com.gongjakso.server.global.security.jwt.JwtFilter;
import com.gongjakso.server.global.security.jwt.TokenProvider;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
Expand All @@ -11,6 +15,7 @@
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
Expand All @@ -22,6 +27,10 @@
@RequiredArgsConstructor
public class SecurityConfig {

private final TokenProvider tokenProvider;
private final JwtAccessDeniedHandler jwtAccessDeniedHandler;
private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;

/**
* FilterChain 설정
* @param http - 시큐리티 설정을 담당하는 객체
Expand All @@ -41,6 +50,14 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.httpBasic(AbstractHttpConfigurer::disable)
.formLogin(AbstractHttpConfigurer::disable);

// JWT 관련 필터 설정 및 예외 처리
http.exceptionHandling((exceptionHandling) ->
exceptionHandling
.accessDeniedHandler(jwtAccessDeniedHandler)
.authenticationEntryPoint(jwtAuthenticationEntryPoint)
);
http.addFilterBefore(new JwtFilter(tokenProvider), UsernamePasswordAuthenticationFilter.class);

// 요청 URI별 권한 설정
http.authorizeHttpRequests((authorize) ->
// Swagger UI 외부 접속 허용
Expand All @@ -51,6 +68,9 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// 이외의 모든 요청은 인증 정보 필요
.anyRequest().permitAll());

// JWT 관련 환경 설정


return http.build();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ public enum ErrorCode {
INVALID_VALUE_EXCEPTION(HttpStatus.BAD_REQUEST, 2002, "올바르지 않은 요청 값입니다."),
UNAUTHORIZED_EXCEPTION(HttpStatus.UNAUTHORIZED, 2003, "권한이 없는 요청입니다."),
ALREADY_DELETE_EXCEPTION(HttpStatus.BAD_REQUEST, 2004, "이미 삭제된 리소스입니다."),
FORBIDDEN_EXCEPTION(HttpStatus.FORBIDDEN, 2005, "인가되지 않는 요청입니다."),


// 3000: Auth Error
KAKAO_TOKEN_EXCEPTION(HttpStatus.INTERNAL_SERVER_ERROR, 3000, "토큰 발급에서 오류가 발생했습니다."),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.gongjakso.server.global.security.jwt;

import jakarta.servlet.ServletException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.gongjakso.server.global.exception.ErrorCode;
import com.gongjakso.server.global.exception.ErrorResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.access.AccessDeniedException;
Expand All @@ -11,12 +13,24 @@

@Component
public class JwtAccessDeniedHandler implements AccessDeniedHandler {
// 인가 실패 관련 403 핸들링
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
accessDeniedException.getCause().printStackTrace();

public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException {
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(accessDeniedException.getMessage());
response.setStatus(HttpServletResponse.SC_FORBIDDEN);

setResponse(response);
}

// Error 관련 응답 Response 생성 메소드
private void setResponse(HttpServletResponse response) throws IOException{
response.setContentType("application/json;charset=UTF-8");
response.setStatus(ErrorCode.FORBIDDEN_EXCEPTION.getHttpStatus().value());

ErrorResponse errorResponse = new ErrorResponse(ErrorCode.FORBIDDEN_EXCEPTION);
ObjectMapper objectMapper = new ObjectMapper();
String errorJson = objectMapper.writeValueAsString(errorResponse);

response.getWriter().write(errorJson);
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package com.gongjakso.server.global.security.jwt;

import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.ServletException;
import com.gongjakso.server.global.exception.ErrorCode;
import com.gongjakso.server.global.exception.ErrorResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.core.AuthenticationException;
Expand All @@ -15,28 +16,29 @@ public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {

// 인증 관련 에러 처리, 401
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {

Object exception = request.getAttribute("exception");

// if (exception instanceof ErrorCode) {
// ErrorCode errorCode = (ErrorCode) exception;
// setResponse(response,errorCode);
//
// return;
// }
// exception에 할당된 속성이 ErrorCode일 경우, 관련된 응답 객체 정보를 삽입하도록 설정
if (exception instanceof ErrorCode) {
setResponse(response, (ErrorCode) exception);

return;
}

response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authException.getMessage());
}

// private void setResponse(HttpServletResponse response, ErrorCode errorCode) throws IOException{
// response.setContentType("application/json;charset=UTF-8");
// response.setStatus(errorCode.getHttpStatus().value());
//
// ApplicationErrorResponse errorResponse = new ApplicationErrorResponse(errorCode);
// ObjectMapper objectMapper = new ObjectMapper();
// String errorJson = objectMapper.writeValueAsString(errorResponse);
//
// response.getWriter().write(errorJson);
// }
// Error 관련 응답 Response 생성 메소드
private void setResponse(HttpServletResponse response, ErrorCode errorCode) throws IOException{
response.setContentType("application/json;charset=UTF-8");
response.setStatus(errorCode.getHttpStatus().value());

ErrorResponse errorResponse = new ErrorResponse(errorCode);
ObjectMapper objectMapper = new ObjectMapper();
String errorJson = objectMapper.writeValueAsString(errorResponse);

response.getWriter().write(errorJson);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.gongjakso.server.global.security.jwt;

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.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;

@Slf4j
@RequiredArgsConstructor
public class JwtFilter extends OncePerRequestFilter {

private final TokenProvider tokenProvider;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String token = resolveToken(request);
String requestURI = request.getRequestURI();

// 토큰이 존재할 경우, Authentication에 인증 정보 저장 및 로그 출력
if (StringUtils.hasText(token) && tokenProvider.validateToken(token)) {
Authentication authentication = tokenProvider.getAuthentication(token);
SecurityContextHolder.getContext().setAuthentication(authentication);
// log.info("Security Context 인증 정보 저장: " + authentication.getEmail(), requestURI);
}

filterChain.doFilter(request, response);
}

// Request Header에서 토큰 조회 및 Bearer 문자열 제거 후 반환하는 메소드
private String resolveToken(HttpServletRequest request) {
String token = request.getHeader("Authorization");

// Token 정보가 존재할 경우 Bearer 문자열 제거
if (StringUtils.hasText(token) && token.startsWith("Bearer ")) {
return token.substring(7);
}

return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public class TokenProvider {
private final RedisTemplate<String, Object> redisTemplate;

// ATK 만료시간: 1일
private static final long accessTokenExpirationTime = 24 * 60 * 60 * 1000L;
private static final long accessTokenExpirationTime = 7 * 24 * 60 * 60 * 1000L;

// RTK 만료시간: 30일
private static final long refreshTokenExpirationTime = 30 * 24 * 60 * 60 * 1000L;
Expand Down

0 comments on commit ad024a5

Please sign in to comment.