Skip to content
This repository
Browse code

SECOAUTH-358: Provide support for Jackson 2

* add both jackson dependencies (2.0 is optional)
* add jackson2 dependency in bundlor template
* use both jackson 1 and 2 for OAuth2Exception
* use both jackson 1 and 2 for OAuth2AccessToken
* add specific jackson2 tests (with their own jackson2 objectMapper instance)
* fix @author tags
  • Loading branch information...
commit 77502bcc958b1d479fee6e18ffe531ffceab049c 1 parent ea2203d
Brian Clozel authored November 23, 2012 dsyer committed November 23, 2012

Showing 17 changed files with 650 additions and 28 deletions. Show diff stats Hide diff stats

  1. 23  spring-security-oauth2/pom.xml
  2. 10  spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/OAuth2AccessToken.java
  3. 6  ...curity/oauth2/common/{OAuth2AccessTokenDeserializer.java → OAuth2AccessTokenJackson1Deserializer.java}
  4. 8  ...k/security/oauth2/common/{OAuth2AccessTokenSerializer.java → OAuth2AccessTokenJackson1Serializer.java}
  5. 100  ...-oauth2/src/main/java/org/springframework/security/oauth2/common/OAuth2AccessTokenJackson2Deserializer.java
  6. 72  ...ty-oauth2/src/main/java/org/springframework/security/oauth2/common/OAuth2AccessTokenJackson2Serializer.java
  7. 14  ...ng-security-oauth2/src/main/java/org/springframework/security/oauth2/common/exceptions/OAuth2Exception.java
  8. 2  ...oauth2/common/exceptions/{OAuth2ExceptionDeserializer.java → OAuth2ExceptionJackson1Deserializer.java}
  9. 2  ...ity/oauth2/common/exceptions/{OAuth2ExceptionSerializer.java → OAuth2ExceptionJackson1Serializer.java}
  10. 125  ...rc/main/java/org/springframework/security/oauth2/common/exceptions/OAuth2ExceptionJackson2Deserializer.java
  11. 50  .../src/main/java/org/springframework/security/oauth2/common/exceptions/OAuth2ExceptionJackson2Serializer.java
  12. 3  ...urity-oauth2/src/test/java/org/springframework/security/oauth2/common/BaseOAuth2AccessTokenJacksonTest.java
  13. 13  ...auth2/common/{TestOAuth2AccessTokenDeserializer.java → TestOAuth2AccessTokenJackson1Deserializer.java}
  14. 13  ...ty/oauth2/common/{TestOAuth2AccessTokenSerializer.java → TestOAuth2AccessTokenJackson1Serializer.java}
  15. 118  ...th2/src/test/java/org/springframework/security/oauth2/common/TestOAuth2AccessTokenJackson2Deserializer.java
  16. 118  ...auth2/src/test/java/org/springframework/security/oauth2/common/TestOAuth2AccessTokenJackson2Serializer.java
  17. 1  spring-security-oauth2/template.mf
23  spring-security-oauth2/pom.xml
@@ -12,6 +12,11 @@
12 12
 	<name>OAuth2 for Spring Security</name>
13 13
 	<description>Module for providing OAuth2 support to Spring Security</description>
14 14
 
  15
+    <properties>
  16
+        <jackson1.version>1.9.2</jackson1.version>
  17
+        <jackson2.version>2.1.1</jackson2.version>
  18
+    </properties>
  19
+
15 20
 	<build>
16 21
 		<plugins>
17 22
 			<plugin>
@@ -142,8 +147,22 @@
142 147
 		<dependency>
143 148
 			<groupId>org.codehaus.jackson</groupId>
144 149
 			<artifactId>jackson-mapper-asl</artifactId>
145  
-			<version>1.9.2</version>
146  
-		</dependency>
  150
+			<version>${jackson1.version}</version>
  151
+		</dependency>
  152
+
  153
+        <dependency>
  154
+            <groupId>com.fasterxml.jackson.core</groupId>
  155
+            <artifactId>jackson-annotations</artifactId>
  156
+            <version>${jackson2.version}</version>
  157
+            <optional>true</optional>
  158
+        </dependency>
  159
+
  160
+        <dependency>
  161
+            <groupId>com.fasterxml.jackson.core</groupId>
  162
+            <artifactId>jackson-databind</artifactId>
  163
+            <version>${jackson2.version}</version>
  164
+            <optional>true</optional>
  165
+        </dependency>
147 166
 
148 167
 		<dependency>
149 168
 			<groupId>junit</groupId>
10  spring-security-oauth2/src/main/java/org/springframework/security/oauth2/common/OAuth2AccessToken.java
@@ -16,15 +16,15 @@
16 16
 import java.util.Map;
17 17
 import java.util.Set;
18 18
 
19  
-import org.codehaus.jackson.map.annotate.JsonDeserialize;
20  
-import org.codehaus.jackson.map.annotate.JsonSerialize;
21  
-
22 19
 /**
23 20
  * @author Dave Syer
24 21
  *
25 22
  */
26  
-@JsonSerialize(using = OAuth2AccessTokenSerializer.class)
27  
-@JsonDeserialize(using = OAuth2AccessTokenDeserializer.class)
  23
+@org.codehaus.jackson.map.annotate.JsonSerialize(using = OAuth2AccessTokenJackson1Serializer.class)
  24
+@org.codehaus.jackson.map.annotate.JsonDeserialize(using = OAuth2AccessTokenJackson1Deserializer.class)
  25
+@com.fasterxml.jackson.databind.annotation.JsonSerialize(using = OAuth2AccessTokenJackson2Serializer.class)
  26
+@com.fasterxml.jackson.databind.annotation.JsonDeserialize(using = OAuth2AccessTokenJackson2Deserializer.class)
  27
+
28 28
 public interface OAuth2AccessToken {
29 29
 
30 30
 	public static String BEARER_TYPE = "Bearer";
6  .../oauth2/common/OAuth2AccessTokenDeserializer.java → ...common/OAuth2AccessTokenJackson1Deserializer.java
@@ -37,12 +37,12 @@
37 37
  * </p>
38 38
  *
39 39
  * @author Rob Winch
40  
- * @see OAuth2AccessTokenDeserializer
  40
+ * @see OAuth2AccessTokenJackson1Serializer
41 41
  */
42 42
 @SuppressWarnings("deprecation")
43  
-public final class OAuth2AccessTokenDeserializer extends StdDeserializer<OAuth2AccessToken> {
  43
+public final class OAuth2AccessTokenJackson1Deserializer extends StdDeserializer<OAuth2AccessToken> {
44 44
 
45  
-	public OAuth2AccessTokenDeserializer() {
  45
+	public OAuth2AccessTokenJackson1Deserializer() {
46 46
 		super(OAuth2AccessToken.class);
47 47
 	}
48 48
 
8  ...ty/oauth2/common/OAuth2AccessTokenSerializer.java → ...2/common/OAuth2AccessTokenJackson1Serializer.java
@@ -26,15 +26,15 @@
26 26
 
27 27
 /**
28 28
  * Provides the ability to serialize an {@link OAuth2AccessToken} with jackson by implementing {@link JsonSerializer}.
29  
- * Refer to {@link OAuth2AccessTokenDeserializer} to learn more about the JSON format that is used.
  29
+ * Refer to {@link OAuth2AccessTokenJackson1Deserializer} to learn more about the JSON format that is used.
30 30
  * 
31 31
  * @author Rob Winch
32  
- * @see OAuth2AccessTokenDeserializer
  32
+ * @see OAuth2AccessTokenJackson1Deserializer
33 33
  */
34 34
 @SuppressWarnings("deprecation")
35  
-public final class OAuth2AccessTokenSerializer extends SerializerBase<OAuth2AccessToken> {
  35
+public final class OAuth2AccessTokenJackson1Serializer extends SerializerBase<OAuth2AccessToken> {
36 36
 
37  
-	public OAuth2AccessTokenSerializer() {
  37
+	public OAuth2AccessTokenJackson1Serializer() {
38 38
 		super(OAuth2AccessToken.class);
39 39
 	}
40 40
 
100  ...uth2/src/main/java/org/springframework/security/oauth2/common/OAuth2AccessTokenJackson2Deserializer.java
... ...
@@ -0,0 +1,100 @@
  1
+/*
  2
+ * Copyright 2006-2010 the original author or authors.
  3
+ *
  4
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
  5
+ * the License. You may obtain a copy of the License at
  6
+ *
  7
+ * http://www.apache.org/licenses/LICENSE-2.0
  8
+ *
  9
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
  10
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
  11
+ * specific language governing permissions and limitations under the License.
  12
+ */
  13
+package org.springframework.security.oauth2.common;
  14
+
  15
+
  16
+import com.fasterxml.jackson.core.JsonParser;
  17
+import com.fasterxml.jackson.core.JsonProcessingException;
  18
+import com.fasterxml.jackson.core.JsonToken;
  19
+import com.fasterxml.jackson.databind.DeserializationContext;
  20
+import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
  21
+import org.springframework.security.oauth2.common.util.OAuth2Utils;
  22
+
  23
+import java.io.IOException;
  24
+import java.util.Date;
  25
+import java.util.LinkedHashMap;
  26
+import java.util.Map;
  27
+import java.util.Set;
  28
+
  29
+/**
  30
+ * <p>
  31
+ * Provides the ability to deserialize JSON response into an {@link org.springframework.security.oauth2.common.OAuth2AccessToken} with jackson2 by implementing
  32
+ * {@link com.fasterxml.jackson.databind.JsonDeserializer}.
  33
+ * </p>
  34
+ * <p>
  35
+ * The expected format of the access token is defined by <a
  36
+ * href="http://tools.ietf.org/html/draft-ietf-oauth-v2-22#section-5.1">Successful Response</a>.
  37
+ * </p>
  38
+ *
  39
+ * @author Rob Winch
  40
+ * @author Brian Clozel
  41
+ * @see org.springframework.security.oauth2.common.OAuth2AccessTokenJackson2Serializer
  42
+ */
  43
+@SuppressWarnings("deprecation")
  44
+public final class OAuth2AccessTokenJackson2Deserializer extends StdDeserializer<OAuth2AccessToken> {
  45
+
  46
+	public OAuth2AccessTokenJackson2Deserializer() {
  47
+		super(OAuth2AccessToken.class);
  48
+	}
  49
+
  50
+	@Override
  51
+	public OAuth2AccessToken deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException,
  52
+			JsonProcessingException {
  53
+
  54
+		String tokenValue = null;
  55
+		String tokenType = null;
  56
+		String refreshToken = null;
  57
+		Long expiresIn = null;
  58
+		Set<String> scope = null;
  59
+		Map<String, Object> additionalInformation = new LinkedHashMap<String, Object>();
  60
+
  61
+		// TODO What should occur if a parameter exists twice
  62
+		while (jp.nextToken() != JsonToken.END_OBJECT) {
  63
+			String name = jp.getCurrentName();
  64
+			jp.nextToken();
  65
+			if (OAuth2AccessToken.ACCESS_TOKEN.equals(name)) {
  66
+				tokenValue = jp.getText();
  67
+			}
  68
+			else if (OAuth2AccessToken.TOKEN_TYPE.equals(name)) {
  69
+				tokenType = jp.getText();
  70
+			}
  71
+			else if (OAuth2AccessToken.REFRESH_TOKEN.equals(name)) {
  72
+				refreshToken = jp.getText();
  73
+			}
  74
+			else if (OAuth2AccessToken.EXPIRES_IN.equals(name)) {
  75
+				expiresIn = jp.getLongValue();
  76
+			}
  77
+			else if (OAuth2AccessToken.SCOPE.equals(name)) {
  78
+				String text = jp.getText();
  79
+				scope = OAuth2Utils.parseParameterList(text);
  80
+			} else {
  81
+				additionalInformation.put(name, jp.readValueAs(Object.class));
  82
+			}
  83
+		}
  84
+
  85
+		// TODO What should occur if a required parameter (tokenValue or tokenType) is missing?
  86
+
  87
+		DefaultOAuth2AccessToken accessToken = new DefaultOAuth2AccessToken(tokenValue);
  88
+		accessToken.setTokenType(tokenType);
  89
+		if (expiresIn != null) {
  90
+			accessToken.setExpiration(new Date(System.currentTimeMillis() + (expiresIn * 1000)));
  91
+		}
  92
+		if (refreshToken != null) {
  93
+			accessToken.setRefreshToken(new DefaultOAuth2RefreshToken(refreshToken));
  94
+		}
  95
+		accessToken.setScope(scope);
  96
+		accessToken.setAdditionalInformation(additionalInformation);
  97
+
  98
+		return accessToken;
  99
+	}
  100
+}
72  ...oauth2/src/main/java/org/springframework/security/oauth2/common/OAuth2AccessTokenJackson2Serializer.java
... ...
@@ -0,0 +1,72 @@
  1
+/*
  2
+ * Copyright 2006-2010 the original author or authors.
  3
+ *
  4
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
  5
+ * the License. You may obtain a copy of the License at
  6
+ *
  7
+ * http://www.apache.org/licenses/LICENSE-2.0
  8
+ *
  9
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
  10
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
  11
+ * specific language governing permissions and limitations under the License.
  12
+ */
  13
+package org.springframework.security.oauth2.common;
  14
+
  15
+import com.fasterxml.jackson.core.JsonGenerationException;
  16
+import com.fasterxml.jackson.core.JsonGenerator;
  17
+import com.fasterxml.jackson.databind.SerializerProvider;
  18
+import com.fasterxml.jackson.databind.ser.std.StdSerializer;
  19
+import org.springframework.util.Assert;
  20
+
  21
+import java.io.IOException;
  22
+import java.util.Date;
  23
+import java.util.Map;
  24
+import java.util.Set;
  25
+
  26
+/**
  27
+ * Provides the ability to serialize an {@link org.springframework.security.oauth2.common.OAuth2AccessToken} with jackson2 by implementing {@link com.fasterxml.jackson.databind.JsonDeserializer}.
  28
+ * Refer to {@link org.springframework.security.oauth2.common.OAuth2AccessTokenJackson1Deserializer} to learn more about the JSON format that is used.
  29
+ *
  30
+ * @author Rob Winch
  31
+ * @author Brian Clozel
  32
+ * @see org.springframework.security.oauth2.common.OAuth2AccessTokenJackson2Deserializer
  33
+ */
  34
+@SuppressWarnings("deprecation")
  35
+public final class OAuth2AccessTokenJackson2Serializer extends StdSerializer<OAuth2AccessToken> {
  36
+
  37
+	public OAuth2AccessTokenJackson2Serializer() {
  38
+		super(OAuth2AccessToken.class);
  39
+	}
  40
+
  41
+	@Override
  42
+	public void serialize(OAuth2AccessToken token, JsonGenerator jgen, SerializerProvider provider) throws IOException,
  43
+			JsonGenerationException {
  44
+		jgen.writeStartObject();
  45
+		jgen.writeStringField(OAuth2AccessToken.ACCESS_TOKEN, token.getValue());
  46
+		jgen.writeStringField(OAuth2AccessToken.TOKEN_TYPE, token.getTokenType());
  47
+		OAuth2RefreshToken refreshToken = token.getRefreshToken();
  48
+		if (refreshToken != null) {
  49
+			jgen.writeStringField(OAuth2AccessToken.REFRESH_TOKEN, refreshToken.getValue());
  50
+		}
  51
+		Date expiration = token.getExpiration();
  52
+		if (expiration != null) {
  53
+			long now = System.currentTimeMillis();
  54
+			jgen.writeNumberField(OAuth2AccessToken.EXPIRES_IN, (expiration.getTime() - now) / 1000);
  55
+		}
  56
+		Set<String> scope = token.getScope();
  57
+		if (scope != null && !scope.isEmpty()) {
  58
+			StringBuffer scopes = new StringBuffer();
  59
+			for (String s : scope) {
  60
+				Assert.hasLength(s, "Scopes cannot be null or empty. Got " + scope + "");
  61
+				scopes.append(s);
  62
+				scopes.append(" ");
  63
+			}
  64
+			jgen.writeStringField(OAuth2AccessToken.SCOPE, scopes.substring(0, scopes.length() - 1));
  65
+		}
  66
+		Map<String, Object> additionalInformation = token.getAdditionalInformation();
  67
+		for (String key : additionalInformation.keySet()) {
  68
+			jgen.writeObjectField(key, additionalInformation.get(key));
  69
+		}
  70
+		jgen.writeEndObject();
  71
+	}
  72
+}
14  ...security-oauth2/src/main/java/org/springframework/security/oauth2/common/exceptions/OAuth2Exception.java
... ...
@@ -1,12 +1,14 @@
1 1
 package org.springframework.security.oauth2.common.exceptions;
2 2
 
  3
+import org.springframework.security.oauth2.common.OAuth2AccessTokenJackson1Deserializer;
  4
+import org.springframework.security.oauth2.common.OAuth2AccessTokenJackson1Serializer;
  5
+import org.springframework.security.oauth2.common.OAuth2AccessTokenJackson2Deserializer;
  6
+import org.springframework.security.oauth2.common.OAuth2AccessTokenJackson2Serializer;
  7
+
3 8
 import java.util.Map;
4 9
 import java.util.Set;
5 10
 import java.util.TreeMap;
6 11
 
7  
-import org.codehaus.jackson.map.annotate.JsonDeserialize;
8  
-import org.codehaus.jackson.map.annotate.JsonSerialize;
9  
-
10 12
 /**
11 13
  * Base exception for OAuth 2 exceptions.
12 14
  * 
@@ -14,8 +16,10 @@
14 16
  * @author Rob Winch
15 17
  * @author Dave Syer
16 18
  */
17  
-@JsonSerialize(using = OAuth2ExceptionSerializer.class)
18  
-@JsonDeserialize(using = OAuth2ExceptionDeserializer.class)
  19
+@org.codehaus.jackson.map.annotate.JsonSerialize(using = OAuth2ExceptionJackson1Serializer.class)
  20
+@org.codehaus.jackson.map.annotate.JsonDeserialize(using = OAuth2ExceptionJackson1Deserializer.class)
  21
+@com.fasterxml.jackson.databind.annotation.JsonSerialize(using = OAuth2ExceptionJackson2Serializer.class)
  22
+@com.fasterxml.jackson.databind.annotation.JsonDeserialize(using = OAuth2ExceptionJackson2Deserializer.class)
19 23
 public class OAuth2Exception extends RuntimeException {
20 24
 
21 25
 	public static final String ERROR = "error";
2  ...ommon/exceptions/OAuth2ExceptionDeserializer.java → ...ceptions/OAuth2ExceptionJackson1Deserializer.java
@@ -28,7 +28,7 @@
28 28
  * @author Dave Syer
29 29
  * 
30 30
  */
31  
-public class OAuth2ExceptionDeserializer extends JsonDeserializer<OAuth2Exception> {
  31
+public class OAuth2ExceptionJackson1Deserializer extends JsonDeserializer<OAuth2Exception> {
32 32
 
33 33
 	@Override
34 34
 	public OAuth2Exception deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException,
2  .../common/exceptions/OAuth2ExceptionSerializer.java → ...exceptions/OAuth2ExceptionJackson1Serializer.java
@@ -24,7 +24,7 @@
24 24
  * @author Dave Syer
25 25
  *
26 26
  */
27  
-public class OAuth2ExceptionSerializer extends JsonSerializer<OAuth2Exception> {
  27
+public class OAuth2ExceptionJackson1Serializer extends JsonSerializer<OAuth2Exception> {
28 28
 
29 29
 	@Override
30 30
 	public void serialize(OAuth2Exception value, JsonGenerator jgen, SerializerProvider provider) throws IOException,
125  ...main/java/org/springframework/security/oauth2/common/exceptions/OAuth2ExceptionJackson2Deserializer.java
... ...
@@ -0,0 +1,125 @@
  1
+/*
  2
+ * Copyright 2006-2011 the original author or authors.
  3
+ * 
  4
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
  5
+ * the License. You may obtain a copy of the License at
  6
+ * 
  7
+ * http://www.apache.org/licenses/LICENSE-2.0
  8
+ * 
  9
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
  10
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
  11
+ * specific language governing permissions and limitations under the License.
  12
+ */
  13
+package org.springframework.security.oauth2.common.exceptions;
  14
+
  15
+
  16
+import com.fasterxml.jackson.core.JsonParser;
  17
+import com.fasterxml.jackson.core.JsonProcessingException;
  18
+import com.fasterxml.jackson.core.JsonToken;
  19
+import com.fasterxml.jackson.databind.DeserializationContext;
  20
+import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
  21
+
  22
+import java.io.IOException;
  23
+import java.util.HashMap;
  24
+import java.util.List;
  25
+import java.util.Map;
  26
+import java.util.Set;
  27
+
  28
+/**
  29
+ * @author Brian Clozel
  30
+ * 
  31
+ */
  32
+public class OAuth2ExceptionJackson2Deserializer extends StdDeserializer<OAuth2Exception> {
  33
+
  34
+    public OAuth2ExceptionJackson2Deserializer() {
  35
+        super(OAuth2Exception.class);
  36
+    }
  37
+
  38
+	@Override
  39
+	public OAuth2Exception deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException,
  40
+			JsonProcessingException {
  41
+
  42
+		JsonToken t = jp.getCurrentToken();
  43
+		if (t == JsonToken.START_OBJECT) {
  44
+			t = jp.nextToken();
  45
+		}
  46
+		Map<String, Object> errorParams = new HashMap<String, Object>();
  47
+		for (; t == JsonToken.FIELD_NAME; t = jp.nextToken()) {
  48
+			// Must point to field name
  49
+			String fieldName = jp.getCurrentName();
  50
+			// And then the value...
  51
+			t = jp.nextToken();
  52
+			// Note: must handle null explicitly here; value deserializers won't
  53
+			Object value;
  54
+			if (t == JsonToken.VALUE_NULL) {
  55
+				value = null;
  56
+			}
  57
+			// Some servers might send back complex content
  58
+			else if (t == JsonToken.START_ARRAY) {
  59
+				value = jp.readValueAs(List.class);
  60
+			}
  61
+			else if (t == JsonToken.START_OBJECT) {
  62
+				value = jp.readValueAs(Map.class);
  63
+			}
  64
+			else {
  65
+				value = jp.getText();
  66
+			}
  67
+			errorParams.put(fieldName, value);
  68
+		}
  69
+
  70
+		Object errorCode = errorParams.get("error");
  71
+		String errorMessage = errorParams.containsKey("error_description") ? errorParams.get("error_description")
  72
+				.toString() : null;
  73
+		if (errorMessage == null) {
  74
+			errorMessage = errorCode == null ? "OAuth Error" : errorCode.toString();
  75
+		}
  76
+
  77
+		OAuth2Exception ex;
  78
+		if ("invalid_client".equals(errorCode)) {
  79
+			ex = new InvalidClientException(errorMessage);
  80
+		}
  81
+		else if ("unauthorized_client".equals(errorCode)) {
  82
+			ex = new UnauthorizedClientException(errorMessage);
  83
+		}
  84
+		else if ("invalid_grant".equals(errorCode)) {
  85
+			ex = new InvalidGrantException(errorMessage);
  86
+		}
  87
+		else if ("invalid_scope".equals(errorCode)) {
  88
+			ex = new InvalidScopeException(errorMessage);
  89
+		}
  90
+		else if ("invalid_token".equals(errorCode)) {
  91
+			ex = new InvalidTokenException(errorMessage);
  92
+		}
  93
+		else if ("invalid_request".equals(errorCode)) {
  94
+			ex = new InvalidRequestException(errorMessage);
  95
+		}
  96
+		else if ("redirect_uri_mismatch".equals(errorCode)) {
  97
+			ex = new RedirectMismatchException(errorMessage);
  98
+		}
  99
+		else if ("unsupported_grant_type".equals(errorCode)) {
  100
+			ex = new UnsupportedGrantTypeException(errorMessage);
  101
+		}
  102
+		else if ("unsupported_response_type".equals(errorCode)) {
  103
+			ex = new UnsupportedResponseTypeException(errorMessage);
  104
+		}
  105
+		else if ("access_denied".equals(errorCode)) {
  106
+			ex = new UserDeniedAuthorizationException(errorMessage);
  107
+		}
  108
+		else {
  109
+			ex = new OAuth2Exception(errorMessage);
  110
+		}
  111
+
  112
+		Set<Map.Entry<String, Object>> entries = errorParams.entrySet();
  113
+		for (Map.Entry<String, Object> entry : entries) {
  114
+			String key = entry.getKey();
  115
+			if (!"error".equals(key) && !"error_description".equals(key)) {
  116
+				Object value = entry.getValue();
  117
+				ex.addAdditionalInformation(key, value == null ? null : value.toString());
  118
+			}
  119
+		}
  120
+
  121
+		return ex;
  122
+
  123
+	}
  124
+
  125
+}
50  ...c/main/java/org/springframework/security/oauth2/common/exceptions/OAuth2ExceptionJackson2Serializer.java
... ...
@@ -0,0 +1,50 @@
  1
+/*
  2
+ * Copyright 2006-2011 the original author or authors.
  3
+ * 
  4
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
  5
+ * the License. You may obtain a copy of the License at
  6
+ * 
  7
+ * http://www.apache.org/licenses/LICENSE-2.0
  8
+ * 
  9
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
  10
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
  11
+ * specific language governing permissions and limitations under the License.
  12
+ */
  13
+package org.springframework.security.oauth2.common.exceptions;
  14
+
  15
+import com.fasterxml.jackson.core.JsonGenerationException;
  16
+import com.fasterxml.jackson.core.JsonGenerator;
  17
+import com.fasterxml.jackson.databind.SerializerProvider;
  18
+import com.fasterxml.jackson.databind.ser.std.StdSerializer;
  19
+import com.fasterxml.jackson.core.JsonProcessingException;
  20
+
  21
+import java.io.IOException;
  22
+import java.util.Map.Entry;
  23
+
  24
+/**
  25
+ * @author Brian Clozel
  26
+ *
  27
+ */
  28
+public class OAuth2ExceptionJackson2Serializer extends StdSerializer<OAuth2Exception> {
  29
+
  30
+    public OAuth2ExceptionJackson2Serializer() {
  31
+        super(OAuth2Exception.class);
  32
+    }
  33
+
  34
+	@Override
  35
+	public void serialize(OAuth2Exception value, JsonGenerator jgen, SerializerProvider provider) throws IOException,
  36
+			JsonProcessingException {
  37
+        jgen.writeStartObject();
  38
+		jgen.writeStringField("error", value.getOAuth2ErrorCode());
  39
+		jgen.writeStringField("error_description", value.getMessage());
  40
+		if (value.getAdditionalInformation()!=null) {
  41
+			for (Entry<String, String> entry : value.getAdditionalInformation().entrySet()) {
  42
+				String key = entry.getKey();
  43
+				String add = entry.getValue();
  44
+				jgen.writeStringField(key, add);				
  45
+			}
  46
+		}
  47
+        jgen.writeEndObject();
  48
+	}
  49
+
  50
+}
3  ...ty-oauth2/src/test/java/org/springframework/security/oauth2/common/BaseOAuth2AccessTokenJacksonTest.java
@@ -60,8 +60,6 @@
60 60
 
61 61
 	protected DefaultOAuth2AccessToken accessToken;
62 62
 
63  
-	protected ObjectMapper mapper;
64  
-
65 63
 	protected Map<String, Object> additionalInformation;
66 64
 
67 65
 	public BaseOAuth2AccessTokenJacksonTest() {
@@ -78,7 +76,6 @@ public void setUp() {
78 76
 
79 77
 		accessToken = new DefaultOAuth2AccessToken("token-value");
80 78
 		accessToken.setExpiration(expiration);
81  
-		mapper = new ObjectMapper();
82 79
 		DefaultOAuth2RefreshToken refreshToken = new DefaultOAuth2RefreshToken("refresh-value");
83 80
 		accessToken.setRefreshToken(refreshToken);
84 81
 		Set<String> scope = new TreeSet<String>();
13  ...th2/common/TestOAuth2AccessTokenDeserializer.java → ...on/TestOAuth2AccessTokenJackson1Deserializer.java
@@ -21,6 +21,8 @@
21 21
 
22 22
 import org.codehaus.jackson.JsonGenerationException;
23 23
 import org.codehaus.jackson.map.JsonMappingException;
  24
+import org.codehaus.jackson.map.ObjectMapper;
  25
+import org.junit.Before;
24 26
 import org.junit.Test;
25 27
 import org.powermock.core.classloader.annotations.PrepareForTest;
26 28
 
@@ -29,8 +31,15 @@
29 31
  *
30 32
  * @author Rob Winch
31 33
  */
32  
-@PrepareForTest(OAuth2AccessTokenDeserializer.class)
33  
-public class TestOAuth2AccessTokenDeserializer extends BaseOAuth2AccessTokenJacksonTest {
  34
+@PrepareForTest(OAuth2AccessTokenJackson1Deserializer.class)
  35
+public class TestOAuth2AccessTokenJackson1Deserializer extends BaseOAuth2AccessTokenJacksonTest {
  36
+
  37
+    protected ObjectMapper mapper;
  38
+
  39
+    @Before
  40
+    public void createObjectMapper() {
  41
+        mapper = new ObjectMapper();
  42
+    }
34 43
 
35 44
 	@Test
36 45
 	public void readValueNoRefresh() throws JsonGenerationException, JsonMappingException, IOException {
13  ...auth2/common/TestOAuth2AccessTokenSerializer.java → ...mmon/TestOAuth2AccessTokenJackson1Serializer.java
@@ -6,6 +6,8 @@
6 6
 
7 7
 import org.codehaus.jackson.JsonGenerationException;
8 8
 import org.codehaus.jackson.map.JsonMappingException;
  9
+import org.codehaus.jackson.map.ObjectMapper;
  10
+import org.junit.Before;
9 11
 import org.junit.Test;
10 12
 import org.powermock.core.classloader.annotations.PrepareForTest;
11 13
 
@@ -14,8 +16,15 @@
14 16
  * 
15 17
  * @author Rob Winch
16 18
  */
17  
-@PrepareForTest(OAuth2AccessTokenSerializer.class)
18  
-public class TestOAuth2AccessTokenSerializer extends BaseOAuth2AccessTokenJacksonTest {
  19
+@PrepareForTest(OAuth2AccessTokenJackson1Serializer.class)
  20
+public class TestOAuth2AccessTokenJackson1Serializer extends BaseOAuth2AccessTokenJacksonTest {
  21
+
  22
+    protected ObjectMapper mapper;
  23
+
  24
+    @Before
  25
+    public void createObjectMapper() {
  26
+        mapper = new ObjectMapper();
  27
+    }
19 28
 
20 29
 	@Test
21 30
 	public void writeValueAsStringNoRefresh() throws JsonGenerationException, JsonMappingException, IOException {
118  .../src/test/java/org/springframework/security/oauth2/common/TestOAuth2AccessTokenJackson2Deserializer.java
... ...
@@ -0,0 +1,118 @@
  1
+/*
  2
+ * Copyright 2011 the original author or authors.
  3
+ *
  4
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
  5
+ * the License. You may obtain a copy of the License at
  6
+ *
  7
+ * http://www.apache.org/licenses/LICENSE-2.0
  8
+ *
  9
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
  10
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
  11
+ * specific language governing permissions and limitations under the License.
  12
+ */
  13
+package org.springframework.security.oauth2.common;
  14
+
  15
+import com.fasterxml.jackson.core.JsonGenerationException;
  16
+import com.fasterxml.jackson.databind.JsonMappingException;
  17
+import com.fasterxml.jackson.databind.ObjectMapper;
  18
+import org.junit.Before;
  19
+import org.junit.Test;
  20
+import org.powermock.core.classloader.annotations.PrepareForTest;
  21
+
  22
+import java.io.IOException;
  23
+import java.util.Date;
  24
+import java.util.HashSet;
  25
+
  26
+import static org.junit.Assert.assertEquals;
  27
+import static org.junit.Assert.assertNull;
  28
+
  29
+/**
  30
+ * Tests deserialization of an {@link org.springframework.security.oauth2.common.OAuth2AccessToken} using jackson.
  31
+ *
  32
+ * @author Rob Winch
  33
+ */
  34
+@PrepareForTest(OAuth2AccessTokenJackson2Deserializer.class)
  35
+public class TestOAuth2AccessTokenJackson2Deserializer extends BaseOAuth2AccessTokenJacksonTest {
  36
+
  37
+    protected ObjectMapper mapper;
  38
+
  39
+    @Before
  40
+    public void createObjectMapper() {
  41
+        mapper = new ObjectMapper();
  42
+    }
  43
+
  44
+	@Test
  45
+	public void readValueNoRefresh() throws JsonGenerationException, JsonMappingException, IOException {
  46
+		accessToken.setRefreshToken(null);
  47
+		accessToken.setScope(null);
  48
+		OAuth2AccessToken actual = mapper.readValue(ACCESS_TOKEN_NOREFRESH, OAuth2AccessToken.class);
  49
+		assertTokenEquals(accessToken,actual);
  50
+	}
  51
+
  52
+	@Test
  53
+	public void readValueWithRefresh() throws JsonGenerationException, JsonMappingException, IOException {
  54
+		accessToken.setScope(null);
  55
+		OAuth2AccessToken actual = mapper.readValue(ACCESS_TOKEN_NOSCOPE, OAuth2AccessToken.class);
  56
+		assertTokenEquals(accessToken,actual);
  57
+	}
  58
+
  59
+	@Test
  60
+	public void readValueWithSingleScopes() throws JsonGenerationException, JsonMappingException, IOException {
  61
+		accessToken.getScope().remove(accessToken.getScope().iterator().next());
  62
+		OAuth2AccessToken actual = mapper.readValue(ACCESS_TOKEN_SINGLESCOPE, OAuth2AccessToken.class);
  63
+		assertTokenEquals(accessToken,actual);
  64
+	}
  65
+
  66
+	@Test
  67
+	public void readValueWithEmptyStringScope() throws JsonGenerationException, JsonMappingException, IOException {
  68
+		accessToken.setScope(new HashSet<String>());
  69
+		OAuth2AccessToken actual = mapper.readValue(ACCESS_TOKEN_EMPTYSCOPE, OAuth2AccessToken.class);
  70
+		assertTokenEquals(accessToken, actual);
  71
+	}
  72
+
  73
+	@Test
  74
+	public void readValueWithMultiScopes() throws Exception {
  75
+		OAuth2AccessToken actual = mapper.readValue(ACCESS_TOKEN_MULTISCOPE, OAuth2AccessToken.class);
  76
+		assertTokenEquals(accessToken,actual);
  77
+	}
  78
+
  79
+	@Test
  80
+	public void readValueWithMac() throws Exception {
  81
+		accessToken.setTokenType("mac");
  82
+		String encodedToken = ACCESS_TOKEN_MULTISCOPE.replace("bearer", accessToken.getTokenType());
  83
+		OAuth2AccessToken actual = mapper.readValue(encodedToken, OAuth2AccessToken.class);
  84
+		assertTokenEquals(accessToken,actual);
  85
+	}
  86
+
  87
+	@Test
  88
+	public void readValueWithAdditionalInformation() throws Exception {
  89
+		OAuth2AccessToken actual = mapper.readValue(ACCESS_TOKEN_ADDITIONAL_INFO, OAuth2AccessToken.class);
  90
+		accessToken.setAdditionalInformation(additionalInformation);
  91
+		accessToken.setRefreshToken(null);
  92
+		accessToken.setScope(null);
  93
+		accessToken.setExpiration(null);
  94
+		assertTokenEquals(accessToken,actual);
  95
+	}
  96
+
  97
+	private static void assertTokenEquals(OAuth2AccessToken expected, OAuth2AccessToken actual) {
  98
+		assertEquals(expected.getTokenType(), actual.getTokenType());
  99
+		assertEquals(expected.getValue(), actual.getValue());
  100
+
  101
+		OAuth2RefreshToken expectedRefreshToken = expected.getRefreshToken();
  102
+		if (expectedRefreshToken == null) {
  103
+			assertNull(actual.getRefreshToken());
  104
+		}
  105
+		else {
  106
+			assertEquals(expectedRefreshToken.getValue(), actual.getRefreshToken().getValue());
  107
+		}
  108
+		assertEquals(expected.getScope(), actual.getScope());
  109
+		Date expectedExpiration = expected.getExpiration();
  110
+		if (expectedExpiration == null) {
  111
+			assertNull(actual.getExpiration());
  112
+		}
  113
+		else {
  114
+			assertEquals(expectedExpiration.getTime(), actual.getExpiration().getTime());
  115
+		}
  116
+		assertEquals(expected.getAdditionalInformation(), actual.getAdditionalInformation());
  117
+	}
  118
+}
118  ...h2/src/test/java/org/springframework/security/oauth2/common/TestOAuth2AccessTokenJackson2Serializer.java
... ...
@@ -0,0 +1,118 @@
  1
+package org.springframework.security.oauth2.common;
  2
+
  3
+import com.fasterxml.jackson.core.JsonGenerationException;
  4
+import com.fasterxml.jackson.databind.JsonMappingException;
  5
+import com.fasterxml.jackson.databind.ObjectMapper;
  6
+import org.junit.Before;
  7
+import org.junit.Test;
  8
+import org.powermock.core.classloader.annotations.PrepareForTest;
  9
+
  10
+import java.io.IOException;
  11
+
  12
+import static org.junit.Assert.assertEquals;
  13
+
  14
+/**
  15
+ * Tests serialization of an {@link org.springframework.security.oauth2.common.OAuth2AccessToken} using jackson.
  16
+ *
  17
+ * @author Rob Winch
  18
+ */
  19
+@PrepareForTest(OAuth2AccessTokenJackson2Serializer.class)
  20
+public class TestOAuth2AccessTokenJackson2Serializer extends BaseOAuth2AccessTokenJacksonTest {
  21
+
  22
+    protected ObjectMapper mapper;
  23
+
  24
+    @Before
  25
+    public void createObjectMapper() {
  26
+        mapper = new ObjectMapper();
  27
+    }
  28
+
  29
+	@Test
  30
+	public void writeValueAsStringNoRefresh() throws JsonGenerationException, JsonMappingException, IOException {
  31
+		accessToken.setRefreshToken(null);
  32
+		accessToken.setScope(null);
  33
+		String encodedAccessToken = mapper.writeValueAsString(accessToken);
  34
+		assertEquals(BaseOAuth2AccessTokenJacksonTest.ACCESS_TOKEN_NOREFRESH, encodedAccessToken);
  35
+	}
  36
+
  37
+	@Test
  38
+	public void writeValueAsStringWithRefresh() throws JsonGenerationException, JsonMappingException, IOException {
  39
+		accessToken.setScope(null);
  40
+		String encodedAccessToken = mapper.writeValueAsString(accessToken);
  41
+		assertEquals(BaseOAuth2AccessTokenJacksonTest.ACCESS_TOKEN_NOSCOPE, encodedAccessToken);
  42
+	}
  43
+
  44
+	@Test
  45
+	public void writeValueAsStringWithEmptyScope() throws JsonGenerationException, JsonMappingException, IOException {
  46
+		accessToken.getScope().clear();
  47
+		String encodedAccessToken = mapper.writeValueAsString(accessToken);
  48
+		assertEquals(BaseOAuth2AccessTokenJacksonTest.ACCESS_TOKEN_NOSCOPE, encodedAccessToken);
  49
+	}
  50
+
  51
+	@Test
  52
+	public void writeValueAsStringWithSingleScopes() throws JsonGenerationException, JsonMappingException, IOException {
  53
+		accessToken.getScope().remove(accessToken.getScope().iterator().next());
  54
+		String encodedAccessToken = mapper.writeValueAsString(accessToken);
  55
+		assertEquals(BaseOAuth2AccessTokenJacksonTest.ACCESS_TOKEN_SINGLESCOPE, encodedAccessToken);
  56
+	}
  57
+
  58
+	@Test
  59
+	public void writeValueAsStringWithNullScope() throws JsonGenerationException, JsonMappingException, IOException {
  60
+		thrown.expect(JsonMappingException.class);
  61
+		thrown.expectMessage("Scopes cannot be null or empty. Got [null]");
  62
+
  63
+		accessToken.getScope().clear();
  64
+		try {
  65
+			accessToken.getScope().add(null);
  66
+		}
  67
+		catch (NullPointerException e) {
  68
+			// short circuit NPE from Java 7 (which is correct but only relevant for this test)
  69
+			throw new JsonMappingException("Scopes cannot be null or empty. Got [null]");
  70
+		}
  71
+		mapper.writeValueAsString(accessToken);
  72
+	}
  73
+
  74
+	@Test
  75
+	public void writeValueAsStringWithEmptyStringScope() throws JsonGenerationException, JsonMappingException,
  76
+			IOException {
  77
+		thrown.expect(JsonMappingException.class);
  78
+		thrown.expectMessage("Scopes cannot be null or empty. Got []");
  79
+
  80
+		accessToken.getScope().clear();
  81
+		accessToken.getScope().add("");
  82
+		mapper.writeValueAsString(accessToken);
  83
+	}
  84
+
  85
+	@Test
  86
+	public void writeValueAsStringWithQuoteInScope() throws JsonGenerationException, JsonMappingException, IOException {
  87
+		accessToken.getScope().add("\"");
  88
+		String encodedAccessToken = mapper.writeValueAsString(accessToken);
  89
+		assertEquals(
  90
+				"{\"access_token\":\"token-value\",\"token_type\":\"bearer\",\"refresh_token\":\"refresh-value\",\"expires_in\":10,\"scope\":\"\\\" read write\"}",
  91
+				encodedAccessToken);
  92
+	}
  93
+
  94
+	@Test
  95
+	public void writeValueAsStringWithMultiScopes() throws JsonGenerationException, JsonMappingException, IOException {
  96
+		String encodedAccessToken = mapper.writeValueAsString(accessToken);
  97
+		assertEquals(ACCESS_TOKEN_MULTISCOPE, encodedAccessToken);
  98
+	}
  99
+
  100
+	@Test
  101
+	public void writeValueAsStringWithMac() throws Exception {
  102
+		accessToken.setTokenType("mac");
  103
+		String expectedEncodedAccessToken = ACCESS_TOKEN_MULTISCOPE.replace("bearer", accessToken.getTokenType());
  104
+		String encodedAccessToken = mapper.writeValueAsString(accessToken);
  105
+		assertEquals(expectedEncodedAccessToken, encodedAccessToken);
  106
+	}
  107
+
  108
+	@Test
  109
+	public void writeValueWithAdditionalInformation() throws JsonGenerationException, JsonMappingException, IOException {
  110
+		accessToken.setRefreshToken(null);
  111
+		accessToken.setScope(null);
  112
+		accessToken.setExpiration(null);
  113
+		accessToken.setAdditionalInformation(additionalInformation);
  114
+		String encodedAccessToken = mapper.writeValueAsString(accessToken);
  115
+		assertEquals(BaseOAuth2AccessTokenJacksonTest.ACCESS_TOKEN_ADDITIONAL_INFO, encodedAccessToken);
  116
+	}
  117
+
  118
+}
1  spring-security-oauth2/template.mf
@@ -6,6 +6,7 @@ Import-Template:
6 6
  org.apache.commons.logging.*;version="[1.1.1, 2.0.0)",
7 7
  org.apache.commons.codec.*;version="[1.3, 2.0.0)",
8 8
  org.codehaus.jackson.*;version="[1.9.3, 2.0.0)",
  9
+ com.fasterxml.jackson.*;version="[2.0.0, 2.1.1)",