OpenID Connect: autorização de aplicativos internos, do genérico ao padrão

Há alguns meses, eu estava implementando um servidor OpenID Connect para controlar o acesso a centenas de nossos aplicativos internos. A partir de nossos próprios desenvolvimentos, convenientes em uma escala menor, passamos ao padrão geralmente aceito. O acesso através de um serviço central simplifica bastante as operações monótonas, reduz o custo da implementação de autorizações, permite encontrar muitas soluções prontas e não quebra o cérebro ao desenvolver novas. Neste artigo, falarei sobre essa transição e os solavancos que conseguimos preencher.

introdução

Era uma vez ... Como tudo começou


Alguns anos atrás, quando havia muitos aplicativos internos para controle manual, escrevemos um aplicativo para controle de acesso dentro da empresa. Era um aplicativo Rails simples, conectado a um banco de dados com informações sobre funcionários, onde o acesso a várias funções era configurado. Em seguida, criamos o primeiro SSO, baseado na verificação de tokens do cliente e do servidor de autorização, o token foi transmitido em forma criptografada com vários parâmetros e verificado no servidor de autorização. Essa não era a opção mais conveniente, pois em cada aplicativo interno era necessário descrever uma camada considerável de lógica, e a base de funcionários era completamente sincronizada com o servidor de autorização.

Depois de algum tempo, decidimos simplificar a tarefa de autorização centralizada. SSO transferido para o balanceador. Usando o OpenResty em Lua, eles adicionaram um modelo que verificou os tokens, sabia em qual aplicativo estava a solicitação e podia verificar se havia acesso lá. Essa abordagem simplificou bastante a tarefa de controlar o acesso de aplicativos internos - no código de cada aplicativo, não era mais necessário descrever lógicas adicionais. Como resultado, fechamos o tráfego externamente e o próprio aplicativo não sabia nada sobre autorização.

No entanto, um dos problemas permaneceu sem solução. E os aplicativos que precisam de informações sobre os funcionários? Você pode escrever uma API para o serviço de autorização, mas precisará adicionar lógica adicional para cada aplicativo. Além disso, queríamos nos livrar da dependência de um de nossos aplicativos auto-escritos, mais orientados à tradução para o OpenSource, em nosso servidor de autorização interno. Falaremos sobre ele outra hora. A solução para os dois problemas foi o OAuth.

Padrões geralmente aceitos


OAuth é um padrão de autorização compreensível e geralmente aceito, mas como sua funcionalidade não é suficiente, o OpenID Connect (OIDC) começou a ser considerado imediatamente. O próprio OIDC é a terceira implementação de um padrão de autenticação aberto que se espalhou para o suplemento pelo protocolo OAuth 2.0 (protocolo de autorização aberta). Essa solução fecha o problema da falta de dados sobre o usuário final e também possibilita alterar o provedor de autorização.

No entanto, não escolhemos um provedor específico e decidimos adicionar a integração com o OIDC para o nosso servidor de autorização existente. A favor dessa solução, o OIDC é muito flexível em termos de autorização do usuário final. Portanto, foi possível implementar o suporte ao OIDC no seu servidor de autorização atual.

imagem

Nossa maneira de implementar nosso próprio servidor OIDC


1) Eles trouxeram os dados para a forma desejada


Para integrar o OIDC, você precisa trazer os dados do usuário atual de uma maneira que seja compreensível para o padrão. No OIDC, isso é chamado de Reivindicações. Marcas são essencialmente campos finais no banco de dados do usuário (nome, email, telefone, etc.). Existe uma lista padrão de marcas e tudo o que não está incluído nesta lista é considerado personalizado. Portanto, o primeiro ponto em que você precisa prestar atenção se desejar escolher um provedor OIDC existente é a capacidade de personalizar convenientemente novas marcas.

O grupo de marcas é combinado no próximo subconjunto - Escopo. Durante a autorização, o acesso é solicitado para não marcas específicas, nomeadamente para escopos, mesmo que algumas das características do escopo não sejam necessárias.

2) Implementou as doações necessárias


A próxima parte da integração do OIDC é a seleção e implementação dos tipos de autorização, as chamadas concessões. O cenário adicional da interação do aplicativo selecionado com o servidor de autorização dependerá da concessão selecionada. Um esquema aproximado para escolher a subvenção certa é apresentado na figura abaixo.

imagem

Para nossa primeira aplicação, usamos a concessão mais comum - o Código de Autorização. Sua diferença em relação aos outros é que são três etapas, ou seja, passa por verificação adicional. Primeiro, o usuário faz um pedido de permissão de autorização, recebe um Token - Código de Autorização e, em seguida, com esse token, como se com um bilhete de viagem, solicitas um token de acesso. Toda a interação principal desse cenário de autorização é baseada em redirecionamentos entre o aplicativo e o servidor de autorização. Leia mais sobre esta concessão aqui .

OAuth adere ao conceito de que os tokens de acesso recebidos após a autorização devem ser temporários e mudar de preferência, em média, a cada 10 minutos. A concessão do Código de Autorização é uma verificação em três etapas através de redirecionamentos; fazer essa etapa a cada 10 minutos não é, francamente, uma experiência agradável para os olhos. Para resolver esse problema, há outra concessão - Refresh Token, que também implantamos. Tudo é mais simples aqui. Durante o teste, de outra concessão, além do token de acesso principal, mais um é emitido - o Refresh Token, que pode ser usado apenas uma vez e sua vida útil, em regra, é significativamente mais longa. Com esse token de atualização, quando o TTL (Time to Live) do token de acesso principal termina, uma solicitação de um novo token de acesso chegará ao terminal de outra concessão. O token de atualização usado é redefinido imediatamente.Essa verificação é feita em duas etapas e pode ser executada em segundo plano, invisivelmente para o usuário.

3) Formatos personalizados de saída de dados do usuário


Após a implementação das concessões selecionadas, a autorização funciona, vale mencionar o recebimento de dados sobre o usuário final. O OIDC possui um terminal separado para isso, no qual você pode solicitar dados do usuário com seu token de acesso atual e, quando relevante. E se os dados do usuário não mudarem com tanta frequência e você precisar seguir as atuais muitas vezes, poderá tomar uma decisão como os tokens JWT. Esses tokens também são suportados pelo padrão. O próprio token JWT consiste em três partes: cabeçalho (informações sobre o token), carga útil (quaisquer dados necessários) e assinatura (assinatura, token é assinado pelo servidor e você pode verificar a origem de sua assinatura no futuro).

Em uma implementação OIDC, um token JWT é chamado id_token. Ele pode ser solicitado junto com um token de acesso regular, e tudo o que resta é verificar a assinatura. O servidor de autorização possui um terminal separado para isso com várias chaves públicas no formato JWK . E por falar nisso, vale ressaltar que existe outro ponto final que, com base no padrão RFC5785, reflete a configuração atual do servidor OIDC. Ele contém todos os endereços de terminais (incluindo o endereço do chaveiro público usado para assinatura), marcas e escopos suportados, algoritmos de criptografia usados, concessões suportadas etc.

Por exemplo no Google:
{
 "issuer": "https://accounts.google.com",
 "authorization_endpoint": "https://accounts.google.com/o/oauth2/v2/auth",
 "device_authorization_endpoint": "https://oauth2.googleapis.com/device/code",
 "token_endpoint": "https://oauth2.googleapis.com/token",
 "userinfo_endpoint": "https://openidconnect.googleapis.com/v1/userinfo",
 "revocation_endpoint": "https://oauth2.googleapis.com/revoke",
 "jwks_uri": "https://www.googleapis.com/oauth2/v3/certs",
 "response_types_supported": [
  "code",
  "token",
  "id_token",
  "code token",
  "code id_token",
  "token id_token",
  "code token id_token",
  "none"
 ],
 "subject_types_supported": [
  "public"
 ],
 "id_token_signing_alg_values_supported": [
  "RS256"
 ],
 "scopes_supported": [
  "openid",
  "email",
  "profile"
 ],
 "token_endpoint_auth_methods_supported": [
  "client_secret_post",
  "client_secret_basic"
 ],
 "claims_supported": [
  "aud",
  "email",
  "email_verified",
  "exp",
  "family_name",
  "given_name",
  "iat",
  "iss",
  "locale",
  "name",
  "picture",
  "sub"
 ],
 "code_challenge_methods_supported": [
  "plain",
  "S256"
 ],
 "grant_types_supported": [
  "authorization_code",
  "refresh_token",
  "urn:ietf:params:oauth:grant-type:device_code",
  "urn:ietf:params:oauth:grant-type:jwt-bearer"
 ]
}


Portanto, usando id_token, você pode transferir todas as características necessárias para a carga útil do token e não entrar em contato com o servidor de autorização todas as vezes para solicitar informações do usuário. A desvantagem dessa abordagem é que a alteração dos dados do usuário do servidor não ocorre imediatamente, mas com um novo token de acesso.

Resultados da implementação


Assim, depois de implementar nosso próprio servidor OIDC e configurar conexões com ele no lado do aplicativo, resolvemos o problema de transmitir informações do usuário.
Como o OIDC é um padrão aberto, temos a oportunidade de escolher uma implementação de provedor ou servidor existente. Tentamos o Keycloak, que se mostrou muito conveniente de configurar. Depois de configurar e alterar as configurações de conexão no lado do aplicativo, ele está pronto para funcionar. No lado do aplicativo, resta apenas alterar a configuração da conexão.

Falando em soluções existentes


Como parte de nossa organização, como o primeiro servidor OIDC, montamos nossa implementação, que foi complementada conforme necessário. Após uma análise detalhada de outras soluções prontas, podemos dizer que esse é um ponto discutível. A preocupação dos provedores com a falta de funcionalidade necessária serviu de solução para a implementação de seu servidor, bem como a presença de um sistema antigo no qual havia várias autorizações personalizadas para alguns serviços e muitos dados foram armazenados sobre os funcionários. No entanto, em implementações prontas, há comodidade para integração. Por exemplo, o Keycloak possui seu próprio sistema de gerenciamento de usuários e os dados são armazenados diretamente nele, e não será difícil ultrapassar seus usuários lá. Para isso, o Keycloak possui uma API que permite implementar totalmente todas as etapas necessárias para a transferência.

Outro exemplo de uma implementação certificada e interessante, na minha opinião, é a Ory Hydra. É interessante, pois consiste em diferentes componentes. Para integração, você precisará vincular seu serviço de gerenciamento de usuários ao serviço de autorização e expandir conforme necessário.

Keycloak e Ory Hydra não são as únicas soluções chave na mão. É melhor selecionar uma implementação certificada do OpenID Foundation. Normalmente, essas soluções têm um selo de certificação OpenID.

Certificação Openid


Além disso, não se esqueça dos provedores pagos existentes, se você não quiser manter seu servidor OIDC. Existem muitas boas opções até o momento.

Qual é o próximo


Num futuro próximo, vamos fechar o tráfego para serviços internos de outra maneira. Planejamos transferir nosso SSO atual no balanceador usando o OpenResty para um proxy baseado no OAuth. Também há muitas soluções prontas aqui, por exemplo:
github.com/bitly/oauth2_proxy
github.com/ory/oathkeeper
github.com/keycloak/keycloak-gatekeeper

Materiais adicionais


jwt.io - bom serviço para verificar tokens
openid.net/developers/certified JWT - lista de implementações certificadas do OIDC

All Articles