-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix deserialize incompatibility with Okta ver claim #743
Fix deserialize incompatibility with Okta ver claim #743
Conversation
Okta includes a "ver" claim in the ID tokens it issues that gets serialized to json, as follows: { "@Class": "java.util.Collections$UnmodifiableMap", "ver": [ "java.lang.Long", 1 ] } This claim is added by Okta automatically (see https://developer.okta.com/docs/reference/api/oidc/#id-token). Spring Authorization Server encounters an error when attempting to deserialize this claim from Okta, as follows: The class with java.lang.Long and name of java.lang.Long is not in the allowlist. If you believe this class is safe to deserialize, please provide an explicit mapping using Jackson annotations or by providing a Mixin. If the serialization is only done by a trusted source, you can also enable default typing. See spring-projects/spring-security#4370 for details java.lang.IllegalArgumentException: The class with java.lang.Long and name of java.lang.Long is not in the allowlist. If you believe this class is safe to deserialize, please provide an explicit mapping using Jackson annotations or by providing a Mixin. If the serialization is only done by a trusted source, you can also enable default typing. See spring-projects/spring-security#4370 for details at org.springframework.security.jackson2.SecurityJackson2Modules$AllowlistTypeIdResolver.typeFromId(SecurityJackson2Modules.java:253) The issue at spring-projects#567 has a similar symptom, though this situation would be encountered by any project wanting to do OpenID Connect with Okta. I thus recommend a general fix rather than a project-specific fix as recommended at spring-projects#397. This commit defines a mixin that doesn't alter the json formatting but does instruct SecurityJackson2Modules.AllowlistTypeIdResolver to permit the deseralization of that Okta claim. For broader context, below is an example of what is stored by Spring Authorization Server in the attributes column of the oauth2_authorization table. { "@Class": "java.util.Collections$UnmodifiableMap", "org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest": { "@Class": "org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest", "authorizationUri": "http://localhost:5000/oauth2/authorize", "authorizationGrantType": { "value": "authorization_code" }, "responseType": { "value": "code" }, "clientId": "login-acceptance-test-client", "redirectUri": "http://127.0.0.1:5000/login-acceptance-test-client", "scopes": [ "java.util.Collections$UnmodifiableSet", [ "openid" ] ], "state": "foo", "additionalParameters": { "@Class": "java.util.Collections$UnmodifiableMap" }, "authorizationRequestUri": "http://localhost:5000/oauth2/authorize?response_type=code&client_id=login-acceptance-test-client&scope=openid&state=foo&redirect_uri=http://127.0.0.1:5000/login-acceptance-test-client", "attributes": { "@Class": "java.util.Collections$UnmodifiableMap" } }, "java.security.Principal": { "@Class": "org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken", "principal": { "@Class": "org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser", "authorities": [ "java.util.Collections$UnmodifiableSet", [ { "@Class": "org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority", "authority": "ROLE_USER", "idToken": { "@Class": "org.springframework.security.oauth2.core.oidc.OidcIdToken", "tokenValue": "actualTokenValueRemoved", "issuedAt": 1652411922.000000000, "expiresAt": 1652415522.000000000, "claims": { "@Class": "java.util.Collections$UnmodifiableMap", "at_hash": "X4TpvHXTMw3kaBMRas-H6A", "sub": "00u4zn2e0w8cRRFv75d1", "ver": [ "java.lang.Long", 1 ], "amr": [ "java.util.ArrayList", [ "pwd" ] ], "iss": [ "java.net.URL", "https://dev-231911394952.okta.com" ], "preferred_username": "login.at.okta.public.local", "nonce": "pWQDfOxvpslr_FWiTqHlIRdCiT5Sfq-PWvBmSyrDki0", "aud": [ "java.util.ArrayList", [ "0oa4varwxxjIMcDph5d7" ] ], "idp": "00otba6t5x5dSt78H5d6", "auth_time": [ "java.time.Instant", 1652411920.000000000 ], "name": "login at okta public local test", "exp": [ "java.time.Instant", 1652415522.000000000 ], "iat": [ "java.time.Instant", 1652411922.000000000 ], "email": "login.at.okta.public.local@example.com", "jti": "ID.IfAKG8WvCxOy023DLl6I-WY-4pHgxMJZv1F79rqAtYM" } }, "userInfo": { "@Class": "org.springframework.security.oauth2.core.oidc.OidcUserInfo", "claims": { "@Class": "java.util.Collections$UnmodifiableMap", "sub": "00u4zn2e0w8cRRFv75d1", "zoneinfo": "America/Los_Angeles", "email_verified": true, "updated_at": [ "java.time.Instant", 1652216788.000000000 ], "name": "login at okta public local test", "preferred_username": "login.at.okta.public.local", "locale": "en_US", "given_name": "login at okta public local", "family_name": "test", "email": "login.at.okta.public.local@example.com" } } }, { "@Class": "org.springframework.security.core.authority.SimpleGrantedAuthority", "authority": "SCOPE_email" }, { "@Class": "org.springframework.security.core.authority.SimpleGrantedAuthority", "authority": "SCOPE_openid" }, { "@Class": "org.springframework.security.core.authority.SimpleGrantedAuthority", "authority": "SCOPE_phone" }, { "@Class": "org.springframework.security.core.authority.SimpleGrantedAuthority", "authority": "SCOPE_profile" } ] ], "idToken": { "@Class": "org.springframework.security.oauth2.core.oidc.OidcIdToken", "tokenValue": "actualTokenValueRemoved", "issuedAt": 1652411922.000000000, "expiresAt": 1652415522.000000000, "claims": { "@Class": "java.util.Collections$UnmodifiableMap", "at_hash": "X4TpvHXTMw3kaBMRas-H6A", "sub": "00u4zn2e0w8cRRFv75d1", "ver": [ "java.lang.Long", 1 ], "amr": [ "java.util.ArrayList", [ "pwd" ] ], "iss": [ "java.net.URL", "https://dev-231911394952.okta.com" ], "preferred_username": "login.at.okta.public.local", "nonce": "pWQDfOxvpslr_FWiTqHlIRdCiT5Sfq-PWvBmSyrDki0", "aud": [ "java.util.ArrayList", [ "0oa4varwxxjIMcDph5d7" ] ], "idp": "00otba6t5x5dSt78H5d6", "auth_time": [ "java.time.Instant", 1652411920.000000000 ], "name": "login at okta public local test", "exp": [ "java.time.Instant", 1652415522.000000000 ], "iat": [ "java.time.Instant", 1652411922.000000000 ], "email": "login.at.okta.public.local@example.com", "jti": "ID.IfAKG8WvCxOy023DLl6I-WY-4pHgxMJZv1F79rqAtYM" } }, "userInfo": { "@Class": "org.springframework.security.oauth2.core.oidc.OidcUserInfo", "claims": { "@Class": "java.util.Collections$UnmodifiableMap", "sub": "00u4zn2e0w8cRRFv75d1", "zoneinfo": "America/Los_Angeles", "email_verified": true, "updated_at": [ "java.time.Instant", 1652216788.000000000 ], "name": "login at okta public local test", "preferred_username": "login.at.okta.public.local", "locale": "en_US", "given_name": "login at okta public local", "family_name": "test", "email": "login.at.okta.public.local@example.com" } }, "nameAttributeKey": "sub" }, "authorities": [ "java.util.Collections$UnmodifiableRandomAccessList", [ { "@Class": "org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority", "authority": "ROLE_USER", "idToken": { "@Class": "org.springframework.security.oauth2.core.oidc.OidcIdToken", "tokenValue": "actualTokenValueRemoved", "issuedAt": 1652411922.000000000, "expiresAt": 1652415522.000000000, "claims": { "@Class": "java.util.Collections$UnmodifiableMap", "at_hash": "X4TpvHXTMw3kaBMRas-H6A", "sub": "00u4zn2e0w8cRRFv75d1", "ver": [ "java.lang.Long", 1 ], "amr": [ "java.util.ArrayList", [ "pwd" ] ], "iss": [ "java.net.URL", "https://dev-231911394952.okta.com" ], "preferred_username": "login.at.okta.public.local", "nonce": "pWQDfOxvpslr_FWiTqHlIRdCiT5Sfq-PWvBmSyrDki0", "aud": [ "java.util.ArrayList", [ "0oa4varwxxjIMcDph5d7" ] ], "idp": "00otba6t5x5dSt78H5d6", "auth_time": [ "java.time.Instant", 1652411920.000000000 ], "name": "login at okta public local test", "exp": [ "java.time.Instant", 1652415522.000000000 ], "iat": [ "java.time.Instant", 1652411922.000000000 ], "email": "login.at.okta.public.local@example.com", "jti": "ID.IfAKG8WvCxOy023DLl6I-WY-4pHgxMJZv1F79rqAtYM" } }, "userInfo": { "@Class": "org.springframework.security.oauth2.core.oidc.OidcUserInfo", "claims": { "@Class": "java.util.Collections$UnmodifiableMap", "sub": "00u4zn2e0w8cRRFv75d1", "zoneinfo": "America/Los_Angeles", "email_verified": true, "updated_at": [ "java.time.Instant", 1652216788.000000000 ], "name": "login at okta public local test", "preferred_username": "login.at.okta.public.local", "locale": "en_US", "given_name": "login at okta public local", "family_name": "test", "email": "login.at.okta.public.local@example.com" } } }, { "@Class": "org.springframework.security.core.authority.SimpleGrantedAuthority", "authority": "SCOPE_email" }, { "@Class": "org.springframework.security.core.authority.SimpleGrantedAuthority", "authority": "SCOPE_openid" }, { "@Class": "org.springframework.security.core.authority.SimpleGrantedAuthority", "authority": "SCOPE_phone" }, { "@Class": "org.springframework.security.core.authority.SimpleGrantedAuthority", "authority": "SCOPE_profile" } ] ], "authorizedClientRegistrationId": "oktaDev", "details": { "@Class": "org.springframework.security.web.authentication.WebAuthenticationDetails", "remoteAddress": "127.0.0.1", "sessionId": "A8634EA2537A251E8B3D22A5CC4A6E3D" } }, "org.springframework.security.oauth2.server.authorization.OAuth2Authorization.AUTHORIZED_SCOPE": [ "java.util.Collections$UnmodifiableSet", [ "openid" ] ] }
Hi @kinsersh, welcome to the project! It sounds like you're using Okta as your identity provider. Is this in the context of federated identity (similar to the federated identity sample)? Is there a reason you can't add this mixin yourself as in, for example, this comment? |
Yes, I'm consuming Okta and others. I have added that mixin per that comment to work around this issue - it works well. I get it that Spring Authorization Server should stay free of project-specifics Here are my reasons for submitting this pull request:
|
Thanks for the feedback and your response @kinsersh! I see your points and while, as you mention, we want to stay free of embedding any customization needed for a specific use case in the core (for the time being), there are two sides to every coin such as increased friction and developer experience in this case. We could open an issue to outline some general improvements to the
It's a small detail, but in this case I'm not sure it's a workaround so much as a customization. So while we want to make customization possible, there are lots of examples where it will be required to customize, as that's the framework's entire purpose to some degree. Just wanted to clarify that point in case it's relevant later. For now, I'll leave this open as a placeholder for that more general issue I want to open, once we've discussed it a bit. |
Sounds good. I'm all for a general fix. What I have here is a narrow fix - solves a compatibility issue with Okta right now, though what's the likelihood that Okta or another provider releases a change to their ID token format that uses a different primitive class for serializing/deserializing that's not yet trusted. |
@kinsersh Thanks for all the details. As discussed, there are customization points that allow an application to configure serialization/deserialization for JSON types. Adding Furthermore, adding this narrow change will open us to adding other narrow changes requested by users (in the future), and ultimately forcing us to maintain more code. I see this as a minor customization on your end and I'm assuming there are other customizations required to federate Spring Authorization Server with Okta, so they all fall into that same group of customizations required to integrate with Okta. Based on the explanation above, we cannot accept this change as it's not required for the framework specific code. |
Okta includes a "ver" claim in the ID tokens it issues that gets serialized to json, as follows:
{
"@Class": "java.util.Collections$UnmodifiableMap",
"ver": [
"java.lang.Long",
1
]
}
This claim is added by Okta automatically (see https://developer.okta.com/docs/reference/api/oidc/#id-token).
Spring Authorization Server encounters an error when attempting to deserialize this claim from Okta, as follows:
The class with java.lang.Long and name of java.lang.Long is not in the allowlist. If you believe this class is safe to deserialize, please provide an explicit mapping using Jackson annotations or by providing a Mixin. If the serialization is only done by a trusted source, you can also enable default typing. See spring-projects/spring-security#4370 for details
java.lang.IllegalArgumentException: The class with java.lang.Long and name of java.lang.Long is not in the allowlist. If you believe this class is safe to deserialize, please provide an explicit mapping using Jackson annotations or by providing a Mixin. If the serialization is only done by a trusted source, you can also enable default typing. See spring-projects/spring-security#4370 for details
at org.springframework.security.jackson2.SecurityJackson2Modules$AllowlistTypeIdResolver.typeFromId(SecurityJackson2Modules.java:253)
The issue at #567 has a similar symptom, though the situation being addressed in this pull request is different in that it would be encountered by any project wanting to do OpenID Connect with Okta. I thus recommend a general fix rather than a project-specific fix as recommended at #397.
This commit defines a mixin that doesn't alter the json formatting but does instruct SecurityJackson2Modules.AllowlistTypeIdResolver to permit the deseralization of that Okta claim.