Estou usando o Keycloak 21 para autenticação e estou tendo um problema em que o valor nonce não está incluído no id_token retornado após eu invocar /token. Estou passando o nonce na solicitação /auth,
Estou tentando usar keycloak no meu aplicativo FastAPI Meu código
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
from jose import jwt, JWTError
from keycloak import KeycloakOpenID
import requests
import logging
import os
from .config import settings
# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# Keycloak configuration
KEYCLOAK_SERVER_URL = settings.KEYCLOAK_SERVER_URL
KEYCLOAK_REALM = settings.KEYCLOAK_REALM
KEYCLOAK_CLIENT_ID = settings.KEYCLOAK_CLIENT_ID
KEYCLOAK_CLIENT_SECRET = settings.KEYCLOAK_CLIENT_SECRET
ALGORITHM = "RS256"
TOKEN_URL = f"{KEYCLOAK_SERVER_URL}/realms/fastapi-realm/protocol/openid-connect/token"
# Initialize KeycloakOpenID
keycloak_openid = KeycloakOpenID(
server_url=f"{KEYCLOAK_SERVER_URL}",
client_id=KEYCLOAK_CLIENT_ID,
realm_name=KEYCLOAK_REALM,
client_secret_key=KEYCLOAK_CLIENT_SECRET,
verify=False
)
config_well_known = keycloak_openid.well_known()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
def verify_token(token: str = Depends(oauth2_scheme)):
try:
decoded_token = keycloak_openid.decode_token(
token,validate=True,
)
username = decoded_token['preferred_username']
logger.info(f"Decoded token: {decoded_token}")
# Verify the issuer claim
issuer = decoded_token["iss"]
expected_issuer = f"{KEYCLOAK_SERVER_URL}/realms/{KEYCLOAK_REALM}"
I# Token example -- token: {'exp': 1731303036, 'iat': 1731267036, 'jti': 'f1b71d25-4de6-4c03-b5f5-d9726b39d51f', 'iss': 'https://feast-keycloak.pimc-st.innodev.local/realms/feast-realm', 'aud': 'account', 'sub': 'ac48f45e-f26b-4380-bde8-e752febb6d18', 'typ': 'Bearer', 'azp': 'feast-client-id', 'session_state': 'b36cd197-247d-447e-9f3d-6cf1fecae7d6', 'acr': '1', 'allowed-origins': ['https://feast-frontend.pimc-st.innodev.local', '/*', 'http://localhost:5173'], 'realm_access': {'roles': ['default-roles-feast-realm', 'offline_access', 'uma_authorization']}, 'resource_access': {'account': {'roles': ['manage-account', 'manage-account-links', 'view-profile']}}, 'scope': 'profile email', 'sid': 'b36cd197-247d-447e-9f3d-6cf1fecae7d6', 'email_verified': False, 'name': 'A B', 'preferred_username': 'my_username', 'given_name': 'A', 'family_name': 'B', 'email': '[email protected]'}
logger.info(f"XXX_ issuer={issuer}")
logger.info(f"XXX_ expected_issuer={expected_issuer}")
if issuer != expected_issuer:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid issuer")
logger.info(f"username: {username}")
return decoded_token
except Exception as e:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token")
No meu main.py tenho o seguinte código
from fastapi import Depends, FastAPI, HTTPException, status, Security
from .keycloak import verify_token, oauth2_scheme, KEYCLOAK_CLIENT_ID, KEYCLOAK_CLIENT_SECRET, TOKEN_URL
app = FastAPI()
@app.post("/project", response_model=schemas.Project, tags=["project",])
def create_project(project: schemas.CreateProject, db: Session = Depends(get_db), payload: dict = Security(verify_token)):
...
@app.post("/login")
def get_token(body: schemas.Login):
data = {
"grant_type": "password", # TODO: clarify grant_type client_credentials (requires only client id and secret or password - requires password and login)
"client_id": KEYCLOAK_CLIENT_ID,
"client_secret": KEYCLOAK_CLIENT_SECRET,
"password": body.password,
"username": body.login
}
response = requests.post(TOKEN_URL, data=data, verify=False)
return JSONResponse(status_code=response.status_code, content=response.json())
Estou obtendo o token com a ajuda do método /login. Então estou aplicando o token assim:
Para solicitação /project
tenho um erro:
"Token inválido"
Como corrigir o erro?
Estou criando o aplicativo FastApi + KeyCloak. Criei o realm, o cliente e o usuário
Posso obter token com a ajuda desta solicitação
Mas para grant_type client-credentials
tenho um erro:
{
"error": "unsupported_grant_type",
"error_description": "Unsupported grant_type"
}
Qual é a diferença entre "password" e "client-credentials"? E como dar suporte a client-credentials?
Na minha configuração do Keycloak, o StackOverflow está configurado como um dos provedores de login. Recentemente, notei que novos usuários não conseguem se registrar no meu site via StackOverflow porque o e-mail retornado é nulo. Qual pode ser o motivo disso?
keycloack.versão 18.0.2
2024-09-02 10:39:51,276 WARN [org.keycloak.services] (executor-thread-11964) KC-SERVICES0020: Email is null. Reset flow and enforce showing reviewProfile page
2024-09-02 10:39:51,276 WARN [org.keycloak.services] (executor-thread-11964) KC-SERVICES0013: Failed authentication: org.keycloak.authentication.AuthenticationFlowException
at org.keycloak.authentication.AuthenticationProcessor.authenticateOnly(AuthenticationProcessor.java:1038)
at org.keycloak.services.resources.LoginActionsService$1.authenticateOnly(LoginActionsService.java:808)
at org.keycloak.authentication.AuthenticationProcessor.authenticate(AuthenticationProcessor.java:892)
at org.keycloak.services.resources.LoginActionsService.processFlow(LoginActionsService.java:323)
at org.keycloak.services.resources.LoginActionsService.brokerLoginFlow(LoginActionsService.java:838)
at org.keycloak.services.resources.LoginActionsService.firstBrokerLoginGet(LoginActionsService.java:732)
at jdk.internal.reflect.GeneratedMethodAccessor343.invoke(Unknown Source)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:170)
at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:130)
at org.jboss.resteasy.core.ResourceMethodInvoker.internalInvokeOnTarget(ResourceMethodInvoker.java:660)
at org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTargetAfterFilter(ResourceMethodInvoker.java:524)
at org.jboss.resteasy.core.ResourceMethodInvoker.lambda$invokeOnTarget$2(ResourceMethodInvoker.java:474)
at org.jboss.resteasy.core.interception.jaxrs.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:364)
at org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTarget(ResourceMethodInvoker.java:476)
at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:434)
at org.jboss.resteasy.core.ResourceLocatorInvoker.invokeOnTargetObject(ResourceLocatorInvoker.java:192)
at org.jboss.resteasy.core.ResourceLocatorInvoker.invoke(ResourceLocatorInvoker.java:141)
at org.jboss.resteasy.core.ResourceLocatorInvoker.invoke(ResourceLocatorInvoker.java:32)
at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:492)
at org.jboss.resteasy.core.SynchronousDispatcher.lambda$invoke$4(SynchronousDispatcher.java:261)
at org.jboss.resteasy.core.SynchronousDispatcher.lambda$preprocess$0(SynchronousDispatcher.java:161)
at org.jboss.resteasy.core.interception.jaxrs.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:364)
at org.jboss.resteasy.core.SynchronousDispatcher.preprocess(SynchronousDispatcher.java:164)
at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:247)
at io.quarkus.resteasy.runtime.standalone.RequestDispatcher.service(RequestDispatcher.java:73)
at io.quarkus.resteasy.runtime.standalone.VertxRequestHandler.dispatch(VertxRequestHandler.java:151)
at io.quarkus.resteasy.runtime.standalone.VertxRequestHandler.handle(VertxRequestHandler.java:82)
at io.quarkus.resteasy.runtime.standalone.VertxRequestHandler.handle(VertxRequestHandler.java:42)
at io.vertx.ext.web.impl.RouteState.handleContext(RouteState.java:1212)
at io.vertx.ext.web.impl.RoutingContextImplBase.iterateNext(RoutingContextImplBase.java:163)
at io.vertx.ext.web.impl.RoutingContextImpl.next(RoutingContextImpl.java:141)
at io.quarkus.vertx.http.runtime.StaticResourcesRecorder$2.handle(StaticResourcesRecorder.java:67)
at io.quarkus.vertx.http.runtime.StaticResourcesRecorder$2.handle(StaticResourcesRecorder.java:55)
at io.vertx.ext.web.impl.RouteState.handleContext(RouteState.java:1212)
at io.vertx.ext.web.impl.RoutingContextImplBase.iterateNext(RoutingContextImplBase.java:163)
at io.vertx.ext.web.impl.RoutingContextImpl.next(RoutingContextImpl.java:141)
at io.quarkus.vertx.http.runtime.VertxHttpRecorder$5.handle(VertxHttpRecorder.java:380)
at io.quarkus.vertx.http.runtime.VertxHttpRecorder$5.handle(VertxHttpRecorder.java:358)
at io.vertx.ext.web.impl.RouteState.handleContext(RouteState.java:1212)
at io.vertx.ext.web.impl.RoutingContextImplBase.iterateNext(RoutingContextImplBase.java:163)
at io.vertx.ext.web.impl.RoutingContextImpl.next(RoutingContextImpl.java:141)
at org.keycloak.quarkus.runtime.integration.web.QuarkusRequestFilter.lambda$createBlockingHandler$1(QuarkusRequestFilter.java:71)
at io.vertx.core.impl.ContextImpl.lambda$null$0(ContextImpl.java:159)
at io.vertx.core.impl.AbstractContext.dispatch(AbstractContext.java:100)
at io.vertx.core.impl.ContextImpl.lambda$executeBlocking$1(ContextImpl.java:157)
at io.quarkus.vertx.core.runtime.VertxCoreRecorder$13.runWith(VertxCoreRecorder.java:543)
at org.jboss.threads.EnhancedQueueExecutor$Task.run(EnhancedQueueExecutor.java:2449)
at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1478)
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:833)
2024-09-02 10:39:51,277 WARN [org.keycloak.events] (executor-thread-11964) type=IDENTITY_PROVIDER_FIRST_LOGIN_ERROR
Estou trabalhando em uma solicitação de troca de token do keycloak em que tento obter o token de acesso do cliente2 enquanto sou autenticado com o cliente1.
Habilitei token_exchange e admin_fine_grained_authz na instância keycloak.
Seguiu a documentação de https://www.keycloak.org/docs/latest/securing_apps/index.html#_internal-token-to-internal-token-exchange
Quando eu acessei a API para troca de tokens como
curl --location 'http://<URL>/realms/Genting/protocol/openid-connect/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'client_id=client2' \
--data-urlencode 'client_secret=<client2 secret>' \
--data-urlencode 'subject_token=<client1 token> \
--data-urlencode 'audience=client2' \
--data-urlencode 'grant_type=urn:ietf:params:oauth:grant-type:token-exchange' \
--data-urlencode 'requested_token_type=urn:ietf:params:oauth:token-type:access_token'
Em resposta, estou recebendo
{
"error": "access_denied",
"error_description": "Client is not within the token audience"
}
Há alguma configuração que perdi aqui para receber o token corretamente?
Existe uma maneira de listar usuários de contas de serviço usando a API Keycloak (19.x)?
Posso listar usuários usando GET /admin/realms/{realm}/users
e posso acessar uma conta de usuário de serviço usando GET /admin/realms/{realm}/users/{service-account-user-id}
, se eu tiver esse ID de, digamos, um evento de login do usuário, mas existe uma maneira de listá-los?
Tudo que preciso são seus IDs e valores de nome de usuário.
Examinei a documentação disponível da API REST do Keycloak e não consegui vê-la em /admin/realms/{realm}/users
ou /admin/realms/{realm}/clients
. Também não há eventos de criação e atualização correspondentes quando esses usuários são criados invertendo contas de serviço habilitadas em um cliente.
Imagine que um reino my-realm
e um cliente my-client
foram criados. É possível criar usuários via API sem admin-cli (com segredo do my-client
cliente)?