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:

enter image description here

enter image description here

enter image description here

How can I solve this issue?

0

There are 0 best solutions below