Skip to content

Commit d77ca17

Browse files
jitendra-bishtRob Winch
authored and
Rob Winch
committed
Add JSON Serialization
Fixes gh-3812
1 parent 4d02a5c commit d77ca17

File tree

47 files changed

+2791
-69
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+2791
-69
lines changed

cas/cas.gradle

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@ dependencies {
88
"org.springframework:spring-web:$springVersion",
99
"org.jasig.cas.client:cas-client-core:$casClientVersion"
1010

11-
optional "net.sf.ehcache:ehcache:$ehcacheVersion"
11+
optional "net.sf.ehcache:ehcache:$ehcacheVersion",
12+
"com.fasterxml.jackson.core:jackson-databind:$jacksonDatavindVersion"
13+
14+
testCompile "org.skyscreamer:jsonassert:$jsonassertVersion"
1215

1316
provided "javax.servlet:javax.servlet-api:$servletApiVersion"
1417
}

cas/src/main/java/org/springframework/security/cas/authentication/CasAuthenticationToken.java

Lines changed: 53 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -50,37 +50,62 @@ public class CasAuthenticationToken extends AbstractAuthenticationToken implemen
5050
/**
5151
* Constructor.
5252
*
53-
* @param key to identify if this object made by a given
54-
* {@link CasAuthenticationProvider}
55-
* @param principal typically the UserDetails object (cannot be <code>null</code>)
53+
* @param key to identify if this object made by a given
54+
* {@link CasAuthenticationProvider}
55+
* @param principal typically the UserDetails object (cannot be <code>null</code>)
5656
* @param credentials the service/proxy ticket ID from CAS (cannot be
57-
* <code>null</code>)
57+
* <code>null</code>)
5858
* @param authorities the authorities granted to the user (from the
59-
* {@link org.springframework.security.core.userdetails.UserDetailsService}) (cannot
60-
* be <code>null</code>)
59+
* {@link org.springframework.security.core.userdetails.UserDetailsService}) (cannot
60+
* be <code>null</code>)
6161
* @param userDetails the user details (from the
62-
* {@link org.springframework.security.core.userdetails.UserDetailsService}) (cannot
63-
* be <code>null</code>)
64-
* @param assertion the assertion returned from the CAS servers. It contains the
65-
* principal and how to obtain a proxy ticket for the user.
66-
*
62+
* {@link org.springframework.security.core.userdetails.UserDetailsService}) (cannot
63+
* be <code>null</code>)
64+
* @param assertion the assertion returned from the CAS servers. It contains the
65+
* principal and how to obtain a proxy ticket for the user.
6766
* @throws IllegalArgumentException if a <code>null</code> was passed
6867
*/
6968
public CasAuthenticationToken(final String key, final Object principal,
70-
final Object credentials,
71-
final Collection<? extends GrantedAuthority> authorities,
72-
final UserDetails userDetails, final Assertion assertion) {
69+
final Object credentials,
70+
final Collection<? extends GrantedAuthority> authorities,
71+
final UserDetails userDetails, final Assertion assertion) {
72+
this(extractKeyHash(key), principal, credentials, authorities, userDetails, assertion);
73+
}
74+
75+
/**
76+
* Private constructor for Jackson Deserialization support
77+
*
78+
* @param keyHash hashCode of provided key to identify if this object made by a given
79+
* {@link CasAuthenticationProvider}
80+
* @param principal typically the UserDetails object (cannot be <code>null</code>)
81+
* @param credentials the service/proxy ticket ID from CAS (cannot be
82+
* <code>null</code>)
83+
* @param authorities the authorities granted to the user (from the
84+
* {@link org.springframework.security.core.userdetails.UserDetailsService}) (cannot
85+
* be <code>null</code>)
86+
* @param userDetails the user details (from the
87+
* {@link org.springframework.security.core.userdetails.UserDetailsService}) (cannot
88+
* be <code>null</code>)
89+
* @param assertion the assertion returned from the CAS servers. It contains the
90+
* principal and how to obtain a proxy ticket for the user.
91+
* @throws IllegalArgumentException if a <code>null</code> was passed
92+
* @since 4.2
93+
*/
94+
private CasAuthenticationToken(final Integer keyHash, final Object principal,
95+
final Object credentials,
96+
final Collection<? extends GrantedAuthority> authorities,
97+
final UserDetails userDetails, final Assertion assertion) {
7398
super(authorities);
7499

75-
if ((key == null) || ("".equals(key)) || (principal == null)
100+
if ((principal == null)
76101
|| "".equals(principal) || (credentials == null)
77102
|| "".equals(credentials) || (authorities == null)
78103
|| (userDetails == null) || (assertion == null)) {
79104
throw new IllegalArgumentException(
80105
"Cannot pass null or empty values to constructor");
81106
}
82107

83-
this.keyHash = key.hashCode();
108+
this.keyHash = keyHash;
84109
this.principal = principal;
85110
this.credentials = credentials;
86111
this.userDetails = userDetails;
@@ -91,6 +116,18 @@ public CasAuthenticationToken(final String key, final Object principal,
91116
// ~ Methods
92117
// ========================================================================================================
93118

119+
private static Integer extractKeyHash(String key) {
120+
Object value = nullSafeValue(key);
121+
return value.hashCode();
122+
}
123+
124+
private static Object nullSafeValue(Object value) {
125+
if (value == null || "".equals(value)) {
126+
throw new IllegalArgumentException("Cannot pass null or empty values to constructor");
127+
}
128+
return value;
129+
}
130+
94131
public boolean equals(final Object obj) {
95132
if (!super.equals(obj)) {
96133
return false;
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Copyright 2015-2016 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.cas.jackson2;
18+
19+
import com.fasterxml.jackson.annotation.*;
20+
import org.jasig.cas.client.authentication.AttributePrincipal;
21+
22+
import java.util.Date;
23+
import java.util.Map;
24+
25+
/**
26+
* Helps in jackson deserialization of class {@link org.jasig.cas.client.validation.AssertionImpl}, which is
27+
* used with {@link org.springframework.security.cas.authentication.CasAuthenticationToken}.
28+
* To use this class we need to register with {@link com.fasterxml.jackson.databind.ObjectMapper}. Type information
29+
* will be stored in @class property.
30+
* <p>
31+
* <pre>
32+
* ObjectMapper mapper = new ObjectMapper();
33+
* mapper.registerModule(new CasJackson2Module());
34+
* </pre>
35+
*
36+
*
37+
* @author Jitendra Singh
38+
* @see CasJackson2Module
39+
* @see org.springframework.security.jackson2.SecurityJacksonModules
40+
* @since 4.2
41+
*/
42+
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY)
43+
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY,
44+
getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE)
45+
@JsonIgnoreProperties(ignoreUnknown = true)
46+
public class AssertionImplMixin {
47+
48+
/**
49+
* Mixin Constructor helps in deserialize {@link org.jasig.cas.client.validation.AssertionImpl}
50+
*
51+
* @param principal the Principal to associate with the Assertion.
52+
* @param validFromDate when the assertion is valid from.
53+
* @param validUntilDate when the assertion is valid to.
54+
* @param authenticationDate when the assertion is authenticated.
55+
* @param attributes the key/value pairs for this attribute.
56+
*/
57+
@JsonCreator
58+
public AssertionImplMixin(@JsonProperty("principal") AttributePrincipal principal,
59+
@JsonProperty("validFromDate") Date validFromDate, @JsonProperty("validUntilDate") Date validUntilDate,
60+
@JsonProperty("authenticationDate") Date authenticationDate, @JsonProperty("attributes") Map<String, Object> attributes){
61+
}
62+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
* Copyright 2015-2016 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.cas.jackson2;
18+
19+
import com.fasterxml.jackson.annotation.*;
20+
import org.jasig.cas.client.proxy.ProxyRetriever;
21+
22+
import java.util.Map;
23+
24+
/**
25+
* Helps in deserialize {@link org.jasig.cas.client.authentication.AttributePrincipalImpl} which is used with
26+
* {@link org.springframework.security.cas.authentication.CasAuthenticationToken}. Type information will be stored
27+
* in property named @class.
28+
* <p>
29+
* <pre>
30+
* ObjectMapper mapper = new ObjectMapper();
31+
* mapper.registerModule(new CasJackson2Module());
32+
* </pre>
33+
*
34+
* @author Jitendra Singh
35+
* @see CasJackson2Module
36+
* @see org.springframework.security.jackson2.SecurityJacksonModules
37+
* @since 4.2
38+
*/
39+
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY)
40+
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
41+
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
42+
@JsonIgnoreProperties(ignoreUnknown = true)
43+
public class AttributePrincipalImplMixin {
44+
45+
/**
46+
* Mixin Constructor helps in deserialize {@link org.jasig.cas.client.authentication.AttributePrincipalImpl}
47+
*
48+
* @param name the unique identifier for the principal.
49+
* @param attributes the key/value pairs for this principal.
50+
* @param proxyGrantingTicket the ticket associated with this principal.
51+
* @param proxyRetriever the ProxyRetriever implementation to call back to the CAS server.
52+
*/
53+
@JsonCreator
54+
public AttributePrincipalImplMixin(@JsonProperty("name") String name, @JsonProperty("attributes") Map<String, Object> attributes,
55+
@JsonProperty("proxyGrantingTicket") String proxyGrantingTicket,
56+
@JsonProperty("proxyRetriever") ProxyRetriever proxyRetriever) {
57+
}
58+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/*
2+
* Copyright 2015-2016 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.cas.jackson2;
18+
19+
import com.fasterxml.jackson.annotation.*;
20+
import org.jasig.cas.client.validation.Assertion;
21+
import org.springframework.security.cas.authentication.CasAuthenticationProvider;
22+
import org.springframework.security.cas.authentication.CasAuthenticationToken;
23+
import org.springframework.security.core.GrantedAuthority;
24+
import org.springframework.security.core.userdetails.UserDetails;
25+
26+
import java.util.Collection;
27+
28+
/**
29+
* Mixin class which helps in deserialize {@link org.springframework.security.cas.authentication.CasAuthenticationToken}
30+
* using jackson. Two more dependent classes needs to register along with this mixin class.
31+
* <ol>
32+
* <li>{@link org.springframework.security.cas.jackson2.AssertionImplMixin}</li>
33+
* <li>{@link org.springframework.security.cas.jackson2.AttributePrincipalImplMixin}</li>
34+
* </ol>
35+
*
36+
* <p>
37+
*
38+
* <pre>
39+
* ObjectMapper mapper = new ObjectMapper();
40+
* mapper.registerModule(new CasJackson2Module());
41+
* </pre>
42+
*
43+
* @author Jitendra Singh
44+
* @see CasJackson2Module
45+
* @see org.springframework.security.jackson2.SecurityJacksonModules
46+
* @since 4.2
47+
*/
48+
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY)
49+
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, isGetterVisibility = JsonAutoDetect.Visibility.NONE,
50+
getterVisibility = JsonAutoDetect.Visibility.NONE, creatorVisibility = JsonAutoDetect.Visibility.ANY)
51+
@JsonIgnoreProperties(ignoreUnknown = true)
52+
public class CasAuthenticationTokenMixin {
53+
54+
/**
55+
* Mixin Constructor helps in deserialize {@link CasAuthenticationToken}
56+
*
57+
* @param keyHash hashCode of provided key to identify if this object made by a given
58+
* {@link CasAuthenticationProvider}
59+
* @param principal typically the UserDetails object (cannot be <code>null</code>)
60+
* @param credentials the service/proxy ticket ID from CAS (cannot be
61+
* <code>null</code>)
62+
* @param authorities the authorities granted to the user (from the
63+
* {@link org.springframework.security.core.userdetails.UserDetailsService}) (cannot
64+
* be <code>null</code>)
65+
* @param userDetails the user details (from the
66+
* {@link org.springframework.security.core.userdetails.UserDetailsService}) (cannot
67+
* be <code>null</code>)
68+
* @param assertion the assertion returned from the CAS servers. It contains the
69+
* principal and how to obtain a proxy ticket for the user.
70+
*/
71+
@JsonCreator
72+
public CasAuthenticationTokenMixin(@JsonProperty("keyHash") Integer keyHash, @JsonProperty("principal") Object principal,
73+
@JsonProperty("credentials") Object credentials,
74+
@JsonProperty("authorities") Collection<? extends GrantedAuthority> authorities,
75+
@JsonProperty("userDetails") UserDetails userDetails, @JsonProperty("assertion") Assertion assertion) {
76+
}
77+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
* Copyright 2015-2016 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.cas.jackson2;
18+
19+
import com.fasterxml.jackson.core.Version;
20+
import com.fasterxml.jackson.databind.ObjectMapper;
21+
import com.fasterxml.jackson.databind.module.SimpleModule;
22+
import org.jasig.cas.client.authentication.AttributePrincipalImpl;
23+
import org.jasig.cas.client.validation.AssertionImpl;
24+
import org.springframework.security.cas.authentication.CasAuthenticationToken;
25+
import org.springframework.security.jackson2.SecurityJacksonModules;
26+
27+
/**
28+
* Jackson module for spring-security-cas. This module register {@link AssertionImplMixin},
29+
* {@link AttributePrincipalImplMixin} and {@link CasAuthenticationTokenMixin}. If no default typing enabled by default then
30+
* it'll enable it because typing info is needed to properly serialize/deserialize objects. In order to use this module just
31+
* add this module into your ObjectMapper configuration.
32+
*
33+
* <pre>
34+
* ObjectMapper mapper = new ObjectMapper();
35+
* mapper.registerModule(new CasJackson2Module());
36+
* </pre>
37+
* <b>Note: use {@link SecurityJacksonModules#getModules()} to get list of all security modules.</b>
38+
*
39+
* @author Jitendra Singh.
40+
* @see org.springframework.security.jackson2.SecurityJacksonModules
41+
* @since 4.2
42+
*/
43+
public class CasJackson2Module extends SimpleModule {
44+
45+
public CasJackson2Module() {
46+
super(CasJackson2Module.class.getName(), new Version(1, 0, 0, null, null, null));
47+
}
48+
49+
@Override
50+
public void setupModule(SetupContext context) {
51+
SecurityJacksonModules.enableDefaultTyping((ObjectMapper) context.getOwner());
52+
context.setMixInAnnotations(AssertionImpl.class, AssertionImplMixin.class);
53+
context.setMixInAnnotations(AttributePrincipalImpl.class, AttributePrincipalImplMixin.class);
54+
context.setMixInAnnotations(CasAuthenticationToken.class, CasAuthenticationTokenMixin.class);
55+
}
56+
}

0 commit comments

Comments
 (0)