Windows의 세션 분리를 어떻게 이해할까 ── Session 0・RDP・다중 사용자 동시 실행
· 小村 豪 · Session 0, 세션 분리, RDP, 원격 데스크톱, Windows 서비스, 다중 사용자, Windows, .NET, C#, 설계, 기술 상담
“서비스에서 띄운 대화상자가 아무에게도 보이지 않는다”, “RDP로 접속하자마자 현지 화면이 로그인 화면으로 바뀌었다”, “중복 실행 방지가 되어야 하는데 다른 사용자로 로그인하니 평범하게 2개가 실행됐다”. 업무 앱 상담에서는 이런 증상 뒤에 공통적으로 ‘Windows의 세션’이라는 개념에 대한 이해 부족이 있습니다. 세션은 서비스 설계에도, 원격 데스크톱 운영에도, 명명된 개체의 중복 실행 방지에도 관련되는 횡단적인 주제이지만, 체계적으로 설명되는 경우는 많지 않습니다.
이 글에서는 Session 0 분리가 왜 도입되었는지, RDP 접속 시 세션이 어떻게 동작하는지, 명명된 개체가 세션별로 어떻게 분리되는지, 그리고 공유 PC・RDS(Remote Desktop Services) 환경에서 흔히 발생하는 설계 실수를 실무 관점에서 정리합니다.
1. 먼저 결론
- 서비스는 Session 0라는 전용 세션에서 동작하며, 로그온 중인 사용자의 대화형 세션과는 분리되어 있습니다. 서비스는 직접 대화상자를 표시할 수 없고, 표시해도 사용자에게 전달되지 않습니다.1
- UI가 필요한 서비스는 UI 앱을 별도 프로세스(사용자 세션 쪽)로 분리하고 IPC로 대화하는 구성이 공식적으로 안내되는 해결책입니다. 태스크 스케줄러에서 대화형 사용자로 시작하는 방법도 선택지에 들어갑니다.2
- Windows Server(RDS)는 여러 동시 세션을 전제로 만들어졌지만, Windows 10/11 Pro 등의 클라이언트 OS는 기본적으로 단일 세션입니다. Azure Virtual Desktop 전용의 ‘Windows Enterprise 멀티 세션’ 에디션을 제외하면, 일반 클라이언트 OS에서 동시에 여러 대화형 세션을 갖는 것은 전제되어 있지 않습니다.3
- 명명된 커널 개체(Mutex, 이벤트, 세마포어 등)에는 세션별 네임스페이스가 있습니다. 컴퓨터 전체・세션을 넘어 하나로 제한하고 싶다면
Global\프리픽스를 명시하지 않으면 의도한 대로 중복 실행을 막을 수 없습니다. 명명된 파이프는 이 구조의 대상이 아니며, 기본적으로 컴퓨터 전체에서(서버 서비스가 동작 중이면 원격에서도) 접근할 수 있는 별도의 네임스페이스를 쓰기 때문에, 세션이나 사용자로 구분하고 싶다면 파이프 이름 자체에 세션 ID 등을 포함해야 합니다.45 - 현재 세션의 종류는
SESSIONNAME환경 변수나GetSystemMetrics(SM_REMOTESESSION)으로 판정할 수 있습니다. 다만 RemoteFX vGPU 사용 시 등 판정이 본래 의도와 어긋나는 경우가 있으므로 과신은 금물입니다.67 - 공유 PC・RDS 환경에서의 동작을 요구사항에 포함할지는 설계 초기 단계에서 확인해야 할 사항입니다. 임시 파일 경로 충돌, AppData 혼동, 장치・동글에 대한 동시 접근은 단독 PC를 전제로 만든 앱이 RDS에 올라간 순간 터지는 전형적인 사고입니다(7장).
2. Session 0 분리란 무엇인가 ── 왜 사용자 세션과 나누었는가
Windows Vista / Windows Server 2008 이전에는 서비스와 처음 로그온한 사용자가 같은 세션 0을 공유했습니다. 같은 세션의 프로세스끼리는 권한과 무관하게 창 메시지를 주고받을 수 있었습니다. 표준 권한 프로세스가 SYSTEM 권한으로 동작하는 서비스의 창에 조작된 메시지를 보내 서비스 쪽 처리를 가로채 권한을 상승시키는 ── 창 메시지 처리의 결함을 파고드는 이 계열의 권한 상승은 2000년대에 반복적으로 보고되었으며, 실제로 Windows 커널의 창 메시지 처리(win32k.sys) 결함을 이용한 권한 상승 취약점이 여러 차례 수정되었습니다.8
Windows Vista 이후로는 이 구조가 바뀌었습니다. Session 0는 서비스와, 대화형 사용자 세션에 연결되지 않는 애플리케이션 전용이 되었고, 처음 로그온한 사용자는 세션 1, 다음 사용자는 세션 2……라는 형태로 서비스와는 다른 세션에 할당됩니다. Session 0는 대화형 조작을 지원하지 않으며, 서비스에서 앱으로 메시지를 보내는 것도, 앱에서 서비스로 보내는 것도 불가능합니다. 서비스가 대화상자 같은 UI 요소를 직접 표시할 수도 없습니다. 이 변경으로, 표준 권한 프로세스가 서비스의 창에 직접 메시지를 보내는 경로 자체가 구조적으로 막혔습니다.1
즉 Session 0 분리는 단순히 ‘서비스는 뒷단이라 화면이 없다’는 운영상의 제약이 아니라, 권한 경계를 넘나드는 창 메시지 교환을 구조적으로 차단하기 위한 보안 설계입니다. 서비스가 UI를 표시할 수 있는 유일한 수단으로 남아 있는 것이 WTSSendMessage 함수인데, 이는 다른 세션에 간단한 메시지 박스를 표시시키는 API이지만, 복잡한 대화상자나 양방향 대화에는 쓸 수 없습니다.1
3. 서비스는 UI를 표시할 수 없다 ── 실무상의 제약과 회피 패턴
Session 0 분리의 실무상 결과는 단순합니다. 서비스 안에서 MessageBox.Show나 폼 표시를 호출해도, 로그온 중인 사용자의 화면에는 아무것도 표시되지 않습니다. 그뿐 아니라 아무도 누를 수 없는 확인 버튼을 계속 기다리며 처리 전체가 멈추는, 전형적인 사고의 원인이 됩니다. 오래된 코드(Windows XP 시절 ‘대화형 서비스’로 동작했던 것 같은 것)를 서비스로 옮길 때는 UI 표시 호출이 남아 있지 않은지 반드시 확인하세요.
공식적으로 안내되는 해결책은 2가지입니다.2
- UI 앱을 별도 프로세스(대화형 사용자 세션 쪽)로 분리하고, IPC로 대화한다. 서비스 쪽에서 처리를 일으키고, 결과나 입력 요청을 UI 프로세스로 전달합니다. UI 프로세스는 알림 영역 아이콘이나 대화상자로 사용자에게 보여주고, 조작 결과를 서비스로 돌려줍니다. 명명된 파이프 등의 IPC를 사용할 경우, 네트워크 너머로 노출되지 않도록 ACL을 적절히 설계해야 합니다. 여러 사용자가 함께 있는 환경에서는 파이프 이름에 세션 ID를 포함하는 등 세션별로 구분하는 설계가 안내되고 있습니다.2 IPC 수단을 고르는 방법은 같은 날 공개된 자매글 “프로세스 간 통신의 판단표“를 참조하세요. 서비스 자체를 만드는 방법이나, 시작 유형・실행 계정・복구 옵션 같은 운영 측면의 설계는 “Windows 서비스 만드는 방법“에 정리되어 있습니다.
- 태스크 스케줄러에서 대화형 사용자로 시작한다. 서비스로 상주시키는 대신, 로그온 중인 사용자의 권한・세션에서 UI를 가진 프로그램을 시작하는 구성입니다. 여기서 주의가 필요한 것은, ‘SYSTEM으로 등록한 태스크’ 자체가 UI를 표시하는 쪽이 되면 안 된다는 점입니다. SYSTEM 계정에는 대화형 로그온 권한이 없어서, SYSTEM 권한으로 실행되는 프로그램이나 태스크를 사용자가 보거나 조작할 수 없습니다.9 상승된 태스크 모델은 본래, 표준 사용자가 시작할 수 있는 보안 기술자를 가진 SYSTEM 태스크에 관리자 권한이 필요한 처리만을 맡기고, UI 자체는 로그온 중인 사용자의 권한・세션에서 동작하는 별도 프로그램(또는 같은 처리를 사용자 계정의 태스크로 등록한 것)에 맡긴다는 역할 분담을 전제로 합니다.10 SYSTEM 태스크는 ‘UI를 표시하지 않는 브로커’에 철저히 맡기고, 눈에 보이는 UI는 반드시 사용자 자신의 세션에서 동작하는 프로세스가 담당하게 하세요.
어느 방식이든, ‘서비스는 뒷단, UI는 별도 프로세스’라는 전제를 처음부터 설계에 넣어 두는 것이 핵심입니다. 나중에 추가하려고 하면 서비스 쪽에 UI 의존 코드가 배어 있어서 분리 비용이 급증한다는 것이 실무에서 자주 보는 전개입니다.
4. RDP 접속 시 세션의 동작 ── RDS와 클라이언트 OS의 차이
‘서버와 클라이언트에서 같은 RDP인데 동작이 다르다’는 혼란은 Remote Desktop Services(RDS) 역할이 있는지에서 비롯됩니다.
Windows Server + RDS 역할은 하나의 서버에 여러 사용자가 동시에 로그온하여, 각자 독립된 세션에서 데스크톱이나 앱을 사용하는, 여러 동시 세션을 전제로 한 구성입니다.11 RDS에서는 일반적인 RDP 관리용 접속(관리자용)은 2세션까지 CAL 없이 사용할 수 있지만, 그 이상의 대수나 일반 사용자의 동시 사용에는 RD Session Host 역할 도입과 적절한 RDS CAL(클라이언트 액세스 라이선스)이 필요합니다.12 CAL에는 per device / per user 두 방식이 있으며, 라이선스 모델의 세부 사항은 환경별로 확인이 필요합니다.13
한편, Windows 10/11 Pro・Enterprise 등의 일반 클라이언트 OS는 RDS 역할이 없고, 기본적으로 단일 세션을 전제로 합니다. ‘Windows Enterprise 멀티 세션’이라는, 여러 대화형 세션을 동시에 가질 수 있는 클라이언트 OS용 특별 에디션도 존재하지만, 이는 Azure Virtual Desktop 전용으로 제공되는 것이며, 일반적인 온프레미스 배포 클라이언트 OS에서는 여러 사용자의 동시 세션이 전제되어 있지 않습니다.3 실무적으로는, ‘RDP로 사내 담당자의 PC에 접속하니 현지 화면이 잠금 화면으로 바뀌었다’는 체감 그대로, 클라이언트 OS에서는 콘솔(현지의 물리적 조작)과 원격 접속이 동시에 공존하는 구성은 기본적으로 성립하지 않는다고 이해하는 것이 안전합니다.
업무 앱 설계에서 짚어 둬야 할 차이를 정리합니다.
| 관점 | Windows Server + RDS | 클라이언트 OS(Windows 10/11 Pro 등) |
|---|---|---|
| 동시 세션 | 여러 사용자가 동시에 로그온 가능11 | 기본적으로 단일 세션. AVD 전용 멀티 세션 에디션을 제외하면 여러 대화형 세션 비지원3 |
| 라이선스 | RD Session Host + RDS CAL 필요(관리용 접속 2개 제외)12 | Remote Desktop 기능 자체는 OS에 포함되지만, 라이선스 요건・허용되는 에디션은 별도 확인 필요 |
| 예상 용도 | 공유 단말에서의 업무 앱 운영, 씬 클라이언트 환경 | 개인 PC로의 원격 유지보수・재택근무 시 접속 |
이 차이를 감안하지 않고 ‘개발기의 클라이언트 OS에서 동작했으니 공유 서버의 RDS 환경에서도 똑같이 동작할 것’이라고 판단하면, 7장에서 다룰 다중 사용자 동시 실행 특유의 결함을 놓치게 됩니다. 반대로 클라이언트 OS에서의 단일 세션 운영만 전제한다면, 공유 PC 대응을 위한 설계 비용을 처음부터 들일 필요는 없습니다. 이 판단은 요구사항 정의 초기 단계에서 해 둬야 합니다(8장).
5. 현재 세션을 판정한다
자신의 프로세스가 지금 어떤 세션에서 동작하고 있는지 알고 싶은 상황은 많습니다. 백그라운드 처리에서 무거운 렌더링 효과를 억제하고 싶다거나, 원격 세션에서는 대역을 쓰는 기능을 비활성화하고 싶다거나 하는 경우입니다. 판정 수단은 여러 가지가 있습니다.
SESSIONNAME환경 변수: 콘솔 세션에서는Console, RDP 접속에서는RDP-Tcp#로 시작하는 이름이 설정됩니다.qwinsta명령으로 목록 표시되는SESSIONNAME열과 같은 정보입니다.14 간단한 판정에 쓸 수 있지만, 공식 API가 아니므로 과도하게 의존하지 않는 것이 안전합니다.GetSystemMetrics(SM_REMOTESESSION): 원격 세션인지 판정하는 Win32 API입니다. 호출한 프로세스가 Terminal Services 클라이언트 세션에 연결되어 있으면 0이 아닌 값을 반환합니다. 다만 Windows 8/Server 2012 이후, RemoteFX vGPU를 사용한 원격 세션에서는 이 함수가 원격 세션을 로컬 세션으로 오인한다는 제약이 있습니다. 그 경우 레지스트리의GlassSessionId와 현재 세션 ID를 비교하는 대체 수단이 안내되고 있습니다.67- .NET(WPF)의
SystemParameters.IsRemoteSession:GetSystemMetrics(SM_REMOTESESSION)을 호출하는 것과 같은 판정을, WPF 앱에서 속성으로 가져올 수 있습니다.15 WinForms나 콘솔 앱에서는 P/Invoke로 직접 호출하는 형태가 됩니다. WTSGetActiveConsoleSessionId: 물리 콘솔에 연결된 세션 ID를 가져오는 API입니다. 자신의 세션 ID와 비교하면 ‘자신은 물리 콘솔에서 동작하고 있는가’를 판정할 수 있습니다. 물리 콘솔에 아무도 연결되지 않은 상태(전환 중)에서는0xFFFFFFFF가 반환된다는 점에 주의하세요.16
이 WTSGetActiveConsoleSessionId를 RDS 환경에서의 세션 식별에 써서는 안 됩니다. 이름 그대로 반환되는 것은 ‘물리 콘솔에 연결된 세션’뿐이며, RDP를 통해 로그온한 사용자의 세션은 대상이 아닙니다.16 서비스가 ‘대화형 사용자의 컴패니언 UI를 어떤 세션에 표시해야 하는가’를 알고 싶은 경우, 콘솔 접속 사용자만 상정한다면 이 API로 충분하지만, RDS에서 여러 RDP 사용자가 동시 로그온하는 환경에서는 WTSEnumerateSessions로 동작 중인 세션을 열거하거나17, 세션 변경 알림(WM_WTSSESSION_CHANGE)이 전달하는 세션 ID를 그대로 써야 합니다. 이 혼동은 RDP 경유 사용자에게만 알림 UI가 나오지 않거나, 잘못된 세션에 나오는, 발견하기 어려운 결함의 전형적인 원인이 됩니다. 3장의 IPC 구성에서 UI 프로세스를 어떤 세션에 시작할지 결정할 때는 이 구분을 반드시 의식하세요.
6. 명명된 개체의 세션 분리
Mutex, 이벤트, 세마포어, 대기 가능 타이머, 파일 매핑 개체 같은 명명된 커널 개체에는 세션별로 독립된 네임스페이스가 있습니다. 어떤 세션에서 MyAppMutex라는 이름의 Mutex를 만들어도, 다른 세션에서 같은 이름으로 열려고 하면 그것은 별개의 것으로 새로 만들어집니다. 서비스처럼 주로 Session 0에서 동작하는 프로세스, 또는 여러 세션을 넘어 개체를 공유하고 싶은 클라이언트/서버 구성을 위해, 이름 앞에 Global\을 붙이면 명시적으로 전역 네임스페이스에 배치할 수 있습니다. 반대로 Local\을 붙이면 명시적으로 자신의 세션 네임스페이스에 배치됩니다.4
이것이 실무에서 효과를 발휘하는 것이 중복 실행 방지를 위한 Mutex입니다.
- ‘같은 사용자가 같은 세션 안에서 앱을 2개 실행할 수 없게 하고 싶다’는 정도라면, 기본(프리픽스 없는) Mutex 이름으로 충분합니다. 세션별 네임스페이스가 자동으로 작동하기 때문에, 다른 사용자의 세션에서는 별개의 Mutex로 취급됩니다.
- ‘RDS 환경에서 여러 사용자가 동시에 로그온해 있어도, 컴퓨터 전체에서 앱 인스턴스를 하나로 제한하고 싶다’는 경우는,
Global\을 명시하지 않으면 목적을 달성할 수 없습니다. 기본값 그대로면 사용자마다 하나씩 실행될 수 있어서, ‘중복 실행 방지가 되어야 하는데 여러 개가 실행됐다’는 결함이 됩니다.
또한, Session 0가 아닌 프로세스가 파일 매핑 개체나 심볼릭 링크 개체를 전역 네임스페이스에 새로 만들려면 SeCreateGlobalPrivilege 권한이 필요합니다(기존 개체를 여는 것만이라면 불필요). 이 권한 검사는 파일 매핑・심볼릭 링크에 한정된 이야기이며, Mutex나 이벤트를 만드는 데는 부과되지 않지만, 공유 메모리(메모리 매핑 파일)를 전역 네임스페이스에서 쓰는 설계라면 기억해 둘 필요가 있습니다.4
또한 클라이언트/서버형 앱 전반에 대한 원칙으로, ‘한 대의 컴퓨터에서의 접속’을 ‘하나의 사용자 세션’과 동일시하지 말라는 것이 안내되고 있습니다. 같은 컴퓨터에서 여러 세션(여러 사용자의 RDP 접속)이 동시에 걸릴 가능성을 감안해, 서버 쪽은 ProcessIdToSessionId 등으로 세션을 식별하고, 세션별로 별도의 통신 채널을 준비하는 설계가 권장됩니다.18
7. 공유 PC・RDS 환경에서 흔히 발생하는 설계 실수
단독 PC의 콘솔 이용만 상정하고 만들어진 앱이 공유 PC나 RDS 환경에 올라간 순간 터지는 결함에는 몇 가지 정형화된 패턴이 있습니다.
- 임시 파일 경로의 충돌. RDS는 기본적으로 세션마다 개별 임시 폴더(사용자 프로필 아래, 이름에 세션 ID를 포함)를 만듭니다.19 즉
%TEMP%환경 변수를 그대로 쓰고 있으면 이 분리의 혜택을 그대로 받을 수 있습니다. 사고가 생기는 것은%TEMP%를 쓰지 않고C:\Work\temp같은 고정 경로를 앱이 하드코딩한 경우입니다. 같은 사용자 계정의 여러 세션, 또는 여러 사용자가 같은 고정 경로에 동시에 쓰면 당연히 파일을 두고 다투게 됩니다. - AppData 혼동. 사용자 설정・캐시・로그를 어디에 저장할지는, Windows 사용자 프로필 구조(로컬/로밍,
%LOCALAPPDATA%와%APPDATA%의 구분)를 이해한 뒤 정해야 합니다. 공유 PC에서는 이 설계의 미흡함이 ‘다른 사람의 설정으로 내 화면이 바뀐다’, ‘로그가 덮어써진다’는 형태로 드러나기 쉬워집니다. 자세한 내용은 “Windows 사용자 프로필 입문“을 참조하세요. - 장치・동글・시리얼 포트에 대한 동시 접근 전제. RDS 세션에서 클라이언트 쪽의 로컬 장치나 시리얼 포트에 접근하려면 리다이렉션 메커니즘을 거쳐야 합니다. 물리 USB 동글이나 시리얼 통신 장치를 쓰는 앱을 여러 세션에서 동시에 쓰게 하려고 하면, 애초에 하드웨어 쪽이 동시 접근을 지원하지 않거나, 리다이렉트 설정에 따라 보이거나 보이지 않는다는 문제에 부딪힙니다. 클라이언트 쪽에 연결된 장치가 서버 쪽 세션에서 어떻게 보이는지는 리다이렉션 구성에 의존하므로, 설계 단계에서 확인이 필수입니다.20
- 프린터・드라이브의 하드코딩. 기본 프린터 이름이나 드라이브 문자를 앱 안에 하드코딩하면, 사용자마다 리다이렉트되는 기본 프린터나 매핑된 드라이브가 다른 공유 PC 환경에서는 쉽게 무너집니다. 프린터 이름은 실행 시에 가져오고, 드라이브는 가능한 한 UNC 경로로 다루는 것이 안전합니다.
- HKCU・사용자 레지스트리를 잘못 읽는 실수.
HKEY_CURRENT_USER는 세션이 아니라 로그온 사용자에 연결된다는 점도 주의가 필요합니다. 같은 사용자가 여러 세션을 갖는 구성(RDS에서 같은 계정을 공유하는 등)에서는 HKCU 값이 모든 세션에서 공유되므로, ‘세션마다 독립시키고 싶은 런타임 상태’를 HKCU에 두면 의도치 않게 다른 세션에 영향을 줍니다. 세션별 분리가 필요한 정보는 6장의 세션 네임스페이스나, 세션 ID를 포함한 파일 경로로 다뤄야 합니다.
이들은 모두 ‘단독 PC라면 알아채지 못한다’, ‘여러 사용자가 동시에 사용해야 비로소 드러난다’는 성질의 결함이며, 테스트 환경이 단독 PC의 개발기뿐이면 실제 운영의 공유 PC・RDS 환경에서 처음 재현되는 전개가 되기 쉽습니다.
8. 판단표 ── ‘공유 PC/RDS 대응’을 요구사항에 포함해야 하는가
공유 PC・RDS 환경에서의 동작을 요구사항에 포함할지는 구현이 진행되기 전, 요구사항 정의 단계에서 확인해 둬야 할 사항입니다. 포함하기로 정했다면, 다음 관점을 체크리스트로 사용하세요.
| 관점 | 단독 PC・콘솔 전제로 괜찮은 경우 | 공유 PC/RDS(다중 사용자 동시) 대응이 필요한 경우 |
|---|---|---|
| 임시 파일・설정 | 고정 경로여도 실제 피해가 잘 나지 않음 | %TEMP% / %LOCALAPPDATA% 등 per-user/per-session 경로를 필수화(7장) |
| 명명된 개체 | 기본값(세션 네임스페이스) 그대로 괜찮음 | ‘컴퓨터 전체에서 하나’라면 Global\을 명시. ‘세션마다 하나’면 기본값 그대로. ‘같은 사용자의 여러 세션을 묶어서 하나’로 하려면 Global\에 사용자 SID를 결합(6장, 중복 실행 방지 글도 참조) |
| 장치・동글 | 로컬 직결로 단순 | 리다이렉트를 거치게 되는 것, 동시 접근 불가 가능성을 전제로 설계 |
| UI와 서비스 | 같은 데스크톱에서 완결하기 쉬움 | Session 0 분리를 감안해 별도 프로세스+IPC로 구성(3장) |
| 라이선스・과금 | 대수 기준으로 단순하게 셀 수 있음 | 동시 세션 수의 개념, RDS CAL 필요 여부를 요구사항 정의 시 확인13 |
판단의 축은 단순합니다. ‘이 앱은 담당자 1명의 PC에서 완결되어 동작하면 되는가, 아니면 공유 단말이나 RDS 환경에서 여러 담당자가 동시에 사용할 가능성이 있는가’를 개발 초기 단계에서 발주 측과 합의해 둬야 합니다. 이를 모호하게 둔 채 구현을 진행하면, 위의 관점을 전부 다시 만들어야 하는 처지가 됩니다.
9. 구현 예 ── 세션 판정과 네임스페이스 구분 사용
5장・6장에서 소개한 판정 방법과 Mutex 이름 짓기의 구분 사용을 .NET 코드로 정리합니다.
using System.Runtime.InteropServices;
internal static class SessionInfo
{
[DllImport("user32.dll")]
private static extern int GetSystemMetrics(int nIndex);
private const int SM_REMOTESESSION = 0x1000;
// WPF 앱이라면 System.Windows.SystemParameters.IsRemoteSession이 같은 판정을 반환한다
public static bool IsRemoteSession() => GetSystemMetrics(SM_REMOTESESSION) != 0;
// 간이 판정. 공식 API가 아니므로 중요한 분기에는 쓰지 말고 로그・진단 정보용으로 남겨 둔다
public static string? SessionName() =>
Environment.GetEnvironmentVariable("SESSIONNAME");
}
중복 실행 방지를 위한 Mutex는, ‘컴퓨터 전체에서 하나’, ‘세션마다 하나’, ‘같은 사용자의 여러 세션을 묶어서 하나’의 3가지를 구분해서 네임스페이스를 나눕니다. 3번째(사용자 단위・세션 횡단)는 기본 Global\만으로는 실현할 수 없고, 이름에 사용자 SID를 포함해야 한다는 점은 “Windows 앱의 중복 실행 방지“에서 자세히 다루고 있습니다.
// using System.Security.AccessControl;와 using System.Security.Principal;가 필요
// (NuGet 패키지 System.Threading.AccessControl도 필요)
// 의도: RDS/공유 PC에서 여러 사용자가 동시 로그온해 있어도, 컴퓨터 전체에서 인스턴스는 하나.
// .NET의 Mutex/MutexAcl.Create는 기존 Mutex를 여는 경우에도 내부적으로 SYNCHRONIZE・
// MUTEX_MODIFY_STATE에 더해 DELETE/READ_CONTROL/WRITE_DAC/WRITE_OWNER까지 요구한다.
// 그래서 '인증된 사용자'에게 FullControl 상당을 허용하지 않으면, 만든 사람 이외의
// 정상 사용자가 createdNew 판정 시점에서 UnauthorizedAccessException이 발생한다.
// (대가로, 인증된 누구나 나중에 ACL을 다시 쓸 수 있는 상태가 되기는 한다. 그것도
// 허용할 수 없다면 Mutex 클래스를 쓰지 않고, P/Invoke로 CreateMutexEx를 직접 호출해
// 필요 최소한의 액세스 권한만 요구하는 설계로 바꿀 것)
//
// 주의: 이 ACL은 자신이 먼저 Mutex를 만들 수 있었던 경우에만 적용된다. 고정된 이름의
// Global\ Mutex는 다른 사용자가 먼저 만들어버리는(이름 스쿼팅) 위험이 있고,
// 그 경우 createdGlobal이 false가 되거나, 상대의 ACL에 따라
// UnauthorizedAccessException이 될 수 있다. 정말로 막고 싶다면 추측하기 어려운
// 이름이나 다른 배타 제어와 함께 쓸 것(자세한 내용은 자매글 5장 참조)
var globalMutexSecurity = new MutexSecurity();
globalMutexSecurity.AddAccessRule(new MutexAccessRule(
new SecurityIdentifier(WellKnownSidType.AuthenticatedUserSid, domainSid: null),
MutexRights.FullControl,
AccessControlType.Allow));
using var globalMutex = MutexAcl.Create(
initiallyOwned: false,
name: @"Global\KomuraSoft.MyApp.SingleInstance",
createdNew: out bool createdGlobal,
mutexSecurity: globalMutexSecurity);
// 의도: 세션마다 하나만 있으면 된다(같은 사용자의 여러 세션은 각각 별도로 취급해도 된다).
// 기본 세션 네임스페이스 그대로 둔다
using var perSessionMutex = new Mutex(
initiallyOwned: false,
name: "KomuraSoft.MyApp.SingleInstance",
createdNew: out bool createdPerSession);
if (!createdGlobal)
{
// 다른 세션을 포함해, 컴퓨터 어딘가에서 이미 실행되고 있다
return;
}
Global\을 붙인 Mutex를 만드는 것 자체에는 특별한 권한이 필요하지 않습니다(앞서 말한 대로, 권한 검사가 부과되는 것은 파일 매핑 개체와 심볼릭 링크 개체를 새로 만드는 경우로 한정됩니다).4 ‘사용자마다 하나’를 의도하면서 기본 세션 네임스페이스를 그대로 쓰는 것은 잘못입니다. 같은 사용자가 연결이 끊긴 RDP 세션과 새 콘솔/RDP 세션을 둘 다 가진 상황은 드물지 않으며, 그 경우 세션마다 별개의 Mutex로 취급되기 때문에, 기본값 그대로면 같은 사용자가 두 번째 인스턴스를 실행할 수 있게 됩니다. ‘컴퓨터 전체에서 하나’, ‘세션마다 하나’, ‘사용자마다 하나(세션 횡단)’ 중 무엇이 요구사항으로 올바른지는 앱의 성격에 따라 정해지므로, 이름을 짓기 전에 먼저 요구사항을 확인하세요.
10. 정리
Windows의 ‘세션’은 서비스 설계・원격 데스크톱 운영・명명된 개체의 중복 실행 방지라는, 보기엔 제각각인 주제에 공통적으로 등장하는 개념입니다. 짚어야 할 골격은 3가지로 요약할 수 있습니다. 서비스는 Session 0라는 격리된 특수 세션에서 동작하며 UI를 직접 표시할 수 없다는 것. RDS는 여러 동시 세션을 전제로 만들어져 있는 한편, 일반 클라이언트 OS는 단일 세션이 기본이라는 것. 그리고 명명된 개체에는 세션별 네임스페이스가 있어 의도에 따라 Global\의 필요 여부를 판단해야 한다는 것.
공유 PC・RDS 환경에서의 동작을 요구사항에 포함할지는, 구현 후반에 알아채면 되돌리기가 큰 주제입니다. 요구사항 정의 단계에서 ‘이 앱은 누가, 어떤 단말에서, 몇 명이 동시에 사용하는가’를 확인해 두는 것을 강력히 추천합니다. 기존 앱을 공유 PC・RDS 환경에 배포할 계획이 있거나, ‘여러 사용자가 쓰면 이상해진다’는 결함의 원인 조사가 필요한 경우는, 실제 구성을 보면서 좁혀 나가는 편이 빠른 경우가 많으니 상담해 주세요.
관련 글
- Windows 서비스 만드는 방법 ── Worker Service 가이드
- Windows의 프로세스 간 통신을 어떻게 고를까 ── 판단표
- Windows 앱의 중복 실행 방지 ── 명명된 Mutex와 재시작 시 활성화
- Windows 사용자 프로필 입문 - AppData와 NTUSER.DAT
- Windows 앱에서 ‘관리자 권한이 필요한 처리만’ 분리하는 구체적인 작성법
관련 상담 분야
합동회사 코무라소프트는 공유 PC・RDS 환경 배포를 염두에 둔 Windows 앱 설계 리뷰, 서비스화・IPC 분리 설계, 다중 사용자 동시 이용 시 특유의 결함 원인 조사를 다룹니다.
참고 링크
-
Microsoft Learn, Service Changes for Windows Vista. Session 0 분리 도입 배경, 서비스와 앱 사이에 창 메시지를 주고받을 수 없게 된 것,
WTSSendMessage에 의한 대체 수단에 대해. ↩ ↩2 ↩3 -
Microsoft Learn, Interactive Services. 서비스가 사용자와 직접 대화할 수 없는 것,
CreateProcessAsUser로 별도 프로세스의 GUI 앱을 시작해 IPC로 연계하는 설계, 파이프 이름을 세션 ID로 구분하는 안내에 대해. ↩ ↩2 ↩3 -
Microsoft Learn, Windows Enterprise multi-session FAQ. 여러 대화형 세션을 동시에 가질 수 있는 멀티 세션 기능이, 기존에는 Windows Server에서만 가능했던 것, Azure Virtual Desktop 전용으로 제공되는 것에 대해. ↩ ↩2 ↩3
-
Microsoft Learn, Kernel object namespaces. 명명된 커널 개체의 세션별 네임스페이스,
Global\/Local\프리픽스, 파일 매핑・심볼릭 링크 개체를 전역 네임스페이스에 새로 만들 때SeCreateGlobalPrivilege가 필요해지는 것에 대해. ↩ ↩2 ↩3 ↩4 -
Microsoft Learn, Named Pipes. 명명된 파이프가 보안 검사 범위 내에서 어떤 프로세스에서도 접근 가능하며, 서버 서비스가 동작 중이면 원격에서도 도달 가능한 것에 대해(Mutex 등의
Global\/Local\세션 네임스페이스와는 다른 구조라는 근거). ↩ -
Microsoft Learn, GetSystemMetrics function (winuser.h).
SM_REMOTESESSION에 의한 원격 세션 판정 사양에 대해. ↩ ↩2 -
Microsoft Learn, Detecting the Remote Desktop Services environment.
GetSystemMetrics(SM_REMOTESESSION)의 예제 코드와, RemoteFX vGPU 사용 시 원격 세션을 로컬 세션으로 오판정하는 제약,GlassSessionId레지스트리 키에 의한 대체 판정에 대해. ↩ ↩2 -
Microsoft Security Bulletin, MS12-034 - Windows and Messages Vulnerability (CVE-2012-0180). Windows 커널 모드 드라이버(win32k.sys)의 창 메시지 처리 결함을 이용한 권한 상승 취약점에 대해(창 메시지를 통한 권한 상승이라는 공격 계열의 한 예). ↩
-
Microsoft Learn, schtasks create. SYSTEM 계정에는 대화형 로그온 권한이 없어, SYSTEM 권한으로 실행되는 프로그램이나 태스크를 사용자가 보거나 조작할 수 없는 것에 대해. ↩
-
Microsoft Learn, Elevated Task Model. 표준 사용자 애플리케이션이, SYSTEM으로 등록하고 표준 사용자도 시작할 수 있도록 보안 기술자를 구성한 태스크를 통해, 관리자 권한이 필요한 처리를 실행하는 구성에 대해. ↩
-
Microsoft Learn, Remote Desktop Services overview in Windows Server. RDS가 멀티 세션형 세션 기반 데스크톱을 제공하는 기반인 것에 대해. ↩ ↩2
-
Microsoft Learn, Troubleshoot Remote desktop disconnected errors. 관리 용도로는 2개까지의 동시 원격 접속이 RDS CAL 없이 가능한 것, 그 이상의 접속에는 RD Session Host 역할과 적절한 RDS CAL이 필요한 것에 대해. ↩ ↩2
-
Microsoft Learn, License Remote Desktop Services with Client Access Licenses (CALs). RDS CAL의 per device / per user 모델의 차이와, 라이선스 서버에 의한 발행・추적 구조에 대해. ↩ ↩2
-
Microsoft Learn, qwinsta.
SESSIONNAME열이 세션에 할당된 이름(콘솔이면console, RDP 접속이면rdp-tcp#로 시작하는 이름)을 나타내는 것에 대해. ↩ -
Microsoft Learn, SystemParameters.IsRemoteSession Property. WPF에서
SM_REMOTESESSION상당의 판정을 가져올 수 있는 속성에 대해. ↩ -
Microsoft Learn, WTSGetActiveConsoleSessionId function (winbase.h). 물리 콘솔에 연결된 세션 ID 가져오기, 전환 중에는
0xFFFFFFFF를 반환하는 것에 대해. ↩ ↩2 -
Microsoft Learn, WTSEnumerateSessions function (wtsapi32.h). RD Session Host 서버상의 모든 세션을 열거할 수 있는 것에 대해. ↩
-
Microsoft Learn, Client/Server application guidelines. ‘한 대의 컴퓨터에서의 접속’을 ‘하나의 사용자 세션’과 동일시해서는 안 되는 것,
ProcessIdToSessionId에 의한 세션 식별에 대해. ↩ -
Microsoft Learn, Policy CSP - ADMX_TerminalServer (TS_TEMP_PER_SESSION). Remote Desktop Services가 기본적으로 세션마다 개별 임시 폴더(사용자 프로필 아래, 이름에 세션 ID를 포함)를 만드는 것에 대해. ↩
-
Microsoft Learn, Peripheral hardware guidelines. RD Session Host 서버에서 클라이언트 쪽 드라이브・프린터가 리다이렉션 구성에 따라 보이는 방식이 달라지는 것, 장치 드라이버 쪽에서 리다이렉션을 비활성화하는 구조에 대해. ↩
관련 기사
같은 태그를 공유하는 최신 기사입니다. 더 가까운 주제로 지식을 넓힐 수 있습니다.
Windows 앱의 중복 실행 방지 ── 네임드 Mutex와 재실행 시 창 활성화
업무용 Windows 앱의 정석 요건인 「같은 앱을 두 번 실행시키지 않는다」를 네임드 Mutex로 구현하는 방법을 정리합니다. Global\ 과 Local\ 네임스페이스의 차이로 인한 RDP 환경의 함정, 소유 스레드 제약과 AbandonedM...
Windows의 프로세스 간 통신을 어떻게 선택할까 ── 네임드 파이프 / TCP / gRPC / 공유 메모리 / COM 판단표
Windows 앱 사이의 연동 수단을 어떻게 선택할지 정리합니다. 네임드 파이프, 로컬 TCP, gRPC, 공유 메모리, 파일 연동, COM 각각의 강점과 함정을 판단표로 정리하고, UI+서비스 분리・32bit/64bit 브리지・권한 경계 같은 ...
Windows 서비스 만드는 방법과 운영 ── 태스크 스케줄러와의 구분 사용부터 BackgroundService의 서비스화까지
상주 처리를 Windows 서비스로 만들어야 할지, 태스크 스케줄러로 충분한지를 판단표로 정리하고, .NET Worker Service(UseWindowsService)를 이용한 서비스 제작 방법, 실행 계정・복구 옵션・이벤트 로그・안전한 정지 ...
Windows 앱 데이터 저장 위치를 고르는 방법 ── SQLite / JSON / 레지스트리 / Access 판단표
Windows 데스크톱 앱의 데이터를 어디에·무엇으로 저장할지 정리합니다. AppData/ProgramData 구분, SQLite・JSON 파일・레지스트리・Access(.accdb) 각각의 강점과 함정을 판단표로 정리하고, 손상 대책과 비트 수 ...
WinForms/WPF 앱에 Entra ID 인증 넣기 ── MSAL.NET과 WAM 브로커 실무 구성
WinForms/WPF 데스크톱 앱에 Entra ID(구 Azure AD) 인증을 도입하는 절차를 실무 관점에서 정리합니다. 퍼블릭 클라이언트의 개념, ROPC 폐지 현황, 앱 등록, MSAL.NET의 AcquireTokenSilent 패턴, W...
관련 토픽
이 기사와 가까운 토픽 페이지입니다. 기사를 출발점 삼아 관련 서비스와 다른 기사로 이어집니다.
Windows 기술 토픽
Windows 개발, 장애 조사, 기존 자산 활용에 관한 KomuraSoft LLC 기사를 모은 토픽 허브입니다.
이 주제와 연결되는 서비스
이 기사는 다음 서비스 페이지로 이어집니다. 가까운 입구부터 확인해 주세요.
Windows 앱 개발
상주 처리, 장비 연동, 운영 로그, 유지 보수 가능한 구조가 필요한 Windows 데스크톱 애플리케이션을 지원합니다.
기술 상담 & 설계 리뷰
설계 방향, 아키텍처 경계, 수명 관리, 기존 Windows 자산 처리 방법을 정리하는 데 도움을 드립니다.
자주 묻는 질문
이 기사 주제에 대해 상담 시 자주 나오는 질문을 모았습니다.
- Windows 서비스에서 대화상자를 표시할 수 없는 이유는 무엇인가요?
- Windows Vista 이후의 Session 0 분리가 원인입니다. 서비스는 Session 0라는 전용 세션에서 동작하며, 로그온 중인 사용자의 대화형 세션과는 분리되어 있습니다. 이는 표준 권한 프로세스가 SYSTEM 권한으로 동작하는 서비스의 창에 조작된 메시지를 보내 권한을 상승시키는 공격을 구조적으로 차단하기 위한 보안 설계입니다. 서비스 안에서 대화상자를 표시해도 사용자에게는 보이지 않으며, 아무도 누를 수 없는 확인 버튼을 계속 기다리다 처리가 멈추는 사고의 원인이 됩니다. 해결책은 UI 앱을 사용자 세션 쪽의 별도 프로세스로 분리하고 IPC로 대화하는 구성입니다.
- RDP로 접속하면 현지 화면이 잠금 화면으로 전환되는 이유는 무엇인가요?
- Windows 10/11 Pro 등 일반 클라이언트 OS는 기본적으로 단일 세션을 전제로 하기 때문입니다. 콘솔(현지의 물리적 조작)과 원격 접속이 동시에 공존하는 구성은 기본적으로 성립하지 않습니다. 여러 사용자가 동시에 로그온하여 독립된 세션을 사용할 수 있는 것은 Windows Server + RDS(Remote Desktop Services) 역할을 구성한 경우이며, 관리용 접속 2개를 넘는 사용에는 RD Session Host 역할과 RDS CAL이 필요합니다. Azure Virtual Desktop 전용인 'Windows Enterprise 멀티 세션' 에디션은 예외입니다.
- RDS 환경에서 중복 실행 방지용 Mutex가 작동하지 않는 이유는 무엇인가요?
- 명명된 커널 개체(Mutex, 이벤트, 세마포어 등)에는 세션별로 독립된 네임스페이스가 있기 때문입니다. 프리픽스가 없는 Mutex 이름은 다른 사용자의 세션에서는 별개의 Mutex로 취급되어, 사용자마다 하나씩 실행될 수 있습니다. 컴퓨터 전체에서 인스턴스를 하나로 제한하고 싶다면 Global\ 프리픽스를 명시해야 합니다. '같은 사용자의 여러 세션을 묶어서 하나로' 하고 싶다면, Global\에 더해 이름에 사용자 SID를 결합해야 합니다.
- 공유 PC・RDS 환경에서 업무 앱이 오작동하는 대표적인 원인은 무엇인가요?
- 정형화된 패턴이 5가지 있습니다. %TEMP%를 쓰지 않고 고정 경로를 하드코딩해서 생기는 임시 파일 충돌, 사용자 프로필(AppData) 구분의 미흡함, USB 동글이나 시리얼 포트에 동시 접근한다는 전제, 프린터 이름・드라이브 문자의 하드코딩, 그리고 세션마다 독립시켜야 할 상태를 HKCU에 두는 설계입니다. 어느 것이든 단독 PC에서는 알아채지 못하고, 여러 사용자가 동시에 사용해야 비로소 드러나는 성질이기 때문에, 공유 PC・RDS 대응 여부는 요구사항 정의 초기 단계에서 확인해야 합니다.
저자 프로필
기사 저자의 프로필 페이지입니다.
Go Komura
합동회사 코무라소프트 대표
Windows 소프트웨어 개발, 기술 상담, 장애 조사를 중심으로 재현이 어려운 장애 조사와 기존 자산이 남아 있는 프로젝트에 강점이 있습니다.
공개 링크