중계: 권한·계정이 바뀌면 열린 연결을 바로 끊고 새 티켓으로 다시 연결
- 대상 PC: 서버의 revokeUser 알림 → 그 사용자의 티켓 연결을 즉시 닫음 - 관리 PC: 기기 권한이 바뀌면 바로 다시 연결 (새 권한 티켓) - 서버 권한이 없어 원격 화면이 막히면 그 이유를 알림 - 통합 점검 37개 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013U497kUNL8ArhV7jkzCpKafeature/relay-server
parent
d2cd87526f
commit
d86aaa1fd4
|
|
@ -622,6 +622,9 @@ namespace JJServiceManager.Remote
|
|||
case "replaced":
|
||||
_log("중계 서버: " + Json.Str(e, "reason"), LogLevel.Warning);
|
||||
break;
|
||||
case "revokeUser":
|
||||
_server.DropTicketUser(e["userId"]?.GetValue<long>() ?? 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -46,6 +46,9 @@ namespace JJServiceManager.Remote
|
|||
public bool IsPassive => _target.Direction == ConnectDirection.Passive;
|
||||
public bool IsRelay => _target.Direction == ConnectDirection.Relay;
|
||||
|
||||
/// <summary>Relay: 중계 서버가 준 이 기기 권한 (연결 hello에서 받음, 없으면 null)</summary>
|
||||
public (bool view, bool control, bool screenView, bool screenControl)? RelayPerms { get; private set; }
|
||||
|
||||
/// <summary>Relay: 실제 연결 방식 ("direct" / "relay", 연결 전에는 빈 값)</summary>
|
||||
public string RelayMode { get; private set; } = "";
|
||||
|
||||
|
|
@ -151,6 +154,9 @@ namespace JJServiceManager.Remote
|
|||
}
|
||||
AllowView = Json.Bool(hello, "allowView");
|
||||
AllowControl = Json.Bool(hello, "allowControl");
|
||||
RelayPerms = hello["perms"] is JsonObject rp
|
||||
? (Json.Bool(rp, "view"), Json.Bool(rp, "control"), Json.Bool(rp, "screenView"), Json.Bool(rp, "screenControl"))
|
||||
: null;
|
||||
SetState(RemoteState.Connected, "연결됨");
|
||||
|
||||
var reader = Task.Run(() => ReadLoopAsync(conn, ct));
|
||||
|
|
|
|||
|
|
@ -47,6 +47,8 @@ namespace JJServiceManager.Remote
|
|||
|
||||
// 화면 보기 중인 세션
|
||||
private readonly ConcurrentDictionary<int, ScreenSession> _screens = new ConcurrentDictionary<int, ScreenSession>();
|
||||
/// <summary>중계 서버 권한 티켓으로 열린 연결 (권한이 바뀌면 바로 끊기 위해)</summary>
|
||||
private readonly ConcurrentDictionary<JsonLineConnection, RelayTicket> _ticketConns = new ConcurrentDictionary<JsonLineConnection, RelayTicket>();
|
||||
private int _nextScreenId;
|
||||
|
||||
public bool IsRunning => _listener != null;
|
||||
|
|
@ -247,6 +249,23 @@ namespace JJServiceManager.Remote
|
|||
lock (_failures) _failures.Enqueue(DateTime.UtcNow);
|
||||
}
|
||||
|
||||
/// <summary>중계 서버가 이 사용자의 권한·계정을 바꿈 → 옛 티켓으로 열린 연결을 바로 끊음 (관리 PC는 새 티켓으로 다시 연결)</summary>
|
||||
public int DropTicketUser(long userId)
|
||||
{
|
||||
int n = 0;
|
||||
foreach (var kv in _ticketConns)
|
||||
{
|
||||
if (kv.Value.UserId != userId) continue;
|
||||
if (_ticketConns.TryRemove(kv.Key, out _))
|
||||
{
|
||||
n++;
|
||||
try { kv.Key.Dispose(); } catch { }
|
||||
}
|
||||
}
|
||||
if (n > 0) _log($"중계 서버에서 사용자 권한이 바뀌어 원격 연결 {n}개를 끊었습니다.", LogLevel.Notice);
|
||||
return n;
|
||||
}
|
||||
|
||||
// ───────────────────────── 요청 처리 ─────────────────────────
|
||||
|
||||
/// <summary>중계 통로로 들어온 연결 (권한 티켓 필수, 세션 ID 일치)</summary>
|
||||
|
|
@ -314,6 +333,7 @@ namespace JJServiceManager.Remote
|
|||
authed = true;
|
||||
}
|
||||
_log($"원격 접속: {who}", LogLevel.Notice);
|
||||
if (ticket != null) _ticketConns[conn] = ticket;
|
||||
var pol = Policy(ticket);
|
||||
var helloResult = new JsonObject
|
||||
{
|
||||
|
|
@ -564,6 +584,7 @@ namespace JJServiceManager.Remote
|
|||
foreach (var t in streams.Values) t.Dispose();
|
||||
streams.Clear();
|
||||
}
|
||||
_ticketConns.TryRemove(conn, out _);
|
||||
var s = Interlocked.Exchange(ref screen, null);
|
||||
EndScreen(s);
|
||||
conn.Dispose();
|
||||
|
|
|
|||
|
|
@ -600,7 +600,11 @@ namespace JJServiceManager
|
|||
}
|
||||
if (!g.Client.AllowView)
|
||||
{
|
||||
Dialogs.Info(this, $"{g.Title} 에서 화면 보기를 허용하지 않았습니다.\n대상 PC 설정 → 원격 접속 허용 → 원격관리에서 '화면 보기 허용'을 켜야 합니다.");
|
||||
// 중계 서버 권한이 없어서인지, 대상 PC 설정이 꺼져 있어서인지 구분
|
||||
if (g.Client.RelayPerms is { } rp && !rp.screenView)
|
||||
Dialogs.Info(this, $"{g.Title} 의 [화면 보기] 권한이 중계 서버에서 주어지지 않았습니다.\n중계 서버 관리자에게 [서버 관리 → 권한]에서 '화면 보기'를 요청하세요.");
|
||||
else
|
||||
Dialogs.Info(this, $"{g.Title} 에서 화면 보기를 허용하지 않았습니다.\n대상 PC 설정 → 원격 접속 허용 → 원격관리에서 '화면 보기 허용'을 켜야 합니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -949,6 +953,9 @@ namespace JJServiceManager
|
|||
}
|
||||
|
||||
/// <summary>중계 서버의 기기 목록 → 초기화면 그룹 (권한 있는 기기만)</summary>
|
||||
private readonly Dictionary<string, string> _relayPermSig = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
private static string PermSig(RelayDevice d) => $"{d.View}{d.Control}{d.ScreenView}{d.ScreenControl}";
|
||||
|
||||
private void SyncRelayGroups()
|
||||
{
|
||||
bool active = _settings.RemoteManageEnabled && _settings.RelayManageEnabled && _relayManageKey != "off" && _relayManageKey != "";
|
||||
|
|
@ -963,6 +970,7 @@ namespace JJServiceManager
|
|||
if (active && devices.Count == 0 && !_relayManager.IsLoggedIn) continue;
|
||||
RemoveRemoteGroup(g);
|
||||
_relayTargets.Remove(g.Id);
|
||||
_relayPermSig.Remove(g.Id);
|
||||
if (_hostTabs.ContainsKey(g.Id)) CloseHostTab(g.Id, save: false);
|
||||
}
|
||||
|
||||
|
|
@ -980,7 +988,10 @@ namespace JJServiceManager
|
|||
existing.Title = t0.Label;
|
||||
existing.TitleChanged();
|
||||
}
|
||||
if (d.Online && existing.Client?.State != RemoteState.Connected) existing.Client?.Nudge();
|
||||
// 권한이 바뀌었으면: 대상 PC가 옛 티켓 연결을 끊음 → 기다리지 않고 새 티켓으로 바로 다시 연결
|
||||
bool permChanged = _relayPermSig.TryGetValue(gid, out var sig0) && sig0 != PermSig(d);
|
||||
_relayPermSig[gid] = PermSig(d);
|
||||
if (d.Online && (permChanged || existing.Client?.State != RemoteState.Connected)) existing.Client?.Nudge();
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -993,6 +1004,7 @@ namespace JJServiceManager
|
|||
Hostname = d.Hostname
|
||||
};
|
||||
_relayTargets[gid] = t;
|
||||
_relayPermSig[gid] = PermSig(d);
|
||||
var deviceId = d.Id;
|
||||
var client = new RemoteClient(t, () =>
|
||||
_settings.RelayDevicePasswords.TryGetValue(deviceId, out var p) ? RemoteSecurity.Unprotect(p) : "", _relayManager);
|
||||
|
|
|
|||
|
|
@ -117,6 +117,21 @@ namespace JJServiceManager
|
|||
catch (RemoteException ex) { Check(ex.Message.Contains("서비스 제어"), "bob Stop 거부 (대상 PC가 티켓 권한 확인)", ex.Message); }
|
||||
try { await c3.RequestAsync("screenStart", new JsonObject { ["monitor"] = 1 }, TimeSpan.FromSeconds(5)); Check(false, "bob 화면 거부"); }
|
||||
catch (RemoteException ex) { Check(ex.Message.Contains("화면 보기"), "bob 원격 화면 거부", ex.Message); }
|
||||
|
||||
Console.WriteLine("== 5-1. 연결 중에 권한을 바꾸면 → 대상 PC가 옛 티켓 연결을 바로 끊고, 새 권한으로 다시 연결");
|
||||
bool sawDrop = false;
|
||||
c3.StateChanged += c => { if (c.State != RemoteState.Connected) sawDrop = true; };
|
||||
await admin.RequestAsync("admin.perms.set", new JsonObject { ["userId"] = bobId, ["deviceId"] = enr.DeviceId, ["view"] = true, ["control"] = true });
|
||||
Check(await Until(() => sawDrop, 10), "권한 변경 → 기존 연결 끊김");
|
||||
Check(await Until(() => bob.Devices.Count == 1 && bob.Devices[0].Control, 10), "bob 기기 목록에 새 권한 반영");
|
||||
c3.Nudge(); // 화면(MainWindow)은 권한이 바뀌면 Nudge → 바로 다시 연결
|
||||
Check(await Until(() => c3.State == RemoteState.Connected && c3.RelayPerms?.control == true, 20), "새 티켓으로 다시 연결 (서비스 제어 권한)", c3.StateMessage);
|
||||
try { await c3.StopServiceAsync("Spooler"); Check(true, "bob Stop 이제 허용 (리눅스라 실행 자체는 실패해도 됨)"); }
|
||||
catch (RemoteException ex) { Check(!ex.Message.Contains("서비스 제어"), "bob Stop 이제 허용", ex.Message); }
|
||||
|
||||
sawDrop = false;
|
||||
await admin.RequestAsync("admin.users.update", new JsonObject { ["userId"] = bobId, ["enabled"] = false });
|
||||
Check(await Until(() => sawDrop, 10), "계정 사용 안 함 → 대상 PC 연결도 바로 끊김");
|
||||
c3.Dispose();
|
||||
|
||||
Console.WriteLine("== 6. 대상 PC 접속 비밀번호가 틀리면 (서버 권한이 있어도) 거부");
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# RelayIntegration
|
||||
|
||||
중계 서버(JJServiceManagerRelay)와 이 프로그램의 원격 코어(대상 PC·관리 PC)를 함께 띄워 확인하는 통합 점검 (32개 항목, Linux에서도 실행).
|
||||
중계 서버(JJServiceManagerRelay)와 이 프로그램의 원격 코어(대상 PC·관리 PC)를 함께 띄워 확인하는 통합 점검 (37개 항목, Linux에서도 실행).
|
||||
화면 캡처·입력은 가짜(Stubs.cs)로 대신합니다.
|
||||
|
||||
1. PostgreSQL + 중계 서버를 `http://127.0.0.1:21900`에 띄움 (첫 관리자 `admin` / `firstpass1`, 빈 DB)
|
||||
|
|
|
|||
Loading…
Reference in New Issue