Browse Source

Add jackson module for authorization server

Fixes problems with serialization of complex attribute values of various framework types such as OAuth2AuthorizationRequest and OAuth2ClientAuthenticationToken.

Closes gh-324
Closes gh-328
Steve Riesenberg 4 years ago
parent
commit
232b3b7ac6
12 changed files with 523 additions and 1 deletions
  1. 1 0
      oauth2-authorization-server/spring-security-oauth2-authorization-server.gradle
  2. 15 0
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/JdbcOAuth2AuthorizationService.java
  3. 40 0
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson2/HashSetMixin.java
  4. 65 0
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson2/JsonNodeUtils.java
  5. 81 0
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson2/OAuth2AuthorizationRequestDeserializer.java
  6. 43 0
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson2/OAuth2AuthorizationRequestMixin.java
  7. 51 0
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson2/OAuth2ClientAuthenticationTokenMixin.java
  8. 73 0
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson2/OAuth2ServerJackson2Module.java
  9. 54 0
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson2/StdConverters.java
  10. 54 0
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson2/UnmodifiableMapDeserializer.java
  11. 45 0
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson2/UnmodifiableMapMixin.java
  12. 1 1
      oauth2-authorization-server/src/main/resources/org/springframework/security/oauth2/server/authorization/oauth2-authorization-schema.sql

+ 1 - 0
oauth2-authorization-server/spring-security-oauth2-authorization-server.gradle

@@ -10,6 +10,7 @@ dependencies {
 	compile 'com.nimbusds:nimbus-jose-jwt'
 	compile 'com.fasterxml.jackson.core:jackson-databind'
 
+	optional 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310'
 	optional 'org.springframework:spring-jdbc'
 
 	testCompile 'org.springframework.security:spring-security-test'

+ 15 - 0
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/JdbcOAuth2AuthorizationService.java

@@ -29,6 +29,7 @@ import java.util.Map;
 import java.util.Set;
 import java.util.function.Function;
 
+import com.fasterxml.jackson.databind.Module;
 import com.fasterxml.jackson.databind.ObjectMapper;
 
 import org.springframework.dao.DataRetrievalFailureException;
@@ -41,6 +42,7 @@ import org.springframework.jdbc.support.lob.DefaultLobHandler;
 import org.springframework.jdbc.support.lob.LobCreator;
 import org.springframework.jdbc.support.lob.LobHandler;
 import org.springframework.lang.Nullable;
+import org.springframework.security.jackson2.SecurityJackson2Modules;
 import org.springframework.security.oauth2.core.AbstractOAuth2Token;
 import org.springframework.security.oauth2.core.AuthorizationGrantType;
 import org.springframework.security.oauth2.core.OAuth2AccessToken;
@@ -51,6 +53,7 @@ import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
 import org.springframework.security.oauth2.core.oidc.OidcIdToken;
 import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
 import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
+import org.springframework.security.oauth2.server.authorization.jackson2.OAuth2ServerJackson2Module;
 import org.springframework.util.Assert;
 import org.springframework.util.CollectionUtils;
 import org.springframework.util.StringUtils;
@@ -312,6 +315,11 @@ public class JdbcOAuth2AuthorizationService implements OAuth2AuthorizationServic
 		public OAuth2AuthorizationRowMapper(RegisteredClientRepository registeredClientRepository) {
 			Assert.notNull(registeredClientRepository, "registeredClientRepository cannot be null");
 			this.registeredClientRepository = registeredClientRepository;
+
+			ClassLoader classLoader = JdbcOAuth2AuthorizationService.class.getClassLoader();
+			List<Module> securityModules = SecurityJackson2Modules.getModules(classLoader);
+			this.objectMapper.registerModules(securityModules);
+			this.objectMapper.registerModule(new OAuth2ServerJackson2Module());
 		}
 
 		@Override
@@ -446,6 +454,13 @@ public class JdbcOAuth2AuthorizationService implements OAuth2AuthorizationServic
 	public static class OAuth2AuthorizationParametersMapper implements Function<OAuth2Authorization, List<SqlParameterValue>> {
 		private ObjectMapper objectMapper = new ObjectMapper();
 
+		public OAuth2AuthorizationParametersMapper() {
+			ClassLoader classLoader = JdbcOAuth2AuthorizationService.class.getClassLoader();
+			List<Module> securityModules = SecurityJackson2Modules.getModules(classLoader);
+			this.objectMapper.registerModules(securityModules);
+			this.objectMapper.registerModule(new OAuth2ServerJackson2Module());
+		}
+
 		@Override
 		public List<SqlParameterValue> apply(OAuth2Authorization authorization) {
 			List<SqlParameterValue> parameters = new ArrayList<>();

+ 40 - 0
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson2/HashSetMixin.java

@@ -0,0 +1,40 @@
+/*
+ * Copyright 2002-2020 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 java.util.HashSet;
+import java.util.Set;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+
+/**
+ * This mixin class is used to serialize/deserialize {@link HashSet}.
+ *
+ * @author Steve Riesenberg
+ * @see HashSet
+ * @see OAuth2ServerJackson2Module
+ * @since 0.1.2
+ */
+@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
+abstract class HashSetMixin {
+
+	@JsonCreator
+	HashSetMixin(Set<?> set) {
+	}
+
+}

+ 65 - 0
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson2/JsonNodeUtils.java

@@ -0,0 +1,65 @@
+/*
+ * Copyright 2002-2020 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 java.util.Map;
+import java.util.Set;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+/**
+ * Utility class for {@code JsonNode}.
+ *
+ * @author Joe Grandja
+ * @since 0.1.2
+ */
+abstract class JsonNodeUtils {
+
+	static final TypeReference<Set<String>> STRING_SET = new TypeReference<Set<String>>() {
+	};
+
+	static final TypeReference<Map<String, Object>> STRING_OBJECT_MAP = new TypeReference<Map<String, Object>>() {
+	};
+
+	static String findStringValue(JsonNode jsonNode, String fieldName) {
+		if (jsonNode == null) {
+			return null;
+		}
+		JsonNode value = jsonNode.findValue(fieldName);
+		return (value != null && value.isTextual()) ? value.asText() : null;
+	}
+
+	static <T> T findValue(JsonNode jsonNode, String fieldName, TypeReference<T> valueTypeReference,
+			ObjectMapper mapper) {
+		if (jsonNode == null) {
+			return null;
+		}
+		JsonNode value = jsonNode.findValue(fieldName);
+		return (value != null && value.isContainerNode()) ? mapper.convertValue(value, valueTypeReference) : null;
+	}
+
+	static JsonNode findObjectNode(JsonNode jsonNode, String fieldName) {
+		if (jsonNode == null) {
+			return null;
+		}
+		JsonNode value = jsonNode.findValue(fieldName);
+		return (value != null && value.isObject()) ? value : null;
+	}
+
+}

+ 81 - 0
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson2/OAuth2AuthorizationRequestDeserializer.java

@@ -0,0 +1,81 @@
+/*
+ * Copyright 2002-2020 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 java.io.IOException;
+
+import com.fasterxml.jackson.core.JsonParseException;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.util.StdConverter;
+
+import org.springframework.security.oauth2.core.AuthorizationGrantType;
+import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
+import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest.Builder;
+
+/**
+ * A {@code JsonDeserializer} for {@link OAuth2AuthorizationRequest}.
+ *
+ * @author Joe Grandja
+ * @since 0.1.2
+ * @see OAuth2AuthorizationRequest
+ * @see OAuth2AuthorizationRequestMixin
+ */
+final class OAuth2AuthorizationRequestDeserializer extends JsonDeserializer<OAuth2AuthorizationRequest> {
+
+	private static final StdConverter<JsonNode, AuthorizationGrantType> AUTHORIZATION_GRANT_TYPE_CONVERTER = new StdConverters.AuthorizationGrantTypeConverter();
+
+	@Override
+	public OAuth2AuthorizationRequest deserialize(JsonParser parser, DeserializationContext context)
+			throws IOException {
+		ObjectMapper mapper = (ObjectMapper) parser.getCodec();
+		JsonNode root = mapper.readTree(parser);
+		return deserialize(parser, mapper, root);
+	}
+
+	private OAuth2AuthorizationRequest deserialize(JsonParser parser, ObjectMapper mapper, JsonNode root)
+			throws JsonParseException {
+		AuthorizationGrantType authorizationGrantType = AUTHORIZATION_GRANT_TYPE_CONVERTER
+				.convert(JsonNodeUtils.findObjectNode(root, "authorizationGrantType"));
+		Builder builder = getBuilder(parser, authorizationGrantType);
+		builder.authorizationUri(JsonNodeUtils.findStringValue(root, "authorizationUri"));
+		builder.clientId(JsonNodeUtils.findStringValue(root, "clientId"));
+		builder.redirectUri(JsonNodeUtils.findStringValue(root, "redirectUri"));
+		builder.scopes(JsonNodeUtils.findValue(root, "scopes", JsonNodeUtils.STRING_SET, mapper));
+		builder.state(JsonNodeUtils.findStringValue(root, "state"));
+		builder.additionalParameters(
+				JsonNodeUtils.findValue(root, "additionalParameters", JsonNodeUtils.STRING_OBJECT_MAP, mapper));
+		builder.authorizationRequestUri(JsonNodeUtils.findStringValue(root, "authorizationRequestUri"));
+		builder.attributes(JsonNodeUtils.findValue(root, "attributes", JsonNodeUtils.STRING_OBJECT_MAP, mapper));
+		return builder.build();
+	}
+
+	private Builder getBuilder(JsonParser parser,
+			AuthorizationGrantType authorizationGrantType) throws JsonParseException {
+		if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(authorizationGrantType)) {
+			return OAuth2AuthorizationRequest.authorizationCode();
+		}
+		if (AuthorizationGrantType.IMPLICIT.equals(authorizationGrantType)) {
+			return OAuth2AuthorizationRequest.implicit();
+		}
+		throw new JsonParseException(parser, "Invalid authorizationGrantType");
+	}
+
+}

+ 43 - 0
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson2/OAuth2AuthorizationRequestMixin.java

@@ -0,0 +1,43 @@
+/*
+ * Copyright 2002-2020 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.JsonAutoDetect;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+
+import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
+
+/**
+ * This mixin class is used to serialize/deserialize {@link OAuth2AuthorizationRequest}.
+ * It also registers a custom deserializer {@link OAuth2AuthorizationRequestDeserializer}.
+ *
+ * @author Joe Grandja
+ * @since 0.1.2
+ * @see OAuth2AuthorizationRequest
+ * @see OAuth2AuthorizationRequestDeserializer
+ * @see OAuth2ServerJackson2Module
+ */
+@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
+@JsonDeserialize(using = OAuth2AuthorizationRequestDeserializer.class)
+@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
+		isGetterVisibility = JsonAutoDetect.Visibility.NONE)
+@JsonIgnoreProperties(ignoreUnknown = true)
+abstract class OAuth2AuthorizationRequestMixin {
+
+}

+ 51 - 0
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson2/OAuth2ClientAuthenticationTokenMixin.java

@@ -0,0 +1,51 @@
+/*
+ * Copyright 2002-2020 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 java.util.Map;
+
+import com.fasterxml.jackson.annotation.JsonAutoDetect;
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+
+import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
+import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken;
+
+/**
+ * This mixin class is used to serialize/deserialize {@link OAuth2ClientAuthenticationToken}.
+ *
+ * @author Joe Grandja
+ * @since 0.1.2
+ * @see OAuth2ClientAuthenticationToken
+ * @see OAuth2ServerJackson2Module
+ */
+@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
+@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
+		isGetterVisibility = JsonAutoDetect.Visibility.NONE)
+@JsonIgnoreProperties(value = { "authenticated" }, ignoreUnknown = true)
+abstract class OAuth2ClientAuthenticationTokenMixin {
+
+	@JsonCreator
+	OAuth2ClientAuthenticationTokenMixin(@JsonProperty("clientId") String clientId,
+			@JsonProperty("clientSecret") String clientSecret,
+			@JsonProperty("clientAuthenticationMethod") ClientAuthenticationMethod clientAuthenticationMethod,
+			@JsonProperty("additionalParameters") Map<String, Object> additionalParameters) {
+	}
+
+}

+ 73 - 0
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson2/OAuth2ServerJackson2Module.java

@@ -0,0 +1,73 @@
+/*
+ * Copyright 2002-2020 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 java.util.Collections;
+import java.util.HashSet;
+
+import com.fasterxml.jackson.core.Version;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+
+import org.springframework.security.jackson2.SecurityJackson2Modules;
+import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
+import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken;
+
+/**
+ * Jackson {@code Module} for {@code spring-authorization-server}, that registers the
+ * following mix-in annotations:
+ *
+ * <ul>
+ * <li>{@link UnmodifiableMapMixin}</li>
+ * <li>{@link HashSetMixin}</li>
+ * <li>{@link OAuth2AuthorizationRequestMixin}</li>
+ * <li>{@link OAuth2ClientAuthenticationTokenMixin}</li>
+ * </ul>
+ *
+ * If not already enabled, default typing will be automatically enabled as type info is
+ * required to properly serialize/deserialize objects. In order to use this module just
+ * add it to your {@code ObjectMapper} configuration.
+ *
+ * <pre>
+ *     ObjectMapper mapper = new ObjectMapper();
+ *     mapper.registerModule(new OAuth2ServerJackson2Module());
+ * </pre>
+ *
+ * @author Steve Riesenberg
+ * @since 0.1.2
+ * @see SecurityJackson2Modules
+ * @see UnmodifiableMapMixin
+ * @see HashSetMixin
+ * @see OAuth2AuthorizationRequestMixin
+ * @see OAuth2ClientAuthenticationTokenMixin
+ */
+public class OAuth2ServerJackson2Module extends SimpleModule {
+
+	public OAuth2ServerJackson2Module() {
+		super(OAuth2ServerJackson2Module.class.getName(), new Version(1, 0, 0, null, null, null));
+	}
+
+	@Override
+	public void setupModule(SetupContext context) {
+		SecurityJackson2Modules.enableDefaultTyping(context.getOwner());
+		context.setMixInAnnotations(Collections.unmodifiableMap(Collections.emptyMap()).getClass(),
+				UnmodifiableMapMixin.class);
+		context.setMixInAnnotations(HashSet.class, HashSetMixin.class);
+		context.setMixInAnnotations(OAuth2AuthorizationRequest.class, OAuth2AuthorizationRequestMixin.class);
+		context.setMixInAnnotations(OAuth2ClientAuthenticationToken.class, OAuth2ClientAuthenticationTokenMixin.class);
+	}
+
+}

+ 54 - 0
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson2/StdConverters.java

@@ -0,0 +1,54 @@
+/*
+ * Copyright 2002-2020 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.databind.JsonNode;
+import com.fasterxml.jackson.databind.util.StdConverter;
+
+import org.springframework.security.oauth2.core.AuthorizationGrantType;
+
+/**
+ * {@code StdConverter} implementations.
+ *
+ * @author Joe Grandja
+ * @since 0.1.2
+ */
+abstract class StdConverters {
+
+	static final class AuthorizationGrantTypeConverter extends StdConverter<JsonNode, AuthorizationGrantType> {
+
+		@Override
+		public AuthorizationGrantType convert(JsonNode jsonNode) {
+			String value = JsonNodeUtils.findStringValue(jsonNode, "value");
+			if (AuthorizationGrantType.AUTHORIZATION_CODE.getValue().equalsIgnoreCase(value)) {
+				return AuthorizationGrantType.AUTHORIZATION_CODE;
+			}
+			if (AuthorizationGrantType.IMPLICIT.getValue().equalsIgnoreCase(value)) {
+				return AuthorizationGrantType.IMPLICIT;
+			}
+			if (AuthorizationGrantType.CLIENT_CREDENTIALS.getValue().equalsIgnoreCase(value)) {
+				return AuthorizationGrantType.CLIENT_CREDENTIALS;
+			}
+			if (AuthorizationGrantType.PASSWORD.getValue().equalsIgnoreCase(value)) {
+				return AuthorizationGrantType.PASSWORD;
+			}
+			return null;
+		}
+
+	}
+
+}

+ 54 - 0
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson2/UnmodifiableMapDeserializer.java

@@ -0,0 +1,54 @@
+/*
+ * Copyright 2002-2020 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 java.io.IOException;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+/**
+ * A {@code JsonDeserializer} for {@link Collections#unmodifiableMap(Map)}.
+ *
+ * @author Joe Grandja
+ * @since 0.1.2
+ * @see Collections#unmodifiableMap(Map)
+ * @see UnmodifiableMapMixin
+ */
+final class UnmodifiableMapDeserializer extends JsonDeserializer<Map<?, ?>> {
+
+	@Override
+	public Map<?, ?> deserialize(JsonParser parser, DeserializationContext context) throws IOException {
+		ObjectMapper mapper = (ObjectMapper) parser.getCodec();
+		JsonNode mapNode = mapper.readTree(parser);
+		Map<String, Object> result = new LinkedHashMap<>();
+		if (mapNode != null && mapNode.isObject()) {
+			Iterable<Map.Entry<String, JsonNode>> fields = mapNode::fields;
+			for (Map.Entry<String, JsonNode> field : fields) {
+				result.put(field.getKey(), mapper.readValue(field.getValue().traverse(mapper), Object.class));
+			}
+		}
+		return Collections.unmodifiableMap(result);
+	}
+
+}

+ 45 - 0
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson2/UnmodifiableMapMixin.java

@@ -0,0 +1,45 @@
+/*
+ * Copyright 2002-2020 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 java.util.Collections;
+import java.util.Map;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+
+/**
+ * This mixin class is used to serialize/deserialize
+ * {@link Collections#unmodifiableMap(Map)}. It also registers a custom deserializer
+ * {@link UnmodifiableMapDeserializer}.
+ *
+ * @author Joe Grandja
+ * @since 0.1.2
+ * @see Collections#unmodifiableMap(Map)
+ * @see UnmodifiableMapDeserializer
+ * @see OAuth2ServerJackson2Module
+ */
+@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
+@JsonDeserialize(using = UnmodifiableMapDeserializer.class)
+abstract class UnmodifiableMapMixin {
+
+	@JsonCreator
+	UnmodifiableMapMixin(Map<?, ?> map) {
+	}
+
+}

+ 1 - 1
oauth2-authorization-server/src/main/resources/org/springframework/security/oauth2/server/authorization/oauth2-authorization-schema.sql

@@ -3,7 +3,7 @@ CREATE TABLE oauth2_authorization (
     registered_client_id varchar(100) NOT NULL,
     principal_name varchar(200) NOT NULL,
     authorization_grant_type varchar(100) NOT NULL,
-    attributes varchar(1000) DEFAULT NULL,
+    attributes varchar(4000) DEFAULT NULL,
     state varchar(1000) DEFAULT NULL,
     authorization_code_value blob DEFAULT NULL,
     authorization_code_issued_at timestamp DEFAULT NULL,