Skip to content

Commit 06e5a21

Browse files
committed
Add documentation and tests
Signed-off-by: Steve Riesenberg <5248162+sjohnr@users.noreply.github.com>
1 parent 5aa968a commit 06e5a21

File tree

16 files changed

+1563
-0
lines changed

16 files changed

+1563
-0
lines changed

docs/modules/ROOT/pages/servlet/authorization/architecture.adoc

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,43 @@ Another manager is the `AuthenticatedAuthorizationManager`.
144144
It can be used to differentiate between anonymous, fully-authenticated and remember-me authenticated users.
145145
Many sites allow certain limited access under remember-me authentication, but require a user to confirm their identity by logging in for full access.
146146

147+
[[authz-authorization-manager-factory]]
148+
=== Creating AuthorizationManager instances
149+
150+
The javadoc:org.springframework.security.authorization.AuthorizationManagerFactory[] interface (introduced in Spring Security 7.0) is used to create generic ``AuthorizationManager``s in xref:servlet/authorization/authorize-http-requests.adoc[request-based] and xref:servlet/authorization/method-security.adoc[method-based] authorization components.
151+
The following is a sketch of the `AuthorizationManagerFactory` interface:
152+
153+
[source,java]
154+
----
155+
public interface AuthorizationManagerFactory<T> {
156+
AuthorizationManager<T> permitAll();
157+
AuthorizationManager<T> denyAll();
158+
AuthorizationManager<T> hasRole(String role);
159+
AuthorizationManager<T> hasAnyRole(String... roles);
160+
AuthorizationManager<T> hasAuthority(String authority);
161+
AuthorizationManager<T> hasAnyAuthority(String... authorities);
162+
AuthorizationManager<T> authenticated();
163+
AuthorizationManager<T> fullyAuthenticated();
164+
AuthorizationManager<T> rememberMe();
165+
AuthorizationManager<T> anonymous();
166+
}
167+
----
168+
169+
The default implementation is javadoc:org.springframework.security.authorization.DefaultAuthorizationManagerFactory[], which allows for customizing the `rolePrefix` (defaults to `"ROLE_"`), `RoleHierarchy` and `AuthenticationTrustManager` that are provided to the ``AuthorizationManager``s created by the factory.
170+
171+
In order to customize the default instance used by Spring Security, simply publish a bean as in the following example:
172+
173+
include-code::./AuthorizationManagerFactoryConfiguration[tag=config,indent=0]
174+
175+
[TIP]
176+
It is also possible to target a specific usage of this factory within Spring Security by providing a concrete parameterized type instead of a generic type.
177+
See examples of each in the xref:servlet/authorization/authorize-http-requests.adoc#customizing-authorization-managers[request-based] and xref:servlet/authorization/method-security.adoc#customizing-authorization-managers[method-based] sections of the documentation.
178+
179+
In addition to simply customizing the default instance of `AuthorizationManagerFactory`, you can provide your own implementation to fully customize the instances created by the factory and provide your own implementations.
180+
181+
[NOTE]
182+
The {gh-url}/core/src/main/java/org/springframework/security/authorization/AuthorizationManagerFactory.java[actual interface] provides default implementations for all factory methods, which allows custom implementations to only implement the methods that need to be customized.
183+
147184
[[authz-authorization-managers]]
148185
==== AuthorizationManagers
149186
There are also helpful static factories in javadoc:org.springframework.security.authorization.AuthorizationManagers[] for composing individual ``AuthorizationManager``s into more sophisticated expressions.

docs/modules/ROOT/pages/servlet/authorization/authorize-http-requests.adoc

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ In many cases, your authorization rules will be more sophisticated than that, so
5757
* I want to <<match-by-custom, match a request programmatically>>
5858
* I want to <<authorize-requests, authorize a request programmatically>>
5959
* I want to <<remote-authorization-manager, delegate request authorization>> to a policy agent
60+
* I want to <<customizing-authorization-managers,customize how authorization managers are created>>
6061

6162
[[request-authorization-architecture]]
6263
== Understanding How Request Authorization Components Work
@@ -765,6 +766,24 @@ You will notice that since we are using the `hasRole` expression we do not need
765766
<6> Any URL that has not already been matched on is denied access.
766767
This is a good strategy if you do not want to accidentally forget to update your authorization rules.
767768

769+
[[customizing-authorization-managers]]
770+
== Customizing Authorization Managers
771+
772+
When you use the `authorizeHttpRequests` DSL, Spring Security takes care of creating the appropriate `AuthorizationManager` instances for you.
773+
In certain cases, you may want to customize what is created in order to have complete control over how authorization decisions are made xref:servlet/authorization/architecture.adoc#authz-delegate-authorization-manager[at the framework level].
774+
775+
In order to take control of creating instances of `AuthorizationManager` for authorizing HTTP requests, you can create a custom xref:servlet/authorization/architecture.adoc#authz-authorization-manager-factory[`AuthorizationManagerFactory`].
776+
For example, let's say you want to create a convention that authenticated users must be authenticated _AND_ have the `USER` role.
777+
To do this, you can create a custom implementation for HTTP requests as in the following example:
778+
779+
include-code::./CustomHttpRequestsAuthorizationManagerFactory[tag=class,indent=0]
780+
781+
Now, whenever you <<activate-request-security,require authentication>>, Spring Security will automatically invoke your custom factory to create an instance of `AuthorizationManager` that requires authentication _AND_ the `USER` role.
782+
783+
[TIP]
784+
We use this as a simple example of creating a custom `AuthorizationManagerFactory`, though it is also possible (and often simpler) to replace a specific `AuthorizationManager` only for a particular request.
785+
See <<remote-authorization-manager>> for an example.
786+
768787
[[authorization-expressions]]
769788
== Expressing Authorization with SpEL
770789

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1995,6 +1995,24 @@ This works on both classes and interfaces.
19951995
This does not work for interfaces, since they do not have debug information about the parameter names.
19961996
For interfaces, either annotations or the `-parameters` approach must be used.
19971997

1998+
[[customizing-authorization-managers]]
1999+
== Customizing Authorization Managers
2000+
2001+
When you use SpEL expressions with <<use-preauthorize,`@PreAuthorize`>>, <<use-postauthorize,`@PostAuthorize`>>, <<use-prefilter,`@PreFilter`>> and <<use-postfilter,`@PostFilter`>>, Spring Security takes care of creating the appropriate `AuthorizationManager` instances for you.
2002+
In certain cases, you may want to customize what is created in order to have complete control over how authorization decisions are made xref:servlet/authorization/architecture.adoc#authz-delegate-authorization-manager[at the framework level].
2003+
2004+
In order to take control of creating instances of `AuthorizationManager` for pre- and post-annotations, you can create a custom xref:servlet/authorization/architecture.adoc#authz-authorization-manager-factory[`AuthorizationManagerFactory`].
2005+
For example, let's say you want to allow users with the `ADMIN` role whenever any other role is required.
2006+
To do this, you can create a custom implementation for method security as in the following example:
2007+
2008+
include-code::./CustomMethodInvocationAuthorizationManagerFactory[tag=class,indent=0]
2009+
2010+
Now, whenever you <<use-preauthorize,use the `@PreAuthorize` annotation>> with `hasRole` or `hasAnyRole`, Spring Security will automatically invoke your custom factory to create an instance of `AuthorizationManager` that allows access for the given role(s) _OR_ the `ADMIN` role.
2011+
2012+
[TIP]
2013+
We use this as a simple example of creating a custom `AuthorizationManagerFactory`, though the same outcome could be accomplished with <<favor-granting-authorities,a role hierarchy>>.
2014+
Use whichever approach fits best in your situation.
2015+
19982016
[[authorize-object]]
19992017
== Authorizing Arbitrary Objects
20002018

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/*
2+
* Copyright 2004-present 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 clients 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.docs.servlet.authorization.authzauthorizationmanagerfactory;
18+
19+
import org.springframework.context.annotation.Bean;
20+
import org.springframework.context.annotation.Configuration;
21+
import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl;
22+
import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
23+
import org.springframework.security.authentication.TestingAuthenticationToken;
24+
import org.springframework.security.authorization.AuthorizationManagerFactory;
25+
import org.springframework.security.authorization.DefaultAuthorizationManagerFactory;
26+
27+
/**
28+
* Documentation for {@link AuthorizationManagerFactory}.
29+
*
30+
* @author Steve Riesenberg
31+
*/
32+
@Configuration(proxyBeanMethods = false)
33+
public class AuthorizationManagerFactoryConfiguration {
34+
35+
// tag::config[]
36+
@Bean
37+
<T> AuthorizationManagerFactory<T> authorizationManagerFactory() {
38+
DefaultAuthorizationManagerFactory<T> authorizationManagerFactory =
39+
new DefaultAuthorizationManagerFactory<>();
40+
authorizationManagerFactory.setTrustResolver(getAuthenticationTrustResolver());
41+
authorizationManagerFactory.setRoleHierarchy(getRoleHierarchy());
42+
authorizationManagerFactory.setRolePrefix("role_");
43+
44+
return authorizationManagerFactory;
45+
}
46+
// end::config[]
47+
48+
private static AuthenticationTrustResolverImpl getAuthenticationTrustResolver() {
49+
AuthenticationTrustResolverImpl authenticationTrustResolver =
50+
new AuthenticationTrustResolverImpl();
51+
authenticationTrustResolver.setAnonymousClass(Anonymous.class);
52+
authenticationTrustResolver.setRememberMeClass(RememberMe.class);
53+
54+
return authenticationTrustResolver;
55+
}
56+
57+
private static RoleHierarchyImpl getRoleHierarchy() {
58+
return RoleHierarchyImpl.fromHierarchy("role_admin > role_user");
59+
}
60+
61+
static class Anonymous extends TestingAuthenticationToken {
62+
63+
Anonymous(String principal) {
64+
super(principal, "", "role_anonymous");
65+
}
66+
67+
}
68+
69+
static class RememberMe extends TestingAuthenticationToken {
70+
71+
RememberMe(String principal) {
72+
super(principal, "", "role_rememberMe");
73+
}
74+
75+
}
76+
77+
}
Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
/*
2+
* Copyright 2004-present 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 clients 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.docs.servlet.authorization.authzauthorizationmanagerfactory;
18+
19+
import org.junit.jupiter.api.Test;
20+
import org.junit.jupiter.api.extension.ExtendWith;
21+
22+
import org.springframework.beans.factory.annotation.Autowired;
23+
import org.springframework.context.annotation.Bean;
24+
import org.springframework.context.annotation.Configuration;
25+
import org.springframework.http.HttpStatus;
26+
import org.springframework.security.access.prepost.PreAuthorize;
27+
import org.springframework.security.authentication.TestingAuthenticationToken;
28+
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
29+
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
30+
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
31+
import org.springframework.security.config.test.SpringTestContext;
32+
import org.springframework.security.config.test.SpringTestContextExtension;
33+
import org.springframework.security.core.Authentication;
34+
import org.springframework.security.docs.servlet.authorization.authzauthorizationmanagerfactory.AuthorizationManagerFactoryConfiguration.Anonymous;
35+
import org.springframework.security.docs.servlet.authorization.authzauthorizationmanagerfactory.AuthorizationManagerFactoryConfiguration.RememberMe;
36+
import org.springframework.security.web.SecurityFilterChain;
37+
import org.springframework.test.web.servlet.MockMvc;
38+
import org.springframework.web.bind.annotation.GetMapping;
39+
import org.springframework.web.bind.annotation.ResponseStatus;
40+
import org.springframework.web.bind.annotation.RestController;
41+
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
42+
43+
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication;
44+
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated;
45+
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
46+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
47+
48+
/**
49+
* Tests for {@link AuthorizationManagerFactoryConfiguration}.
50+
*
51+
* @author Steve Riesenberg
52+
*/
53+
@ExtendWith(SpringTestContextExtension.class)
54+
public class AuthorizationManagerFactoryConfigurationTests {
55+
56+
public final SpringTestContext spring = new SpringTestContext(this);
57+
58+
@Autowired
59+
MockMvc mockMvc;
60+
61+
@Test
62+
void getAnonymousWhenCustomAnonymousClassThenOk() throws Exception {
63+
this.spring.register(AuthorizationManagerFactoryConfiguration.class, SecurityConfiguration.class,
64+
TestController.class).autowire();
65+
Authentication authentication = new Anonymous("anonymous");
66+
// @formatter:off
67+
this.mockMvc.perform(get("/anonymous").with(authentication(authentication)))
68+
.andExpect(status().isOk())
69+
.andExpect(authenticated().withAuthentication(authentication));
70+
// @formatter:on
71+
}
72+
73+
@Test
74+
void getAnonymousWhenAuthenticatedThenForbidden() throws Exception {
75+
this.spring.register(AuthorizationManagerFactoryConfiguration.class, SecurityConfiguration.class,
76+
TestController.class).autowire();
77+
Authentication authentication = new TestingAuthenticationToken("user", "", "role_user");
78+
// @formatter:off
79+
this.mockMvc.perform(get("/anonymous").with(authentication(authentication)))
80+
.andExpect(status().isForbidden())
81+
.andExpect(authenticated().withAuthentication(authentication));
82+
// @formatter:on
83+
}
84+
85+
@Test
86+
void getRememberMeWhenCustomRememberMeClassThenOk() throws Exception {
87+
this.spring.register(AuthorizationManagerFactoryConfiguration.class, SecurityConfiguration.class,
88+
TestController.class).autowire();
89+
Authentication authentication = new RememberMe("rememberMe");
90+
// @formatter:off
91+
this.mockMvc.perform(get("/rememberMe").with(authentication(authentication)))
92+
.andExpect(status().isOk())
93+
.andExpect(authenticated().withAuthentication(authentication));
94+
// @formatter:on
95+
}
96+
97+
@Test
98+
void getRememberMeWhenAuthenticatedThenForbidden() throws Exception {
99+
this.spring.register(AuthorizationManagerFactoryConfiguration.class, SecurityConfiguration.class,
100+
TestController.class).autowire();
101+
Authentication authentication = new TestingAuthenticationToken("user", "", "role_user");
102+
// @formatter:off
103+
this.mockMvc.perform(get("/rememberMe").with(authentication(authentication)))
104+
.andExpect(status().isForbidden())
105+
.andExpect(authenticated().withAuthentication(authentication));
106+
// @formatter:on
107+
}
108+
109+
@Test
110+
void getUserWhenCustomUserRoleThenOk() throws Exception {
111+
this.spring.register(AuthorizationManagerFactoryConfiguration.class, SecurityConfiguration.class,
112+
TestController.class).autowire();
113+
Authentication authentication = new TestingAuthenticationToken("user", "", "role_user");
114+
// @formatter:off
115+
this.mockMvc.perform(get("/user").with(authentication(authentication)))
116+
.andExpect(status().isOk())
117+
.andExpect(authenticated().withAuthentication(authentication));
118+
// @formatter:on
119+
}
120+
121+
@Test
122+
void getUserWhenCustomAdminRoleThenOk() throws Exception {
123+
this.spring.register(AuthorizationManagerFactoryConfiguration.class, SecurityConfiguration.class,
124+
TestController.class).autowire();
125+
Authentication authentication = new TestingAuthenticationToken("admin", "", "role_admin");
126+
// @formatter:off
127+
this.mockMvc.perform(get("/user").with(authentication(authentication)))
128+
.andExpect(status().isOk())
129+
.andExpect(authenticated().withAuthentication(authentication));
130+
// @formatter:on
131+
}
132+
133+
@Test
134+
void getPreAuthorizeWhenCustomUserRoleThenOk() throws Exception {
135+
this.spring.register(AuthorizationManagerFactoryConfiguration.class, SecurityConfiguration.class,
136+
TestController.class).autowire();
137+
Authentication authentication = new TestingAuthenticationToken("user", "", "role_user");
138+
// @formatter:off
139+
this.mockMvc.perform(get("/preAuthorize").with(authentication(authentication)))
140+
.andExpect(status().isOk())
141+
.andExpect(authenticated().withAuthentication(authentication));
142+
// @formatter:on
143+
}
144+
145+
@Test
146+
void getPreAuthorizeWhenCustomAdminRoleThenOk() throws Exception {
147+
this.spring.register(AuthorizationManagerFactoryConfiguration.class, SecurityConfiguration.class,
148+
TestController.class).autowire();
149+
Authentication authentication = new TestingAuthenticationToken("admin", "", "role_admin");
150+
// @formatter:off
151+
this.mockMvc.perform(get("/preAuthorize").with(authentication(authentication)))
152+
.andExpect(status().isOk())
153+
.andExpect(authenticated().withAuthentication(authentication));
154+
// @formatter:on
155+
}
156+
157+
@Test
158+
void getPreAuthorizeWhenOtherRoleThenForbidden() throws Exception {
159+
this.spring.register(AuthorizationManagerFactoryConfiguration.class, SecurityConfiguration.class,
160+
TestController.class).autowire();
161+
Authentication authentication = new TestingAuthenticationToken("other", "", "role_other");
162+
// @formatter:off
163+
this.mockMvc.perform(get("/preAuthorize").with(authentication(authentication)))
164+
.andExpect(status().isForbidden())
165+
.andExpect(authenticated().withAuthentication(authentication));
166+
// @formatter:on
167+
}
168+
169+
@EnableWebMvc
170+
@EnableWebSecurity
171+
@EnableMethodSecurity
172+
@Configuration
173+
static class SecurityConfiguration {
174+
175+
@Bean
176+
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
177+
// @formatter:off
178+
http
179+
.authorizeHttpRequests((authorize) -> authorize
180+
.requestMatchers("/anonymous").anonymous()
181+
.requestMatchers("/rememberMe").rememberMe()
182+
.requestMatchers("/user").hasRole("user")
183+
.requestMatchers("/preAuthorize").permitAll()
184+
.anyRequest().denyAll()
185+
);
186+
// @formatter:on
187+
return http.build();
188+
}
189+
190+
}
191+
192+
@RestController
193+
static class TestController {
194+
195+
@GetMapping({ "/anonymous", "/rememberMe", "/user" })
196+
@ResponseStatus(HttpStatus.OK)
197+
void httpRequest() {
198+
}
199+
200+
@GetMapping("/preAuthorize")
201+
@ResponseStatus(HttpStatus.OK)
202+
@PreAuthorize("hasRole('user')")
203+
void preAuthorize() {
204+
}
205+
206+
}
207+
208+
}

0 commit comments

Comments
 (0)