diff --git a/cas/cas.gradle b/cas/cas.gradle
index 9149d794839..eb3e27b2784 100644
--- a/cas/cas.gradle
+++ b/cas/cas.gradle
@@ -8,7 +8,10 @@ dependencies {
"org.springframework:spring-web:$springVersion",
"org.jasig.cas.client:cas-client-core:$casClientVersion"
- optional "net.sf.ehcache:ehcache:$ehcacheVersion"
+ optional "net.sf.ehcache:ehcache:$ehcacheVersion",
+ "com.fasterxml.jackson.core:jackson-databind:$jacksonDatavindVersion"
+
+ testCompile "org.skyscreamer:jsonassert:$jsonassertVersion"
provided "javax.servlet:javax.servlet-api:$servletApiVersion"
}
diff --git a/cas/src/main/java/org/springframework/security/cas/authentication/CasAuthenticationToken.java b/cas/src/main/java/org/springframework/security/cas/authentication/CasAuthenticationToken.java
index 856781ae5e1..e8250195f89 100644
--- a/cas/src/main/java/org/springframework/security/cas/authentication/CasAuthenticationToken.java
+++ b/cas/src/main/java/org/springframework/security/cas/authentication/CasAuthenticationToken.java
@@ -50,29 +50,54 @@ public class CasAuthenticationToken extends AbstractAuthenticationToken implemen
/**
* Constructor.
*
- * @param key to identify if this object made by a given
- * {@link CasAuthenticationProvider}
- * @param principal typically the UserDetails object (cannot be null
)
+ * @param key to identify if this object made by a given
+ * {@link CasAuthenticationProvider}
+ * @param principal typically the UserDetails object (cannot be null
)
* @param credentials the service/proxy ticket ID from CAS (cannot be
- * null
)
+ * null
)
* @param authorities the authorities granted to the user (from the
- * {@link org.springframework.security.core.userdetails.UserDetailsService}) (cannot
- * be null
)
+ * {@link org.springframework.security.core.userdetails.UserDetailsService}) (cannot
+ * be null
)
* @param userDetails the user details (from the
- * {@link org.springframework.security.core.userdetails.UserDetailsService}) (cannot
- * be null
)
- * @param assertion the assertion returned from the CAS servers. It contains the
- * principal and how to obtain a proxy ticket for the user.
- *
+ * {@link org.springframework.security.core.userdetails.UserDetailsService}) (cannot
+ * be null
)
+ * @param assertion the assertion returned from the CAS servers. It contains the
+ * principal and how to obtain a proxy ticket for the user.
* @throws IllegalArgumentException if a null
was passed
*/
public CasAuthenticationToken(final String key, final Object principal,
- final Object credentials,
- final Collection extends GrantedAuthority> authorities,
- final UserDetails userDetails, final Assertion assertion) {
+ final Object credentials,
+ final Collection extends GrantedAuthority> authorities,
+ final UserDetails userDetails, final Assertion assertion) {
+ this(extractKeyHash(key), principal, credentials, authorities, userDetails, assertion);
+ }
+
+ /**
+ * Private constructor for Jackson Deserialization support
+ *
+ * @param keyHash hashCode of provided key to identify if this object made by a given
+ * {@link CasAuthenticationProvider}
+ * @param principal typically the UserDetails object (cannot be null
)
+ * @param credentials the service/proxy ticket ID from CAS (cannot be
+ * null
)
+ * @param authorities the authorities granted to the user (from the
+ * {@link org.springframework.security.core.userdetails.UserDetailsService}) (cannot
+ * be null
)
+ * @param userDetails the user details (from the
+ * {@link org.springframework.security.core.userdetails.UserDetailsService}) (cannot
+ * be null
)
+ * @param assertion the assertion returned from the CAS servers. It contains the
+ * principal and how to obtain a proxy ticket for the user.
+ * @throws IllegalArgumentException if a null
was passed
+ * @since 4.2
+ */
+ private CasAuthenticationToken(final Integer keyHash, final Object principal,
+ final Object credentials,
+ final Collection extends GrantedAuthority> authorities,
+ final UserDetails userDetails, final Assertion assertion) {
super(authorities);
- if ((key == null) || ("".equals(key)) || (principal == null)
+ if ((principal == null)
|| "".equals(principal) || (credentials == null)
|| "".equals(credentials) || (authorities == null)
|| (userDetails == null) || (assertion == null)) {
@@ -80,7 +105,7 @@ public CasAuthenticationToken(final String key, final Object principal,
"Cannot pass null or empty values to constructor");
}
- this.keyHash = key.hashCode();
+ this.keyHash = keyHash;
this.principal = principal;
this.credentials = credentials;
this.userDetails = userDetails;
@@ -91,6 +116,18 @@ public CasAuthenticationToken(final String key, final Object principal,
// ~ Methods
// ========================================================================================================
+ private static Integer extractKeyHash(String key) {
+ Object value = nullSafeValue(key);
+ return value.hashCode();
+ }
+
+ private static Object nullSafeValue(Object value) {
+ if (value == null || "".equals(value)) {
+ throw new IllegalArgumentException("Cannot pass null or empty values to constructor");
+ }
+ return value;
+ }
+
public boolean equals(final Object obj) {
if (!super.equals(obj)) {
return false;
diff --git a/cas/src/main/java/org/springframework/security/cas/jackson2/AssertionImplMixin.java b/cas/src/main/java/org/springframework/security/cas/jackson2/AssertionImplMixin.java
new file mode 100644
index 00000000000..0e3dc552bed
--- /dev/null
+++ b/cas/src/main/java/org/springframework/security/cas/jackson2/AssertionImplMixin.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2015-2016 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
+ *
+ * http://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.cas.jackson2;
+
+import com.fasterxml.jackson.annotation.*;
+import org.jasig.cas.client.authentication.AttributePrincipal;
+
+import java.util.Date;
+import java.util.Map;
+
+/**
+ * Helps in jackson deserialization of class {@link org.jasig.cas.client.validation.AssertionImpl}, which is
+ * used with {@link org.springframework.security.cas.authentication.CasAuthenticationToken}.
+ * To use this class we need to register with {@link com.fasterxml.jackson.databind.ObjectMapper}. Type information
+ * will be stored in @class property.
+ *
+ *
+ * ObjectMapper mapper = new ObjectMapper(); + * mapper.registerModule(new CasJackson2Module()); + *+ * + * + * @author Jitendra Singh + * @see CasJackson2Module + * @see org.springframework.security.jackson2.SecurityJacksonModules + * @since 4.2 + */ +@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY) +@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, + getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE) +@JsonIgnoreProperties(ignoreUnknown = true) +public class AssertionImplMixin { + + /** + * Mixin Constructor helps in deserialize {@link org.jasig.cas.client.validation.AssertionImpl} + * + * @param principal the Principal to associate with the Assertion. + * @param validFromDate when the assertion is valid from. + * @param validUntilDate when the assertion is valid to. + * @param authenticationDate when the assertion is authenticated. + * @param attributes the key/value pairs for this attribute. + */ + @JsonCreator + public AssertionImplMixin(@JsonProperty("principal") AttributePrincipal principal, + @JsonProperty("validFromDate") Date validFromDate, @JsonProperty("validUntilDate") Date validUntilDate, + @JsonProperty("authenticationDate") Date authenticationDate, @JsonProperty("attributes") Map
+ *
+ * ObjectMapper mapper = new ObjectMapper(); + * mapper.registerModule(new CasJackson2Module()); + *+ * + * @author Jitendra Singh + * @see CasJackson2Module + * @see org.springframework.security.jackson2.SecurityJacksonModules + * @since 4.2 + */ +@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY) +@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE, + isGetterVisibility = JsonAutoDetect.Visibility.NONE) +@JsonIgnoreProperties(ignoreUnknown = true) +public class AttributePrincipalImplMixin { + + /** + * Mixin Constructor helps in deserialize {@link org.jasig.cas.client.authentication.AttributePrincipalImpl} + * + * @param name the unique identifier for the principal. + * @param attributes the key/value pairs for this principal. + * @param proxyGrantingTicket the ticket associated with this principal. + * @param proxyRetriever the ProxyRetriever implementation to call back to the CAS server. + */ + @JsonCreator + public AttributePrincipalImplMixin(@JsonProperty("name") String name, @JsonProperty("attributes") Map
+ * + *
+ * ObjectMapper mapper = new ObjectMapper(); + * mapper.registerModule(new CasJackson2Module()); + *+ * + * @author Jitendra Singh + * @see CasJackson2Module + * @see org.springframework.security.jackson2.SecurityJacksonModules + * @since 4.2 + */ +@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY) +@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, isGetterVisibility = JsonAutoDetect.Visibility.NONE, + getterVisibility = JsonAutoDetect.Visibility.NONE, creatorVisibility = JsonAutoDetect.Visibility.ANY) +@JsonIgnoreProperties(ignoreUnknown = true) +public class CasAuthenticationTokenMixin { + + /** + * Mixin Constructor helps in deserialize {@link CasAuthenticationToken} + * + * @param keyHash hashCode of provided key to identify if this object made by a given + * {@link CasAuthenticationProvider} + * @param principal typically the UserDetails object (cannot be
null
)
+ * @param credentials the service/proxy ticket ID from CAS (cannot be
+ * null
)
+ * @param authorities the authorities granted to the user (from the
+ * {@link org.springframework.security.core.userdetails.UserDetailsService}) (cannot
+ * be null
)
+ * @param userDetails the user details (from the
+ * {@link org.springframework.security.core.userdetails.UserDetailsService}) (cannot
+ * be null
)
+ * @param assertion the assertion returned from the CAS servers. It contains the
+ * principal and how to obtain a proxy ticket for the user.
+ */
+ @JsonCreator
+ public CasAuthenticationTokenMixin(@JsonProperty("keyHash") Integer keyHash, @JsonProperty("principal") Object principal,
+ @JsonProperty("credentials") Object credentials,
+ @JsonProperty("authorities") Collection extends GrantedAuthority> authorities,
+ @JsonProperty("userDetails") UserDetails userDetails, @JsonProperty("assertion") Assertion assertion) {
+ }
+}
diff --git a/cas/src/main/java/org/springframework/security/cas/jackson2/CasJackson2Module.java b/cas/src/main/java/org/springframework/security/cas/jackson2/CasJackson2Module.java
new file mode 100644
index 00000000000..8d7bf6046af
--- /dev/null
+++ b/cas/src/main/java/org/springframework/security/cas/jackson2/CasJackson2Module.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2015-2016 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
+ *
+ * http://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.cas.jackson2;
+
+import com.fasterxml.jackson.core.Version;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+import org.jasig.cas.client.authentication.AttributePrincipalImpl;
+import org.jasig.cas.client.validation.AssertionImpl;
+import org.springframework.security.cas.authentication.CasAuthenticationToken;
+import org.springframework.security.jackson2.SecurityJacksonModules;
+
+/**
+ * Jackson module for spring-security-cas. This module register {@link AssertionImplMixin},
+ * {@link AttributePrincipalImplMixin} and {@link CasAuthenticationTokenMixin}. If no default typing enabled by default then
+ * it'll enable it because typing info is needed to properly serialize/deserialize objects. In order to use this module just
+ * add this module into your ObjectMapper configuration.
+ *
+ * + * ObjectMapper mapper = new ObjectMapper(); + * mapper.registerModule(new CasJackson2Module()); + *+ * Note: use {@link SecurityJacksonModules#getModules()} to get list of all security modules. + * + * @author Jitendra Singh. + * @see org.springframework.security.jackson2.SecurityJacksonModules + * @since 4.2 + */ +public class CasJackson2Module extends SimpleModule { + + public CasJackson2Module() { + super(CasJackson2Module.class.getName(), new Version(1, 0, 0, null, null, null)); + } + + @Override + public void setupModule(SetupContext context) { + SecurityJacksonModules.enableDefaultTyping((ObjectMapper) context.getOwner()); + context.setMixInAnnotations(AssertionImpl.class, AssertionImplMixin.class); + context.setMixInAnnotations(AttributePrincipalImpl.class, AttributePrincipalImplMixin.class); + context.setMixInAnnotations(CasAuthenticationToken.class, CasAuthenticationTokenMixin.class); + } +} diff --git a/cas/src/test/java/org/springframework/security/cas/jackson2/CasAuthenticationTokenMixinTests.java b/cas/src/test/java/org/springframework/security/cas/jackson2/CasAuthenticationTokenMixinTests.java new file mode 100644 index 00000000000..8605a12a230 --- /dev/null +++ b/cas/src/test/java/org/springframework/security/cas/jackson2/CasAuthenticationTokenMixinTests.java @@ -0,0 +1,123 @@ +/* + * Copyright 2015-2016 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 + * + * http://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.cas.jackson2; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.jasig.cas.client.authentication.AttributePrincipalImpl; +import org.jasig.cas.client.validation.Assertion; +import org.jasig.cas.client.validation.AssertionImpl; +import org.json.JSONException; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.runners.MockitoJUnitRunner; +import org.skyscreamer.jsonassert.JSONAssert; +import org.springframework.security.cas.authentication.CasAuthenticationToken; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.jackson2.SecurityJacksonModules; + +import java.io.IOException; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Jitendra Singh + * @since 4.2 + */ +@RunWith(MockitoJUnitRunner.class) +public class CasAuthenticationTokenMixinTests { + + private final String KEY = "casKey"; + private final String PASSWORD = "pass"; + Date startDate = new Date(); + Date endDate = new Date(); + String expectedJson = "{\"@class\": \"org.springframework.security.cas.authentication.CasAuthenticationToken\", \"keyHash\": " + KEY.hashCode() + "," + + "\"principal\": {\"@class\": \"org.springframework.security.core.userdetails.User\", \"username\": \"username\", \"password\": %s, \"accountNonExpired\": true, \"enabled\": true," + + "\"accountNonLocked\": true, \"credentialsNonExpired\": true, \"authorities\": [\"java.util.Collections$UnmodifiableSet\"," + + "[{\"@class\": \"org.springframework.security.core.authority.SimpleGrantedAuthority\", \"role\": \"USER\"}]]}, \"credentials\": \"" + PASSWORD + "\", \"authorities\": [\"java.util.ArrayList\", [{\"@class\": \"org.springframework.security.core.authority.SimpleGrantedAuthority\", \"role\": \"ROLE_USER\"}]]," + + "\"userDetails\": {\"@class\": \"org.springframework.security.core.userdetails.User\",\"username\": \"user\", \"password\": \"" + PASSWORD + "\", \"enabled\": true, \"accountNonExpired\": true, \"accountNonLocked\": true, \"credentialsNonExpired\": true, \"authorities\": [\"java.util.Collections$UnmodifiableSet\", [{\"@class\": \"org.springframework.security.core.authority.SimpleGrantedAuthority\", \"role\": \"ROLE_USER\"}]]}," + + "\"authenticated\": true, \"details\": null," + + "\"assertion\": {" + + "\"@class\": \"org.jasig.cas.client.validation.AssertionImpl\", \"principal\": {\"@class\": \"org.jasig.cas.client.authentication.AttributePrincipalImpl\", \"name\": \"assertName\", \"attributes\": {\"@class\": \"java.util.Collections$EmptyMap\"}, \"proxyGrantingTicket\": null, \"proxyRetriever\": null}, " + + "\"validFromDate\": [\"java.util.Date\", " + startDate.getTime() + "], \"validUntilDate\": [\"java.util.Date\", " + endDate.getTime() + "]," + + "\"authenticationDate\": [\"java.util.Date\", " + startDate.getTime() + "], \"attributes\": {\"@class\": \"java.util.Collections$EmptyMap\"}" + + "}}"; + + private CasAuthenticationToken createCasAuthenticationToken() { + User principal = new User("username", PASSWORD, Collections.singletonList(new SimpleGrantedAuthority("USER"))); + Collection extends GrantedAuthority> authorities = Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER")); + Assertion assertion = new AssertionImpl(new AttributePrincipalImpl("assertName"), startDate, endDate, startDate, Collections.
UserDetails
)
+ * @param key to identify if this object made by an authorised client
+ * @param principal the principal (typically a UserDetails
)
* @param authorities the authorities granted to the principal
- *
* @throws IllegalArgumentException if a null
was passed
*/
public AnonymousAuthenticationToken(String key, Object principal,
- Collection extends GrantedAuthority> authorities) {
+ Collection extends GrantedAuthority> authorities) {
+ this(extractKeyHash(key), nullSafeValue(principal), authorities);
+ }
+
+ /**
+ * Constructor helps in Jackson Deserialization
+ *
+ * @param keyHash hashCode of provided Key, constructed by above constructor
+ * @param principal the principal (typically a UserDetails
)
+ * @param authorities the authorities granted to the principal
+ * @since 4.2
+ */
+ private AnonymousAuthenticationToken(Integer keyHash, Object principal,
+ Collection extends GrantedAuthority> authorities) {
super(authorities);
- if ((key == null) || ("".equals(key)) || (principal == null)
- || "".equals(principal) || (authorities == null)
- || (authorities.isEmpty())) {
- throw new IllegalArgumentException(
- "Cannot pass null or empty values to constructor");
+ if (authorities == null || authorities.isEmpty()) {
+ throw new IllegalArgumentException("Cannot pass null or empty values to constructor");
}
- this.keyHash = key.hashCode();
+ this.keyHash = keyHash;
this.principal = principal;
setAuthenticated(true);
}
@@ -66,6 +75,18 @@ public AnonymousAuthenticationToken(String key, Object principal,
// ~ Methods
// ========================================================================================================
+ private static Integer extractKeyHash(String key) {
+ Object value = nullSafeValue(key);
+ return value.hashCode();
+ }
+
+ private static Object nullSafeValue(Object value) {
+ if (value == null || "".equals(value)) {
+ throw new IllegalArgumentException("Cannot pass null or empty values to constructor");
+ }
+ return value;
+ }
+
public boolean equals(Object obj) {
if (!super.equals(obj)) {
return false;
diff --git a/core/src/main/java/org/springframework/security/authentication/RememberMeAuthenticationToken.java b/core/src/main/java/org/springframework/security/authentication/RememberMeAuthenticationToken.java
index fa5007fb6c8..3fd5486cbe2 100644
--- a/core/src/main/java/org/springframework/security/authentication/RememberMeAuthenticationToken.java
+++ b/core/src/main/java/org/springframework/security/authentication/RememberMeAuthenticationToken.java
@@ -46,14 +46,13 @@ public class RememberMeAuthenticationToken extends AbstractAuthenticationToken {
/**
* Constructor.
*
- * @param key to identify if this object made by an authorised client
- * @param principal the principal (typically a UserDetails
)
+ * @param key to identify if this object made by an authorised client
+ * @param principal the principal (typically a UserDetails
)
* @param authorities the authorities granted to the principal
- *
* @throws IllegalArgumentException if a null
was passed
*/
public RememberMeAuthenticationToken(String key, Object principal,
- Collection extends GrantedAuthority> authorities) {
+ Collection extends GrantedAuthority> authorities) {
super(authorities);
if ((key == null) || ("".equals(key)) || (principal == null)
@@ -67,6 +66,22 @@ public RememberMeAuthenticationToken(String key, Object principal,
setAuthenticated(true);
}
+ /**
+ * Private Constructor to help in Jackson deserialization.
+ *
+ * @param keyHash hashCode of above given key.
+ * @param principal the principal (typically a UserDetails
)
+ * @param authorities the authorities granted to the principal
+ * @since 4.2
+ */
+ private RememberMeAuthenticationToken(Integer keyHash, Object principal, Collection extends GrantedAuthority> authorities) {
+ super(authorities);
+
+ this.keyHash = keyHash;
+ this.principal = principal;
+ setAuthenticated(true);
+ }
+
// ~ Methods
// ========================================================================================================
diff --git a/core/src/main/java/org/springframework/security/jackson2/AnonymousAuthenticationTokenMixin.java b/core/src/main/java/org/springframework/security/jackson2/AnonymousAuthenticationTokenMixin.java
new file mode 100644
index 00000000000..c8207958246
--- /dev/null
+++ b/core/src/main/java/org/springframework/security/jackson2/AnonymousAuthenticationTokenMixin.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2015-2016 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
+ *
+ * http://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.jackson2;
+
+import com.fasterxml.jackson.annotation.*;
+import org.springframework.security.core.GrantedAuthority;
+
+import java.util.Collection;
+
+/**
+ * This is a Jackson mixin class helps in serialize/deserialize
+ * {@link org.springframework.security.authentication.AnonymousAuthenticationToken} class. To use this class you need to register it
+ * with {@link com.fasterxml.jackson.databind.ObjectMapper} and {@link SimpleGrantedAuthorityMixin} because
+ * AnonymousAuthenticationToken contains SimpleGrantedAuthority.
+ * + * ObjectMapper mapper = new ObjectMapper(); + * mapper.registerModule(new CoreJackson2Module()); + *+ * + * Note: This class will save full class name into a property called @class + * + * @author Jitendra Singh + * @see CoreJackson2Module + * @see SecurityJacksonModules + * @since 4.2 + */ +@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY) +@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, isGetterVisibility = JsonAutoDetect.Visibility.NONE, + getterVisibility = JsonAutoDetect.Visibility.NONE, creatorVisibility = JsonAutoDetect.Visibility.ANY) +@JsonIgnoreProperties(ignoreUnknown = true) +public class AnonymousAuthenticationTokenMixin { + + /** + * Constructor used by Jackson to create object of {@link org.springframework.security.authentication.AnonymousAuthenticationToken}. + * + * @param keyHash hashCode of key provided at the time of token creation by using + * {@link org.springframework.security.authentication.AnonymousAuthenticationToken#AnonymousAuthenticationToken(String, Object, Collection)} + * @param principal the principal (typically a
UserDetails
)
+ * @param authorities the authorities granted to the principal
+ */
+ @JsonCreator
+ public AnonymousAuthenticationTokenMixin(@JsonProperty("keyHash") Integer keyHash, @JsonProperty("principal") Object principal,
+ @JsonProperty("authorities") Collection extends GrantedAuthority> authorities) {
+ }
+}
diff --git a/core/src/main/java/org/springframework/security/jackson2/CoreJackson2Module.java b/core/src/main/java/org/springframework/security/jackson2/CoreJackson2Module.java
new file mode 100644
index 00000000000..7db25de05f0
--- /dev/null
+++ b/core/src/main/java/org/springframework/security/jackson2/CoreJackson2Module.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2015-2016 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
+ *
+ * http://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.jackson2;
+
+import com.fasterxml.jackson.core.Version;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+import org.springframework.security.authentication.AnonymousAuthenticationToken;
+import org.springframework.security.authentication.RememberMeAuthenticationToken;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.userdetails.User;
+
+import java.util.Collections;
+
+/**
+ * Jackson module for spring-security-core. This module register {@link AnonymousAuthenticationTokenMixin},
+ * {@link RememberMeAuthenticationTokenMixin}, {@link SimpleGrantedAuthorityMixin}, {@link UnmodifiableSetMixin},
+ * {@link UserMixin} and {@link UsernamePasswordAuthenticationTokenMixin}. If no default typing enabled by default then
+ * it'll enable it because typing info is needed to properly serialize/deserialize objects. In order to use this module just
+ * add this module into your ObjectMapper configuration.
+ *
+ * + * ObjectMapper mapper = new ObjectMapper(); + * mapper.registerModule(new CoreJackson2Module()); + *+ * Note: use {@link SecurityJacksonModules#getModules()} to get list of all security modules. + * + * @author Jitendra Singh. + * @see SecurityJacksonModules + * @since 4.2 + */ +public class CoreJackson2Module extends SimpleModule { + + public CoreJackson2Module() { + super(CoreJackson2Module.class.getName(), new Version(1, 0, 0, null, null, null)); + } + + @Override + public void setupModule(SetupContext context) { + SecurityJacksonModules.enableDefaultTyping((ObjectMapper) context.getOwner()); + context.setMixInAnnotations(AnonymousAuthenticationToken.class, AnonymousAuthenticationTokenMixin.class); + context.setMixInAnnotations(RememberMeAuthenticationToken.class, RememberMeAuthenticationTokenMixin.class); + context.setMixInAnnotations(SimpleGrantedAuthority.class, SimpleGrantedAuthorityMixin.class); + context.setMixInAnnotations(Collections.unmodifiableSet(Collections.EMPTY_SET).getClass(), UnmodifiableSetMixin.class); + context.setMixInAnnotations(User.class, UserMixin.class); + context.setMixInAnnotations(UsernamePasswordAuthenticationToken.class, UsernamePasswordAuthenticationTokenMixin.class); + } +} diff --git a/core/src/main/java/org/springframework/security/jackson2/RememberMeAuthenticationTokenMixin.java b/core/src/main/java/org/springframework/security/jackson2/RememberMeAuthenticationTokenMixin.java new file mode 100644 index 00000000000..5ea4230895f --- /dev/null +++ b/core/src/main/java/org/springframework/security/jackson2/RememberMeAuthenticationTokenMixin.java @@ -0,0 +1,66 @@ +/* + * Copyright 2015-2016 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 + * + * http://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.jackson2; + +import com.fasterxml.jackson.annotation.*; +import org.springframework.security.core.GrantedAuthority; + +import java.util.Collection; + +/** + * This mixin class helps in serialize/deserialize + * {@link org.springframework.security.authentication.RememberMeAuthenticationToken} class. To use this class you need to register it + * with {@link com.fasterxml.jackson.databind.ObjectMapper} and 2 more mixin classes. + * + *
+ * ObjectMapper mapper = new ObjectMapper(); + * mapper.registerModule(new CoreJackson2Module()); + *+ * + * Note: This class will save TypeInfo (full class name) into a property called @class + * + * @author Jitendra Singh + * @see CoreJackson2Module + * @see SecurityJacksonModules + * @since 4.2 + */ +@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY) +@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE, + isGetterVisibility = JsonAutoDetect.Visibility.NONE, creatorVisibility = JsonAutoDetect.Visibility.ANY) +@JsonIgnoreProperties(ignoreUnknown = true) +public class RememberMeAuthenticationTokenMixin { + + /** + * Constructor used by Jackson to create + * {@link org.springframework.security.authentication.RememberMeAuthenticationToken} object. + * + * @param keyHash hashCode of above given key. + * @param principal the principal (typically a
UserDetails
)
+ * @param authorities the authorities granted to the principal
+ */
+ @JsonCreator
+ public RememberMeAuthenticationTokenMixin(@JsonProperty("keyHash") Integer keyHash,
+ @JsonProperty("principal") Object principal,
+ @JsonProperty("authorities") Collection extends GrantedAuthority> authorities) {
+ }
+}
diff --git a/core/src/main/java/org/springframework/security/jackson2/SecurityJacksonModules.java b/core/src/main/java/org/springframework/security/jackson2/SecurityJacksonModules.java
new file mode 100644
index 00000000000..27603fe97ac
--- /dev/null
+++ b/core/src/main/java/org/springframework/security/jackson2/SecurityJacksonModules.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2015-2016 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
+ *
+ * http://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.jackson2;
+
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import com.fasterxml.jackson.databind.Module;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.jsontype.TypeResolverBuilder;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.util.ClassUtils;
+import org.springframework.util.ObjectUtils;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * This utility class will find all the SecurityModules in classpath.
+ *
+ * + *
+ * ObjectMapper mapper = new ObjectMapper(); + * mapper.registerModules(SecurityJacksonModules.getModules()); + *+ * Above code is equivalent to + *
+ *
+ * ObjectMapper mapper = new ObjectMapper(); + * mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY); + * mapper.registerModule(new CoreJackson2Module()); + * mapper.registerModule(new CasJackson2Module()); + * mapper.registerModule(new WebJackson2Module()); + *+ * + * @author Jitendra Singh. + * @since 4.2 + */ +public final class SecurityJacksonModules { + + private static final Log logger = LogFactory.getLog(SecurityJacksonModules.class); + private static final List
+ * ObjectMapper mapper = new ObjectMapper(); + * mapper.registerModule(new CoreJackson2Module()); + *+ * @author Jitendra Singh + * @see CoreJackson2Module + * @see SecurityJacksonModules + * @since 4.2 + */ +@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY) +@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE, + isGetterVisibility = JsonAutoDetect.Visibility.NONE) +@JsonIgnoreProperties(ignoreUnknown = true) +public abstract class SimpleGrantedAuthorityMixin { + + /** + * Mixin Constructor. + * @param role + */ + @JsonCreator + public SimpleGrantedAuthorityMixin(@JsonProperty("role") String role) { + } + + /** + * This method will ensure that getAuthority() doesn't serialized to authority key, it will be serialized + * as role key. Because above mixin constructor will look for role key to properly deserialize. + * + * @return + */ + @JsonProperty("role") + public abstract String getAuthority(); +} diff --git a/core/src/main/java/org/springframework/security/jackson2/UnmodifiableSetMixin.java b/core/src/main/java/org/springframework/security/jackson2/UnmodifiableSetMixin.java new file mode 100644 index 00000000000..3fd3875efde --- /dev/null +++ b/core/src/main/java/org/springframework/security/jackson2/UnmodifiableSetMixin.java @@ -0,0 +1,47 @@ +/* + * Copyright 2015-2016 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 + * + * http://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.jackson2; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonTypeInfo; + +import java.util.Set; + +/** + * This mixin class used to deserialize java.util.Collections$UnmodifiableSet and used with various AuthenticationToken + * implementation's mixin classes. + * + *
+ * ObjectMapper mapper = new ObjectMapper(); + * mapper.registerModule(new CoreJackson2Module()); + *+ * + * @author Jitendra Singh + * @see CoreJackson2Module + * @see SecurityJacksonModules + * @since 4.2 + */ +@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY) +public class UnmodifiableSetMixin { + + /** + * Mixin Constructor + * @param s + */ + @JsonCreator + UnmodifiableSetMixin(Set s) {} +} diff --git a/core/src/main/java/org/springframework/security/jackson2/UserDeserializer.java b/core/src/main/java/org/springframework/security/jackson2/UserDeserializer.java new file mode 100644 index 00000000000..5367fd481e8 --- /dev/null +++ b/core/src/main/java/org/springframework/security/jackson2/UserDeserializer.java @@ -0,0 +1,72 @@ +/* + * Copyright 2015-2016 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 + * + * http://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.jackson2; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +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.node.MissingNode; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.User; + +import java.io.IOException; +import java.util.Set; + +/** + * Custom Deserializer for {@link User} class. This is already registered with {@link UserMixin}. + * You can also use it directly with your mixin class. + * + * @author Jitendra Singh + * @see UserMixin + * @since 4.2 + */ +public class UserDeserializer extends JsonDeserializer
+ * ObjectMapper mapper = new ObjectMapper(); + * mapper.registerModule(new CoreJackson2Module()); + *+ * + * @author Jitendra Singh + * @see UserDeserializer + * @see CoreJackson2Module + * @see SecurityJacksonModules + * @since 4.2 + */ +@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY) +@JsonDeserialize(using = UserDeserializer.class) +@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE, + isGetterVisibility = JsonAutoDetect.Visibility.NONE) +@JsonIgnoreProperties(ignoreUnknown = true) +public abstract class UserMixin { +} diff --git a/core/src/main/java/org/springframework/security/jackson2/UsernamePasswordAuthenticationTokenDeserializer.java b/core/src/main/java/org/springframework/security/jackson2/UsernamePasswordAuthenticationTokenDeserializer.java new file mode 100644 index 00000000000..985f605e4f3 --- /dev/null +++ b/core/src/main/java/org/springframework/security/jackson2/UsernamePasswordAuthenticationTokenDeserializer.java @@ -0,0 +1,85 @@ +/* + * Copyright 2015-2016 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 + * + * http://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.jackson2; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +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.node.MissingNode; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.User; + +import java.io.IOException; +import java.util.List; + +/** + * Custom deserializer for {@link UsernamePasswordAuthenticationToken}. At the time of deserialization + * it will invoke suitable constructor depending on the value of authenticated property. + * It will ensure that the token's state must not change. + *
+ * This deserializer is already registered with {@link UsernamePasswordAuthenticationTokenMixin} but
+ * you can also registered it with your own mixin class.
+ *
+ * @author Jitendra Singh
+ * @see UsernamePasswordAuthenticationTokenMixin
+ * @since 4.2
+ */
+public class UsernamePasswordAuthenticationTokenDeserializer extends JsonDeserializer
+ *
* All URL arguments are considered but not cookies, locales, headers or parameters.
*
- * @param request the actual request to be matched against this one
+ * @param request the actual request to be matched against this one
* @param portResolver used to obtain the server port of the request
* @return true if the request is deemed to match this one.
- *
*/
public boolean doesRequestMatch(HttpServletRequest request, PortResolver portResolver) {
@@ -341,8 +375,7 @@ private boolean propertyEquals(String log, Object arg1, Object arg2) {
}
return true;
- }
- else {
+ } else {
if (logger.isDebugEnabled()) {
logger.debug(log + ": arg1=" + arg1 + "; arg2=" + arg2
+ " (property not equals)");
@@ -355,4 +388,115 @@ private boolean propertyEquals(String log, Object arg1, Object arg2) {
public String toString() {
return "DefaultSavedRequest[" + getRedirectUrl() + "]";
}
+
+ /**
+ * @since 4.2
+ */
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ @JsonPOJOBuilder(withPrefix = "set")
+ public static class Builder {
+
+ private List>() {
+ });
+ if (authenticated) {
+ token = new UsernamePasswordAuthenticationToken(principal, credentials, authorities);
+ } else {
+ token = new UsernamePasswordAuthenticationToken(principal, credentials);
+ }
+ token.setDetails(readJsonNode(jsonNode, "details"));
+ return token;
+ }
+
+ private JsonNode readJsonNode(JsonNode jsonNode, String field) {
+ return jsonNode.has(field) ? jsonNode.get(field) : MissingNode.getInstance();
+ }
+}
diff --git a/core/src/main/java/org/springframework/security/jackson2/UsernamePasswordAuthenticationTokenMixin.java b/core/src/main/java/org/springframework/security/jackson2/UsernamePasswordAuthenticationTokenMixin.java
new file mode 100644
index 00000000000..9815b687372
--- /dev/null
+++ b/core/src/main/java/org/springframework/security/jackson2/UsernamePasswordAuthenticationTokenMixin.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2015-2016 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
+ *
+ * http://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.jackson2;
+
+import com.fasterxml.jackson.annotation.*;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+
+/**
+ * This mixin class is used to serialize / deserialize
+ * {@link org.springframework.security.authentication.UsernamePasswordAuthenticationToken}. This class register
+ * a custom deserializer {@link UsernamePasswordAuthenticationTokenDeserializer}.
+ *
+ * In order to use this mixin you'll need to add 3 more mixin classes.
+ *
+ *
+ *
+ *
+ * ObjectMapper mapper = new ObjectMapper();
+ * mapper.registerModule(new CoreJackson2Module());
+ *
+ * @author Jitendra Singh
+ * @see CoreJackson2Module
+ * @see SecurityJacksonModules
+ * @since 4.2
+ */
+@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "@class")
+@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
+ isGetterVisibility = JsonAutoDetect.Visibility.NONE)
+@JsonDeserialize(using = UsernamePasswordAuthenticationTokenDeserializer.class)
+public abstract class UsernamePasswordAuthenticationTokenMixin {
+}
diff --git a/core/src/main/java/org/springframework/security/jackson2/package-info.java b/core/src/main/java/org/springframework/security/jackson2/package-info.java
new file mode 100644
index 00000000000..fadd25a8cf5
--- /dev/null
+++ b/core/src/main/java/org/springframework/security/jackson2/package-info.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2002-2016 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
+ *
+ * http://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.
+ */
+/**
+ * Mix-in classes to add Jackson serialization support.
+ *
+ * @author Jitendra Singh
+ * @since 4.2
+ */
+package org.springframework.security.jackson2;
+
+/**
+ * Package contains Jackson mixin classes.
+ */
\ No newline at end of file
diff --git a/core/src/test/java/org/springframework/security/jackson2/AbstractMixinTests.java b/core/src/test/java/org/springframework/security/jackson2/AbstractMixinTests.java
new file mode 100644
index 00000000000..8b78118d3e9
--- /dev/null
+++ b/core/src/test/java/org/springframework/security/jackson2/AbstractMixinTests.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2015-2016 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
+ *
+ * http://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.jackson2;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.runner.RunWith;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.userdetails.User;
+import org.springframework.util.ObjectUtils;
+
+import java.util.Collections;
+
+/**
+ * @author Jitenra Singh
+ * @since 4.2
+ */
+@RunWith(MockitoJUnitRunner.class)
+public abstract class AbstractMixinTests {
+
+ ObjectMapper mapper;
+
+ protected ObjectMapper buildObjectMapper() {
+ if (ObjectUtils.isEmpty(mapper)) {
+ mapper = new ObjectMapper();
+ mapper.registerModules(SecurityJacksonModules.getModules());
+ }
+ return mapper;
+ }
+
+ User createDefaultUser() {
+ return createUser("dummy", "password", "ROLE_USER");
+ }
+
+ User createUser(String username, String password, String authority) {
+ return new User(username, password, Collections.singletonList(new SimpleGrantedAuthority(authority)));
+ }
+}
diff --git a/core/src/test/java/org/springframework/security/jackson2/AnonymousAuthenticationTokenMixinTests.java b/core/src/test/java/org/springframework/security/jackson2/AnonymousAuthenticationTokenMixinTests.java
new file mode 100644
index 00000000000..542148e8f8f
--- /dev/null
+++ b/core/src/test/java/org/springframework/security/jackson2/AnonymousAuthenticationTokenMixinTests.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2015-2016 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
+ *
+ * http://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.jackson2;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import org.json.JSONException;
+import org.junit.Test;
+import org.skyscreamer.jsonassert.JSONAssert;
+import org.springframework.security.authentication.AnonymousAuthenticationToken;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.userdetails.User;
+
+import java.io.IOException;
+import java.util.Collections;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * @author Jitendra Singh
+ * @since 4.2
+ */
+public class AnonymousAuthenticationTokenMixinTests extends AbstractMixinTests {
+
+ String hashKey = "key";
+ String anonymousAuthTokenJson = "{\"@class\": \"org.springframework.security.authentication.AnonymousAuthenticationToken\", \"details\": null," +
+ "\"principal\": {\"@class\": \"org.springframework.security.core.userdetails.User\", \"username\": \"dummy\", \"password\": %s," +
+ " \"accountNonExpired\": true, \"enabled\": true, " +
+ "\"accountNonLocked\": true, \"credentialsNonExpired\": true, \"authorities\": [\"java.util.Collections$UnmodifiableSet\"," +
+ "[{\"@class\": \"org.springframework.security.core.authority.SimpleGrantedAuthority\", \"role\": \"ROLE_USER\"}]]}, \"authenticated\": true, \"keyHash\": " + hashKey.hashCode() + "," +
+ "\"authorities\": [\"java.util.ArrayList\", [{\"@class\": \"org.springframework.security.core.authority.SimpleGrantedAuthority\", \"role\": \"ROLE_USER\"}]]}";
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testWithNullAuthorities() throws JsonProcessingException, JSONException {
+ new AnonymousAuthenticationToken("key", "principal", null);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testWithEmptyAuthorities() throws JsonProcessingException, JSONException {
+ new AnonymousAuthenticationToken("key", "principal", Collections.
+ * ObjectMapper mapper = new ObjectMapper();
+ * mapper.registerModule(new WebJackson2Module());
+ *
+ *
+ * @author Jitendra Singh
+ * @see WebJackson2Module
+ * @see org.springframework.security.jackson2.SecurityJacksonModules
+ * @since 4.2
+ */
+@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY)
+@JsonDeserialize(using = CookieDeserializer.class)
+@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, isGetterVisibility = JsonAutoDetect.Visibility.NONE)
+public abstract class CookieMixin {
+}
diff --git a/web/src/main/java/org/springframework/security/web/jackson2/DefaultCsrfTokenMixin.java b/web/src/main/java/org/springframework/security/web/jackson2/DefaultCsrfTokenMixin.java
new file mode 100644
index 00000000000..46be5b66701
--- /dev/null
+++ b/web/src/main/java/org/springframework/security/web/jackson2/DefaultCsrfTokenMixin.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2015-2016 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
+ *
+ * http://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.web.jackson2;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+
+/**
+ * Jackson mixin class to serialize/deserialize {@link org.springframework.security.web.csrf.DefaultCsrfToken}
+ * serialization support.
+ *
+ *
+ * ObjectMapper mapper = new ObjectMapper();
+ * mapper.registerModule(new WebJackson2Module());
+ *
+ *
+ * @author Jitendra Singh
+ * @see WebJackson2Module
+ * @see org.springframework.security.jackson2.SecurityJacksonModules
+ * @since 4.2
+ */
+@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "@class")
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class DefaultCsrfTokenMixin {
+
+ /**
+ * JsonCreator constructor needed by Jackson to create {@link org.springframework.security.web.csrf.DefaultCsrfToken}
+ * object.
+ *
+ * @param headerName
+ * @param parameterName
+ * @param token
+ */
+ @JsonCreator
+ public DefaultCsrfTokenMixin(@JsonProperty("headerName") String headerName,
+ @JsonProperty("parameterName") String parameterName, @JsonProperty("token") String token) {
+ }
+}
diff --git a/web/src/main/java/org/springframework/security/web/jackson2/DefaultSavedRequestMixin.java b/web/src/main/java/org/springframework/security/web/jackson2/DefaultSavedRequestMixin.java
new file mode 100644
index 00000000000..b896882e202
--- /dev/null
+++ b/web/src/main/java/org/springframework/security/web/jackson2/DefaultSavedRequestMixin.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2015-2016 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
+ *
+ * http://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.web.jackson2;
+
+import com.fasterxml.jackson.annotation.JsonAutoDetect;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import org.springframework.security.web.savedrequest.DefaultSavedRequest;
+
+/**
+ * Jackson mixin class to serialize/deserialize {@link DefaultSavedRequest}. This mixin use
+ * {@link org.springframework.security.web.savedrequest.DefaultSavedRequest.Builder} to
+ * deserialized json.In order to use this mixin class you also need to register
+ * {@link CookieMixin}.
+ *
+ * ObjectMapper mapper = new ObjectMapper();
+ * mapper.registerModule(new WebJackson2Module());
+ *
+ *
+ * @author Jitendra Singh
+ * @see WebJackson2Module
+ * @see org.springframework.security.jackson2.SecurityJacksonModules
+ * @since 4.2
+ */
+@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY)
+@JsonDeserialize(builder = DefaultSavedRequest.Builder.class)
+@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE)
+public abstract class DefaultSavedRequestMixin {
+}
diff --git a/web/src/main/java/org/springframework/security/web/jackson2/SavedCookieMixin.java b/web/src/main/java/org/springframework/security/web/jackson2/SavedCookieMixin.java
new file mode 100644
index 00000000000..a048cb82f2e
--- /dev/null
+++ b/web/src/main/java/org/springframework/security/web/jackson2/SavedCookieMixin.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2015-2016 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
+ *
+ * http://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.web.jackson2;
+
+import com.fasterxml.jackson.annotation.*;
+
+/**
+ * Jackson mixin class to serialize/deserialize {@link org.springframework.security.web.savedrequest.SavedCookie}
+ * serialization support.
+ *
+ *
+ * ObjectMapper mapper = new ObjectMapper();
+ * mapper.registerModule(new WebJackson2Module());
+ *
+ *
+ * @author Jitendra Singh.
+ * @see WebJackson2Module
+ * @see org.springframework.security.jackson2.SecurityJacksonModules
+ * @since 4.2
+ */
+@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY)
+@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY,
+ getterVisibility = JsonAutoDetect.Visibility.NONE)
+@JsonIgnoreProperties(ignoreUnknown = true)
+public abstract class SavedCookieMixin {
+
+ @JsonCreator
+ public SavedCookieMixin(@JsonProperty("name") String name, @JsonProperty("value") String value,
+ @JsonProperty("comment") String comment, @JsonProperty("domain") String domain,
+ @JsonProperty("maxAge") int maxAge, @JsonProperty("path") String path,
+ @JsonProperty("secure") boolean secure, @JsonProperty("version") int version) {
+
+ }
+}
diff --git a/web/src/main/java/org/springframework/security/web/jackson2/WebAuthenticationDetailsMixin.java b/web/src/main/java/org/springframework/security/web/jackson2/WebAuthenticationDetailsMixin.java
new file mode 100644
index 00000000000..c07918aeab7
--- /dev/null
+++ b/web/src/main/java/org/springframework/security/web/jackson2/WebAuthenticationDetailsMixin.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2015-2016 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
+ *
+ * http://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.web.jackson2;
+
+import com.fasterxml.jackson.annotation.*;
+
+/**
+ * Jackson mixin class to serialize/deserialize {@link org.springframework.security.web.authentication.WebAuthenticationDetails}.
+ *
+ *
+ * ObjectMapper mapper = new ObjectMapper();
+ * mapper.registerModule(new WebJackson2Module());
+ *
+ *
+ * @author Jitendra Singh
+ * @see WebJackson2Module
+ * @see org.springframework.security.jackson2.SecurityJacksonModules
+ * @since 4.2
+ */
+@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY)
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
+ isGetterVisibility = JsonAutoDetect.Visibility.NONE, creatorVisibility = JsonAutoDetect.Visibility.ANY)
+public class WebAuthenticationDetailsMixin {
+
+ @JsonCreator
+ WebAuthenticationDetailsMixin(@JsonProperty("remoteAddress") String remoteAddress,
+ @JsonProperty("sessionId") String sessionId) {
+ }
+}
diff --git a/web/src/main/java/org/springframework/security/web/jackson2/WebJackson2Module.java b/web/src/main/java/org/springframework/security/web/jackson2/WebJackson2Module.java
new file mode 100644
index 00000000000..197537e865f
--- /dev/null
+++ b/web/src/main/java/org/springframework/security/web/jackson2/WebJackson2Module.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2015-2016 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
+ *
+ * http://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.web.jackson2;
+
+import com.fasterxml.jackson.core.Version;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+import org.springframework.security.jackson2.SecurityJacksonModules;
+import org.springframework.security.web.authentication.WebAuthenticationDetails;
+import org.springframework.security.web.csrf.DefaultCsrfToken;
+import org.springframework.security.web.savedrequest.DefaultSavedRequest;
+import org.springframework.security.web.savedrequest.SavedCookie;
+
+import javax.servlet.http.Cookie;
+
+/**
+ * Jackson module for spring-security-web. This module register {@link CookieMixin},
+ * {@link DefaultCsrfTokenMixin}, {@link DefaultSavedRequestMixin} and {@link WebAuthenticationDetailsMixin}. If no
+ * default typing enabled by default then it'll enable it because typing info is needed to properly serialize/deserialize objects.
+ * In order to use this module just add this module into your ObjectMapper configuration.
+ *
+ *
+ * ObjectMapper mapper = new ObjectMapper();
+ * mapper.registerModule(new WebJackson2Module());
+ *
+ * Note: use {@link SecurityJacksonModules#getModules()} to get list of all security modules.
+ *
+ * @author Jitendra Singh
+ * @see SecurityJacksonModules
+ * @since 4.2
+ */
+public class WebJackson2Module extends SimpleModule {
+
+ public WebJackson2Module() {
+ super(WebJackson2Module.class.getName(), new Version(1, 0, 0, null, null, null));
+ }
+
+ @Override
+ public void setupModule(SetupContext context) {
+ SecurityJacksonModules.enableDefaultTyping((ObjectMapper) context.getOwner());
+ context.setMixInAnnotations(Cookie.class, CookieMixin.class);
+ context.setMixInAnnotations(SavedCookie.class, SavedCookieMixin.class);
+ context.setMixInAnnotations(DefaultCsrfToken.class, DefaultCsrfTokenMixin.class);
+ context.setMixInAnnotations(DefaultSavedRequest.class, DefaultSavedRequestMixin.class);
+ context.setMixInAnnotations(WebAuthenticationDetails.class, WebAuthenticationDetailsMixin.class);
+ }
+}
diff --git a/web/src/main/java/org/springframework/security/web/jackson2/package-info.java b/web/src/main/java/org/springframework/security/web/jackson2/package-info.java
new file mode 100644
index 00000000000..9bc066de22c
--- /dev/null
+++ b/web/src/main/java/org/springframework/security/web/jackson2/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2002-2016 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
+ *
+ * http://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.
+ */
+/**
+ * Mix-in classes to provide Jackson serialization support.
+ *
+ * @author Jitendra Singh
+ * @since 4.2
+ */
+package org.springframework.security.web.jackson2;
\ No newline at end of file
diff --git a/web/src/main/java/org/springframework/security/web/savedrequest/DefaultSavedRequest.java b/web/src/main/java/org/springframework/security/web/savedrequest/DefaultSavedRequest.java
index 1ac42d7a455..5a5811c77af 100644
--- a/web/src/main/java/org/springframework/security/web/savedrequest/DefaultSavedRequest.java
+++ b/web/src/main/java/org/springframework/security/web/savedrequest/DefaultSavedRequest.java
@@ -16,11 +16,14 @@
package org.springframework.security.web.savedrequest;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.security.web.PortResolver;
import org.springframework.security.web.util.UrlUtils;
import org.springframework.util.Assert;
+import org.springframework.util.ObjectUtils;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
@@ -84,13 +87,7 @@ public DefaultSavedRequest(HttpServletRequest request, PortResolver portResolver
Assert.notNull(portResolver, "PortResolver required");
// Cookies
- Cookie[] cookies = request.getCookies();
-
- if (cookies != null) {
- for (Cookie cookie : cookies) {
- this.addCookie(cookie);
- }
- }
+ addCookies(request.getCookies());
// Headers
Enumeration