Skip to content

Commit

Permalink
Improve serialize of proxy objects generated by AuthorizeReturnObject
Browse files Browse the repository at this point in the history
  • Loading branch information
kse-music committed Aug 26, 2024
1 parent 561c786 commit 023387f
Show file tree
Hide file tree
Showing 5 changed files with 142 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ public Object proxy(Object target) {
factory.addAdvisors(advisor);
}
factory.setProxyTargetClass(!Modifier.isFinal(target.getClass().getModifiers()));
factory.addInterface(AuthorizeReturnObject.class);
return factory.getProxy();
}

Expand Down Expand Up @@ -262,6 +263,18 @@ public Iterator<AuthorizationAdvisor> iterator() {
return this.advisors.iterator();
}

/**
* Tag interface for
* {@link org.springframework.security.authorization.method.AuthorizeReturnObject}
* generated proxy object
*
* @author DingHao
* @version 6.4
*/
public interface AuthorizeReturnObject {

}

/**
* An interface to handle how the {@link AuthorizationAdvisorProxyFactory} should step
* through the target's object hierarchy.
Expand Down Expand Up @@ -357,6 +370,7 @@ public Object visit(AuthorizationAdvisorProxyFactory proxyFactory, Object object
ProxyFactory factory = new ProxyFactory();
factory.setTargetClass(targetClass);
factory.setInterfaces(ClassUtils.getAllInterfacesForClass(targetClass));
factory.addInterface(AuthorizeReturnObject.class);
factory.setProxyTargetClass(!Modifier.isFinal(targetClass.getModifiers()));
for (Advisor advisor : proxyFactory) {
factory.addAdvisors(advisor);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* Copyright 2024 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
*
* https://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 java.io.IOException;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;

import org.springframework.aop.support.AopUtils;
import org.springframework.security.authorization.method.AuthorizationAdvisorProxyFactory;

/**
* Add serializer to serialize
* {@link AuthorizationAdvisorProxyFactory.AuthorizeReturnObject } proxy object In order
* to use this module just add this module into your ObjectMapper configuration.
*
* <pre>
* ObjectMapper mapper = new ObjectMapper();
* mapper.registerModule(new AuthorizeReturnObjectJackson2Module());
*
* </pre> <b>Note: use {@link SecurityJackson2Modules#getModules(ClassLoader)} to get list
* of all security modules.</b>
*
* @author DingHao
* @since 6.4
* @see AuthorizationAdvisorProxyFactory.AuthorizeReturnObject
*/
public final class AuthorizeReturnObjectJackson2Module extends SimpleModule {

public AuthorizeReturnObjectJackson2Module() {
addSerializer(new AuthorizeReturnObjectSerializer());
}

private static final class AuthorizeReturnObjectSerializer
extends StdSerializer<AuthorizationAdvisorProxyFactory.AuthorizeReturnObject> {

private AuthorizeReturnObjectSerializer() {
super(AuthorizationAdvisorProxyFactory.AuthorizeReturnObject.class);
}

@Override
public void serialize(AuthorizationAdvisorProxyFactory.AuthorizeReturnObject value, JsonGenerator gen,
SerializerProvider provider) throws IOException {
Class<?> targetClass = AopUtils.getTargetClass(value);
JsonSerializer<Object> serializer = provider.findValueSerializer(targetClass);
serializer.serialize(value, gen, provider);
}

}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2015-2021 the original author or authors.
* Copyright 2015-2024 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.
Expand Down Expand Up @@ -58,6 +58,7 @@
* ObjectMapper mapper = new ObjectMapper();
* mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
* mapper.registerModule(new CoreJackson2Module());
* mapper.registerModule(new AuthorizeReturnObjectJackson2Module());
* mapper.registerModule(new CasJackson2Module());
* mapper.registerModule(new WebJackson2Module());
* mapper.registerModule(new WebServletJackson2Module());
Expand All @@ -75,6 +76,7 @@ public final class SecurityJackson2Modules {

private static final List<String> securityJackson2ModuleClasses = Arrays.asList(
"org.springframework.security.jackson2.CoreJackson2Module",
"org.springframework.security.jackson2.AuthorizeReturnObjectJackson2Module",
"org.springframework.security.cas.jackson2.CasJackson2Module",
"org.springframework.security.web.jackson2.WebJackson2Module",
"org.springframework.security.web.server.jackson2.WebServerJackson2Module");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@
import java.util.function.Supplier;
import java.util.stream.Stream;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.exc.InvalidDefinitionException;
import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.Test;

Expand All @@ -46,6 +49,7 @@
import org.springframework.security.authorization.method.AuthorizationAdvisorProxyFactory.TargetVisitor;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.jackson2.AuthorizeReturnObjectJackson2Module;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
Expand Down Expand Up @@ -336,6 +340,25 @@ public void setTargetVisitorIgnoreValueTypesThenIgnores() {
assertThat(factory.proxy(35)).isEqualTo(35);
}

@Test
public void serializeCglibObjectThrowException() {
SecurityContextHolder.getContext().setAuthentication(this.admin);
AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withDefaults();
User user = proxy(factory, this.alan);
ObjectMapper mapper = new ObjectMapper();
assertThatExceptionOfType(InvalidDefinitionException.class).isThrownBy(() -> mapper.writeValueAsString(user));
}

@Test
public void serializeCglibObjectSuccess() throws JsonProcessingException {
SecurityContextHolder.getContext().setAuthentication(this.admin);
AuthorizationAdvisorProxyFactory factory = AuthorizationAdvisorProxyFactory.withDefaults();
User user = proxy(factory, this.alan);
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new AuthorizeReturnObjectJackson2Module());
assertThat(mapper.writeValueAsString(user)).isInstanceOf(String.class);
}

private Authentication authenticated(String user, String... authorities) {
return TestAuthentication.authenticated(TestAuthentication.withUsername(user).authorities(authorities).build());
}
Expand Down
33 changes: 33 additions & 0 deletions docs/modules/ROOT/pages/servlet/authorization/method-security.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -2258,6 +2258,39 @@ class User
----
======

Or register `AuthorizeReturnObjectJackson2Module` to `ObjectMapper`:

[source,java]
----
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new AuthorizeReturnObjectJackson2Module());
----

If you are using Spring Boot, you can also publish `AuthorizeReturnObjectJackson2Module` as a bean:

[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
public AuthorizeReturnObjectJackson2Module authorizeReturnObjectJackson2Module() {
return new AuthorizeReturnObjectJackson2Module();
}
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Bean
fun authorizeReturnObjectJackson2Module(): AuthorizeReturnObjectJackson2Module {
return AuthorizeReturnObjectJackson2Module()
}
----
======

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:

[tabs]
Expand Down

0 comments on commit 023387f

Please sign in to comment.