Since weeks I´m struggeling with the integration in my JAVA EE project with the libs:
- Apache Shiro
- Pac4j
- Keycloak (Version 23.0.1)
I know, there is a demo project available: https://github.com/pac4j/buji-pac4j-demo
But I think it was never tested for Keycloak?
I got this exception:
org.pac4j.core.exception.TechnicalException: Bad token response, error=invalid_grant, description=Incorrect redirect_uri
Thrown via:
org.pac4j.oidc.credentials.authenticator.OidcAuthenticator.executeTokenRequest(OidcAuthenticator.java:206)
The log is:
09:37:50,632 DEBUG [org.pac4j.core.engine.DefaultCallbackLogic] (default task-4) === CALLBACK === 09:37:50,636 DEBUG [org.pac4j.core.client.finder.DefaultCallbackClientFinder] (default task-4) result: [] 09:37:50,637 DEBUG [org.pac4j.core.client.finder.DefaultCallbackClientFinder] (default task-4) Defaulting to the only client: #OidcClient# | name: OidcClient | callbackUrl: http://localhost:8080/gixxjobsharing/common/redirectLogin.jsf | callbackUrlResolver: org.pac4j.core.http.callback.QueryParameterCallbackUrlResolver@2727da6c | ajaxRequestResolver: org.pac4j.core.http.ajax.DefaultAjaxRequestResolver@19ad26b2 | redirectionActionBuilder: org.pac4j.oidc.redirect.OidcRedirectionActionBuilder@2caded54 | credentialsExtractor: org.pac4j.oidc.credentials.extractor.OidcExtractor@7bea281c | authenticator: org.pac4j.oidc.credentials.authenticator.OidcAuthenticator@620769b7 | profileCreator: org.pac4j.oidc.profile.creator.OidcProfileCreator@126f4bf3 | logoutActionBuilder: org.pac4j.oidc.logout.OidcLogoutActionBuilder@516bd3a7 | authorizationGenerators: [] | configuration: #OidcConfiguration# | clientId: gixxjobsharing-frontend | secret: [protected] | discoveryURI: http://localhost:9009/auth/realms/gixx/.well-known/openid-configuration | scope: null | customParams: {} | clientAuthenticationMethod: client_secret_basic | useNonce: true | preferredJwsAlgorithm: null | maxAge: null | maxClockSkew: 30 | connectTimeout: 500 | readTimeout: 5000 | resourceRetriever: com.nimbusds.jose.util.DefaultResourceRetriever@7bebbb5e | responseType: code | responseMode: null | logoutUrl: http://localhost:9009/auth/realms/gixx/protocol/openid-connect/logout | withState: false | stateGenerator: org.pac4j.core.util.generator.RandomValueGenerator@2b2aa145 | logoutHandler: #DefaultLogoutHandler# | store: #GuavaStore# | size: 10000 | timeout: 30 | timeUnit: MINUTES | | destroySession: false | | tokenValidator: null | mappedClaims: {} | allowUnsignedIdTokens: false | SSLFactory: null | privateKeyJWTClientAuthnMethodConfig: null | callUserInfoEndpoint: true | | 09:37:50,637 DEBUG [org.pac4j.core.engine.DefaultCallbackLogic] (default task-4) foundClient: #OidcClient# | name: OidcClient | callbackUrl: http://localhost:8080/gixxjobsharing/common/redirectLogin.jsf | callbackUrlResolver: org.pac4j.core.http.callback.QueryParameterCallbackUrlResolver@2727da6c | ajaxRequestResolver: org.pac4j.core.http.ajax.DefaultAjaxRequestResolver@19ad26b2 | redirectionActionBuilder: org.pac4j.oidc.redirect.OidcRedirectionActionBuilder@2caded54 | credentialsExtractor: org.pac4j.oidc.credentials.extractor.OidcExtractor@7bea281c | authenticator: org.pac4j.oidc.credentials.authenticator.OidcAuthenticator@620769b7 | profileCreator: org.pac4j.oidc.profile.creator.OidcProfileCreator@126f4bf3 | logoutActionBuilder: org.pac4j.oidc.logout.OidcLogoutActionBuilder@516bd3a7 | authorizationGenerators: [] | configuration: #OidcConfiguration# | clientId: gixxjobsharing-frontend | secret: [protected] | discoveryURI: http://localhost:9009/auth/realms/gixx/.well-known/openid-configuration | scope: null | customParams: {} | clientAuthenticationMethod: client_secret_basic | useNonce: true | preferredJwsAlgorithm: null | maxAge: null | maxClockSkew: 30 | connectTimeout: 500 | readTimeout: 5000 | resourceRetriever: com.nimbusds.jose.util.DefaultResourceRetriever@7bebbb5e | responseType: code | responseMode: null | logoutUrl: http://localhost:9009/auth/realms/gixx/protocol/openid-connect/logout | withState: false | stateGenerator: org.pac4j.core.util.generator.RandomValueGenerator@2b2aa145 | logoutHandler: #DefaultLogoutHandler# | store: #GuavaStore# | size: 10000 | timeout: 30 | timeUnit: MINUTES | | destroySession: false | | tokenValidator: null | mappedClaims: {} | allowUnsignedIdTokens: false | SSLFactory: null | privateKeyJWTClientAuthnMethodConfig: null | callUserInfoEndpoint: true | | 09:37:50,638 DEBUG [org.pac4j.oidc.credentials.extractor.OidcExtractor] (default task-4) Authentication response successful 09:37:50,639 DEBUG [org.pac4j.jee.context.session.JEESessionStore] (default task-4) createSession: false, retrieved session: null 09:37:50,640 DEBUG [org.pac4j.jee.context.session.JEESessionStore] (default task-4) Can't get value for key: OidcClient$codeVerifierSessionParameter, no session available 09:37:50,650 DEBUG [org.pac4j.oidc.credentials.authenticator.OidcAuthenticator] (default task-4) Token response: status=400, content={"error":"invalid_grant","error_description":"Code not valid"}
09:37:52,575 DEBUG [org.pac4j.oidc.client.OidcClient] (default task-4) Credentials validation took: 1936 ms 09:37:52,576 ERROR [io.undertow.request] (default task-4) UT005023: Exception handling request to /gixxjobsharing/common/redirectLogin.jsf: javax.servlet.ServletException: org.pac4j.core.exception.TechnicalException:
I´m using this URL to login: http://localhost:9009/auth/realms/gixx/protocol/openid-connect/auth?client_id=gixxjobsharing-frontend&redirect_uri=http://localhost:8080/gixxjobsharing/common/redirectLogin.jsf&response_type=code&scope=openid
redirectLogin.jsf is like this:
<!DOCTYPE html>
<html lang="#{localeBean.language}" xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:p="http://primefaces.org/ui">
<h:head>
<f:facet name="first">
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta http-equiv="refresh" content="0;url=/gixxjobsharing/portal/dashboard.jsf" />
</f:facet>
<link rel="shortcut icon" type="image/x-icon"
href="#{resource['images/favicon.ico']}" />
<title>Login</title>
</h:head>
<h:body>
</h:body>
</html>
My shiro.ini is like this:
[main]
#### Session
sessionIdCookie=org.apache.shiro.web.servlet.SimpleCookie
#sessionIdCookie.path = /
#sessionIdCookie.httpOnly = true
#sessionIdCookie.name = sid
#sessionIdCookie.domain = localhost
#sessionIdCookie.maxAge=28800000
#sessionIdCookie.secure = true
#sessionIdCookie.sameSite = LAX
sessionManager=org.apache.shiro.web.session.mgt.DefaultWebSessionManager
#sessionManager.sessionIdCookie=$sessionIdCookie
#sessionManager.sessionIdCookieEnabled=true
securityManager.sessionManager=$sessionManager
# Session Timeout nach 8 Stunden
sessionManager.globalSessionTimeout= 28800000
sessionListener1= de.dpunkt.myaktion.util.MySessionListener1
sessionManager.sessionListeners=$sessionListener1
# Session validation = 5 minutes
sessionManager.sessionValidationInterval = 300000
#sessionManager = org.apache.shiro.web.session.mgt.DefaultWebSessionManager
#securityManager.sessionMode=native
sessionValidationScheduler=org.apache.shiro.session.mgt.ExecutorServiceSessionValidationScheduler
sessionValidationScheduler.interval = 60000
sessionValidationScheduler.sessionManager=$sessionManager
sessionManager.sessionValidationScheduler=$sessionValidationScheduler
sessionManager.deleteInvalidSessions=true
#sessionFactory=org.apache.shiro.session.mgt.OnlineSessionFactory
#sessionManager.sessionFactory=$sessionFactory
#securityManager.subjectDAO.sessionStorageEvaluator.sessionStorageEnabled = false
#Keycloack
oidcConfig = org.pac4j.oidc.config.OidcConfiguration
oidcConfig.discoveryURI = http://localhost:9009/auth/realms/gixx/.well-known/openid-configuration
oidcConfig.clientId = gixxjobsharing-frontend
oidcConfig.secret = XXXXXXXXXXXXXXXXXXXXXXX
oidcConfig.clientAuthenticationMethodAsString = client_secret_basic
oidcConfig.useNonce = true
oidcConfig.withState = false
oidcConfig.logoutUrl = http://localhost:9009/auth/realms/gixx/protocol/openid-connect/logout
oidcClient = org.pac4j.oidc.client.OidcClient
oidcClient.configuration = $oidcConfig
clients = org.pac4j.core.client.Clients
clients.callbackUrl = http://localhost:8080/gixxjobsharing/common/redirectLogin.jsf
clients.clients = $oidcClient
pac4jRealm = io.buji.pac4j.realm.Pac4jRealm
pac4jRealm.principalNameAttribute = preferred_username
pac4jSubjectFactory = io.buji.pac4j.subject.Pac4jSubjectFactory
securityManager.subjectFactory = $pac4jSubjectFactory
config = org.pac4j.core.config.Config
config.clients = $clients
oidcSecurityFilter = org.pac4j.jee.filter.SecurityFilter
oidcSecurityFilter.config = $config
oidcSecurityFilter.clients = $oidcClient
### Callback Filters
callbackFilter = org.pac4j.jee.filter.CallbackFilter
callbackFilter.defaultUrl = http://localhost:8080/gixxjobsharing/portal/dashboard.jsf
callbackFilter.config = $config
ajaxRequestResolver = org.pac4j.core.http.ajax.DefaultAjaxRequestResolver
ajaxRequestResolver.addRedirectionUrlAsHeader = true
oidcClient.ajaxRequestResolver = $ajaxRequestResolver
#logoutFilter = io.buji.pac4j.filter.LogoutFilter
#logoutFilter.defaultUrl = http://localhost:8080/gixxjobsharing
#logoutFilter.localLogout = true
#logoutFilter.centralLogout = true
#logoutFilter.config = $config
# DataSource
ds = com.mysql.cj.jdbc.MysqlDataSource
ds.serverName = localhost
ds.user = root
ds.password = test1234
ds.databaseName = gixxjobsharing
ds.useSSL = false
ds.serverTimezone = Europe/Berlin
# password hashing specification, put something big for hasIterations
sha512Matcher = org.apache.shiro.authc.credential.HashedCredentialsMatcher
sha512Matcher.hashAlgorithmName=SHA-512
sha512Matcher.hashIterations=1
# Configure JDBC realm datasource.
jdbcRealm = org.apache.shiro.realm.jdbc.JdbcRealm
jdbcRealm.permissionsLookupEnabled = true
jdbcRealm.authenticationQuery = select password FROM user where UPPER(email)=UPPER(?) and status = 'ACTIVE'
jdbcRealm.userRolesQuery = SELECT r.unique_name FROM permission_role_employee pe JOIN permission_role r ON pe.permission_role_fk = r.permission_role_id JOIN employee e ON pe.employee_fk = e.employee_id JOIN user u ON e.user_fk = u.user_id WHERE UPPER(u.email)=UPPER(?) AND pe.delete_flag = false
jdbcRealm.permissionsQuery = SELECT p.unique_name FROM permission_role_object po JOIN permission p ON po.permission_fk = p.permission_id JOIN permission_role r ON po.permission_role_fk = r.permission_role_id WHERE UPPER(r.unique_name)=UPPER(?) AND po.delete_flag = false
jdbcRealm.dataSource = $ds
jdbcRealm.credentialsMatcher = $sha512Matcher
# Realm for Token Login
tcRealm = org.apache.shiro.realm.jdbc.JdbcRealm
tcRealm.permissionsLookupEnabled = true
tcRealm.authenticationQuery = SELECT token FROM api_token WHERE token = ?
tcRealm.userRolesQuery = SELECT r.unique_name FROM permission_role_employee pe JOIN permission_role r ON pe.permission_role_fk = r.permission_role_id JOIN employee e ON pe.employee_fk = e.employee_id JOIN api_token t ON t.employee_fk = e.employee_id WHERE UPPER(t.token)=UPPER(?) AND t.delete_flag = false
tcRealm.permissionsQuery = SELECT p.unique_name FROM permission_role_object po JOIN permission p ON po.permission_fk = p.permission_id JOIN permission_role r ON po.permission_role_fk = r.permission_role_id WHERE UPPER(r.unique_name)=UPPER(?) AND po.delete_flag = false
tcRealm.dataSource = $ds
# AuthStrategy
authenticator = org.apache.shiro.authc.pam.ModularRealmAuthenticator
authcStrategy = org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy
authenticator = org.apache.shiro.authc.pam.ModularRealmAuthenticator
securityManager.authenticator = $authenticator
securityManager.authenticator.authenticationStrategy = $authcStrategy
securityManager.realms = $jdbcRealm, $tcRealm, $pac4jRealm
# Caching
cacheManager = org.apache.shiro.cache.MemoryConstrainedCacheManager
securityManager.cacheManager = $cacheManager
# Using default form based security filter org.apache.shiro.web.filter.authc.FormAuthenticationFilter
authc = org.apache.shiro.web.filter.authc.FormAuthenticationFilter
authc.loginUrl = /common/login.jsf
#authc.loginUrl = http://localhost:9009/auth/realms/gixx/protocol/openid-connect/auth?client_id=gixxjobsharing-frontend&redirect_uri=http://localhost:8080/gixxjobsharing/callback&response_mode=fragment&response_type=code
authc.successUrl = /portal/dashboard.jsf
# Redirect to an access denied page if user does not have access rights
#[roles]
#roles.unauthorizedUrl = /common/access-denied.jsf
#perms.unauthorizedUrl = /accessdenied.jsp
anyofpermission = de.dpunkt.myaktion.util.CustomPermissionsAuthorizationFilter
# Protected URLs
[urls]
## OTHER
/WEB-INF/layout/portal/** = oidcSecurityFilter
/portal/** = oidcSecurityFilter
/admin/** = oidcSecurityFilter
/community/** = oidcSecurityFilter
/common/redirectLogin.jsf = callbackFilter
Version:
<pac4jVersion>5.7.2</pac4jVersion>
<bujiVersion>8.0.0</bujiVersion>
<jeePac4jVersion>7.1.0</jeePac4jVersion>
<!-- PAC4J -->
<dependency>
<groupId>io.buji</groupId>
<artifactId>buji-pac4j</artifactId>
<version>${bujiVersion}</version>
</dependency>
<dependency>
<groupId>org.pac4j</groupId>
<artifactId>javaee-pac4j</artifactId>
<version>${jeePac4jVersion}</version>
</dependency>
<dependency>
<groupId>org.pac4j</groupId>
<artifactId>pac4j-javaee</artifactId>
<version>${pac4jVersion}</version>
</dependency>
<dependency>
<groupId>org.pac4j</groupId>
<artifactId>pac4j-core</artifactId>
<version>${pac4jVersion}</version>
</dependency>
<dependency>
<groupId>org.pac4j</groupId>
<artifactId>pac4j-cas</artifactId>
<version>${pac4jVersion}</version>
</dependency>
<dependency>
<groupId>org.pac4j</groupId>
<artifactId>pac4j-saml</artifactId>
<version>${pac4jVersion}</version>
</dependency>
<dependency>
<groupId>org.pac4j</groupId>
<artifactId>pac4j-oidc</artifactId>
<version>${pac4jVersion}</version>
</dependency>
<dependency>
<groupId>org.pac4j</groupId>
<artifactId>pac4j-http</artifactId>
<version>${pac4jVersion}</version>
</dependency>
If I open this link: http://localhost:9009/auth/realms/gixx/protocol/openid-connect/auth?client_id=gixxjobsharing-frontend&redirect_uri=http://localhost:8080/gixxjobsharing/common/redirectLogin.jsf&response_type=code&scope=openid
I will get the login screen from Keycloak. The login is fine and I will redirect to the callback page. Here are the settings in Keycloak:
How can I solve this issue?