[api] swagger 셋팅중
- cors 화이트리스트 허용방식 변경
This commit is contained in:
@@ -5,8 +5,9 @@
|
|||||||
### 코드 수정 시 반드시 따를 것
|
### 코드 수정 시 반드시 따를 것
|
||||||
1. **절대로 바로 파일을 수정하지 말 것**
|
1. **절대로 바로 파일을 수정하지 말 것**
|
||||||
2. **먼저 수정 방향과 계획을 설명**
|
2. **먼저 수정 방향과 계획을 설명**
|
||||||
3. **수정할 코드를 먼저 보여주기** (사용자가 직접 수정할 수도 있도록)
|
3. **수정할 코드를 보여줄지 말지 물어보고 보여주기**
|
||||||
4. **사용자 확인 후 자동 작성 진행** 또는 사용자가 요청 시에만 작성
|
4. **수정할 코드를 먼저 보여주기** (사용자가 직접 수정할 수도 있도록)
|
||||||
|
5. **사용자 확인 후 자동 작성 진행** 또는 사용자가 요청 시에만 작성
|
||||||
|
|
||||||
### 작업 순서 예시
|
### 작업 순서 예시
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -1,34 +0,0 @@
|
|||||||
package com.alist.api.config;
|
|
||||||
|
|
||||||
import com.alist.api.config.properties.CorsProperties;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
import org.springframework.web.cors.CorsConfiguration;
|
|
||||||
import org.springframework.web.cors.CorsConfigurationSource;
|
|
||||||
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
|
||||||
|
|
||||||
@Configuration
|
|
||||||
@EnableConfigurationProperties(CorsProperties.class)
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
public class CorsConfig {
|
|
||||||
|
|
||||||
private final CorsProperties corsProperties;
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
public CorsConfigurationSource corsConfigurationSource() {
|
|
||||||
CorsConfiguration configuration = new CorsConfiguration();
|
|
||||||
|
|
||||||
configuration.setAllowedOrigins(corsProperties.getAllowedOrigins());
|
|
||||||
configuration.setAllowCredentials(corsProperties.getAllowCredentials());
|
|
||||||
configuration.setAllowedMethods(corsProperties.getAllowedMethods());
|
|
||||||
configuration.setAllowedHeaders(corsProperties.getAllowedHeaders());
|
|
||||||
configuration.setExposedHeaders(corsProperties.getExposedHeaders());
|
|
||||||
configuration.setMaxAge(corsProperties.getMaxAge());
|
|
||||||
|
|
||||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
|
||||||
source.registerCorsConfiguration("/**", configuration);
|
|
||||||
return source;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
package com.alist.api.config.filter;
|
||||||
|
import com.alist.api.config.jwt.JwtTokenProvider;
|
||||||
|
import com.alist.api.modules.auth.mapper.TestCorsMapper;
|
||||||
|
import jakarta.servlet.FilterChain;
|
||||||
|
import jakarta.servlet.ServletException;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.core.Ordered;
|
||||||
|
import org.springframework.core.annotation.Order;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.web.filter.OncePerRequestFilter;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
@Order(Ordered.HIGHEST_PRECEDENCE)
|
||||||
|
public class DynamicCorsFilter extends OncePerRequestFilter {
|
||||||
|
private final TestCorsMapper testCorsMapper;
|
||||||
|
private final JwtTokenProvider jwtTokenProvider;
|
||||||
|
|
||||||
|
public DynamicCorsFilter(TestCorsMapper testCorsMapper, JwtTokenProvider jwtTokenProvider) {
|
||||||
|
this.testCorsMapper = testCorsMapper;
|
||||||
|
this.jwtTokenProvider = jwtTokenProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doFilterInternal(HttpServletRequest request,
|
||||||
|
HttpServletResponse response,
|
||||||
|
FilterChain filterChain) throws ServletException, IOException {
|
||||||
|
|
||||||
|
String origin = request.getHeader("Origin");
|
||||||
|
Integer userTokenIdx = extractUserTokenIdx(request);
|
||||||
|
|
||||||
|
// DB에서 허용 도메인 조회
|
||||||
|
List<String> allowedOrigins = testCorsMapper.selectTestCorsAllowedList(userTokenIdx);
|
||||||
|
|
||||||
|
// CORS 체크 결과 로깅
|
||||||
|
if (origin != null) {
|
||||||
|
boolean isAllowed = allowedOrigins.contains("*") || allowedOrigins.contains(origin);
|
||||||
|
|
||||||
|
if (isAllowed && log.isDebugEnabled()) {
|
||||||
|
// 허용된 경우 DEBUG 레벨
|
||||||
|
log.debug("CORS allowed - Origin: {}, UserTokenIdx: {}", origin, userTokenIdx);
|
||||||
|
} else if (!isAllowed) {
|
||||||
|
// 차단된 경우 WARN 레벨 (보안 모니터링)
|
||||||
|
log.warn("CORS blocked - Origin: {}, UserTokenIdx: {}, AllowedOrigins: {}",
|
||||||
|
origin, userTokenIdx, allowedOrigins);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Origin이 허용 목록에 있거나 "*"가 있으면 CORS 헤더 설정
|
||||||
|
if (origin != null && (allowedOrigins.contains("*") || allowedOrigins.contains(origin))) {
|
||||||
|
response.setHeader("Access-Control-Allow-Origin", origin);
|
||||||
|
response.setHeader("Access-Control-Allow-Credentials", "true");
|
||||||
|
response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS");
|
||||||
|
response.setHeader("Access-Control-Allow-Headers", "*");
|
||||||
|
response.setHeader("Access-Control-Expose-Headers", "Authorization, Set-Cookie");
|
||||||
|
response.setHeader("Access-Control-Max-Age", "3600");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Preflight 요청(OPTIONS) 처리
|
||||||
|
if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
|
||||||
|
response.setStatus(HttpServletResponse.SC_OK);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
filterChain.doFilter(request, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request에서 user_token_idx 추출
|
||||||
|
* JWT 토큰 또는 API Key를 통해 추출
|
||||||
|
*/
|
||||||
|
private Integer extractUserTokenIdx(HttpServletRequest request) {
|
||||||
|
try {
|
||||||
|
String token = null;
|
||||||
|
|
||||||
|
// 1. Authorization Bearer 토큰
|
||||||
|
String authHeader = request.getHeader("Authorization");
|
||||||
|
if (authHeader != null && authHeader.startsWith("Bearer ")) {
|
||||||
|
token = authHeader.substring(7);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Cookie에서 accessToken
|
||||||
|
if (token == null && request.getCookies() != null) {
|
||||||
|
for (var cookie : request.getCookies()) {
|
||||||
|
if ("accessToken".equals(cookie.getName())) {
|
||||||
|
token = cookie.getValue();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 토큰이 있으면 검증 후 userTokenIdx 추출
|
||||||
|
if (token != null && jwtTokenProvider.validateToken(token)) {
|
||||||
|
String subject = jwtTokenProvider.getUserId(token);
|
||||||
|
|
||||||
|
// subject를 Integer로 변환 시도
|
||||||
|
try {
|
||||||
|
return Integer.parseInt(subject);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
// 숫자가 아닌 문자열이면 null 반환 (기본값 0으로 조회)
|
||||||
|
log.debug("Subject is not a number: {}", subject);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.debug("Failed to extract userTokenIdx: {}", e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
return null; // 기본값 0만 조회
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -89,4 +89,5 @@ public class JwtTokenProvider {
|
|||||||
.parseClaimsJws(token)
|
.parseClaimsJws(token)
|
||||||
.getBody();
|
.getBody();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
package com.alist.api.config.properties;
|
|
||||||
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.Setter;
|
|
||||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
@ConfigurationProperties(prefix = "cors")
|
|
||||||
public class CorsProperties {
|
|
||||||
List<String> allowedOrigins;
|
|
||||||
Boolean allowCredentials = true;
|
|
||||||
List<String> allowedMethods = List.of("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS");
|
|
||||||
List<String> allowedHeaders = List.of("*");
|
|
||||||
List<String> exposedHeaders = List.of("Authorization", "Set-Cookie");
|
|
||||||
Long maxAge = 3600L;
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package com.alist.api.modules.auth.mapper;
|
||||||
|
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import org.springframework.cache.annotation.Cacheable;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface TestCorsMapper {
|
||||||
|
@Cacheable(value = "corsAllowedOrigins", key = "#userTokenIdx")
|
||||||
|
List<String> selectTestCorsAllowedList(Integer userTokenIdx);
|
||||||
|
}
|
||||||
@@ -18,16 +18,6 @@ jwt:
|
|||||||
cookie:
|
cookie:
|
||||||
secure: false # 로컬 개발 환경 (HTTP)
|
secure: false # 로컬 개발 환경 (HTTP)
|
||||||
|
|
||||||
cors:
|
|
||||||
allowed-origins:
|
|
||||||
- "http://localhost:8100"
|
|
||||||
- "http://localhost:8101"
|
|
||||||
- "http://localhost:8102"
|
|
||||||
- "http://localhost:8103"
|
|
||||||
- "http://localhost:8104"
|
|
||||||
- "http://localhost:8105"
|
|
||||||
- "http://localhost:8108"
|
|
||||||
|
|
||||||
swagger:
|
swagger:
|
||||||
login:
|
login:
|
||||||
id: alist
|
id: alist
|
||||||
|
|||||||
@@ -20,23 +20,6 @@ jwt:
|
|||||||
cookie:
|
cookie:
|
||||||
secure: true # 프로젝트 환경 (HTTPS)
|
secure: true # 프로젝트 환경 (HTTPS)
|
||||||
|
|
||||||
cors:
|
|
||||||
allowed-origins:
|
|
||||||
- "http://localhost:8100"
|
|
||||||
- "http://localhost:8101"
|
|
||||||
- "http://localhost:8102"
|
|
||||||
- "http://localhost:8103"
|
|
||||||
- "http://localhost:8104"
|
|
||||||
- "http://localhost:8105"
|
|
||||||
- "http://localhost:8108"
|
|
||||||
- "https://wwwl-alist.pjt.kr"
|
|
||||||
- "https://engl-alist.pjt.kr"
|
|
||||||
- "https://admin-alist.pjt.kr"
|
|
||||||
- "https://wwwc-alist.pjt.kr"
|
|
||||||
- "https://class-alist.pjt.kr"
|
|
||||||
- "https://student-alist.pjt.kr"
|
|
||||||
- "https://storybook-alist.pjt.kr"
|
|
||||||
|
|
||||||
swagger:
|
swagger:
|
||||||
login:
|
login:
|
||||||
id: ${SWAGGER_ID}
|
id: ${SWAGGER_ID}
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||||
|
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
|
||||||
|
<mapper namespace="com.alist.api.modules.auth.mapper.TestCorsMapper">
|
||||||
|
|
||||||
|
<select id="selectTestCorsAllowedList" resultType="String">
|
||||||
|
/* TestCorsMapper.selectAllowedOrigins */
|
||||||
|
SELECT DISTINCT allowed_origin
|
||||||
|
FROM test_cors_allowed_list
|
||||||
|
WHERE del_yn = 1
|
||||||
|
AND (
|
||||||
|
user_token_idx = 0
|
||||||
|
<if test="userTokenIdx != null">
|
||||||
|
OR user_token_idx = #{userTokenIdx}
|
||||||
|
</if>
|
||||||
|
)
|
||||||
|
</select>
|
||||||
|
|
||||||
|
</mapper>
|
||||||
Reference in New Issue
Block a user