Skip to content

Commit 001fb02

Browse files
committed
Improve serialize of proxy objects generated by AuthorizeReturnObject
Issue gh-15561
1 parent 84fc5a7 commit 001fb02

File tree

5 files changed

+151
-0
lines changed

5 files changed

+151
-0
lines changed

core/src/main/java/org/springframework/security/authorization/method/AuthorizationAdvisorProxyFactory.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@ public Object proxy(Object target) {
172172
factory.addAdvisors(advisor);
173173
}
174174
factory.setProxyTargetClass(!Modifier.isFinal(target.getClass().getModifiers()));
175+
factory.addInterface(AuthorizationProxy.class);
175176
return factory.getProxy();
176177
}
177178

@@ -357,6 +358,7 @@ public Object visit(AuthorizationAdvisorProxyFactory proxyFactory, Object object
357358
ProxyFactory factory = new ProxyFactory();
358359
factory.setTargetClass(targetClass);
359360
factory.setInterfaces(ClassUtils.getAllInterfacesForClass(targetClass));
361+
factory.addInterface(AuthorizationProxy.class);
360362
factory.setProxyTargetClass(!Modifier.isFinal(targetClass.getModifiers()));
361363
for (Advisor advisor : proxyFactory) {
362364
factory.addAdvisors(advisor);
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
* Copyright 2024 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+
* https://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.authorization.method;
18+
19+
/**
20+
* Marker interface implemented by Authorization proxies. Used to detect whether objects
21+
* are AuthorizeReturnObject proxies.
22+
*
23+
* @author DingHao
24+
* @since 6.4
25+
* @see org.springframework.security.authorization.method.AuthorizeReturnObject
26+
*/
27+
public interface AuthorizationProxy {
28+
29+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* Copyright 2024 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+
* https://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.jackson2;
18+
19+
import java.io.IOException;
20+
21+
import com.fasterxml.jackson.core.JsonGenerator;
22+
import com.fasterxml.jackson.databind.SerializerProvider;
23+
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
24+
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
25+
26+
import org.springframework.aop.framework.AopProxyUtils;
27+
import org.springframework.security.authorization.method.AuthorizationProxy;
28+
import org.springframework.security.authorization.method.AuthorizeReturnObject;
29+
30+
/**
31+
* Serialize AuthorizationProxy objects generated by {@link AuthorizeReturnObject}
32+
*
33+
* @author DingHao
34+
* @since 6.4
35+
*/
36+
public final class AuthorizationProxySerializer extends StdSerializer<AuthorizationProxy> {
37+
38+
public AuthorizationProxySerializer() {
39+
super(AuthorizationProxy.class);
40+
}
41+
42+
@Override
43+
public void serialize(AuthorizationProxy value, JsonGenerator gen, SerializerProvider serializers)
44+
throws IOException {
45+
gen.writeObject(AopProxyUtils.getSingletonTarget(value));
46+
}
47+
48+
@Override
49+
public void serializeWithType(AuthorizationProxy value, JsonGenerator gen, SerializerProvider serializers,
50+
TypeSerializer typeSer) throws IOException {
51+
serialize(value, gen, serializers);
52+
}
53+
54+
}

core/src/main/java/org/springframework/security/jackson2/CoreJackson2Module.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,12 @@ public class CoreJackson2Module extends SimpleModule {
5252

5353
public CoreJackson2Module() {
5454
super(CoreJackson2Module.class.getName(), new Version(1, 0, 0, null, null, null));
55+
addSerializer(new AuthorizationProxySerializer());
5556
}
5657

5758
@Override
5859
public void setupModule(SetupContext context) {
60+
super.setupModule(context);
5961
SecurityJackson2Modules.enableDefaultTyping(context.getOwner());
6062
context.setMixInAnnotations(AnonymousAuthenticationToken.class, AnonymousAuthenticationTokenMixin.class);
6163
context.setMixInAnnotations(RememberMeAuthenticationToken.class, RememberMeAuthenticationTokenMixin.class);

core/src/test/java/org/springframework/security/authorization/AuthorizationAdvisorProxyFactoryTests.java

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@
3434
import java.util.function.Supplier;
3535
import java.util.stream.Stream;
3636

37+
import com.fasterxml.jackson.core.JsonProcessingException;
38+
import com.fasterxml.jackson.databind.ObjectMapper;
39+
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
40+
import com.fasterxml.jackson.databind.exc.InvalidDefinitionException;
3741
import org.jetbrains.annotations.NotNull;
3842
import org.junit.jupiter.api.Test;
3943

@@ -46,6 +50,7 @@
4650
import org.springframework.security.authorization.method.AuthorizationAdvisorProxyFactory.TargetVisitor;
4751
import org.springframework.security.core.Authentication;
4852
import org.springframework.security.core.context.SecurityContextHolder;
53+
import org.springframework.security.jackson2.SecurityJackson2Modules;
4954

5055
import static org.assertj.core.api.Assertions.assertThat;
5156
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
@@ -336,6 +341,34 @@ public void setTargetVisitorIgnoreValueTypesThenIgnores() {
336341
assertThat(factory.proxy(35)).isEqualTo(35);
337342
}
338343

344+
@Test
345+
public void serializeAuthorizationProxyObjectWhenProvideJsonSerialize() throws JsonProcessingException {
346+
SecurityContextHolder.getContext().setAuthentication(this.admin);
347+
AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withDefaults();
348+
JsonSerializeUser user1 = new JsonSerializeUser("used JsonSerialize annotation");
349+
NoJsonSerializeUser user2 = new NoJsonSerializeUser("unused JsonSerialize annotation");
350+
351+
ObjectMapper mapper = new ObjectMapper();
352+
mapper.registerModules(SecurityJackson2Modules.getModules(getClass().getClassLoader()));
353+
354+
assertThat(mapper.writeValueAsString(proxy(factory, user1))).doesNotContain("description");
355+
assertThat(mapper.writeValueAsString(proxy(factory, user2))).contains("description");
356+
}
357+
358+
@Test
359+
public void serializeAuthorizationProxyObject() throws JsonProcessingException {
360+
SecurityContextHolder.getContext().setAuthentication(this.admin);
361+
AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withDefaults();
362+
User user = proxy(factory, this.alan);
363+
ObjectMapper mapper = new ObjectMapper();
364+
assertThatExceptionOfType(InvalidDefinitionException.class).isThrownBy(() -> mapper.writeValueAsString(user));
365+
366+
ObjectMapper objectMapper = new ObjectMapper();
367+
objectMapper.registerModules(SecurityJackson2Modules.getModules(getClass().getClassLoader()));
368+
String actual = objectMapper.writeValueAsString(user);
369+
assertThat(actual).isInstanceOf(String.class);
370+
}
371+
339372
private Authentication authenticated(String user, String... authorities) {
340373
return TestAuthentication.authenticated(TestAuthentication.withUsername(user).authorities(authorities).build());
341374
}
@@ -363,6 +396,37 @@ interface Identifiable {
363396

364397
}
365398

399+
@JsonSerialize(as = User.class)
400+
public static class JsonSerializeUser extends User {
401+
402+
private final String description;
403+
404+
JsonSerializeUser(String description) {
405+
super("alan", "alan", "turing");
406+
this.description = description;
407+
}
408+
409+
public String getDescription() {
410+
return this.description;
411+
}
412+
413+
}
414+
415+
public static class NoJsonSerializeUser extends User {
416+
417+
private final String description;
418+
419+
NoJsonSerializeUser(String description) {
420+
super("alan", "alan", "turing");
421+
this.description = description;
422+
}
423+
424+
public String getDescription() {
425+
return this.description;
426+
}
427+
428+
}
429+
366430
public static class User implements Identifiable, Comparable<User> {
367431

368432
private final String id;

0 commit comments

Comments
 (0)