I'm trying to get a response from a REST method in my service (https://jwt.test.local:8443/Site/rest/user/getCurrent) using JWT (passing it in headers like token:eyJjdHkiOiJKV1QiL...).
If CAS doesn't use U2F (remove cas.properties cas.authn.mfa.globalProviderId = mfa-u2f from cas.properties), then everything works as should. successful request
If I use U2F and additionally put cas.authn.mfa.u2f.bypass.credential-class-type =. * Jwt. * |. * Jwt. * |. * JWT. * |. * Token. * |. * Token. * |. * UsernamePassword. * in cas.properties, then authorization via the web form works correctly (U2F check is skipped), but it's not working when I try to get response from REST with JWT (CAS try to redirect me to login page and some error appears in the log)
Log:
ERROR [org.springframework.boot.web.servlet.support.ErrorPageFilter] - <Cannot forward to error page for request [/login] as the response has already been committed. As a result, the response may have the wrong status code. If your application is running on WebSphere Application Server you may be able to resolve this problem by setting com.ibm.ws.webcontainer.invokeFlushAfterService to false>
org.springframework.webflow.execution.FlowExecutionException: Exception thrown in state 'viewLoginForm' of flow 'login'
at org.springframework.webflow.engine.impl.FlowExecutionImpl.wrap(FlowExecutionImpl.java:573) ~[spring-webflow-2.5.1.RELEASE.jar:2.5.1.RELEASE]
at org.springframework.webflow.engine.impl.FlowExecutionImpl.start(FlowExecutionImpl.java:227) ~[spring-webflow-2.5.1.RELEASE.jar:2.5.1.RELEASE]
at org.springframework.webflow.executor.FlowExecutorImpl.launchExecution(FlowExecutorImpl.java:139) ~[spring-webflow-2.5.1.RELEASE.jar:2.5.1.RELEASE]
at jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:?]
at jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:?]
at jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:?]
at java.lang.reflect.Method.invoke(Method.java:567) ~[?:?]
at org.springframework.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:282) ~[spring-core-5.2.6.RELEASE.jar:5.2.6.RELEASE]
at org.springframework.cloud.context.scope.GenericScope$LockedScopedProxyFactoryBean.invoke(GenericScope.java:499) ~[spring-cloud-context-2.2.2.RELEASE.jar:2.2.2.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.2.6.RELEASE.jar:5.2.6.RELEASE]
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:212) ~[spring-aop-5.2.6.RELEASE.jar:5.2.6.RELEASE]
at com.sun.proxy.$Proxy275.launchExecution(Unknown Source) ~[?:?]
at org.springframework.webflow.mvc.servlet.FlowHandlerAdapter.handle(FlowHandlerAdapter.java:264) ~[spring-webflow-2.5.1.RELEASE.jar:2.5.1.RELEASE]
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040) ~[spring-webmvc-5.2.6.RELEASE.jar:5.2.6.RELEASE]
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943) ~[spring-webmvc-5.2.6.RELEASE.jar:5.2.6.RELEASE]
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006) ~[spring-webmvc-5.2.6.RELEASE.jar:5.2.6.RELEASE]
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898) ~[spring-webmvc-5.2.6.RELEASE.jar:5.2.6.RELEASE]
at javax.servlet.http.HttpServlet.service(HttpServlet.java:634) ~[servlet-api.jar:?]
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883) ~[spring-webmvc-5.2.6.RELEASE.jar:5.2.6.RELEASE]
at javax.servlet.http.HttpServlet.service(HttpServlet.java:741) ~[servlet-api.jar:?]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231) ~[catalina.jar:8.5.45]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[catalina.jar:8.5.45]
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52) ~[tomcat-websocket.jar:8.5.45]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[catalina.jar:8.5.45]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[catalina.jar:8.5.45]
at org.apereo.cas.web.support.AuthenticationCredentialsThreadLocalBinderClearingFilter.doFilter(AuthenticationCredentialsThreadLocalBinderClearingFilter.java:28) ~[cas-server-core-web-api-6.2.2.jar:6.2.2]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[catalina.jar:8.5.45]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[catalina.jar:8.5.45]
at org.apereo.cas.web.support.filters.RequestParameterPolicyEnforcementFilter.doFilter(RequestParameterPolicyEnforcementFilter.java:409) ~[cas-server-core-web-api-6.2.2.jar:6.2.2]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[catalina.jar:8.5.45]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[catalina.jar:8.5.45]
at org.apereo.cas.web.support.filters.ResponseHeadersEnforcementFilter.doFilter(ResponseHeadersEnforcementFilter.java:199) ~[cas-server-core-web-api-6.2.2.jar:6.2.2]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[catalina.jar:8.5.45]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[catalina.jar:8.5.45]
at org.apereo.cas.web.support.filters.AddResponseHeadersFilter.doFilter(AddResponseHeadersFilter.java:63) ~[cas-server-core-web-api-6.2.2.jar:6.2.2]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[catalina.jar:8.5.45]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[catalina.jar:8.5.45]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:320) ~[spring-security-web-5.3.2.RELEASE.jar:5.3.2.RELEASE]
at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:126) ~[spring-security-web-5.3.2.RELEASE.jar:5.3.2.RELEASE]
at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:90) ~[spring-security-web-5.3.2.RELEASE.jar:5.3.2.RELEASE]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.3.2.RELEASE.jar:5.3.2.RELEASE]
at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:118) ~[spring-security-web-5.3.2.RELEASE.jar:5.3.2.RELEASE]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.3.2.RELEASE.jar:5.3.2.RELEASE]
at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:137) ~[spring-security-web-5.3.2.RELEASE.jar:5.3.2.RELEASE]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.3.2.RELEASE.jar:5.3.2.RELEASE]
at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:111) ~[spring-security-web-5.3.2.RELEASE.jar:5.3.2.RELEASE]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.3.2.RELEASE.jar:5.3.2.RELEASE]
at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:158) ~[spring-security-web-5.3.2.RELEASE.jar:5.3.2.RELEASE]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.3.2.RELEASE.jar:5.3.2.RELEASE]
at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:63) ~[spring-security-web-5.3.2.RELEASE.jar:5.3.2.RELEASE]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.3.2.RELEASE.jar:5.3.2.RELEASE]
at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:105) ~[spring-security-web-5.3.2.RELEASE.jar:5.3.2.RELEASE]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.3.2.RELEASE.jar:5.3.2.RELEASE]
at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:56) ~[spring-security-web-5.3.2.RELEASE.jar:5.3.2.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.6.RELEASE.jar:5.2.6.RELEASE]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.3.2.RELEASE.jar:5.3.2.RELEASE]
at org.springframework.security.web.access.channel.ChannelProcessingFilter.doFilter(ChannelProcessingFilter.java:157) ~[spring-security-web-5.3.2.RELEASE.jar:5.3.2.RELEASE]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.3.2.RELEASE.jar:5.3.2.RELEASE]
at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:215) ~[spring-security-web-5.3.2.RELEASE.jar:5.3.2.RELEASE]
how I generated the token (I use CasCommandLineShellApplication):
cas>generate-jwt --subject username --encryptionMethod A192CBC-HS384
==== Signing Secret ====
pIDhCAxHjhZrszsMyPJ90eXZT2F6rYymfuGMpzAME6nwwFWl5bgMPbux...
==== Encryption Secret ====
[uVa7pe8zrLBuDlZk3VvODY...]
Generating JWT for subject [username] with signing key size [256], signing algorithm [HS256], encryption key size [48], encryption method [A192CBC-HS384] and encryption algorithm [dir]
==== JWT ====
[eyJjdHkiOiJKV1QiLCJlbmMiOiJBMTkyQ0JDLUhTMzg0IiwiYWxnIjoiZGlyIn0..xJpqSR...]
service:
{
"@class" : "org.jasig.cas.services.RegexRegisteredService",
"serviceId" : ".*Site.*",
"informationUrl": "Site",
"name" : "Site",
"id" : 10000005,
"description" : "Site",
"logoutType" : "BACK_CHANNEL",
"attributeReleasePolicy" : {
"@class" : "org.jasig.cas.services.ReturnAllAttributeReleasePolicy",
"authorizedToReleaseCredentialPassword" : false,
"authorizedToReleaseProxyGrantingTicket" : false
},
"accessStrategy" : {
"@class" : "org.jasig.cas.services.DefaultRegisteredServiceAccessStrategy",
"enabled" : true,
"ssoEnabled" : true
},
"properties" : {
"@class" : "java.util.HashMap",
"jwtSigningSecret" : {
"@class" : "org.apereo.cas.services.DefaultRegisteredServiceProperty",
"values" : [
"java.util.HashSet", [
"pIDhCAxHjhZrszsMyPJ90eXZT2F6rYymfuGMpzAME6nwwFWl5bgMPbux..."
]
]
},
"jwtEncryptionSecret" : {
"@class" : "org.apereo.cas.services.DefaultRegisteredServiceProperty",
"values" : [
"java.util.LinkedHashSet", [
"uVa7pe8zrLBuDlZk3VvODY..."
]
]
}
}
}
cas.properties:
cas.server.name=https://jwt.test.local:8443
cas.server.prefix=${cas.server.name}/cas
logging.config: file:/etc/cas/config/log4j2.xml
cas.authn.ldap[0].type=AUTHENTICATED
cas.authn.ldap[0].ldapUrl=ldap://localhost:389
cas.authn.ldap[0].useSsl=false
cas.authn.ldap[0].searchFilter=cn={user}
cas.authn.ldap[0].baseDn=ou=users,dc=domain123
cas.authn.ldap[0].bindDn=cn=admin,dc=domain123
cas.authn.ldap[0].bindCredential=0000
cas.authn.ldap[0].principalAttributeList=memberOf,memberof,cn,description
cas.serviceRegistry.watcherEnabled=true
cas.serviceRegistry.schedule.repeatInterval=120000
cas.serviceRegistry.schedule.startDelay=15000
# Auto-initialize the registry from default JSON service definitions
cas.serviceRegistry.initFromJson=true
cas.serviceRegistry.managementType=DEFAULT
cas.serviceRegistry.json.location=classpath:/services
cas.tgc.secure=false
cas.tgc.same-site-policy=none
cas.ticket.tgt.rememberMe.enabled=true
cas.ticket.tgt.rememberMe.timeToKillInSeconds=1728000
cas.ticket.tgt.timeout.maxTimeToLiveInSeconds=1728000
management.endpoints.web.exposure.include=registeredServices
management.endpoint.registeredServices.enabled=true
cas.monitor.endpoints.endpoint.registeredServices.access=PERMIT
spring.security.user.name=casuser
spring.security.user.password=Mellon
###
cas.authn.mfa.globalProviderId=mfa-u2f
#cas.authn.mfa.trusted.deviceFingerprint.cookie.enabled=false
###
#cas.authn.mfa.u2f.rank=0
#cas.authn.mfa.u2f.name=AAA
# cas.authn.mfa.u2f.order=
###
cas.authn.mfa.u2f.expireRegistrations=30
cas.authn.mfa.u2f.expireRegistrationsTimeUnit=SECONDS
cas.authn.mfa.u2f.expireDevices=30
cas.authn.mfa.u2f.expireDevicesTimeUnit=DAYS
###
cas.authn.mfa.u2f.json.location=file:///etc/cas/config/u2fdevices.json
cas.authn.mfa.u2f.bypass.credential-class-type=.*Jwt.*|.*jwt.*|.*JWT.*|.*Token.*|.*token.*|.*UsernamePassword.*
gradle.properties (from cas-overlay-template):
# Versions
cas.version=6.2.2
springBootVersion=2.2.8.RELEASE
# Use -jetty, -undertow to other containers
# Or blank if you want to deploy to an external container
appServer=-tomcat
executable=false
tomcatVersion=9.0.37
group=org.apereo.cas
sourceCompatibility=11
targetCompatibility=11
jibVersion=2.4.0
# Location of the downloaded CAS shell JAR
shellDir=build/libs
ivyVersion=2.4.0
gradleDownloadTaskVersion=3.4.3
gradleMavenPluginVersion=4.1.5
gradleLombokPluginVersion=4.1.5
# use without "-slim" in tag name if you want tools like jstack, adds about 100MB to image size
# (https://hub.docker.com/r/adoptopenjdk/openjdk11/tags/)
baseDockerImage=adoptopenjdk/openjdk11:alpine-jre
allowInsecureRegistries=false
build.gradle dependencies (from cas-overlay-template):
dependencies {
// Other CAS dependencies/modules may be listed here...
// implementation "org.apereo.cas:cas-server-support-json-service-registry:${casServerVersion}"
compile "org.apereo.cas:cas-server-support-ldap:${project.'cas.version'}"
compile "org.apereo.cas:cas-server-support-rest:${project.'cas.version'}"
compile "org.apereo.cas:cas-server-support-reports:${project.'cas.version'}"
compile "org.apereo.cas:cas-server-support-u2f:${project.'cas.version'}"
compile "org.apereo.cas:cas-server-support-rest-tokens:${project.'cas.version'}"
compile "org.apereo.cas:cas-server-support-token-webflow:${project.'cas.version'}"
compile "org.apereo.cas:cas-server-support-token-tickets:${project.'cas.version'}"
compile "org.apereo.cas:cas-server-support-swagger:${project.'cas.version'}"
}