리버스 프록시 레시피와 콜백 계약
모든 리버스 프록시에는 같은 두 조각이 필요합니다. 요청이 유효한 패스를 지녔는지 Caputchin에 묻는 인가자 확인과, 푼 패스를 cpt_gate 쿠키로 바꾸는, 당신 오리진 위의 콜백입니다. nginx 안내가 끝에서 끝까지의 형태를 보여 줍니다. 이 페이지는 다른 프록시들, 정확한 콜백 계약, 그리고 완전한 Authelia 예시를 줍니다. YOUR_SITE_KEY는 전체에서 당신의 공개 키로 바꾸세요.
엔드포인트는 어디서나 같습니다:
- 인가자:
GET https://verify.caputchin.com/v1/gate/authz?site=YOUR_SITE_KEY, 방문자의Cookie헤더를 전달하여.204는 허용,401은 챌린지를 뜻합니다. - 챌린지: 브라우저를
https://verify.caputchin.com/v1/gate/challenge?site=YOUR_SITE_KEY&return=<원래 URL>로 리디렉션합니다.
Traefik
인가자에는 forwardAuth 미들웨어를 씁니다. Traefik은 들어오는 Cookie 헤더를 기본으로 전달합니다. 미들웨어에서 401이 오면, 브라우저를 챌린지로 리디렉션합니다(302를 내는 작은 라우트를 가리키는 에러 페이지 미들웨어, 또는 당신의 엣지에서 401을 처리).
http:
middlewares:
cpt-gate:
forwardAuth:
address: "https://verify.caputchin.com/v1/gate/authz?site=YOUR_SITE_KEY"
# The 401 from forwardAuth is your signal to send the browser to:
# https://verify.caputchin.com/v1/gate/challenge?site=YOUR_SITE_KEY&return=<original-url>
routers:
portal:
rule: "Host(`auth.example.com`)"
middlewares: ["cpt-gate"]
service: your-appCaddy
인가자에는 forward_auth를, 401을 리디렉션으로 바꾸는 데는 handle_errors를 씁니다. 이것은 출발점입니다. 지시어 구문은 당신의 Caddy 버전에 맞게 조정하세요.
auth.example.com {
forward_auth https://verify.caputchin.com {
uri /v1/gate/authz?site=YOUR_SITE_KEY
copy_headers Cookie
}
handle_errors {
@challenge expression {http.error.status_code} == 401
redir @challenge https://verify.caputchin.com/v1/gate/challenge?site=YOUR_SITE_KEY&return={scheme}://{host}{uri} 302
}
reverse_proxy your-app:8080
}콜백
호스팅 챌린지 페이지는 끝에 https://<당신의-오리진>/__cpt/callback으로 POST 폼을 자동 제출하며, 본문에 두 필드, cpt_gate(패스)와 to(방문자를 어디로 보낼지)를 담습니다. 이 한 라우트를 당신 오리진에 더하세요(또는 프록시 위의 전용 location으로). 그것은 반드시:
cpt_gate와to를 POST 본문에서 읽고, URL에서는 결코 읽지 않아야 합니다(URL 안의 패스는 로그와 리퍼러로 샙니다).to가 당신 자신의 오리진 위인지 확인하고, 그 외 무엇이든 거부해야 합니다. 그래야 콜백을 오픈 리디렉션으로 바꿀 수 없습니다.- 아래 속성으로 쿠키를 설정해야 합니다.
to로 (303으로) 리디렉션해야 합니다.
// Any tiny handler on your origin works. Express shown; ~15 lines.
app.post("/__cpt/callback", express.urlencoded({ extended: false }), (req, res) => {
const pass = String(req.body.cpt_gate || "");
const to = String(req.body.to || "/");
const target = new URL(to, `https://${req.headers.host}`);
if (target.host !== req.headers.host) return res.status(400).end(); // same-origin only
res.setHeader(
"Set-Cookie",
`cpt_gate=${pass}; Path=/; HttpOnly; Secure; SameSite=Lax; Max-Age=1800`
);
res.redirect(303, target.pathname + target.search);
});쿠키 속성이 보안 모델의 전부입니다. 패스는 기기에 묶이지 않으므로, HttpOnly(스크립트 절도를 막음), Secure(HTTPS만), SameSite=Lax, 그리고 짧은 Max-Age(당신의 통과 TTL에 맞춤)가 그것을 보호하는 것입니다. 또한 cpt_gate 값을 당신의 액세스 로그에 기록하지 말고, 콜백에 Referrer-Policy: no-referrer를 유지해 패스가 리퍼러 헤더에 얹혀 함께 실려 가는 일이 결코 없게 하세요.
실전 예시: Authelia
Authelia의 로그인 포털은 위젯을 삽입할 자리도 플러그인 훅도 없는, 컴파일된 단일 페이지 앱이므로, 게이트가 그것을 보호하는 방법입니다. Authelia는 이미 리버스 프록시 뒤에 앉아 있고(그 자신의 ForwardAuth가 그렇게 동작합니다), 그 프록시가 게이트가 가는 곳입니다. Authelia 자체는 전혀 수정되지 않은 채로 남습니다.
게이트를 포털의 vhost(예컨대 auth.example.com)에 놓습니다:
- 포털 HTML과 1차 인증 로그인 엔드포인트(
/api/firstfactor)에 게이트를 겁니다. - Authelia 자신의 ForwardAuth verify 엔드포인트(
/api/verify)를 제외합니다. 프록시는 보호된 각 업스트림 요청마다 이것을 호출합니다. 여기에 게이트를 걸면 Authelia 뒤의 보호된 각 앱이 깨집니다. - 헬스 체크와 챌린지 페이지 자체가 필요로 하는 정적 에셋을 제외합니다.
- 위와 같이
/__cpt/callback라우트를 더합니다.
게이트는 봇을 포털에서 떼어 놓습니다. Authelia 내장 Regulation(재시도 한도와 밴)은 통과한 누구에게든 자격 증명 재시도를 여전히 제한하니, 두 층이 겹칩니다. 게이트는 상호작용 포털(로그인과 동의 UI)만 보호합니다. 그것은 비상호작용 토큰 엔드포인트에 게이트를 걸지 않으며, 걸어서도 안 됩니다.
함께 보기
- 프록시 게이트 설정하기: nginx 안내와 미리보기-모드-먼저 롤아웃.
- 개요: 개념과 통과 쿠키.
- 통계: 챌린지 대 통과를 지켜보며 배선을 확인.