Skip to content

Commit 697daea

Browse files
author
Rob Winch
committed
Add Jackson2 Support for PreAuthenticatedAuthenticationToken
Fixes gh-4120
1 parent f97f38f commit 697daea

File tree

4 files changed

+208
-0
lines changed

4 files changed

+208
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
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.web.jackson2;
18+
19+
import java.io.IOException;
20+
import java.util.List;
21+
22+
import org.springframework.security.core.GrantedAuthority;
23+
import org.springframework.security.core.userdetails.User;
24+
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
25+
26+
import com.fasterxml.jackson.core.JsonParser;
27+
import com.fasterxml.jackson.core.JsonProcessingException;
28+
import com.fasterxml.jackson.core.type.TypeReference;
29+
import com.fasterxml.jackson.databind.DeserializationContext;
30+
import com.fasterxml.jackson.databind.JsonDeserializer;
31+
import com.fasterxml.jackson.databind.JsonNode;
32+
import com.fasterxml.jackson.databind.ObjectMapper;
33+
import com.fasterxml.jackson.databind.node.MissingNode;
34+
35+
/**
36+
* Custom deserializer for {@link PreAuthenticatedAuthenticationToken}. At the time of deserialization
37+
* it will invoke suitable constructor depending on the value of <b>authenticated</b> property.
38+
* It will ensure that the token's state must not change.
39+
* <p>
40+
* This deserializer is already registered with {@link PreAuthenticatedAuthenticationTokenMixin} but
41+
* you can also registered it with your own mixin class.
42+
*
43+
* @author Jitendra Singh
44+
* @see PreAuthenticatedAuthenticationTokenMixin
45+
* @since 4.2
46+
*/
47+
class PreAuthenticatedAuthenticationTokenDeserializer extends JsonDeserializer<PreAuthenticatedAuthenticationToken> {
48+
49+
/**
50+
* This method construct {@link PreAuthenticatedAuthenticationToken} object from serialized json.
51+
* @param jp the JsonParser
52+
* @param ctxt the DeserializationContext
53+
* @return the user
54+
* @throws IOException if a exception during IO occurs
55+
* @throws JsonProcessingException if an error during JSON processing occurs
56+
*/
57+
@Override
58+
public PreAuthenticatedAuthenticationToken deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
59+
PreAuthenticatedAuthenticationToken token = null;
60+
ObjectMapper mapper = (ObjectMapper) jp.getCodec();
61+
JsonNode jsonNode = mapper.readTree(jp);
62+
Boolean authenticated = readJsonNode(jsonNode, "authenticated").asBoolean();
63+
JsonNode principalNode = readJsonNode(jsonNode, "principal");
64+
Object principal = null;
65+
if(principalNode.isObject()) {
66+
principal = mapper.readValue(principalNode.toString(), new TypeReference<User>() {});
67+
} else {
68+
principal = principalNode.asText();
69+
}
70+
Object credentials = readJsonNode(jsonNode, "credentials").asText();
71+
List<GrantedAuthority> authorities = mapper.readValue(
72+
readJsonNode(jsonNode, "authorities").toString(), new TypeReference<List<GrantedAuthority>>() {
73+
});
74+
if (authenticated) {
75+
token = new PreAuthenticatedAuthenticationToken(principal, credentials, authorities);
76+
} else {
77+
token = new PreAuthenticatedAuthenticationToken(principal, credentials);
78+
}
79+
token.setDetails(readJsonNode(jsonNode, "details"));
80+
return token;
81+
}
82+
83+
private JsonNode readJsonNode(JsonNode jsonNode, String field) {
84+
return jsonNode.has(field) ? jsonNode.get(field) : MissingNode.getInstance();
85+
}
86+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
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.web.jackson2;
18+
19+
import org.springframework.security.jackson2.SecurityJackson2Modules;
20+
import org.springframework.security.jackson2.SimpleGrantedAuthorityMixin;
21+
22+
import com.fasterxml.jackson.annotation.JsonAutoDetect;
23+
import com.fasterxml.jackson.annotation.JsonTypeInfo;
24+
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
25+
26+
/**
27+
* This mixin class is used to serialize / deserialize
28+
* {@link org.springframework.security.authentication.UsernamePasswordAuthenticationToken}. This class register
29+
* a custom deserializer {@link PreAuthenticatedAuthenticationTokenDeserializer}.
30+
*
31+
* In order to use this mixin you'll need to add 3 more mixin classes.
32+
* <ol>
33+
* <li>{@link UnmodifiableSetMixin}</li>
34+
* <li>{@link SimpleGrantedAuthorityMixin}</li>
35+
* <li>{@link UserMixin}</li>
36+
* </ol>
37+
*
38+
* <pre>
39+
* ObjectMapper mapper = new ObjectMapper();
40+
* mapper.registerModule(new CoreJackson2Module());
41+
* </pre>
42+
* @author Jitendra Singh
43+
* @see Webackson2Module
44+
* @see SecurityJackson2Modules
45+
* @since 4.2
46+
*/
47+
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "@class")
48+
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
49+
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
50+
@JsonDeserialize(using = PreAuthenticatedAuthenticationTokenDeserializer.class)
51+
abstract class PreAuthenticatedAuthenticationTokenMixin {
52+
}

web/src/main/java/org/springframework/security/web/jackson2/WebJackson2Module.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
import org.springframework.security.jackson2.SecurityJackson2Modules;
2222
import org.springframework.security.web.authentication.WebAuthenticationDetails;
23+
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
2324
import org.springframework.security.web.csrf.DefaultCsrfToken;
2425
import org.springframework.security.web.savedrequest.DefaultSavedRequest;
2526
import org.springframework.security.web.savedrequest.SavedCookie;
@@ -58,5 +59,6 @@ public void setupModule(SetupContext context) {
5859
context.setMixInAnnotations(DefaultCsrfToken.class, DefaultCsrfTokenMixin.class);
5960
context.setMixInAnnotations(DefaultSavedRequest.class, DefaultSavedRequestMixin.class);
6061
context.setMixInAnnotations(WebAuthenticationDetails.class, WebAuthenticationDetailsMixin.class);
62+
context.setMixInAnnotations(PreAuthenticatedAuthenticationToken.class, PreAuthenticatedAuthenticationTokenMixin.class);
6163
}
6264
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
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.web.jackson2;
18+
19+
import static org.assertj.core.api.Assertions.assertThat;
20+
21+
import org.json.JSONException;
22+
import org.junit.Before;
23+
import org.junit.Test;
24+
import org.skyscreamer.jsonassert.JSONAssert;
25+
import org.springframework.security.core.authority.AuthorityUtils;
26+
import org.springframework.security.jackson2.SimpleGrantedAuthorityMixinTests;
27+
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
28+
29+
import com.fasterxml.jackson.core.JsonProcessingException;
30+
31+
/**
32+
* @author Rob Winch
33+
* @since 4.2
34+
*/
35+
public class PreAuthenticatedAuthenticationTokenMixinTests extends AbstractMixinTests {
36+
// @formatter:off
37+
private static final String PREAUTH_JSON = "{"
38+
+ "\"@class\": \"org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken\","
39+
+ "\"principal\": \"principal\", "
40+
+ "\"credentials\": \"credentials\", "
41+
+ "\"authenticated\": true, "
42+
+ "\"details\": null, "
43+
+ "\"authorities\": "+ SimpleGrantedAuthorityMixinTests.AUTHORITIES_ARRAYLIST_JSON
44+
+ "}";
45+
// @formatter:on
46+
47+
PreAuthenticatedAuthenticationToken expected;
48+
49+
@Before
50+
public void setupExpected() {
51+
expected = new PreAuthenticatedAuthenticationToken("principal", "credentials", AuthorityUtils.createAuthorityList("ROLE_USER"));
52+
}
53+
54+
@Test
55+
public void serializeWhenPrincipalCredentialsAuthoritiesThenSuccess() throws JsonProcessingException, JSONException {
56+
String serializedJson = mapper.writeValueAsString(expected);
57+
JSONAssert.assertEquals(PREAUTH_JSON, serializedJson, true);
58+
}
59+
60+
@Test
61+
public void deserializeAuthenticatedUsernamePasswordAuthenticationTokenMixinTest() throws Exception{
62+
PreAuthenticatedAuthenticationToken deserialized = mapper
63+
.readValue(PREAUTH_JSON, PreAuthenticatedAuthenticationToken.class);
64+
assertThat(deserialized).isNotNull();
65+
assertThat(deserialized.isAuthenticated()).isTrue();
66+
assertThat(deserialized.getAuthorities()).isEqualTo(expected.getAuthorities());
67+
}
68+
}

0 commit comments

Comments
 (0)