Skip to content

Commit

Permalink
Fix deserialize incompatibility with Okta ver claim
Browse files Browse the repository at this point in the history
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"
    ]
  ]
}
  • Loading branch information
kinsersh committed May 18, 2022
1 parent fc05e22 commit 9300062
Show file tree
Hide file tree
Showing 3 changed files with 39 additions and 0 deletions.
@@ -0,0 +1,31 @@
/*
* Copyright 2020-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.server.authorization.jackson2;

import com.fasterxml.jackson.annotation.JsonProperty;

/**
* This mixin class is used to serialize/deserialize {@link Long} for solving a compatibility issue with the Okta "ver"
* claim.
*
* @author Stephen Kinser
* @since 0.2.4
*/
abstract class LongMixin {
@SuppressWarnings("unused")
@JsonProperty("long")
Long value;
}
Expand Up @@ -82,6 +82,7 @@ public void setupModule(SetupContext context) {
context.setMixInAnnotations(SignatureAlgorithm.class, JwsAlgorithmMixin.class);
context.setMixInAnnotations(MacAlgorithm.class, JwsAlgorithmMixin.class);
context.setMixInAnnotations(OAuth2TokenFormat.class, OAuth2TokenFormatMixin.class);
context.setMixInAnnotations(Long.class, LongMixin.class);
}

}
Expand Up @@ -71,4 +71,11 @@ public void readValueWhenLinkedHashSetThenSuccess() throws Exception {
String json = this.objectMapper.writeValueAsString(set);
assertThat(this.objectMapper.readValue(json, STRING_SET)).isEqualTo(set);
}

@Test
public void readValueWhenUnmodifiableMapWithLongThenSuccess() throws Exception {
Map<String, Object> map = Collections.unmodifiableMap(new HashMap<>(Collections.singletonMap("ver", 1L)));
String json = this.objectMapper.writeValueAsString(map);
assertThat(this.objectMapper.readValue(json, STRING_OBJECT_MAP)).isEqualTo(map);
}
}

0 comments on commit 9300062

Please sign in to comment.