Skip to content

Commit 6aa19ca

Browse files
committed
Improve serialize of proxy objects generated by AuthorizeReturnObject
Issue spring-projectsgh-15561
1 parent 561c786 commit 6aa19ca

File tree

5 files changed

+141
-1
lines changed

5 files changed

+141
-1
lines changed

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

Lines changed: 13 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.setInterfaces(AuthorizeReturnObject.class);
175176
return factory.getProxy();
176177
}
177178

@@ -262,6 +263,18 @@ public Iterator<AuthorizationAdvisor> iterator() {
262263
return this.advisors.iterator();
263264
}
264265

266+
/**
267+
* Tag interface for
268+
* {@link org.springframework.security.authorization.method.AuthorizeReturnObject}
269+
* generated proxy object
270+
*
271+
* @author DingHao
272+
* @version 6.4
273+
*/
274+
public interface AuthorizeReturnObject {
275+
276+
}
277+
265278
/**
266279
* An interface to handle how the {@link AuthorizationAdvisorProxyFactory} should step
267280
* through the target's object hierarchy.
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
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.JsonSerializer;
23+
import com.fasterxml.jackson.databind.SerializerProvider;
24+
import com.fasterxml.jackson.databind.module.SimpleModule;
25+
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
26+
27+
import org.springframework.aop.support.AopUtils;
28+
import org.springframework.security.authorization.method.AuthorizationAdvisorProxyFactory;
29+
30+
/**
31+
* Add serializer to serialize
32+
* {@link AuthorizationAdvisorProxyFactory.AuthorizeReturnObject } proxy object In order
33+
* to use this module just add this module into your ObjectMapper configuration.
34+
*
35+
* <pre>
36+
* ObjectMapper mapper = new ObjectMapper();
37+
* mapper.registerModule(new AuthorizeReturnObjectJackson2Module());
38+
*
39+
* </pre> <b>Note: use {@link SecurityJackson2Modules#getModules(ClassLoader)} to get list
40+
* of all security modules.</b>
41+
*
42+
* @author DingHao
43+
* @since 6.4
44+
* @see AuthorizationAdvisorProxyFactory.AuthorizeReturnObject
45+
*/
46+
public final class AuthorizeReturnObjectJackson2Module extends SimpleModule {
47+
48+
public AuthorizeReturnObjectJackson2Module() {
49+
addSerializer(new AuthorizeReturnObjectSerializer());
50+
}
51+
52+
private static final class AuthorizeReturnObjectSerializer
53+
extends StdSerializer<AuthorizationAdvisorProxyFactory.AuthorizeReturnObject> {
54+
55+
private AuthorizeReturnObjectSerializer() {
56+
super(AuthorizationAdvisorProxyFactory.AuthorizeReturnObject.class);
57+
}
58+
59+
@Override
60+
public void serialize(AuthorizationAdvisorProxyFactory.AuthorizeReturnObject value, JsonGenerator gen,
61+
SerializerProvider provider) throws IOException {
62+
Class<?> targetClass = AopUtils.getTargetClass(value);
63+
JsonSerializer<Object> serializer = provider.findValueSerializer(targetClass);
64+
serializer.serialize(value, gen, provider);
65+
}
66+
67+
}
68+
69+
}

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2015-2021 the original author or authors.
2+
* Copyright 2015-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -58,6 +58,7 @@
5858
* ObjectMapper mapper = new ObjectMapper();
5959
* mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
6060
* mapper.registerModule(new CoreJackson2Module());
61+
* mapper.registerModule(new AuthorizeReturnObjectJackson2Module());
6162
* mapper.registerModule(new CasJackson2Module());
6263
* mapper.registerModule(new WebJackson2Module());
6364
* mapper.registerModule(new WebServletJackson2Module());
@@ -75,6 +76,7 @@ public final class SecurityJackson2Modules {
7576

7677
private static final List<String> securityJackson2ModuleClasses = Arrays.asList(
7778
"org.springframework.security.jackson2.CoreJackson2Module",
79+
"org.springframework.security.jackson2.AuthorizeReturnObjectJackson2Module",
7880
"org.springframework.security.cas.jackson2.CasJackson2Module",
7981
"org.springframework.security.web.jackson2.WebJackson2Module",
8082
"org.springframework.security.web.server.jackson2.WebServerJackson2Module");

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

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@
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.exc.InvalidDefinitionException;
3740
import org.jetbrains.annotations.NotNull;
3841
import org.junit.jupiter.api.Test;
3942

@@ -46,6 +49,7 @@
4649
import org.springframework.security.authorization.method.AuthorizationAdvisorProxyFactory.TargetVisitor;
4750
import org.springframework.security.core.Authentication;
4851
import org.springframework.security.core.context.SecurityContextHolder;
52+
import org.springframework.security.jackson2.AuthorizeReturnObjectJackson2Module;
4953

5054
import static org.assertj.core.api.Assertions.assertThat;
5155
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
@@ -336,6 +340,25 @@ public void setTargetVisitorIgnoreValueTypesThenIgnores() {
336340
assertThat(factory.proxy(35)).isEqualTo(35);
337341
}
338342

343+
@Test
344+
public void serializeCglibObjectThrowException() {
345+
SecurityContextHolder.getContext().setAuthentication(this.admin);
346+
AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withDefaults();
347+
User user = proxy(factory, this.alan);
348+
ObjectMapper mapper = new ObjectMapper();
349+
assertThatExceptionOfType(InvalidDefinitionException.class).isThrownBy(() -> mapper.writeValueAsString(user));
350+
}
351+
352+
@Test
353+
public void serializeCglibObjectSuccess() throws JsonProcessingException {
354+
SecurityContextHolder.getContext().setAuthentication(this.admin);
355+
AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withDefaults();
356+
User user = proxy(factory, this.alan);
357+
ObjectMapper mapper = new ObjectMapper();
358+
mapper.registerModule(new AuthorizeReturnObjectJackson2Module());
359+
assertThat(mapper.writeValueAsString(user)).isInstanceOf(String.class);
360+
}
361+
339362
private Authentication authenticated(String user, String... authorities) {
340363
return TestAuthentication.authenticated(TestAuthentication.withUsername(user).authorities(authorities).build());
341364
}

docs/modules/ROOT/pages/servlet/authorization/method-security.adoc

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2258,6 +2258,39 @@ class User
22582258
----
22592259
======
22602260

2261+
Or register `AuthorizeReturnObjectJackson2Module` to `ObjectMapper`:
2262+
2263+
[source,java]
2264+
----
2265+
ObjectMapper mapper = new ObjectMapper();
2266+
mapper.registerModule(new AuthorizeReturnObjectJackson2Module());
2267+
----
2268+
2269+
If you are using Spring Boot, you can also publish `AuthorizeReturnObjectJackson2Module` as a bean:
2270+
2271+
[tabs]
2272+
======
2273+
Java::
2274+
+
2275+
[source,java,role="primary"]
2276+
----
2277+
@Bean
2278+
public AuthorizeReturnObjectJackson2Module authorizeReturnObjectJackson2Module() {
2279+
return new AuthorizeReturnObjectJackson2Module();
2280+
}
2281+
----
2282+
2283+
Kotlin::
2284+
+
2285+
[source,kotlin,role="secondary"]
2286+
----
2287+
@Bean
2288+
fun authorizeReturnObjectJackson2Module(): AuthorizeReturnObjectJackson2Module {
2289+
return AuthorizeReturnObjectJackson2Module()
2290+
}
2291+
----
2292+
======
2293+
22612294
Finally, you will need to publish a <<custom_advice, custom interceptor>> to catch the `AccessDeniedException` thrown for each field, which you can do like so:
22622295

22632296
[tabs]

0 commit comments

Comments
 (0)