Autenticação CAS e SSO no Nginx: Protegendo Qualquer Aplicativo Atrás do Nginx com Casdoor
O nginx open-source não possui um módulo CAS, então "autenticação CAS no nginx" na prática significa uma das duas configurações. Ou o nginx consulta um serviço de autenticação sobre cada solicitação através de seu módulo auth_request, e o Casdoor responde (diretamente em /api/forward-auth, ou através de casdoor-forward-auth ou OAuth2 Proxy), ou o aplicativo atrás do nginx é ele próprio um cliente CAS e conversa com os endpoints do servidor CAS do Casdoor. Se você puder recompilar o nginx, o módulo nginx do Casdoor executa o login dentro do nginx, e se você preferir não executar o nginx, o proxy de site do Casdoor pode ficar na frente do aplicativo. Em todos os casos, a página de login é a do Casdoor, então uma sessão SSO, um conjunto de permissões e uma política MFA cobrem todos os aplicativos.
As opções em resumo
| Opção | Onde a lógica de login roda | O que o nginx precisa | Protocolo para o Casdoor | Quem entra | Escolha quando |
|---|---|---|---|---|---|
| Forward auth do Casdoor | Casdoor, /api/forward-auth | auth_request | Cookie de sessão do Casdoor no domínio do app | Permissões da aplicação, verificadas novamente pelo menos uma vez por minuto | O nginx já está na frente e você não quer serviços extras |
| casdoor-forward-auth | Um pequeno serviço Go | auth_request | Código de autorização OIDC | Funções ou grupos do Casdoor, por localização | Um cookie de sessão compartilhado por muitos subdomínios |
| OAuth2 Proxy | OAuth2 Proxy | auth_request | OIDC | allowed_groups, domínios de e-mail | Você já executa o OAuth2 Proxy |
| casdoor-nginx-module | O próprio nginx | Um módulo compilado | OIDC com PKCE, tokens verificados localmente | Qualquer usuário autenticado do app | Nenhum processo extra, além de tokens bearer para APIs |
| Cliente CAS no app | O aplicativo | Nada, ele apenas faz proxy | CAS 1.0, 2.0, 3.0 | O que o app fizer com os atributos | O app já suporta CAS |
| Proxy de site do Casdoor | Casdoor nas portas 80 e 443 | Sem nginx | Sessão do Casdoor | Permissões da aplicação | Nada mais está na frente do app |
As três primeiras diferem apenas em quem responde à sub-requisição do nginx.
Por que não existe um "módulo CAS para nginx"
CAS é uma conversa entre uma aplicação e o servidor CAS: o app envia o navegador para o /login do servidor com uma URL service, recebe um ticket de serviço de volta nessa URL e valida o ticket com uma chamada servidor-a-servidor. O Apache httpd tem o mod_auth_cas da Apereo para fazer isso no servidor web, mas o nginx não tem um módulo CAS entre seus módulos oficiais. Para o nginx, o bloco de construção padrão é ngx_http_auth_request_module: antes de servir uma requisição, o nginx envia uma sub-requisição para uma URL que você escolhe. Uma resposta 2xx permite a passagem da requisição, 401 ou 403 a nega com esse código, e qualquer outro código é um erro.
Para o usuário, um login CAS e um login OIDC parecem iguais: um redirecionamento para a página de login do Casdoor e de volta. Então, para um app sem suporte a CAS, a resposta prática é OIDC na camada do nginx. Informações básicas sobre o protocolo: O que é CAS.
O módulo auth_request não é construído por padrão: adicione --with-http_auth_request_module ao compilar, ou verifique nginx -V em um nginx empacotado.
Opção 1: Forward auth do próprio Casdoor
O Casdoor pode responder à sub-requisição do nginx sozinho. Você adiciona um Site no Casdoor cujo Domain seja o domínio do app e cujo Casdoor app seja a aplicação onde os usuários fazem login, e adiciona https://app.example.com/caswaf-handler às Redirect URLs dessa aplicação. Host e porta podem permanecer vazios, pois o tráfego não passa pelo Casdoor.
Como auth_request não pode seguir redirecionamentos, o Casdoor responde a uma sub-requisição não autenticada com 401 e coloca a URL de login em Location. O nginx transforma isso em um 302:
server {
server_name app.example.com;
proxy_buffer_size 16k;
proxy_buffers 8 16k;
location = /caswaf-handler {
proxy_pass http://casdoor:8000/api/forward-auth;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $http_host;
proxy_set_header X-Forwarded-Uri $request_uri;
}
location = /casdoor-auth {
internal;
proxy_pass http://casdoor:8000/api/forward-auth;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
proxy_set_header X-Original-URL $scheme://$http_host$request_uri;
proxy_set_header X-Forwarded-Method $request_method;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location / {
auth_request /casdoor-auth;
auth_request_set $casdoor_redirect $upstream_http_location;
auth_request_set $casdoor_user $upstream_http_x_forwarded_user;
auth_request_set $casdoor_email $upstream_http_x_forwarded_email;
auth_request_set $casdoor_groups $upstream_http_x_forwarded_groups;
error_page 401 =302 $casdoor_redirect;
proxy_set_header X-Forwarded-User $casdoor_user;
proxy_set_header X-Forwarded-Email $casdoor_email;
proxy_set_header X-Forwarded-Groups $casdoor_groups;
proxy_pass http://app:3000;
}
}
O backend recebe o usuário em X-Forwarded-User, X-Forwarded-Email e X-Forwarded-Groups. O acesso segue uma permissão na aplicação, verificada novamente pelo menos uma vez por minuto, então um usuário desabilitado perde o acesso rapidamente. Os Public paths do site (webhooks, verificações de saúde) passam sem login.
Consulte Usando seu próprio proxy reverso para a referência e Adicionar login a um app sem OIDC para um tutorial do Grafana. O aplicativo só deve ser acessível através do nginx, caso contrário, qualquer pessoa pode enviar X-Forwarded-User por conta própria.
Opção 2: casdoor-forward-auth ou OAuth2 Proxy
Se você preferir não criar um site Casdoor para cada aplicativo, execute um pequeno serviço que realiza o fluxo OIDC contra o Casdoor e responde à sub-solicitação.
casdoor-forward-auth é o serviço independente do Casdoor para isso (substituiu o traefik-casdoor-auth). Ele mantém o usuário em um cookie assinado, sem estado no servidor. Para o nginx, ele expõe /verify, que responde com 200 e cabeçalhos de identidade ou 401, e o nginx redireciona para seu /login em caso de 401:
server {
server_name app.example.com;
location = /_casdoor_verify {
internal;
proxy_pass http://casdoor-forward-auth:9999/verify?groups=built-in/dev;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
}
location @casdoor_login {
return 302 https://auth.example.com/login?rd=$scheme://$http_host$request_uri;
}
location / {
auth_request /_casdoor_verify;
error_page 401 = @casdoor_login;
auth_request_set $casdoor_user $upstream_http_x_forwarded_user;
auth_request_set $casdoor_roles $upstream_http_x_forwarded_roles;
proxy_set_header X-Forwarded-User $casdoor_user;
proxy_set_header X-Forwarded-Roles $casdoor_roles;
proxy_pass http://app:8080;
}
}
Os parâmetros de consulta roles e groups na URL de verificação restringem cada aplicativo aos detentores de uma função Casdoor ou membros de um grupo; usuários conectados sem essas permissões recebem 403. As funções e grupos são lidos no momento do login e mantidos durante a sessão, portanto, as alterações entram em vigor após o usuário efetuar login novamente. Definir COOKIE_DOMAIN=example.com permite que um único login cubra todos os subdomínios. O Guia do Traefik aborda o mesmo serviço para o Traefik.
O OAuth2 Proxy funciona da mesma maneira com provider = "oidc" e oidc_issuer_url definido como a URL do seu Casdoor. Seu endpoint /oauth2/auth retorna 202 ou 401, os cabeçalhos de usuário e e-mail exigem --set-xauthrequest, e o tratamento de cabeçalhos encaminhados no exemplo de nginx requer --reverse-proxy. Duas configurações do Casdoor são importantes: use o formato de token JWT-Standard, caso contrário, usuários com e-mail não verificado serão rejeitados, e mantenha o escopo profile para que grupos e funções sejam retornados. Consulte OAuth2 Proxy com Casdoor e o guia da edição comunitária do nginx.
Opção 3: o módulo nginx do Casdoor
O casdoor-nginx-module realiza o login dentro do nginx, sem um processo separado. Navegadores sem sessão são direcionados para a página de login do Casdoor; o módulo troca o código com PKCE, verifica a assinatura do token de acesso, exp, aud e tipo contra o certificado do Casdoor, e mantém o usuário em um cookie assinado. Clientes de API podem enviar Authorization: Bearer com um token de acesso do Casdoor, verificado localmente sem uma chamada ao Casdoor.
Ele requer nginx 1.22 ou mais recente e OpenSSL 1.1.1 ou mais recente. Você pode compilá-lo como um módulo dinâmico para o nginx que já executa:
./configure --with-compat --with-http_ssl_module --add-dynamic-module=/path/to/casdoor-nginx-module
make modules
cp objs/ngx_http_casdoor_module.so /etc/nginx/modules/
load_module modules/ngx_http_casdoor_module.so;
events {}
http {
server {
listen 443 ssl;
server_name app.example.com;
ssl_certificate /etc/nginx/app.crt;
ssl_certificate_key /etc/nginx/app.key;
casdoor_endpoint https://door.example.com;
casdoor_client_id <client-id>;
casdoor_client_secret <client-secret>;
casdoor_certificate /etc/nginx/casdoor.pem;
casdoor_redirect_uri https://app.example.com/casdoor/callback;
casdoor_token_location /_casdoor/token;
location = /_casdoor/token {
internal;
proxy_pass https://door.example.com/api/login/oauth/access_token;
proxy_ssl_server_name on;
}
location = /casdoor/callback {
casdoor_callback;
}
location / {
casdoor on;
proxy_set_header X-User $casdoor_user;
proxy_pass http://127.0.0.1:3000;
}
}
}
Substitua os marcadores de posição pelos valores do aplicativo. A sessão não é verificada novamente com o Casdoor até expirar, então desabilitar um usuário entra em vigor quando a sessão termina; casdoor_session_timeout reduz esse intervalo. O módulo não possui verificações de função ou grupo. No NGINX Plus, consulte o guia do NGINX Plus em vez disso.
Opção 4: o aplicativo é um cliente CAS
Se o aplicativo já fala CAS, como um aplicativo Java com o cliente Apereo CAS, o nginx apenas faz proxy e o Casdoor é o servidor CAS. Cada aplicação do Casdoor tem endpoints CAS sob /cas/<organization>/<application>: /login e /logout, /validate para CAS 1.0, /serviceValidate, /proxyValidate e /proxy para 2.0, /p3/serviceValidate e /p3/proxyValidate para 3.0, e /samlValidate para validação SAML 1.1.
O aplicativo envia o navegador para /login?service=..., o Casdoor redireciona de volta com ticket=ST-..., e o aplicativo valida isso:
curl "https://door.example.com/cas/my-org/my-app/p3/serviceValidate?ticket=ST-1234567890&service=https%3A%2F%2Fapp.example.com%2F"
Uma resposta bem-sucedida é um cas:serviceResponse com cas:user e atributos como e-mail, nome de exibição e memberOf; adicione format=json para JSON. Pontos que costumam causar confusão:
- Cada URL de
servicedeve estar listada nos Redirect URLs da aplicação, e oservicena validação deve corresponder exatamente àquele para o qual o ticket foi emitido. - Os tickets funcionam uma única vez e expiram após 5 minutos.
- Os tickets são mantidos na memória, então, com várias réplicas do Casdoor, a validação deve alcançar a réplica que emitiu o ticket (sessões pegajosas).
- Atrás do nginx, passe
HosteX-Forwarded-Protopara que o aplicativo construa a mesma URL deserviceque o navegador viu.
A configuração, incluindo os filtros do cliente Java para cada versão do CAS, está em Conectar um cliente CAS.
Opção 5: deixe o Casdoor ser o proxy reverso
Quando nada mais está na frente do aplicativo, o Casdoor pode servi-lo diretamente: o proxy do site escuta em gatewayHttpPort e gatewayHttpsPort (80 e 443 por padrão) assim que um site existe. Defina o Host do site para o backend, escolha um certificado, defina Casdoor app e aponte o DNS para o Casdoor. Login, permissões e cabeçalhos se comportam como na opção 1. Consulte Sites.
Adicionando MFA
MFA não precisa de nenhuma mudança no nginx ou no aplicativo. O Casdoor solicita o segundo fator em sua própria página de login antes de emitir um código de autorização OAuth ou um ticket de serviço CAS, então todas as opções acima o obtêm. Os usuários podem usar um aplicativo TOTP, SMS, e-mail ou RADIUS, com códigos de recuperação, e os administradores podem exigir MFA para uma organização com Itens MFA. Isso também responde a "CAS 2FA": com o Casdoor como servidor CAS, o segundo fator faz parte do login CAS.
MFA é aplicado no login, não por solicitação: uma sessão que o nginx ou um serviço forward-auth já possui dura até expirar, e o MFA remember time da organização permite que os usuários pulem o segundo fator em um dispositivo confiável por um tempo. Consulte o Guia MFA e O que é MFA.
Depois de conectado ao Casdoor, os usuários acessam outros aplicativos que os enviam para o Casdoor sem um novo prompt de senha; com Auto signin, o Casdoor também pula o seletor de contas. Consulte single sign-on e Single logout.
Qual escolher
- Sem login no aplicativo e nginx já na frente: autenticante direto do Casdoor.
- Um login em vários subdomínios, ou verificações de função por local: casdoor-forward-auth (ou OAuth2 Proxy se você já o utiliza).
- Você compila o nginx por conta própria e também protege APIs com tokens bearer: casdoor-nginx-module.
- O aplicativo suporta CAS: endpoints CAS do Casdoor, nginx como proxy simples.
- Nenhum proxy ainda: proxy de site do Casdoor.
Referências
- nginx: ngx_http_auth_request_module
- OAuth2 Proxy: integração com nginx
- Apereo: Especificação do Protocolo CAS, mod_auth_cas
- Casdoor: casdoor-forward-auth, casdoor-nginx-module
- Documentação do Casdoor: Sites, Conectar um cliente CAS, MFA