[보안 설정 개선] JwtTokenIssuanceFilter 및 JwtTokenValidationFilter 추가로 JWT 인증 로직을 강화하고, SecurityPathConfig를 통해 허용 경로 설정을 외부화하여 보안 유연성 향상. JwtAccessDeniedHandler 및 JwtAuthenticationEntryPoint 삭제로 코드 정리.
This commit is contained in:
		@@ -2,10 +2,12 @@ package com.bio.bio_backend;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import org.springframework.boot.SpringApplication;
 | 
					import org.springframework.boot.SpringApplication;
 | 
				
			||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
 | 
					import org.springframework.boot.autoconfigure.SpringBootApplication;
 | 
				
			||||||
 | 
					import org.springframework.boot.context.properties.EnableConfigurationProperties;
 | 
				
			||||||
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
 | 
					import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@SpringBootApplication
 | 
					@SpringBootApplication
 | 
				
			||||||
@EnableJpaAuditing
 | 
					@EnableJpaAuditing
 | 
				
			||||||
 | 
					@EnableConfigurationProperties
 | 
				
			||||||
public class BioBackendApplication {
 | 
					public class BioBackendApplication {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public static void main(String[] args) {
 | 
						public static void main(String[] args) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,45 @@
 | 
				
			|||||||
 | 
					package com.bio.bio_backend.global.config;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import org.springframework.boot.context.properties.ConfigurationProperties;
 | 
				
			||||||
 | 
					import org.springframework.stereotype.Component;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.util.List;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Component
 | 
				
			||||||
 | 
					@ConfigurationProperties(prefix = "security")
 | 
				
			||||||
 | 
					public class SecurityPathConfig {
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    private List<String> permitAllPaths;
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    public List<String> getPermitAllPaths() {
 | 
				
			||||||
 | 
					        return permitAllPaths;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    public void setPermitAllPaths(List<String> permitAllPaths) {
 | 
				
			||||||
 | 
					        this.permitAllPaths = permitAllPaths;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 주어진 경로가 허용된 경로인지 확인
 | 
				
			||||||
 | 
					     * @param path 확인할 경로
 | 
				
			||||||
 | 
					     * @param contextPath 컨텍스트 경로
 | 
				
			||||||
 | 
					     * @return 허용 여부
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public boolean isPermittedPath(String path, String contextPath) {
 | 
				
			||||||
 | 
					        if (permitAllPaths == null) {
 | 
				
			||||||
 | 
					            return false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        return permitAllPaths.stream()
 | 
				
			||||||
 | 
					                .anyMatch(permittedPath -> {
 | 
				
			||||||
 | 
					                    String fullPath = contextPath + permittedPath;
 | 
				
			||||||
 | 
					                    if (permittedPath.endsWith("/**")) {
 | 
				
			||||||
 | 
					                        // /** 패턴 처리
 | 
				
			||||||
 | 
					                        String basePath = fullPath.substring(0, fullPath.length() - 2);
 | 
				
			||||||
 | 
					                        return path.startsWith(basePath);
 | 
				
			||||||
 | 
					                    } else {
 | 
				
			||||||
 | 
					                        return path.equals(fullPath);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,41 +0,0 @@
 | 
				
			|||||||
package com.bio.bio_backend.global.exception;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import java.io.IOException;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import org.springframework.beans.factory.annotation.Qualifier;
 | 
					 | 
				
			||||||
import org.springframework.http.MediaType;
 | 
					 | 
				
			||||||
import org.springframework.security.access.AccessDeniedException;
 | 
					 | 
				
			||||||
import org.springframework.security.web.access.AccessDeniedHandler;
 | 
					 | 
				
			||||||
import org.springframework.stereotype.Component;
 | 
					 | 
				
			||||||
import org.springframework.web.servlet.HandlerExceptionResolver;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import com.fasterxml.jackson.databind.ObjectMapper;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import jakarta.servlet.ServletException;
 | 
					 | 
				
			||||||
import jakarta.servlet.http.HttpServletRequest;
 | 
					 | 
				
			||||||
import jakarta.servlet.http.HttpServletResponse;
 | 
					 | 
				
			||||||
import com.bio.bio_backend.global.dto.ApiResponseDto;
 | 
					 | 
				
			||||||
import com.bio.bio_backend.global.constants.ApiResponseCode;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@Component
 | 
					 | 
				
			||||||
public class JwtAccessDeniedHandler implements AccessDeniedHandler {
 | 
					 | 
				
			||||||
    private final HandlerExceptionResolver resolver;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public JwtAccessDeniedHandler(@Qualifier("handlerExceptionResolver") HandlerExceptionResolver resolver) {
 | 
					 | 
				
			||||||
        this.resolver = resolver;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        Exception exception = (Exception) request.getAttribute("exception");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (exception != null) {
 | 
					 | 
				
			||||||
            resolver.resolveException(request, response, null, exception);
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            response.setStatus(HttpServletResponse.SC_FORBIDDEN);
 | 
					 | 
				
			||||||
            response.setContentType(MediaType.APPLICATION_JSON_VALUE);
 | 
					 | 
				
			||||||
            new ObjectMapper().writeValue(response.getWriter(), ApiResponseDto.fail(ApiResponseCode.COMMON_FORBIDDEN));
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,35 +0,0 @@
 | 
				
			|||||||
package com.bio.bio_backend.global.exception;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import jakarta.servlet.ServletException;
 | 
					 | 
				
			||||||
import jakarta.servlet.http.HttpServletRequest;
 | 
					 | 
				
			||||||
import jakarta.servlet.http.HttpServletResponse;
 | 
					 | 
				
			||||||
import lombok.extern.slf4j.Slf4j;
 | 
					 | 
				
			||||||
import org.springframework.beans.factory.annotation.Qualifier;
 | 
					 | 
				
			||||||
import org.springframework.security.core.AuthenticationException;
 | 
					 | 
				
			||||||
import org.springframework.security.web.AuthenticationEntryPoint;
 | 
					 | 
				
			||||||
import org.springframework.stereotype.Component;
 | 
					 | 
				
			||||||
import org.springframework.web.servlet.HandlerExceptionResolver;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import java.io.IOException;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@Component
 | 
					 | 
				
			||||||
@Slf4j
 | 
					 | 
				
			||||||
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
 | 
					 | 
				
			||||||
    private final HandlerExceptionResolver resolver;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public JwtAuthenticationEntryPoint(@Qualifier("handlerExceptionResolver") HandlerExceptionResolver resolver) {
 | 
					 | 
				
			||||||
        this.resolver = resolver;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        Exception exception = (Exception) request.getAttribute("exception");
 | 
					 | 
				
			||||||
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (exception == null) {
 | 
					 | 
				
			||||||
            return;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        resolver.resolveException(request, response, null, exception);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
package com.bio.bio_backend.global.security;
 | 
					package com.bio.bio_backend.global.filter;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import com.fasterxml.jackson.databind.ObjectMapper;
 | 
					import com.fasterxml.jackson.databind.ObjectMapper;
 | 
				
			||||||
import jakarta.servlet.FilterChain;
 | 
					import jakarta.servlet.FilterChain;
 | 
				
			||||||
@@ -9,13 +9,11 @@ import com.bio.bio_backend.global.dto.ApiResponseDto;
 | 
				
			|||||||
import com.bio.bio_backend.domain.base.member.dto.LoginRequestDto;
 | 
					import com.bio.bio_backend.domain.base.member.dto.LoginRequestDto;
 | 
				
			||||||
import com.bio.bio_backend.domain.base.member.dto.LoginResponseDto;
 | 
					import com.bio.bio_backend.domain.base.member.dto.LoginResponseDto;
 | 
				
			||||||
import com.bio.bio_backend.domain.base.member.dto.MemberDto;
 | 
					import com.bio.bio_backend.domain.base.member.dto.MemberDto;
 | 
				
			||||||
import com.bio.bio_backend.domain.base.member.service.MemberService;
 | 
					 | 
				
			||||||
import com.bio.bio_backend.global.constants.ApiResponseCode;
 | 
					import com.bio.bio_backend.global.constants.ApiResponseCode;
 | 
				
			||||||
import com.bio.bio_backend.global.utils.JwtUtils;
 | 
					import com.bio.bio_backend.global.utils.JwtUtils;
 | 
				
			||||||
import lombok.RequiredArgsConstructor;
 | 
					import lombok.RequiredArgsConstructor;
 | 
				
			||||||
import lombok.SneakyThrows;
 | 
					import lombok.SneakyThrows;
 | 
				
			||||||
import lombok.extern.slf4j.Slf4j;
 | 
					import lombok.extern.slf4j.Slf4j;
 | 
				
			||||||
import org.springframework.core.env.Environment;
 | 
					 | 
				
			||||||
import org.springframework.http.HttpStatus;
 | 
					import org.springframework.http.HttpStatus;
 | 
				
			||||||
import org.springframework.http.MediaType;
 | 
					import org.springframework.http.MediaType;
 | 
				
			||||||
import org.springframework.security.authentication.AuthenticationManager;
 | 
					import org.springframework.security.authentication.AuthenticationManager;
 | 
				
			||||||
@@ -30,36 +28,22 @@ import org.springframework.security.web.authentication.UsernamePasswordAuthentic
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import java.io.IOException;
 | 
					import java.io.IOException;
 | 
				
			||||||
import java.time.LocalDateTime;
 | 
					import java.time.LocalDateTime;
 | 
				
			||||||
import java.util.Objects;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
@RequiredArgsConstructor
 | 
					@RequiredArgsConstructor
 | 
				
			||||||
@Slf4j
 | 
					@Slf4j
 | 
				
			||||||
public class JwtTokenIssuanceFilter extends UsernamePasswordAuthenticationFilter {
 | 
					public class JwtTokenIssuanceFilter extends UsernamePasswordAuthenticationFilter {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private final AuthenticationManager authenticationManager;
 | 
					    private final AuthenticationManager authenticationManager;
 | 
				
			||||||
    private final MemberService memberService;
 | 
					 | 
				
			||||||
    private final JwtUtils jwtUtils;
 | 
					    private final JwtUtils jwtUtils;
 | 
				
			||||||
    private final Environment env;
 | 
					 | 
				
			||||||
    private final ObjectMapper objectMapper;
 | 
					    private final ObjectMapper objectMapper;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // 사용자 login 인증 처리
 | 
					    // 사용자 login 인증 처리
 | 
				
			||||||
    @SneakyThrows
 | 
					    @SneakyThrows
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
 | 
					    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
 | 
				
			||||||
            throws AuthenticationException {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        LoginRequestDto requestDto = new ObjectMapper().readValue(request.getInputStream(), LoginRequestDto.class);
 | 
					        LoginRequestDto requestDto = new ObjectMapper().readValue(request.getInputStream(), LoginRequestDto.class);
 | 
				
			||||||
 | 
					 | 
				
			||||||
//        UsernamePasswordAuthenticationToken authToken;
 | 
					 | 
				
			||||||
        UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(requestDto.getUserId(), requestDto.getPassword());
 | 
					        UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(requestDto.getUserId(), requestDto.getPassword());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /*
 | 
					 | 
				
			||||||
        if (req.getLoginLogFlag() == 1) {
 | 
					 | 
				
			||||||
            authToken = new UsernamePasswordAuthenticationToken("admin2", "test123!"); // 비밀번호는 실제 비밀번호로 설정해야 함
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            throw new AuthenticationServiceException("login fail");
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
         */
 | 
					 | 
				
			||||||
        return authenticationManager.authenticate(authToken);
 | 
					        return authenticationManager.authenticate(authToken);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -69,23 +53,22 @@ public class JwtTokenIssuanceFilter extends UsernamePasswordAuthenticationFilter
 | 
				
			|||||||
                                            FilterChain chain, Authentication authResult) throws IOException, ServletException {
 | 
					                                            FilterChain chain, Authentication authResult) throws IOException, ServletException {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        UserDetails userDetails = (UserDetails) authResult.getPrincipal();
 | 
					        UserDetails userDetails = (UserDetails) authResult.getPrincipal();
 | 
				
			||||||
 | 
					 | 
				
			||||||
        MemberDto member = (MemberDto) userDetails;
 | 
					        MemberDto member = (MemberDto) userDetails;
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        String accessToken = jwtUtils.generateToken(member.getUserId(), member.getRole().getValue(),
 | 
					        // 토큰 생성
 | 
				
			||||||
                Long.parseLong(Objects.requireNonNull(env.getProperty("token.expiration_time_access"))));
 | 
					        String accessToken = jwtUtils.createAccessToken(member.getUserId(), member.getRole().getValue());
 | 
				
			||||||
 | 
					        String refreshToken = jwtUtils.createRefreshToken(member.getUserId(), member.getRole().getValue());
 | 
				
			||||||
        // 모듈화된 메서드 사용
 | 
					 | 
				
			||||||
        String refreshToken = jwtUtils.createAndSaveRefreshToken(member.getUserId(), member.getRole().getValue());
 | 
					 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
 | 
					        // Refresh 토큰을 DB에 저장
 | 
				
			||||||
 | 
					        jwtUtils.saveRefreshToken(member.getUserId(), refreshToken);
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        member.setRefreshToken(refreshToken);
 | 
					        member.setRefreshToken(refreshToken);
 | 
				
			||||||
        member.setLastLoginAt(LocalDateTime.now());
 | 
					        member.setLastLoginAt(LocalDateTime.now());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Refresh 토큰 쿠키 저장 - 모듈화된 메서드 사용
 | 
					        // Refresh 토큰 쿠키 저장
 | 
				
			||||||
        jwtUtils.setRefreshTokenCookie(response, refreshToken);
 | 
					        jwtUtils.setRefreshTokenCookie(response, refreshToken);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // JWT 토큰 전달
 | 
					        // Access 토큰 전달
 | 
				
			||||||
        response.setHeader("Authorization", "Bearer " + accessToken);
 | 
					        response.setHeader("Authorization", "Bearer " + accessToken);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        SecurityContextHolderStrategy contextHolder = SecurityContextHolder.getContextHolderStrategy();
 | 
					        SecurityContextHolderStrategy contextHolder = SecurityContextHolder.getContextHolderStrategy();
 | 
				
			||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
package com.bio.bio_backend.global.security;
 | 
					package com.bio.bio_backend.global.filter;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import java.io.IOException;
 | 
					import java.io.IOException;
 | 
				
			||||||
import java.util.Objects;
 | 
					import java.util.Objects;
 | 
				
			||||||
@@ -21,6 +21,7 @@ import com.bio.bio_backend.global.dto.ApiResponseDto;
 | 
				
			|||||||
import com.bio.bio_backend.domain.base.member.service.MemberService;
 | 
					import com.bio.bio_backend.domain.base.member.service.MemberService;
 | 
				
			||||||
import com.bio.bio_backend.global.constants.ApiResponseCode;
 | 
					import com.bio.bio_backend.global.constants.ApiResponseCode;
 | 
				
			||||||
import com.bio.bio_backend.global.utils.JwtUtils;
 | 
					import com.bio.bio_backend.global.utils.JwtUtils;
 | 
				
			||||||
 | 
					import com.bio.bio_backend.global.config.SecurityPathConfig;
 | 
				
			||||||
import lombok.RequiredArgsConstructor;
 | 
					import lombok.RequiredArgsConstructor;
 | 
				
			||||||
import lombok.extern.slf4j.Slf4j;
 | 
					import lombok.extern.slf4j.Slf4j;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -31,16 +32,14 @@ public class JwtTokenValidationFilter extends OncePerRequestFilter {
 | 
				
			|||||||
    private final JwtUtils jwtUtils;
 | 
					    private final JwtUtils jwtUtils;
 | 
				
			||||||
    private final MemberService memberService;
 | 
					    private final MemberService memberService;
 | 
				
			||||||
    private final Environment env;
 | 
					    private final Environment env;
 | 
				
			||||||
 | 
					    private final SecurityPathConfig securityPathConfig;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
 | 
					    protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
 | 
				
			||||||
        String path = request.getRequestURI();
 | 
					        String path = request.getRequestURI();
 | 
				
			||||||
        // permitAll 경로는 JWT 검증을 건너뜀 (WebSecurity.java의 설정과 동기화)
 | 
					        String contextPath = env.getProperty("server.servlet.context-path", "");
 | 
				
			||||||
        return path.equals("/login") ||
 | 
					        
 | 
				
			||||||
               path.startsWith("/members/register") ||
 | 
					        return securityPathConfig.isPermittedPath(path, contextPath);
 | 
				
			||||||
               path.startsWith("/swagger-ui/") ||
 | 
					 | 
				
			||||||
               path.startsWith("/api-docs/") ||
 | 
					 | 
				
			||||||
               path.startsWith("/ws/");
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
@@ -1,11 +1,9 @@
 | 
				
			|||||||
package com.bio.bio_backend.global.security;
 | 
					package com.bio.bio_backend.global.filter;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import java.util.Arrays;
 | 
					 | 
				
			||||||
import java.util.regex.Pattern;
 | 
					import java.util.regex.Pattern;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import org.springframework.beans.factory.annotation.Autowired;
 | 
					import org.springframework.beans.factory.annotation.Autowired;
 | 
				
			||||||
import org.springframework.context.annotation.Bean;
 | 
					 | 
				
			||||||
import org.springframework.stereotype.Component;
 | 
					import org.springframework.stereotype.Component;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import jakarta.servlet.http.HttpServletRequest;
 | 
					import jakarta.servlet.http.HttpServletRequest;
 | 
				
			||||||
@@ -1,5 +1,7 @@
 | 
				
			|||||||
package com.bio.bio_backend.global.security;
 | 
					package com.bio.bio_backend.global.security;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import com.bio.bio_backend.global.filter.JwtTokenIssuanceFilter;
 | 
				
			||||||
 | 
					import com.bio.bio_backend.global.filter.JwtTokenValidationFilter;
 | 
				
			||||||
import org.springframework.context.annotation.Bean;
 | 
					import org.springframework.context.annotation.Bean;
 | 
				
			||||||
import org.springframework.context.annotation.Configuration;
 | 
					import org.springframework.context.annotation.Configuration;
 | 
				
			||||||
import org.springframework.core.env.Environment;
 | 
					import org.springframework.core.env.Environment;
 | 
				
			||||||
@@ -18,9 +20,8 @@ import com.fasterxml.jackson.databind.ObjectMapper;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import com.bio.bio_backend.domain.base.member.service.MemberService;
 | 
					import com.bio.bio_backend.domain.base.member.service.MemberService;
 | 
				
			||||||
import com.bio.bio_backend.global.exception.CustomAuthenticationFailureHandler;
 | 
					import com.bio.bio_backend.global.exception.CustomAuthenticationFailureHandler;
 | 
				
			||||||
import com.bio.bio_backend.global.exception.JwtAccessDeniedHandler;
 | 
					 | 
				
			||||||
import com.bio.bio_backend.global.exception.JwtAuthenticationEntryPoint;
 | 
					 | 
				
			||||||
import com.bio.bio_backend.global.utils.JwtUtils;
 | 
					import com.bio.bio_backend.global.utils.JwtUtils;
 | 
				
			||||||
 | 
					import com.bio.bio_backend.global.config.SecurityPathConfig;
 | 
				
			||||||
import lombok.RequiredArgsConstructor;
 | 
					import lombok.RequiredArgsConstructor;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Configuration
 | 
					@Configuration
 | 
				
			||||||
@@ -33,18 +34,17 @@ public class WebSecurity {
 | 
				
			|||||||
    private final JwtUtils jwtUtils;
 | 
					    private final JwtUtils jwtUtils;
 | 
				
			||||||
    private final ObjectMapper objectMapper;
 | 
					    private final ObjectMapper objectMapper;
 | 
				
			||||||
    private final Environment env;
 | 
					    private final Environment env;
 | 
				
			||||||
    private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
 | 
					    private final SecurityPathConfig securityPathConfig;
 | 
				
			||||||
    private final JwtAccessDeniedHandler jwtAccessDeniedHandler;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private JwtTokenIssuanceFilter getJwtTokenIssuanceFilter(AuthenticationManager authenticationManager) throws Exception {
 | 
					    private JwtTokenIssuanceFilter getJwtTokenIssuanceFilter(AuthenticationManager authenticationManager) throws Exception {
 | 
				
			||||||
        JwtTokenIssuanceFilter filter = new JwtTokenIssuanceFilter(authenticationManager, memberService, jwtUtils, env, objectMapper);
 | 
					        JwtTokenIssuanceFilter filter = new JwtTokenIssuanceFilter(authenticationManager, jwtUtils, objectMapper);
 | 
				
			||||||
        filter.setFilterProcessesUrl("/login"); // 로그인 EndPoint
 | 
					        filter.setFilterProcessesUrl("/login");
 | 
				
			||||||
        filter.setAuthenticationFailureHandler(new CustomAuthenticationFailureHandler(objectMapper));
 | 
					        filter.setAuthenticationFailureHandler(new CustomAuthenticationFailureHandler(objectMapper));
 | 
				
			||||||
        return filter;
 | 
					        return filter;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private JwtTokenValidationFilter getJwtTokenValidationFilter() {
 | 
					    private JwtTokenValidationFilter getJwtTokenValidationFilter() {
 | 
				
			||||||
        return new JwtTokenValidationFilter(jwtUtils, memberService, env);
 | 
					        return new JwtTokenValidationFilter(jwtUtils, memberService, env, securityPathConfig);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -59,12 +59,13 @@ public class WebSecurity {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        AuthenticationManager authenticationManager = authenticationManagerBuilder.build();
 | 
					        AuthenticationManager authenticationManager = authenticationManagerBuilder.build();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 설정 파일에서 허용할 경로 가져오기
 | 
				
			||||||
 | 
					        String[] permitAllPaths = securityPathConfig.getPermitAllPaths().toArray(new String[0]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        http.csrf(AbstractHttpConfigurer::disable)          //csrf 비활성화
 | 
					        http.csrf(AbstractHttpConfigurer::disable)          //csrf 비활성화
 | 
				
			||||||
                .authorizeHttpRequests(request ->               //request 허용 설정
 | 
					                .authorizeHttpRequests(request ->               //request 허용 설정
 | 
				
			||||||
                        request
 | 
					                        request
 | 
				
			||||||
                            .requestMatchers("/members/register").permitAll()
 | 
					                            .requestMatchers(permitAllPaths).permitAll()  // 설정 파일에서 허용할 경로
 | 
				
			||||||
                            .requestMatchers("/swagger-ui/**", "/api-docs/**").permitAll()  // Swagger 허용
 | 
					 | 
				
			||||||
                            .requestMatchers("/ws/**").permitAll()  // WebSocket 허용
 | 
					 | 
				
			||||||
                            .anyRequest().authenticated()  // 나머지 요청은 인증 필요
 | 
					                            .anyRequest().authenticated()  // 나머지 요청은 인증 필요
 | 
				
			||||||
                )
 | 
					                )
 | 
				
			||||||
                .authenticationManager(authenticationManager)
 | 
					                .authenticationManager(authenticationManager)
 | 
				
			||||||
@@ -72,14 +73,6 @@ public class WebSecurity {
 | 
				
			|||||||
                    session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)  // 세션 사용 안함
 | 
					                    session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)  // 세션 사용 안함
 | 
				
			||||||
                )
 | 
					                )
 | 
				
			||||||
                .logout(AbstractHttpConfigurer::disable);
 | 
					                .logout(AbstractHttpConfigurer::disable);
 | 
				
			||||||
 | 
					 | 
				
			||||||
        // 예외 처리 핸들링
 | 
					 | 
				
			||||||
        http.exceptionHandling((exceptionConfig) ->
 | 
					 | 
				
			||||||
                exceptionConfig
 | 
					 | 
				
			||||||
                        .authenticationEntryPoint(jwtAuthenticationEntryPoint)
 | 
					 | 
				
			||||||
                        .accessDeniedHandler(jwtAccessDeniedHandler)
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        http
 | 
					        http
 | 
				
			||||||
            // 1단계: JWT 토큰 발급 필터 (로그인 요청 처리 및 토큰 발급)
 | 
					            // 1단계: JWT 토큰 발급 필터 (로그인 요청 처리 및 토큰 발급)
 | 
				
			||||||
            .addFilterBefore(getJwtTokenIssuanceFilter(authenticationManager), UsernamePasswordAuthenticationFilter.class)
 | 
					            .addFilterBefore(getJwtTokenIssuanceFilter(authenticationManager), UsernamePasswordAuthenticationFilter.class)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -93,17 +93,30 @@ public class JwtUtils {
 | 
				
			|||||||
        return null;
 | 
					        return null;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Refresh Token 생성 및 DB 저장
 | 
					    // Access Token 생성
 | 
				
			||||||
    public String createAndSaveRefreshToken(String username, String role) {
 | 
					    public String createAccessToken(String username, String role) {
 | 
				
			||||||
        String refreshToken = generateToken(username, role,
 | 
					        return generateToken(username, role,
 | 
				
			||||||
                Long.parseLong(Objects.requireNonNull(env.getProperty("token.expiration_time_refresh"))));
 | 
					                Long.parseLong(Objects.requireNonNull(env.getProperty("token.expiration_time_access"))));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
        // MemberDto 객체 생성하여 updateRefreshToken 호출
 | 
					    // Refresh Token 생성
 | 
				
			||||||
 | 
					    public String createRefreshToken(String username, String role) {
 | 
				
			||||||
 | 
					        return generateToken(username, role,
 | 
				
			||||||
 | 
					                Long.parseLong(Objects.requireNonNull(env.getProperty("token.expiration_time_refresh"))));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    // Refresh Token을 DB에 저장
 | 
				
			||||||
 | 
					    public void saveRefreshToken(String username, String refreshToken) {
 | 
				
			||||||
        MemberDto member = new MemberDto();
 | 
					        MemberDto member = new MemberDto();
 | 
				
			||||||
        member.setUserId(username);
 | 
					        member.setUserId(username);
 | 
				
			||||||
        member.setRefreshToken(refreshToken);
 | 
					        member.setRefreshToken(refreshToken);
 | 
				
			||||||
        memberService.updateRefreshToken(member);
 | 
					        memberService.updateRefreshToken(member);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
 | 
					    // Refresh Token 생성 및 DB 저장 (기존 호환성을 위한 메서드)
 | 
				
			||||||
 | 
					    public String createAndSaveRefreshToken(String username, String role) {
 | 
				
			||||||
 | 
					        String refreshToken = createRefreshToken(username, role);
 | 
				
			||||||
 | 
					        saveRefreshToken(username, refreshToken);
 | 
				
			||||||
        return refreshToken;
 | 
					        return refreshToken;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -95,3 +95,8 @@ springdoc.swagger-ui.doc-expansion=none
 | 
				
			|||||||
springdoc.swagger-ui.disable-swagger-default-url=true
 | 
					springdoc.swagger-ui.disable-swagger-default-url=true
 | 
				
			||||||
springdoc.default-produces-media-type=application/json
 | 
					springdoc.default-produces-media-type=application/json
 | 
				
			||||||
springdoc.default-consumes-media-type=application/json
 | 
					springdoc.default-consumes-media-type=application/json
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# ========================================
 | 
				
			||||||
 | 
					# 보안 설정 - 허용할 경로
 | 
				
			||||||
 | 
					# ========================================
 | 
				
			||||||
 | 
					security.permit-all-paths=/login,/members/register,/swagger-ui/**,/api-docs/**,/ws/**
 | 
				
			||||||
		Reference in New Issue
	
	Block a user