Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adicionar exemplo de como integrar o Keycloak como Identity Broker e o Gov.br como Identity Provider #25

Open
piantino opened this issue Dec 1, 2022 · 17 comments

Comments

@piantino
Copy link

piantino commented Dec 1, 2022

Olá,

Gostaria de compartilhar a configuração que fiz para o Keycloak usar o Gov.br como Identity provider, ou seja, como adicionar um botão no Keycloak para o usuário se logar com o Gov.br.

image

A configuração utilizada:
image

Acredito que seria interessante colocar no exemplo de implementação, mas apenas ficando registrado aqui já pode ajudar alguém.

@rdurelli
Copy link

Alguem testou isso? Deu certo?

@weltonrodrigo
Copy link

weltonrodrigo commented Apr 19, 2023

@rdurelli é basicamente isso, eu sugeriria apenas algumas alterações:

  • validação de assinaturas: a solução do gov.br atual e o keycloak interpretam diferente o RFC e o formato do jwks não é reconhecido pelo keycloak. Dessa forma, precisa fazer validação manual, extraindo a chave (campo n) em https://sso.acesso.gov.br/jwk e colocando no campo validar assinatura (precisa estar atento porque quando a chave for rotacionada o login vai parar de funcionar)
  • A url de user info é https://sso.acesso.gov.br/userinfo
  • o Default Scopes é openid email profile govbr_confiabilidades (embora o govbr_confiabilidades parece não ser mais utilizado, é o que tá na documentação).
  • O accept prompt=none pode estar ON, e é importante que esteja, para permitir checagem do SSO ao abrir o app (mas a técnica de checagem via iframe padrão da lib keycloak (chamada nos docs de silent sso check) é bloqueada (só em produção) [e provavelmente deveria ser mesmo] no gov.br via Content-Security-Policy

É importante notar que:

  1. Obtenção de informações do usuário: Para obter foto do usuário, níveis da conta (bronze, prata, ouro) e selos de confiabilidade (diversos), é preciso fazer chamadas na API e utilizar o token gov.br original, pra isso, é preciso ativar as opções Store token e Stored tokens readable para que o app possa obter o token gov.br original e chamar o endpoint apropriado. A obtenção do token é feita como descrito no doc https://www.keycloak.org/docs/latest/server_admin/#retrieving-external-idp-tokens:

    # para o nome de provider (gov.br) e realm (dev) usados na imagem :
    curl https://meu.servidorkeycloak.com.br/realms/dev/broker/gov.br/token \
        -H "Authorization: Bearer <KEYCLOAK ACCESS TOKEN DO USUÁRIO>"

    A resposta é o token original gov.br que a aplicação pode usar pra chamar os endpoints.

  2. Confiabilidade e nível de conta: Talvez antigamente as confiabilidades e nível da conta viessem dentro do token (daí a existência do escopo), mas atualmente não vem mais e é preciso essa chamada subsequente. É importante que o dev compreenda que é necessário fazer essa checagem do nível da conta e confiabilidade no backend e não confie numa checagem feita no frontend.
    Isso é importante porque embora o frontend possa ser mexido pelo usuário, quando ele submete o token no backend para chamar uma api do app, como o token tem uma garantia criptográfica de que está correto (e pode ser checado), o backend pode confiar que se o token é válido, as informações ali dentro também são e que o usuário muito provavelmente se logou corretamente (cuidado com token hijacking). Mas como a informação de nível de conta e confiabilidade não está DENTRO do token, precisaria ser passada por fora do token e por isso está sujeita a ser forjada.
    Dessa forma, a aplicação precisa ou checar o nível de confiabilidade toda vez (com um cache adequado) que os seus endpoints próprios sejam chamados, ou precisa considerar o usuário logado e autorizado apenas depois da checagem ocorrer no backend e aí emitir seu mecanismo próprio de controle de sessão (cookies ou um token JWT próprio).

  3. Set-up de Autenticação: É uma pena também que a solução atual não consiga fazer o set-up authentication (ver Existe algum plano de implementar step-up authentication? #23) porque o Keycloak atualmente suporta e seria transparente passar a exigir segundo fator (ou múltiplos fatores) de autenticação para algumas operações.

  4. Integração transparente: se o gov.br é a única base de usuários e se a sua aplicação já tem o botão (obrigatório) "Entrar com gov.br", é possível esconder a tela de login do keycloak tornando o gov.br o authentication provider padrão:
    No exemplo da imagem, o id a ser inserido na configuração é gov.br (pode ser qualquer string que estiver como "Alias" do OIDC provider).

Eu venho usando o keycloak como intermediário entre o órgão e o gov.br há anos com essas configurações e funciona muito bem, mas com essas ressalvas.

UPDATE:

@rdurelli
Copy link

Basicamente vc deu uma aula :) irei teste. Qualquer coisa lhe peço ajuda. Muito obrigado

@piantino
Copy link
Author

Perfeito as explicações @weltonrodrigo,

Sobre o scope "govbr_confiabilidades" é necessário para permitir que o token retornado pelo Gov.br tenha permissão de chamar os serviços de confiabilidade, do contrário retornará um erro assim:

{
  "errors": [
    {
      "status":401,
      "code":"ACCESSTOKEN_SCOPE_MUSTCONTAINSEXPECTEDSCOPE",
      "title":"Escopo requerido não encontrado. Valor esperado: '{0}', valor recebido: '{1}'." 
    }
  ]
}

Pelo menos foi o que aconteceu nos meus testes.

@caduvieira
Copy link
Contributor

@rdurelli é basicamente isso, eu sugeriria apenas algumas alterações:

  • validação de assinaturas: a solução do gov.br atual interpreta errado o RFC e o formato do jwks não é reconhecido pelo keycloak. Dessa forma, precisa fazer validação manual, extraindo a chave (campo n) em https://sso.acesso.gov.br/jwk e colocando no campo validar assinatura (precisa estar atento porque quando a chave for rotacionada o login vai parar de funcionar)

Em que ponto o Login gov.br interpreta a RFC 7517 errada ? O exemplo no https://www.rfc-editor.org/rfc/rfc7517#appendix-A tem os mesmos campos retornados pelo https://sso.acesso.gov.br/jwk. Há algum problema com os valores?

@weltonrodrigo
Copy link

Em que ponto o Login gov.br interpreta a RFC 7517 errada ? O exemplo no https://www.rfc-editor.org/rfc/rfc7517#appendix-A tem os mesmos campos retornados pelo https://sso.acesso.gov.br/jwk. Há algum problema com os valores?

Você tem razão que o RFC coloca o campo 'use' como OPTIONAL. É mais correto eu dizer que o gov.br e o keycloak discordam sobre o formato do jwk.

No teste que realizei pra fazer esse comentário o pyjwt conseguiu validar corretamente a partir da url do keyset e quando eu criei esse repo, isso não acontecia, tinha que fornecer a chave manualmente. Suponho então que o pyjwt tenha se alinhado.

@acostaaluiz
Copy link

Quando adiciono um novo identity provider no keycloak, ele coloca um valor default no campo redirect uri. Eu não consigo edita-lo e para colocar o valor https://sso.tjsc..... conforme mostra no print. Alguma dica? Valeu

@piantino
Copy link
Author

Quando adiciono um novo identity provider no keycloak, ele coloca um valor default no campo redirect uri. Eu não consigo edita-lo e para colocar o valor https://sso.tjsc..... conforme mostra no print. Alguma dica? Valeu

O Redirect URI é a URL do seu Keycloak (no print é o Keycloak do TJSC) e não pode ser alterado, já o Authorization URL é a do Gov.br.
Pensa que o seu keycloak é o cliente do SSO do Gov.br, e outras aplicações serão os clientes do seu keycloak.
Ficou mais claro agora?

@carlosrodovalho
Copy link

estou tantando fazer a implementação do gov.br em um servidor de homologação da minha instituição, no entanto após informar o CPF e senha na tela de login do Gov.br, o usuário é retornado pra aplicação e o keycloak exibe a mensagem:

Unexpected error when authenticating with identity provider
« Back to Application

no log do keycloak aparece esse erro:

2024-08-05 17:10:16,864 WARN  [org.keycloak.events] (executor-thread-173) type="IDENTITY_PROVIDER_LOGIN_ERROR", realmId="35-secret-e072bb", realmName="virtualif-homologacao", clientId="virtualif-modulo-copese", userId="null", ipAddress="192.168.1.254", error="identity_provider_login_failure", code_id="8158e5-secret-29abf3bc"
2024-08-05 18:19:51,484 WARN  [org.keycloak.keys.infinispan.InfinispanPublicKeyStorageProvider] (executor-thread-187) PublicKey wasn't found in the storage. Requested kid: 'rsa1' . Available kids: '[]'
2024-08-05 18:19:51,486 ERROR [org.keycloak.broker.oidc.AbstractOAuth2IdentityProvider] (executor-thread-187) Failed to make identity provider oauth callback: org.keycloak.broker.provider.IdentityBrokerException: token signature validation failed
        at org.keycloak.broker.oidc.OIDCIdentityProvider.parseTokenInput(OIDCIdentityProvider.java:679)
        at org.keycloak.broker.oidc.OIDCIdentityProvider.validateToken(OIDCIdentityProvider.java:696)
        at org.keycloak.broker.oidc.OIDCIdentityProvider.validateToken(OIDCIdentityProvider.java:690)
        at org.keycloak.broker.oidc.OIDCIdentityProvider.getFederatedIdentity(OIDCIdentityProvider.java:379)
        at org.keycloak.broker.oidc.AbstractOAuth2IdentityProvider$Endpoint.authResponse(AbstractOAuth2IdentityProvider.java:557)
        at org.keycloak.broker.oidc.AbstractOAuth2IdentityProvider$Endpoint$quarkusrestinvoker$authResponse_ab908fbdd086ee82e140d8a818c077362a2d04b4.invoke(Unknown Source)
        at org.jboss.resteasy.reactive.server.handlers.InvocationHandler.handle(InvocationHandler.java:29)
        at io.quarkus.resteasy.reactive.server.runtime.QuarkusResteasyReactiveRequestContext.invokeHandler(QuarkusResteasyReactiveRequestContext.java:141)
        at org.jboss.resteasy.reactive.common.core.AbstractResteasyReactiveContext.run(AbstractResteasyReactiveContext.java:147)
        at io.quarkus.vertx.core.runtime.VertxCoreRecorder$14.runWith(VertxCoreRecorder.java:582)
        at org.jboss.threads.EnhancedQueueExecutor$Task.run(EnhancedQueueExecutor.java:2513)
        at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1538)
        at org.jboss.threads.DelegatingRunnable.run(DelegatingRunnable.java:29)
        at org.jboss.threads.ThreadLocalResettingRunnable.run(ThreadLocalResettingRunnable.java:29)
        at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
        at java.base/java.lang.Thread.run(Thread.java:1583)

alguém tem alguma ideia do que pode ser? desde já agradeço

@weltonrodrigo
Copy link

estou tantando fazer a implementação do gov.br em um servidor de homologação da minha instituição, no entanto após informar o CPF e senha na tela de login do Gov.br, o usuário é retornado pra aplicação e o keycloak exibe a mensagem:

Unexpected error when authenticating with identity provider
« Back to Application

no log do keycloak aparece esse erro:

2024-08-05 17:10:16,864 WARN  [org.keycloak.events] (executor-thread-173) type="IDENTITY_PROVIDER_LOGIN_ERROR", realmId="35-secret-e072bb", realmName="virtualif-homologacao", clientId="virtualif-modulo-copese", userId="null", ipAddress="192.168.1.254", error="identity_provider_login_failure", code_id="8158e5-secret-29abf3bc"
2024-08-05 18:19:51,484 WARN  [org.keycloak.keys.infinispan.InfinispanPublicKeyStorageProvider] (executor-thread-187) PublicKey wasn't found in the storage. Requested kid: 'rsa1' . Available kids: '[]'
2024-08-05 18:19:51,486 ERROR [org.keycloak.broker.oidc.AbstractOAuth2IdentityProvider] (executor-thread-187) Failed to make identity provider oauth callback: org.keycloak.broker.provider.IdentityBrokerException: token signature validation failed
        at org.keycloak.broker.oidc.OIDCIdentityProvider.parseTokenInput(OIDCIdentityProvider.java:679)
        at org.keycloak.broker.oidc.OIDCIdentityProvider.validateToken(OIDCIdentityProvider.java:696)
        at org.keycloak.broker.oidc.OIDCIdentityProvider.validateToken(OIDCIdentityProvider.java:690)
        at org.keycloak.broker.oidc.OIDCIdentityProvider.getFederatedIdentity(OIDCIdentityProvider.java:379)
        at org.keycloak.broker.oidc.AbstractOAuth2IdentityProvider$Endpoint.authResponse(AbstractOAuth2IdentityProvider.java:557)
        at org.keycloak.broker.oidc.AbstractOAuth2IdentityProvider$Endpoint$quarkusrestinvoker$authResponse_ab908fbdd086ee82e140d8a818c077362a2d04b4.invoke(Unknown Source)
        at org.jboss.resteasy.reactive.server.handlers.InvocationHandler.handle(InvocationHandler.java:29)
        at io.quarkus.resteasy.reactive.server.runtime.QuarkusResteasyReactiveRequestContext.invokeHandler(QuarkusResteasyReactiveRequestContext.java:141)
        at org.jboss.resteasy.reactive.common.core.AbstractResteasyReactiveContext.run(AbstractResteasyReactiveContext.java:147)
        at io.quarkus.vertx.core.runtime.VertxCoreRecorder$14.runWith(VertxCoreRecorder.java:582)
        at org.jboss.threads.EnhancedQueueExecutor$Task.run(EnhancedQueueExecutor.java:2513)
        at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1538)
        at org.jboss.threads.DelegatingRunnable.run(DelegatingRunnable.java:29)
        at org.jboss.threads.ThreadLocalResettingRunnable.run(ThreadLocalResettingRunnable.java:29)
        at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
        at java.base/java.lang.Thread.run(Thread.java:1583)

alguém tem alguma ideia do que pode ser? desde já agradeço

Experimenta desativar a opção “validate signature”.

@carlosrodovalho
Copy link

Experimenta desativar a opção “validate signature”.

Muito obrigado @weltonrodrigo . Desativando a checagem pelo JWKS o login funciona perfeitamente. No entanto ao fazer isso eu não estaria diminuindo a segurança do processo de login? Existe alguma maneira de realizar a integração mantendo a checagem da assinatura e tmb usando PKCE?
Mais uma vez obrigado pela ajuda

@caduvieira
Copy link
Contributor

caduvieira commented Aug 6, 2024

Isso está relacionado a issue #29.

Experimenta desativar a opção “validate signature”.

Muito obrigado @weltonrodrigo . Desativando a checagem pelo JWKS o login funciona perfeitamente. No entanto ao fazer isso eu não estaria diminuindo a segurança do processo de login?

Sim. Estaria.

@carlosrodovalho
Copy link

Sim. Estaria.

Entendi. Obrigado.

Caso eu crie um endopoint próprio, que leia o objeto retornado por https://sso.acesso.gov.br/jwk, adicione a ele o campo use: sig, e responda a requisição com objeto "completo", conforme o keycloak espera. E então configurar esse endpoint como 'JWKS URL' no keycloak.

Em teoria, com essa estratégia eu poderia realizar a assinatura, correto?

E imagino que nesse cenário não seria possível ativar a opção 'Use PKCE', certo?

@weltonrodrigo
Copy link

weltonrodrigo commented Aug 6, 2024

Experimenta desativar a opção “validate signature”.

Muito obrigado @weltonrodrigo . Desativando a checagem pelo JWKS o login funciona perfeitamente. No entanto ao fazer isso eu não estaria diminuindo a segurança do processo de login? Existe alguma maneira de realizar a integração mantendo a checagem da assinatura e tmb usando PKCE? Mais uma vez obrigado pela ajuda

Sim, @carlosrodovalho, diminui. Mas não é teu grave pq tá num ambiente controlado e você recebe o token gov.br a partir de um request que você mesmo faz ao endpoint de token do Gov.br. Nao é algo que você recebe dos clientes e precisa validar.

Você pode manter a validação copiando e colando a chave no campo apropriado (e substituindo manualmente quando o gov.br trocar a chave).

O PKCE é independente e não relacionado. Você pode ativar ele mesmo sem validar assinaturas do token.

Sobre o seu endpoint ajustando o keyset, vai funcionar de boas.

@rosendoalex
Copy link

Bom dia!

Estou fazendo a integração entre o Keycloak e o Gov.br, está funcionando, porém consigo realizar o logout do Gov.br, mas o Keycloak ainda permanece logado. Alguém já viu esse caso?

Fiz as configurações de logout URL que o passo a passo do Gov.br forneceu.

@weltonrodrigo
Copy link

weltonrodrigo commented Sep 30, 2024

Estou fazendo a integração entre o Keycloak e o Gov.br, está funcionando, porém consigo realizar o logout do Gov.br, mas o Keycloak ainda permanece logado. Alguém já viu esse caso?

Entendi que você fez logout no gov.br usando uma outra aplicação, sem envolver a aplicação que está falando com o keycloak. Por exemplo, você fez login usando a sua aplicação A, que fala com o gov.br por intermédio do keycloak e aí você entrou no acesso.gov.br (já estava logado), fez logout por lá e aí quando você foi fazer login por outra aplicação B sua também envolvendo o keycloak o usuário já estava logado?

Tá correto o entendimento?

Salvo melhor juízo, o gov.br não implementa o logout centralizado. Isto é, se você logar com gov.br numa aplicação via keycloak e depois numa outra aba do mesmo browser você acessar acesso.gov.br e fizer logout por lá (note que o acesso.gov.br é uma aplicação que não tem nenhuma ligação com o seu keycloak, só com o gov.br), o keycloak não vai saber que você deslogou do gov.br.

Isso é o chamado RP-Initiated logout (o Relaying Party no caso aí é o acesso.gov.br) e o gov.br enquanto Identity Provider deveria implementar ou o Front-channel logout ou o Backchannel logout.

Em outras palavras, o gov.br é que é responsável por avisar as outras aplicações A,B ou C (ou o seu keycloak, no caso) que foi feito um logout no gov.br em outra janela, iniciado por outra aplicação D. Até onde eu sei ele não faz isso, mas seria ótimo que eu estivesse errado e só minha configuração que foi mal feita.

Esse comportamento (não executar o logout centralizado) não é tão absurdo assim e uma evidência disso é que o Login.gov americano não faz isso

O workaround nesse caso é você limitar o tempo máximo de sessão do seu keycloak para 59 minutos (pra ser menor que o do gov.br) e configurar sua aplicação para ter uma expiração adequada do token de acesso (e usar refresh tokens). Assim, o seu usuário vai ficar no máximo 59 minutos + logado no seu keycloak depois de ter feito logout no gov.br em outro lugar.

@rosendoalex
Copy link

Olá @weltonrodrigo!

Perfeita sua explicação, muito obrigado!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants