CAS 6.2.2 doesn't work with token (JWT) as one of auth factors (MFA-U2F)

967 views Asked by At

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'}"
}
0

There are 0 answers