Skip to content

Commit d7a3a22

Browse files
committed
Add Security Hint AOT Processing
This API allows for hint registration based on Spring Security's infrastructural beans
1 parent add5c56 commit d7a3a22

File tree

13 files changed

+641
-0
lines changed

13 files changed

+641
-0
lines changed

config/spring-security-config.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ dependencies {
2121
api 'org.springframework:spring-context'
2222
api 'org.springframework:spring-core'
2323

24+
optional project(':spring-security-data')
2425
optional project(':spring-security-ldap')
2526
optional project(':spring-security-messaging')
2627
optional project(path: ':spring-security-saml2-service-provider')

config/src/main/java/org/springframework/security/config/annotation/method/configuration/AuthorizationProxyConfiguration.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@
2626
import org.springframework.context.annotation.Bean;
2727
import org.springframework.context.annotation.Configuration;
2828
import org.springframework.context.annotation.Role;
29+
import org.springframework.security.aot.hint.AuthorizeReturnObjectHintsRegistrar;
30+
import org.springframework.security.aot.hint.SecurityHintsRegistrar;
31+
import org.springframework.security.authorization.AuthorizationProxyFactory;
2932
import org.springframework.security.authorization.method.AuthorizationAdvisor;
3033
import org.springframework.security.authorization.method.AuthorizationAdvisorProxyFactory;
3134
import org.springframework.security.authorization.method.AuthorizeReturnObjectMethodInterceptor;
@@ -54,4 +57,13 @@ static MethodInterceptor authorizeReturnObjectMethodInterceptor(ObjectProvider<A
5457
return interceptor;
5558
}
5659

60+
@Bean
61+
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
62+
static SecurityHintsRegistrar authorizeReturnObjectHintsRegistrar(AuthorizationProxyFactory proxyFactory,
63+
ObjectProvider<Customizer<AuthorizeReturnObjectHintsRegistrar>> customizers) {
64+
AuthorizeReturnObjectHintsRegistrar registrar = new AuthorizeReturnObjectHintsRegistrar(proxyFactory);
65+
customizers.forEach((c) -> c.customize(registrar));
66+
return registrar;
67+
}
68+
5769
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* Copyright 2002-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.config.annotation.method.configuration;
18+
19+
import org.springframework.aop.framework.AopInfrastructureBean;
20+
import org.springframework.beans.factory.config.BeanDefinition;
21+
import org.springframework.context.annotation.Bean;
22+
import org.springframework.context.annotation.Configuration;
23+
import org.springframework.context.annotation.Role;
24+
import org.springframework.security.aot.hint.SecurityHintsRegistrar;
25+
import org.springframework.security.authorization.AuthorizationProxyFactory;
26+
import org.springframework.security.data.aot.hint.AuthorizeReturnObjectDataHintsRegistrar;
27+
28+
@Configuration(proxyBeanMethods = false)
29+
final class AuthorizationProxyDataConfiguration implements AopInfrastructureBean {
30+
31+
@Bean
32+
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
33+
static SecurityHintsRegistrar authorizeReturnObjectDataHintsRegistrar(AuthorizationProxyFactory proxyFactory) {
34+
return new AuthorizeReturnObjectDataHintsRegistrar(proxyFactory);
35+
}
36+
37+
}

config/src/main/java/org/springframework/security/config/annotation/method/configuration/MethodSecuritySelector.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import org.springframework.context.annotation.ImportSelector;
2727
import org.springframework.core.type.AnnotationMetadata;
2828
import org.springframework.lang.NonNull;
29+
import org.springframework.util.ClassUtils;
2930

3031
/**
3132
* Dynamically determines which imports to include using the {@link EnableMethodSecurity}
@@ -37,6 +38,9 @@
3738
*/
3839
final class MethodSecuritySelector implements ImportSelector {
3940

41+
private static final boolean isDataPresent = ClassUtils
42+
.isPresent("org.springframework.security.data.aot.hint.AuthorizeReturnObjectDataHintsRegistrar", null);
43+
4044
private final ImportSelector autoProxy = new AutoProxyRegistrarSelector();
4145

4246
@Override
@@ -57,6 +61,9 @@ public String[] selectImports(@NonNull AnnotationMetadata importMetadata) {
5761
imports.add(Jsr250MethodSecurityConfiguration.class.getName());
5862
}
5963
imports.add(AuthorizationProxyConfiguration.class.getName());
64+
if (isDataPresent) {
65+
imports.add(AuthorizationProxyDataConfiguration.class.getName());
66+
}
6067
return imports.toArray(new String[0]);
6168
}
6269

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
/*
2+
* Copyright 2002-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.config.annotation.method.configuration.aot;
18+
19+
import java.util.ArrayList;
20+
import java.util.Collection;
21+
22+
import javax.sql.DataSource;
23+
24+
import org.junit.jupiter.api.Test;
25+
import org.junit.jupiter.api.extension.ExtendWith;
26+
27+
import org.springframework.aot.generate.GenerationContext;
28+
import org.springframework.aot.hint.RuntimeHints;
29+
import org.springframework.beans.factory.annotation.Autowired;
30+
import org.springframework.beans.factory.aot.BeanFactoryInitializationCode;
31+
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
32+
import org.springframework.context.annotation.Bean;
33+
import org.springframework.context.annotation.Configuration;
34+
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
35+
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
36+
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
37+
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
38+
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
39+
import org.springframework.security.aot.hint.SecurityHintsAotProcessor;
40+
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
41+
import org.springframework.security.config.test.SpringTestContext;
42+
import org.springframework.security.config.test.SpringTestContextExtension;
43+
import org.springframework.test.context.junit.jupiter.SpringExtension;
44+
45+
import static org.assertj.core.api.Assertions.assertThat;
46+
import static org.mockito.BDDMockito.given;
47+
import static org.mockito.Mockito.mock;
48+
49+
/**
50+
* AOT Tests for {@code PrePostMethodSecurityConfiguration}.
51+
*
52+
* @author Evgeniy Cheban
53+
* @author Josh Cummings
54+
*/
55+
@ExtendWith({ SpringExtension.class, SpringTestContextExtension.class })
56+
public class EnableMethodSecurityAotTests {
57+
58+
public final SpringTestContext spring = new SpringTestContext(this);
59+
60+
private final RuntimeHints hints = new RuntimeHints();
61+
62+
private final GenerationContext context = mock(GenerationContext.class);
63+
64+
private final BeanFactoryInitializationCode code = mock(BeanFactoryInitializationCode.class);
65+
66+
private final SecurityHintsAotProcessor proxy = new SecurityHintsAotProcessor();
67+
68+
@Autowired
69+
ConfigurableListableBeanFactory beanFactory;
70+
71+
@Test
72+
void findsAllNeededClassesToProxy() {
73+
this.spring.register(AppConfig.class).autowire();
74+
given(this.context.getRuntimeHints()).willReturn(this.hints);
75+
this.proxy.processAheadOfTime(this.beanFactory).applyTo(this.context, this.code);
76+
Collection<String> canonicalNames = new ArrayList<>();
77+
this.hints.reflection().typeHints().forEach((hint) -> canonicalNames.add(hint.getType().getCanonicalName()));
78+
assertThat(canonicalNames).contains(
79+
"org.springframework.security.config.annotation.method.configuration.aot.Message$$SpringCGLIB$$0",
80+
"org.springframework.security.config.annotation.method.configuration.aot.User$$SpringCGLIB$$0");
81+
}
82+
83+
@Configuration
84+
@EnableMethodSecurity
85+
@EnableJpaRepositories
86+
static class AppConfig {
87+
88+
@Bean
89+
DataSource dataSource() {
90+
EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder();
91+
return builder.setType(EmbeddedDatabaseType.HSQL).build();
92+
}
93+
94+
@Bean
95+
LocalContainerEntityManagerFactoryBean entityManagerFactory() {
96+
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
97+
vendorAdapter.setGenerateDdl(true);
98+
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
99+
factory.setJpaVendorAdapter(vendorAdapter);
100+
factory.setPackagesToScan("org.springframework.security.config.annotation.method.configuration.aot");
101+
factory.setDataSource(dataSource());
102+
return factory;
103+
}
104+
105+
}
106+
107+
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/*
2+
* Copyright 2002-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.config.annotation.method.configuration.aot;
18+
19+
import java.time.Instant;
20+
21+
import jakarta.persistence.Entity;
22+
import jakarta.persistence.GeneratedValue;
23+
import jakarta.persistence.GenerationType;
24+
import jakarta.persistence.Id;
25+
import jakarta.persistence.ManyToOne;
26+
27+
import org.springframework.security.access.prepost.PreAuthorize;
28+
import org.springframework.security.authorization.method.AuthorizeReturnObject;
29+
30+
@Entity
31+
public class Message {
32+
33+
@Id
34+
@GeneratedValue(strategy = GenerationType.AUTO)
35+
private Long id;
36+
37+
private String text;
38+
39+
private String summary;
40+
41+
private Instant created = Instant.now();
42+
43+
@ManyToOne
44+
private User to;
45+
46+
@AuthorizeReturnObject
47+
public User getTo() {
48+
return this.to;
49+
}
50+
51+
public void setTo(User to) {
52+
this.to = to;
53+
}
54+
55+
public Long getId() {
56+
return this.id;
57+
}
58+
59+
public void setId(Long id) {
60+
this.id = id;
61+
}
62+
63+
public Instant getCreated() {
64+
return this.created;
65+
}
66+
67+
public void setCreated(Instant created) {
68+
this.created = created;
69+
}
70+
71+
@PreAuthorize("hasAuthority('message:read')")
72+
public String getText() {
73+
return this.text;
74+
}
75+
76+
public void setText(String text) {
77+
this.text = text;
78+
}
79+
80+
@PreAuthorize("hasAuthority('message:read')")
81+
public String getSummary() {
82+
return this.summary;
83+
}
84+
85+
public void setSummary(String summary) {
86+
this.summary = summary;
87+
}
88+
89+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Copyright 2002-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.config.annotation.method.configuration.aot;
18+
19+
import java.util.List;
20+
21+
import org.springframework.data.jpa.repository.Query;
22+
import org.springframework.data.repository.CrudRepository;
23+
import org.springframework.security.authorization.method.AuthorizeReturnObject;
24+
import org.springframework.stereotype.Repository;
25+
26+
/**
27+
* A repository for accessing {@link Message}s.
28+
*
29+
* @author Rob Winch
30+
*/
31+
@Repository
32+
@AuthorizeReturnObject
33+
public interface MessageRepository extends CrudRepository<Message, Long> {
34+
35+
@Query("select m from Message m where m.to.id = ?#{ authentication.name }")
36+
List<Message> findAll();
37+
38+
}

0 commit comments

Comments
 (0)