WOPI 어댑터 연결하기
WOPI 어댑터는 WOPI 프로토콜을 구현한 문서 서버에 Thinkfree Office(이하 웹오피스)를 연결합니다. WOPI(Web Application Open Platform Interface)는 문서를 보관하는 서버(WOPI 호스트)와 문서를 열고 편집하는 웹 앱(WOPI 클라이언트) 사이의 통신을 정한 표준 프로토콜입니다. 이미 WOPI 호스트로 동작하는 문서 관리 시스템이 있다면 저장소를 바꾸지 않고 웹오피스를 편집기로 연결할 수 있습니다.
WOPI 호스트는 웹오피스의 discovery에서 실행 주소를 확인하고 문서별 access token을 준비합니다. 브라우저는 이 정보로 웹오피스 편집기를 열고, 웹오피스는 호스트에 직접 문서 정보를 요청하고 파일을 읽고 저장합니다. 그래서 이 가이드는 관리자 페이지에 어댑터를 등록하는 방법과 함께, WOPI 호스트에서 웹오피스를 여는 방법까지 설명합니다.
연결 정보 준비하기
WOPI 호스트의 파일 엔드포인트 주소와 웹오피스 주소를 준비합니다.
| 항목 | 준비할 값 |
|---|---|
| 파일 엔드포인트 | WOPI 호스트가 파일 요청을 받는 주소. 웹오피스가 {FILE_ENDPOINT}/{FILE_ID} 형식으로 호출합니다.예: https://wopi.example.com/wopi/files. 웹오피스 서버에서 접근할 수 있어야 합니다. |
| 웹오피스 주소 | 사용자의 브라우저가 편집기에 접근하는 웹오피스 주소. 경로 없이 스킴, 호스트와 포트만 사용합니다. 예: http://localhost:8080 |
WOPI 호스트는 다음 WOPI 작업을 구현해야 합니다.
웹오피스는 접근 토큰을 access_token 쿼리 파라미터와 Authorization: Bearer 헤더로 함께 보내므로, 호스트는 둘 중 한 가지로 토큰을 검증합니다.
| WOPI 작업 | 요청 | 용도 |
|---|---|---|
| CheckFileInfo | GET {FILE_ENDPOINT}/{FILE_ID} | 문서 이름, 크기, 권한과 사용자 이름 조회. 문서 열기에 필수 |
| GetFile | GET {FILE_ENDPOINT}/{FILE_ID}/contents | 문서 내용 다운로드. 문서 열기에 필수 |
| Lock, RefreshLock, Unlock | POST {FILE_ENDPOINT}/{FILE_ID}와 X-WOPI-Override 헤더 | 편집 중 lock 설정, 만료 시간 갱신과 해제 |
| PutFile | POST {FILE_ENDPOINT}/{FILE_ID}/contents와 X-WOPI-Override: PUT 헤더 | 편집한 문서 전체 저장 |
웹오피스는 CheckFileInfo 응답에서 다음 항목을 사용합니다.
| CheckFileInfo 항목 | 웹오피스의 사용 |
|---|---|
BaseFileName | 편집기에 표시할 문서 이름. 확장자로 문서 형식을 판단합니다. |
Size | 문서 크기 |
UserCanWrite | true일 때만 저장할 수 있습니다. |
UserFriendlyName | 협업자 목록과 커서에 표시할 사용자 이름. 없으면 UserId를 사용합니다. |
WOPI 어댑터 추가하기
아래 캡처는 관리자 페이지의 입력 예입니다. WOPI Host URL에는 사용하는 호스트의 파일 엔드포인트를 입력합니다.

- 웹오피스 관리자 페이지의 외부연동에서 어댑터 추가를 누릅니다.
- 어댑터 유형에서 WOPI를 선택합니다.
- 다음 연결 정보를 입력합니다.
| 항목 | 입력할 값 |
|---|---|
| 이름 | wopi. 편집기 실행 페이지가 이 이름으로 어댑터를 찾으므로 다른 이름을 사용하지 않습니다. |
| 설명 | 어댑터의 용도. 미리 입력된 설명을 그대로 두어도 됩니다. |
| WOPI Host URL | 파일 엔드포인트. 경로까지 입력합니다. 예: https://wopi.example.com/wopi/files |
| WOPI Client Domain | 웹오피스 주소. 경로 없이 입력합니다. 예: http://localhost:8080 |
- 등록을 누릅니다. WOPI는 문서별 접근 토큰이 있어야 호스트를 호출할 수 있으므로 연결 테스트 없이 등록합니다.
- 어댑터 목록에서
wopi가 실행 중인지 확인합니다.
WOPI Host URL은 웹오피스가 문서를 열 때 요청을 보내는 유일한 주소입니다.
호스트가 보낸 WOPISrc에서 파일 ID를 제외한 앞부분이 이 값과 다르면 웹오피스는 문서 열기를 거부합니다.
WOPI Client Domain은 discovery XML의 편집기 실행 주소 앞부분으로 사용됩니다.
discovery XML은 실행 중인 WOPI 어댑터 1개의 WOPI Client Domain을 사용합니다. 호스트를 바꾸려면 등록한 어댑터를 수정합니다.
discovery 확인하기
어댑터를 등록한 뒤 웹오피스 주소에 /hosting/discovery를 붙여 요청하면 WOPI 호스트가 읽을 discovery XML을 확인할 수 있습니다.
curl http://localhost:8080/hosting/discovery
응답에는 파일 확장자별 편집기 실행 주소와 요청 검증용 공개 키가 들어 있습니다. 아래는 응답의 일부입니다.
<wopi-discovery>
<net-zone name="external-http">
<app name="writer">
<action default="true" ext="docx" name="edit" requires="update,locks" urlsrc="http://localhost:8080/hosting/word.html?"/>
<action default="true" ext="dotx" name="view" urlsrc="http://localhost:8080/hosting/word_v.html?"/>
</app>
<app name="calc">
<action default="true" ext="xlsx" name="edit" requires="update,locks" urlsrc="http://localhost:8080/hosting/spreadsheet.html?"/>
</app>
<app name="presentation">
<action default="true" ext="pptx" name="edit" requires="update,locks" urlsrc="http://localhost:8080/hosting/presentation.html?"/>
</app>
</net-zone>
<proof-key value="..." modulus="..." exponent="AQAB" oldvalue="..." oldmodulus="..." oldexponent="AQAB"/>
</wopi-discovery>
urlsrc의 앞부분은 관리자 페이지에 입력한 WOPI Client Domain입니다.
WOPI 어댑터가 없거나 중지된 상태에서는 503 응답과 함께 어댑터를 등록하라는 메시지가 표시됩니다.
WOPI 호스트에서 문서 열기
편집기 실행 주소 선택하기
호스트는 discovery XML의 action 중에서 문서의 확장자(ext)와 동작(name)이 맞는 항목의 urlsrc를 선택합니다.
edit은 편집기, view는 뷰어를 엽니다.
| 문서 | 편집기 urlsrc | 뷰어 urlsrc |
|---|---|---|
Word (docx 등) | {WOPI_CLIENT_DOMAIN}/hosting/word.html? | {WOPI_CLIENT_DOMAIN}/hosting/word_v.html? |
Cell (xlsx 등) | {WOPI_CLIENT_DOMAIN}/hosting/spreadsheet.html? | {WOPI_CLIENT_DOMAIN}/hosting/spreadsheet_v.html? |
Show (pptx 등) | {WOPI_CLIENT_DOMAIN}/hosting/presentation.html? | {WOPI_CLIENT_DOMAIN}/hosting/presentation_v.html? |
편집기 실행 요청 보내기
호스트는 선택한 urlsrc 뒤에 WOPISrc 쿼리 파라미터를 붙이고, 접근 토큰을 access_token 폼 필드에 담아 브라우저에서 POST로 전송합니다.
WOPISrc에는 파일 엔드포인트 뒤에 파일 ID를 한 구간으로 붙인 주소를 URL 인코딩해 넣습니다.
아래는 파일 ID가 doc-001인 Word 문서를 편집기로 여는 폼 예제입니다.
<form method="post" action="http://localhost:8080/hosting/word.html?WOPISrc=https%3A%2F%2Fwopi.example.com%2Fwopi%2Ffiles%2Fdoc-001">
<input type="hidden" name="access_token" value="{ACCESS_TOKEN}" />
<button type="submit">Open in Office</button>
</form>
웹오피스는 이 요청을 받아 파일 ID와 접근 토큰으로 편집기를 엽니다.
access_token이 없으면 400 응답을 반환합니다.
WOPISrc에서 파일 ID를 제외한 앞부분은 관리자 페이지의 WOPI Host URL과 같아야 합니다. 위 예제에서는 https://wopi.example.com/wopi/files입니다.
접근 토큰은 호스트가 사용자와 문서별로 발급하고 검증하는 값입니다. 웹오피스는 토큰의 내용을 해석하지 않고 호스트에 보내는 모든 요청에 그대로 담습니다.
웹오피스가 호스트에 보내는 요청 확인하기
다음은 문서를 열고 편집·저장할 때 사용하는 요청입니다. 뷰어에서는 문서 정보와 내용을 조회하며, 편집 중에는 Lock·RefreshLock·PutFile·Unlock도 사용합니다. 이 표는 모든 요청이 한 번씩 고정된 순서로 호출된다는 뜻은 아닙니다.
모든 요청에는 access_token 쿼리 파라미터와 Authorization: Bearer 헤더가 붙습니다. 아래 표는 작업별 추가 헤더를 보여 줍니다. 공통 서명 헤더는 요청 검증하기에서 설명합니다.
| 시점 | 요청 | 작업별 추가 헤더 |
|---|---|---|
| 문서를 열 때 | GET {FILE_ENDPOINT}/{FILE_ID} (CheckFileInfo) | - |
| 문서를 열 때 | GET {FILE_ENDPOINT}/{FILE_ID}/contents (GetFile) | - |
| 편집을 시작할 때 | POST {FILE_ENDPOINT}/{FILE_ID} (Lock) | X-WOPI-Override: LOCK, X-WOPI-Lock: TFO:{FILE_ID} |
| 편집 중 10분마다 | POST {FILE_ENDPOINT}/{FILE_ID} (RefreshLock) | X-WOPI-Override: REFRESH_LOCK, X-WOPI-Lock: TFO:{FILE_ID} |
| 저장할 때 | POST {FILE_ENDPOINT}/{FILE_ID}/contents (PutFile) | X-WOPI-Override: PUT, X-WOPI-Lock: TFO:{FILE_ID} |
| 편집을 마치고 문서를 닫을 때 | POST {FILE_ENDPOINT}/{FILE_ID} (Unlock) | X-WOPI-Override: UNLOCK, X-WOPI-Lock: TFO:{FILE_ID} |
문서의 lock ID는 TFO:{FILE_ID}이며, 같은 문서를 여는 사용자들은 같은 값을 보냅니다. 문서에 lock이 없으면 호스트는 요청의 ID로 lock을 설정하고 200을 반환합니다. 이미 lock이 있으면 Lock 요청의 X-WOPI-Lock을 현재 lock ID와 비교해야 합니다. 같으면 만료 시간을 갱신하고 200을 반환합니다. 다르면 409와 함께 현재 lock ID를 X-WOPI-Lock 응답 헤더로 반환합니다.
호스트는 30분 동안 갱신되지 않은 lock을 만료시켜야 합니다. 웹오피스는 편집 중 RefreshLock 요청으로 만료 시간을 갱신합니다. 자세한 규칙은 WOPI Lock을 참고합니다.
PutFile은 편집한 문서 전체를 요청 본문으로 보냅니다.
이 편집 흐름에서 호스트는 X-WOPI-Lock이 현재 lock ID와 같을 때만 저장하고, 성공하면 200을 반환합니다.
요청 검증하기
웹오피스는 호스트에 보내는 WOPI 요청에 X-WOPI-Proof, X-WOPI-ProofOld, X-WOPI-TimeStamp 헤더를 붙입니다.
호스트는 discovery XML의 proof-key에 있는 공개 키로 서명을 검증해야 합니다. 키 교체 시점에는 호스트와 웹오피스가 가진 키 정보가 다를 수 있으므로, 다음 세 조합 중 하나의 검증이 성공해야 합니다.
| 서명 헤더 | 검증에 사용할 공개 키 |
|---|---|
X-WOPI-Proof | 현재 키 |
X-WOPI-ProofOld | 현재 키 |
X-WOPI-Proof | 이전 키 |
X-WOPI-ProofOld를 이전 키로 검증한 결과만으로 요청을 허용하면 안 됩니다. 공개 키의 형식과 서명 검증 방법은 WOPI proof key 검증을 참고합니다.
호스트는 다음 조건도 확인해야 합니다.
X-WOPI-TimeStamp는 요청 시점을 .NET ticks 단위로 나타냅니다. 호스트는 20분보다 오래된 요청을 거부해야 합니다.- 서명 검증과 별개로
access_token의 유효성과 해당 사용자·문서·작업의 접근 권한을 확인합니다. - 검증에 실패하면 문서를 읽거나 변경하지 않고 오류를 반환합니다. 오류 응답의
X-WOPI-ServerError에는 민감정보를 제외한 사유를 넣을 수 있습니다. 웹오피스가 이 값을 로그에 남기므로 원인을 확인하는 데 도움이 됩니다.
연결 확인하기
- 호스트에서 Word 문서를 열어 웹오피스 편집기가 새 창 또는 프레임에 표시되는지 확인합니다.
- 문서를 편집하고 저장한 뒤, 호스트가 PutFile 요청을 받아 문서를 갱신했는지 확인합니다.
- 문서를 닫고 다시 열어 변경 내용이 유지되는지 확인합니다.
- 두 사용자가 같은 문서를 열어 공동 편집이 동작하는지 확인합니다.
문제가 생기면 다음을 확인합니다.
| 증상 | 확인할 것 |
|---|---|
discovery 요청이 503을 반환합니다. | WOPI 어댑터가 등록되어 있고 실행 중인지 확인합니다. |
| 편집기 대신 오류 페이지가 표시됩니다. | 어댑터 이름이 wopi인지, WOPISrc에서 파일 ID를 제외한 앞부분이 WOPI Host URL과 같은지 확인합니다. |
편집기 실행 요청이 400을 반환합니다. | 폼에 access_token 필드가 있는지 확인합니다. |
호스트가 401을 반환합니다. | 호스트의 접근 토큰 검증 로직과 토큰 유효 기간을 확인합니다. |
Lock 요청이 409를 반환합니다. | 요청의 X-WOPI-Lock과 문서에 설정된 lock ID를 비교합니다. 같으면 만료 시간을 갱신하고 200을 반환합니다. 다르면 409와 현재 lock ID를 반환합니다. |
| 문서는 열리지만 저장되지 않습니다. | CheckFileInfo의 UserCanWrite가 true인지, PutFile이 X-WOPI-Lock을 검증한 뒤 200을 반환하는지 확인합니다. |
더 알아보기: WOPI 연동의 동작 흐름
WOPI 호스트는 문서를 보관하고 편집기를 여는 데 필요한 정보를 브라우저에 제공합니다. 브라우저는 웹오피스 편집기를 표시하며, WOPI 클라이언트인 웹오피스가 호스트에 직접 파일을 요청하고 저장합니다.
관리자 페이지에 WOPI 어댑터를 등록하면 웹오피스가 /hosting/discovery에서 WOPI discovery XML을 제공하기 시작하고, 그 뒤의 연동은 그림의 번호 순서로 동작합니다.
- WOPI 호스트가 discovery XML을 읽어 파일 확장자별 편집기 실행 주소(
urlsrc)와 요청 검증용 공개 키(proof-key)를 확인합니다. - 사용자가 호스트에서 문서를 열면, 호스트는 문서 주소(
WOPISrc)와 접근 토큰(access_token)을 담은 폼을 브라우저에 제공합니다. - 브라우저가 폼을 편집기 실행 주소로 POST하면 웹오피스가 편집기를 엽니다.
- 웹오피스는 접근 토큰으로 호스트에 문서 정보와 내용을 요청해 문서를 표시하고, 편집 중에는
Lock·RefreshLock으로 lock을 유지하며, 저장할 때 변경한 문서를 호스트에 전달합니다.