Hello folks,
Does anyone have a working CAS 5.x deployment delegating authentication via
OpenID Connect? I'd love to pick your brain.
I am working on a CAS 5.3.9 deployment, testing CAS delegation to Azure AD
via OpenID Connect. I think I am close, but I'm running into a problem.
When I try to access '/cas/clientredirect?client_name=REDACTED' via the
link on the `/cas/login' page, I receive a 500 error with a stacktrace as
follows below. There is no STDOUT/STDERR emitted when this occurs, and I
have tweaked my log levels so that I would expect to see *something*. I'll
share those later.
What worries me, is if I start nosing around in IDEA, the IDE also throws a
hint that this is an invalid cast. (I used './gradlew idea' in a clone of
the cas repository, and opened the generated idea project in IntelliJ IDEA
Ultimate).
Here's the stack trace:
> org.pac4j.core.exception.TechnicalException: java.lang.ClassCastException:
> java.util.Collections$SingletonList cannot be cast to java.lang.String
> at
> org.pac4j.oidc.redirect.OidcRedirectActionBuilder.buildAuthenticationRequestUrl(OidcRedirectActionBuilder.java:113)
> at
> org.pac4j.oidc.redirect.OidcRedirectActionBuilder.redirect(OidcRedirectActionBuilder.java:78)
> at
> org.pac4j.core.client.IndirectClient.getRedirectAction(IndirectClient.java:109)
> at
> org.apereo.cas.web.DelegatedClientNavigationController.redirectToProvider(DelegatedClientNavigationController.java:83)
> at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
> at
> sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
> at
> sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
> at java.lang.reflect.Method.invoke(Method.java:498)
> at
> org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205)
> at
> org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:133)
> at
> org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:97)
> at
> org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:849)
> at
> org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:760)
> at
> org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85)
> at
> org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:967)
> at
> org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:901)
> at
> org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970)
> at
> org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:861)
> at javax.servlet.http.HttpServlet.service(HttpServlet.java:687)
> at
> org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846)
> at javax.servlet.http.HttpServlet.service(HttpServlet.java:790)
> at
> org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:867)
> at
> org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1623)
> at
> org.eclipse.jetty.websocket.server.WebSocketUpgradeFilter.doFilter(WebSocketUpgradeFilter.java:214)
> at
> org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1610)
> at
> org.apereo.cas.web.support.AuthenticationCredentialsThreadLocalBinderClearingFilter.doFilter(AuthenticationCredentialsThreadLocalBinderClearingFilter.java:30)
> at
> org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1610)
> at
> org.apereo.cas.security.RequestParameterPolicyEnforcementFilter.doFilter(RequestParameterPolicyEnforcementFilter.java:261)
> at
> org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1610)
> at
> org.apereo.cas.security.ResponseHeadersEnforcementFilter.doFilter(ResponseHeadersEnforcementFilter.java:240)
> at
> org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1610)
> at
> org.apereo.cas.security.AddResponseHeadersFilter.doFilter(AddResponseHeadersFilter.java:94)
> at
> org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1610)
> at
> org.springframework.boot.actuate.trace.WebRequestTraceFilter.doFilterInternal(WebRequestTraceFilter.java:111)
> at
> org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
> at
> org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1610)
> at
> org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99)
> at
> org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
> at
> org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1610)
> at
> org.springframework.web.filter.HttpPutFormContentFilter.doFilterInternal(HttpPutFormContentFilter.java:109)
> at
> org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
> at
> org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1610)
> at
> org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:93)
> at
> org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
> at
> org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1610)
> at
> org.apereo.cas.logging.web.ThreadContextMDCServletFilter.doFilter(ThreadContextMDCServletFilter.java:91)
> at
> org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1610)
> at
> org.springframework.boot.actuate.autoconfigure.MetricsFilter.doFilterInternal(MetricsFilter.java:103)
> at
> org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
> at
> org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1610)
> at
> org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:197)
> at
> org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
> at
> org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1610)
> at
> org.springframework.boot.web.support.ErrorPageFilter.doFilter(ErrorPageFilter.java:130)
> at
> org.springframework.boot.web.support.ErrorPageFilter.access$000(ErrorPageFilter.java:66)
> at
> org.springframework.boot.web.support.ErrorPageFilter$1.doFilterInternal(ErrorPageFilter.java:105)
> at
> org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
> at
> org.springframework.boot.web.support.ErrorPageFilter.doFilter(ErrorPageFilter.java:123)
> at
> org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1610)
> at
> org.apereo.inspektr.common.web.ClientInfoThreadLocalFilter.doFilter(ClientInfoThreadLocalFilter.java:66)
> at
> org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1610)
> at
> org.apache.logging.log4j.web.Log4jServletFilter.doFilter(Log4jServletFilter.java:71)
> at
> org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1610)
> at
> org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:540)
> at
> org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:146)
> at
> org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:548)
> at
> org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:132)
> at
> org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:257)
> at
> org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:1588)
> at
> org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:255)
> at
> org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1345)
> at
> org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:203)
> at
> org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:480)
> at
> org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:1557)
> at
> org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:201)
> at
> org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1247)
> at
> org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:144)
> at
> org.eclipse.jetty.server.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:220)
> at
> org.eclipse.jetty.server.handler.HandlerCollection.handle(HandlerCollection.java:126)
> at
> org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:132)
> at org.eclipse.jetty.server.Server.handle(Server.java:502)
> at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:364)
> at org.eclipse.jetty.server.HttpChannel.run(HttpChannel.java:305)
> at
> org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.runTask(EatWhatYouKill.java:333)
> at
> org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.doProduce(EatWhatYouKill.java:310)
> at
> org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.tryProduce(EatWhatYouKill.java:168)
> at
> org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.produce(EatWhatYouKill.java:132)
> at
> org.eclipse.jetty.http2.HTTP2Connection.produce(HTTP2Connection.java:171)
> at
> org.eclipse.jetty.http2.HTTP2Connection.onFillable(HTTP2Connection.java:126)
> at
> org.eclipse.jetty.http2.HTTP2Connection$FillableCallback.succeeded(HTTP2Connection.java:338)
> at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:103)
> at
> org.eclipse.jetty.io.ssl.SslConnection$DecryptedEndPoint.onFillable(SslConnection.java:411)
> at
> org.eclipse.jetty.io.ssl.SslConnection.onFillable(SslConnection.java:305)
> at
> org.eclipse.jetty.io.ssl.SslConnection$2.succeeded(SslConnection.java:159)
> at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:103)
> at org.eclipse.jetty.io.ChannelEndPoint$2.run(ChannelEndPoint.java:118)
> at
> org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.runTask(EatWhatYouKill.java:333)
> at
> org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.doProduce(EatWhatYouKill.java:310)
> at
> org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.tryProduce(EatWhatYouKill.java:168)
> at
> org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.run(EatWhatYouKill.java:126)
> at
> org.eclipse.jetty.util.thread.ReservedThreadExecutor$ReservedThread.run(ReservedThreadExecutor.java:366)
> at
> org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:765)
> at
> org.eclipse.jetty.util.thread.QueuedThreadPool$2.run(QueuedThreadPool.java:683)
> at java.lang.Thread.run(Thread.java:748)
> Caused by: java.lang.ClassCastException: java.util.Collections$SingletonList
> cannot be cast to java.lang.String
> at
> com.nimbusds.oauth2.sdk.AuthorizationRequest.parse(AuthorizationRequest.java:972)
> at
> com.nimbusds.openid.connect.sdk.AuthenticationRequest.parse(AuthenticationRequest.java:1374)
> at
> com.nimbusds.openid.connect.sdk.AuthenticationRequest.parse(AuthenticationRequest.java:1340)
> at
> org.pac4j.oidc.redirect.OidcRedirectActionBuilder.buildAuthenticationRequestUrl(OidcRedirectActionBuilder.java:110)
> ... 103 more
>
>
Here's the code from pac4j in question:
protected String buildAuthenticationRequestUrl(final Map<String, String>
params) {
// Build authentication request query string
final String queryString;
try {
queryString = AuthenticationRequest.parse(params.entrySet().
stream().collect(
Collectors.toMap(Map.Entry::getKey, e -> Collections.
singletonList(e.getValue())))).toQueryString();
} catch (Exception e) {
throw new TechnicalException(e);
}
return configuration.getProviderMetadata().
getAuthorizationEndpointURI().toString() + "?" + queryString;
}
IDEA whines about two things, here. I've underlined them both. The first is
"Non-static method cannot be referenced from a static context" and the
second "Bad return type in lambda expression: List<String> cannot be
converted to U." Interestingly this second issue is exactly what lives at
the top of the stacktrace... Is this a bug? In case it's not, and I've just
done something wrong or silly...
Now, for some build/environment stuff. I'm stuffing cas.war into a docker
container with jetty
<https://github.com/appropriate/docker-jetty/blob/master/9.4-jre8/Dockerfile>
(tweaked to work around a bug
<https://github.com/appropriate/docker-jetty/issues/104>). I'm building a
standalone WAR without tomcat etc. Note that I have my own pom.xml based on
cas-overlay. This deployment is working fine for several other CAS
deployments that I haven't yet pulled forward from CAS 5.2.x, which use
JDBC authentication to an Oracle instance instead.
Here's what the relevant (I think) section of my cas.properties looks like,
with appropriate fields redacted. The log4j2.xml is unmodified except for
changing the on-disk output paths.
logging.level.org.apereo.cas=INFO
logging.level.org.springframework=FATAL
logging.level.org.apereo.inspektr.audit=INFO
logging.level.org.apereo.cas.support.pac4j=DEBUG
logging.level.org.pac4j=DEBUG
logging.config=file:/etc/cas/config/log4j2.xml
cas.authn.pac4j.cookie.crypto.encryption.key=REDACTED
cas.authn.pac4j.cookie.crypto.signing.key=REDACTED
cas.authn.pac4j.name=DelegatePAC4J
cas.authn.pac4j.oidc[0].autoRedirect=false
cas.authn.pac4j.oidc[0].azureTenantId=REDACTED.onmicrosoft.com
cas.authn.pac4j.oidc[0].clientName=REDACTED
cas.authn.pac4j.oidc[0].discoveryUri=https:
//login.microsoftonline.com/REDACTED.onmicrosoft.com/v2.0/.well-known/openid-configuration
cas.authn.pac4j.oidc[0].id=REDACTED
cas.authn.pac4j.oidc[0].logoutUrl=https:
//login.microsoftonline.com/REDACTED.onmicrosoft.com}/oauth2/v2.0/logout
cas.authn.pac4j.oidc[0].maxClockSkew=600
cas.authn.pac4j.oidc[0].preferredJwsAlgorithm=RS256
cas.authn.pac4j.oidc[0].principalAttributeId=oid
cas.authn.pac4j.oidc[0].responseMode=form_post
cas.authn.pac4j.oidc[0].responseType=code
cas.authn.pac4j.oidc[0].scope=openid profile
cas.authn.pac4j.oidc[0].secret=REDACTED
cas.authn.pac4j.oidc[0].type=AZURE
cas.authn.pac4j.oidc[0].useNonce=true
cas.authn.pac4j.oidc[0].usePathBasedCallbackUrl=true
cas.authn.policy.req.enabled=true
cas.authn.policy.req.handlerName=DelegatePAC4J
cas.authn.policy.req.tryAll=false
I have also tried the non-v2.0 URLs but saw no difference.
I see the Pac4jAuthenticationEventExecutionPlanConfiguration emit the
following log entry while CAS is initializing, the contents look OK to me
(newlines added for clarity)
> DEBUG
> [org.apereo.cas.support.pac4j.config.support.authentication.Pac4jAuthenticationEventExecutionPlanConfiguration]
>
> - <
> The following clients are built: [
> [
> #AzureAdClient# |
> name: REDACTED |
> callbackUrl: null |
> callbackUrlResolver:
> org.pac4j.core.http.callback.PathParameterCallbackUrlResolver@74bfba13 |
> ajaxRequestResolver: null |
> redirectActionBuilder: null |
> credentialsExtractor: null |
> authenticator: null |
> profileCreator:
> org.pac4j.core.profile.creator.AuthenticatorProfileCreator@52907b1e |
> logoutActionBuilder:
> org.pac4j.core.logout.NoLogoutActionBuilder@1865ede |
> authorizationGenerators: [] |
> configuration: #AzureAdOidcConfiguration# |
> clientId: REDACTED |
> secret: [protected] |
> discoveryURI:
> https://login.microsoftonline.com/REDACTED.onmicrosoft.com/.well-known/openid-configuration
>
> |
> scope: openid profile |
> customParams: {} |
> clientAuthenticationMethod: null |
> useNonce: true |
> preferredJwsAlgorithm: RS256 |
> maxAge: null |
> maxClockSkew: 600 |
> connectTimeout: 500 |
> readTimeout: 5000 |
> resourceRetriever: null |
> responseType: code |
> responseMode: form_post |
> logoutUrl:
> https://login.microsoftonline.com/REDACTED.onmicrosoft.com/oauth2/v2.0/logout
> |
> withState: false |
> stateGenerator:
> org.pac4j.core.state.StaticOrRandomStateGenerator@68b2c3aa |
> |
> ]
> ]
> >
>
I wouldn't be surprised in the least to find i'm doing something stupid,
here. The docs just throw parameter names at you, with no explanation or
type information - and I had to refer to the source code many times.
For giggles, my pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi=
"http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd ">
<modelVersion>4.0.0</modelVersion>
<groupId>edu.usf.epi</groupId>
<artifactId>cas-overlay</artifactId>
<packaging>war</packaging>
<version>1.0</version>
<properties>
<cas.version>5.3.9</cas.version>
<springboot.version>1.5.18.RELEASE</springboot.version>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8
</project.reporting.outputEncoding>
<mainClassName>org.springframework.boot.loader.WarLauncher
</mainClassName>
<isExecutable>false</isExecutable>
<manifestFileToUse>
${project.build.directory}/war/work/org.apereo.cas/cas-server-webapp/META-INF/MANIFEST.MF
</manifestFileToUse>
</properties>
<profiles>
<profile>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<id>default</id>
<dependencies>
<dependency>
<groupId>org.apereo.cas</groupId>
<artifactId>cas-server-webapp</artifactId>
<version>${cas.version}</version>
<type>war</type>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.apereo.cas</groupId>
<artifactId>cas-server-support-pac4j-webflow</artifactId>
<version>${cas.version}</version>
</dependency>
<dependency>
<groupId>org.apereo.cas</groupId>
<artifactId>cas-server-support-json-service-registry</artifactId>
<version>${cas.version}</version>
</dependency>
<dependency>
<groupId>org.apereo.cas</groupId>
<artifactId>cas-server-support-redis-ticket-registry</artifactId>
<version>${cas.version}</version>
</dependency>
<dependency>
<!-- see also Dockerfile and go.sh -->
<groupId>com.oracle.jdbc</groupId>
<artifactId>ojdbc8</artifactId>
<version>12.2.0.1</version>
</dependency>
</dependencies>
</profile>
</profiles>
<repositories>
<repository>
<id>sonatype-releases</id>
<url>http://oss.sonatype.org/content/repositories/releases/</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
<releases>
<enabled>true</enabled>
</releases>
</repository>
<repository>
<id>sonatype-snapshots</id>
<url>https://oss.sonatype.org/content/repositories/snapshots/</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
<releases>
<enabled>false</enabled>
</releases>
</repository>
<repository>
<id>shibboleth-releases</id>
<url>https://build.shibboleth.net/nexus/content/repositories/releases
</url>
</repository>
</repositories>
<build>
<plugins> <!-- keep versions in sync with ../cas-redirect/pom.xml for
efficiency -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${springboot.version}</version>
<configuration>
<mainClass>${mainClassName}</mainClass>
<addResources>true</addResources>
<executable>${isExecutable}</executable>
<layout>WAR</layout>
<excludeDevtools>true</excludeDevtools>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.6</version>
<configuration>
<warName>cas</warName>
<failOnMissingWebXml>false</failOnMissingWebXml>
<recompressZippedFiles>false</recompressZippedFiles>
<archive>
<compress>false</compress>
<manifestFile>${manifestFileToUse}</manifestFile>
</archive>
<overlays>
<overlay>
<groupId>org.apereo.cas</groupId>
<artifactId>cas-server-webapp</artifactId>
</overlay>
</overlays>
<!-- because excludeDevtools is broken in
spring-boot-maven-plugin, we use the nuclear option. -->
<!-- also tomcat leaks in and 'mvn dependency:tree' doesn't show
why... -->
<packagingExcludes>
WEB-INF/lib/spring-boot-devtools-*.jar,
WEB-INF/lib/spring-boot-starter-tomcat-*.jar,
WEB-INF/lib/tomcat-*.jar
</packagingExcludes>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.3</version>
</plugin>
</plugins>
<finalName>cas</finalName>
</build>
</project>
--
- Website: https://apereo.github.io/cas
- Gitter Chatroom: https://gitter.im/apereo/cas
- List Guidelines: https://goo.gl/1VRrw7
- Contributions: https://goo.gl/mh7qDG
---
You received this message because you are subscribed to the Google Groups "CAS
Community" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To view this discussion on the web visit
https://groups.google.com/a/apereo.org/d/msgid/cas-user/d17efbfa-cd4b-42be-910f-98a7b382c376%40apereo.org.