Skip to content

Commit 4743fa0

Browse files
authored
Merge pull request #18 from Geumpumta/wifi
refactor : Wi-Fi 요청 데이터 SSID -> IP Gateway 변경
2 parents ccca620 + cbb1b94 commit 4743fa0

File tree

7 files changed

+54
-39
lines changed

7 files changed

+54
-39
lines changed

src/main/java/com/gpt/geumpumtabackend/study/api/StudySessionApi.java

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,13 @@ ResponseEntity<ResponseBody<StudySessionResponse>> getTodayStudySession(
7373
새로운 학습 세션을 시작합니다. 캠퍼스 Wi-Fi 검증이 필수입니다.
7474
7575
🔐 **Wi-Fi 검증 과정:**
76-
1. SSID 검증 - 허용된 캠퍼스 Wi-Fi인지 확인
77-
2. BSSID 검증 - 특정 액세스포인트 확인 (선택사항)
78-
3. IP 대역 검증 - 캠퍼스 IP 범위 내인지 확인
76+
1. Gateway IP 검증 - 캠퍼스 게이트웨이 IP (172.30.64.1)와 일치하는지 확인
77+
2. BSSID 검증 - 등록된 캠퍼스 액세스 포인트인지 확인
78+
3. IP 대역 검증 - 클라이언트 IP가 캠퍼스 범위(172.30.64.0/18) 내인지 확인
79+
80+
💡 **보안 특징:**
81+
- 클라이언트 IP는 서버에서 HttpServletRequest로 추출하여 조작 방지
82+
- Gateway IP는 점-십진 표기법 문자열로 전송 (예: "172.30.64.1")
7983
8084
✅ **성공 시:**
8185
- 새로운 학습 세션 생성
@@ -144,9 +148,10 @@ ResponseEntity<ResponseBody<Void>> endStudySession(
144148
⏱️ **전송 주기:** 30초마다 자동 전송 권장
145149
146150
🔄 **동작 원리:**
147-
1. Wi-Fi 연결 상태 재검증
148-
2. 세션의 lastHeartBeatAt 시간 업데이트
149-
3. 90초 이상 하트비트 없으면 좀비 세션으로 분류
151+
1. Wi-Fi 연결 상태 재검증 (Gateway IP + BSSID 확인)
152+
2. 클라이언트 실제 IP 주소 재확인 (서버에서 추출)
153+
3. 세션의 lastHeartBeatAt 시간 업데이트
154+
4. 90초 이상 하트비트 없으면 좀비 세션으로 분류
150155
151156
🚨 **실패 시 대응:**
152157
- Wi-Fi 연결 끊김: 재연결 후 다시 `/start` 호출

src/main/java/com/gpt/geumpumtabackend/study/dto/request/HeartBeatRequest.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,20 @@
11
package com.gpt.geumpumtabackend.study.dto.request;
22

3+
import io.swagger.v3.oas.annotations.media.Schema;
34
import jakarta.validation.constraints.NotBlank;
45
import jakarta.validation.constraints.NotNull;
56

7+
@Schema(description = "학습 세션 하트비트 요청")
68
public record HeartBeatRequest(
9+
@Schema(description = "학습 세션 ID", example = "1")
710
@NotNull(message = "sessionId는 필수입니다")
811
Long sessionId,
912

10-
@NotBlank(message = "SSID는 필수입니다")
11-
String ssid,
13+
@Schema(description = "캠퍼스 네트워크 게이트웨이 IP 주소", example = "172.30.64.1")
14+
@NotBlank(message = "Gateway IP는 필수입니다")
15+
String gatewayIp,
1216

17+
@Schema(description = "Wi-Fi 액세스 포인트 BSSID", example = "a4:88:73:ac:8d:cd")
1318
@NotBlank(message = "BSSID는 필수입니다")
1419
String bssid
1520

src/main/java/com/gpt/geumpumtabackend/study/dto/request/StudyStartRequest.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,22 @@
11
package com.gpt.geumpumtabackend.study.dto.request;
22

3+
import io.swagger.v3.oas.annotations.media.Schema;
34
import jakarta.validation.constraints.NotBlank;
45
import jakarta.validation.constraints.NotNull;
56

67
import java.time.LocalDateTime;
78

9+
@Schema(description = "학습 세션 시작 요청")
810
public record StudyStartRequest(
11+
@Schema(description = "학습 시작 시간", example = "2024-01-15T09:00:00")
912
@NotNull(message = "startTime은 필수입니다")
1013
LocalDateTime startTime,
1114

12-
@NotBlank(message = "SSID는 필수입니다")
13-
String ssid,
15+
@Schema(description = "캠퍼스 네트워크 게이트웨이 IP 주소", example = "172.30.64.1")
16+
@NotBlank(message = "Gateway IP는 필수입니다")
17+
String gatewayIp,
1418

19+
@Schema(description = "Wi-Fi 액세스 포인트 BSSID", example = "a4:88:73:ac:8d:cd")
1520
@NotBlank(message = "BSSID는 필수입니다")
1621
String bssid
1722

src/main/java/com/gpt/geumpumtabackend/study/service/StudySessionService.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ public StudySessionResponse getTodayStudySession(Long userId) {
5050
public StudyStartResponse startStudySession(StudyStartRequest request, Long userId, HttpServletRequest httpServletRequest) {
5151
// Wi-Fi 검증
5252
WiFiValidationResult validationResult = wifiValidationService.validateFromCache(
53-
request.ssid(), request.bssid(), httpServletRequest
53+
request.gatewayIp(), request.bssid(), httpServletRequest
5454
);
5555

5656
if (!validationResult.isValid()) {
@@ -88,7 +88,7 @@ public void updateHeartBeat(HeartBeatRequest heartBeatRequest, Long userId, Http
8888

8989
// Wi-Fi 검증 (캐시 우선 사용)
9090
WiFiValidationResult validationResult = wifiValidationService.validateFromCache(
91-
heartBeatRequest.ssid(), heartBeatRequest.bssid(), httpServletRequest
91+
heartBeatRequest.gatewayIp(), heartBeatRequest.bssid(), httpServletRequest
9292
);
9393

9494
if (!validationResult.isValid()) {

src/main/java/com/gpt/geumpumtabackend/wifi/config/CampusWiFiProperties.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,19 +20,20 @@ public record CampusWiFiProperties(
2020
networks = List.of();
2121
}
2222
if (validation == null) {
23-
validation = new ValidationConfig(60, 32);
23+
validation = new ValidationConfig(60);
2424
}
2525
}
2626

2727
public record WiFiNetwork(String name,
28-
String ssid,
28+
String gatewayIp,
2929
List<String> bssids,
3030
List<String> ipRanges,
3131
Boolean active,
3232
String description) {
3333

34-
public boolean isValidSSID(String ssid) {
35-
return this.ssid != null && this.ssid.equals(ssid);
34+
public boolean isValidGatewayIP(String gatewayIp) {
35+
return this.gatewayIp != null && this.gatewayIp.equals(gatewayIp);
36+
}
3637
}
3738

3839
public boolean isValidBSSID(String bssid) {
@@ -59,8 +60,7 @@ private boolean isIpInRange(String ipAddress, String cidr) {
5960

6061

6162

62-
public record ValidationConfig(Integer cacheTtlMinutes,
63-
Integer maxSsidLength) {
63+
public record ValidationConfig(Integer cacheTtlMinutes) {
6464

6565
}
6666
}

src/main/java/com/gpt/geumpumtabackend/wifi/service/CampusWiFiValidationService.java

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -25,21 +25,23 @@ public class CampusWiFiValidationService {
2525
private static final String WIFI_CACHE_KEY_PREFIX = "campus_wifi_validation:";
2626

2727

28-
public WiFiValidationResult validateCampusWiFi(String ssid, String bssid, HttpServletRequest request) {
28+
public WiFiValidationResult validateCampusWiFi(String gatewayIp, String bssid, HttpServletRequest request) {
2929

3030
try {
3131
// 서버에서 클라이언트 IP 추출
3232
String ipAddress = IpUtil.getClientIp(request);
33-
log.info("Wi-Fi validation request - SSID: {}, BSSID: {}, IP: {}", ssid, bssid, ipAddress);
33+
34+
log.info("Wi-Fi validation request - Gateway IP: {}, BSSID: {}, Client IP: {}", gatewayIp, bssid, ipAddress);
35+
3436

3537
// 캠퍼스 내부인지 확인
36-
boolean isInCampus = isInCampusNetwork(ssid, bssid, ipAddress);
38+
boolean isInCampus = isInCampusNetwork(gatewayIp, bssid, ipAddress);
3739

3840
if (isInCampus) {
39-
cacheValidationResult(ssid, ipAddress, true);
41+
cacheValidationResult(gatewayIp, ipAddress, true);
4042
return WiFiValidationResult.valid("캠퍼스 네트워크입니다");
4143
} else {
42-
cacheValidationResult(ssid, ipAddress, false);
44+
cacheValidationResult(gatewayIp, ipAddress, false);
4345
return WiFiValidationResult.invalid("캠퍼스 네트워크가 아닙니다");
4446
}
4547

@@ -50,43 +52,41 @@ public WiFiValidationResult validateCampusWiFi(String ssid, String bssid, HttpSe
5052
}
5153

5254

53-
public WiFiValidationResult validateFromCache(String ssid, String bssid, HttpServletRequest request) {
55+
public WiFiValidationResult validateFromCache(String gatewayIp, String bssid, HttpServletRequest request) {
5456
try {
5557
// 서버에서 클라이언트 IP 추출
5658
String ipAddress = IpUtil.getClientIp(request);
57-
log.info("Wi-Fi validation cache request - SSID: {}, BSSID: {}, IP: {}", ssid, bssid, ipAddress);
58-
// IP 주소와 SSID를 통해 키를 생성 후 Redis에서 조회
59-
String cacheKey = buildCacheKey(ssid, ipAddress);
59+
// Gateway IP와 클라이언트 IP를 통해 키를 생성 후 Redis에서 조회
60+
String cacheKey = buildCacheKey(gatewayIp, ipAddress);
6061
Boolean cachedResult = (Boolean) redisTemplate.opsForValue().get(cacheKey);
6162

6263
if (cachedResult != null) {
63-
log.debug("Wi-Fi validation cache hit - SSID: {}, IP: {}, Result: {}", ssid, ipAddress, cachedResult);
6464
return cachedResult
6565
? WiFiValidationResult.valid("캠퍼스 네트워크입니다 (캐시)")
6666
: WiFiValidationResult.invalid("캠퍼스 네트워크가 아닙니다 (캐시)");
6767
}
6868

6969
// 캐시에 없으면 전체 검증 수행
70-
log.debug("Wi-Fi validation cache miss - performing full validation");
71-
return validateCampusWiFi(ssid, bssid, request);
70+
71+
return validateCampusWiFi(gatewayIp, bssid, request);
7272

7373
} catch (Exception e) {
74-
log.error("Wi-Fi cache validation error", e);
74+
7575
return WiFiValidationResult.error("Wi-Fi 검증 중 오류가 발생했습니다: " + e.getMessage());
7676
}
7777
}
7878

7979

80-
private boolean isInCampusNetwork(String ssid, String bssid, String ipAddress) {
80+
private boolean isInCampusNetwork(String gatewayIp, String bssid, String ipAddress) {
8181

8282
// 설정 파일 Wi-fi 목록 불러오기
8383
List<CampusWiFiProperties.WiFiNetwork> activeNetworks = wifiProperties.networks()
8484
.stream()
8585
.filter(CampusWiFiProperties.WiFiNetwork::active)
8686
.toList();
8787
for (CampusWiFiProperties.WiFiNetwork network : activeNetworks) {
88-
// 1. SSID 체크
89-
if (!network.isValidSSID(ssid)) {
88+
// 1. Gateway IP 체크 (SSID 대신 사용)
89+
if (!network.isValidGatewayIP(gatewayIp)) {
9090
continue;
9191
}
9292
if (bssid != null && !bssid.isEmpty() && !network.isValidBSSID(bssid)) {
@@ -100,13 +100,13 @@ private boolean isInCampusNetwork(String ssid, String bssid, String ipAddress) {
100100
}
101101

102102

103-
private String buildCacheKey(String ssid, String ipAddress) {
104-
return WIFI_CACHE_KEY_PREFIX + ssid + ":" + ipAddress;
103+
private String buildCacheKey(String gatewayIp, String ipAddress) {
104+
return WIFI_CACHE_KEY_PREFIX + gatewayIp + ":" + ipAddress;
105105
}
106106

107107

108-
private void cacheValidationResult(String ssid, String ipAddress, boolean isValid) {
109-
String cacheKey = buildCacheKey(ssid, ipAddress);
108+
private void cacheValidationResult(String gatewayIp, String ipAddress, boolean isValid) {
109+
String cacheKey = buildCacheKey(gatewayIp, ipAddress);
110110
Duration ttl = Duration.ofMinutes(wifiProperties.validation().cacheTtlMinutes());
111111
redisTemplate.opsForValue().set(cacheKey, isValid, ttl);
112112
}

src/main/resources/security

0 commit comments

Comments
 (0)