1
0
mirror of synced 2026-08-04 01:07:02 +00:00

Add AuthorizationManagerFactory

Signed-off-by: Steve Riesenberg <5248162+sjohnr@users.noreply.github.com>
This commit is contained in:
Steve Riesenberg
2025-09-02 12:47:53 -05:00
committed by Rob Winch
parent a4f813ab29
commit eeb4574bb3
37 changed files with 2719 additions and 178 deletions
@@ -144,6 +144,43 @@ Another manager is the `AuthenticatedAuthorizationManager`.
It can be used to differentiate between anonymous, fully-authenticated and remember-me authenticated users.
Many sites allow certain limited access under remember-me authentication, but require a user to confirm their identity by logging in for full access.
[[authz-authorization-manager-factory]]
=== Creating AuthorizationManager instances
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.
The following is a sketch of the `AuthorizationManagerFactory` interface:
[source,java]
----
public interface AuthorizationManagerFactory<T> {
AuthorizationManager<T> permitAll();
AuthorizationManager<T> denyAll();
AuthorizationManager<T> hasRole(String role);
AuthorizationManager<T> hasAnyRole(String... roles);
AuthorizationManager<T> hasAuthority(String authority);
AuthorizationManager<T> hasAnyAuthority(String... authorities);
AuthorizationManager<T> authenticated();
AuthorizationManager<T> fullyAuthenticated();
AuthorizationManager<T> rememberMe();
AuthorizationManager<T> anonymous();
}
----
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.
In order to customize the default instance used by Spring Security, simply publish a bean as in the following example:
include-code::./AuthorizationManagerFactoryConfiguration[tag=config,indent=0]
[TIP]
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.
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.
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.
[NOTE]
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.
[[authz-authorization-managers]]
==== AuthorizationManagers
There are also helpful static factories in javadoc:org.springframework.security.authorization.AuthorizationManagers[] for composing individual ``AuthorizationManager``s into more sophisticated expressions.
@@ -57,6 +57,7 @@ In many cases, your authorization rules will be more sophisticated than that, so
* I want to <<match-by-custom, match a request programmatically>>
* I want to <<authorize-requests, authorize a request programmatically>>
* I want to <<remote-authorization-manager, delegate request authorization>> to a policy agent
* I want to <<customizing-authorization-managers,customize how authorization managers are created>>
[[request-authorization-architecture]]
== 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
<6> Any URL that has not already been matched on is denied access.
This is a good strategy if you do not want to accidentally forget to update your authorization rules.
[[customizing-authorization-managers]]
== Customizing Authorization Managers
When you use the `authorizeHttpRequests` DSL, Spring Security takes care of creating the appropriate `AuthorizationManager` instances for you.
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].
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`].
For example, let's say you want to create a convention that authenticated users must be authenticated _AND_ have the `USER` role.
To do this, you can create a custom implementation for HTTP requests as in the following example:
include-code::./CustomHttpRequestsAuthorizationManagerFactory[tag=class,indent=0]
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.
[TIP]
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.
See <<remote-authorization-manager>> for an example.
[[authorization-expressions]]
== Expressing Authorization with SpEL
@@ -1995,6 +1995,24 @@ This works on both classes and interfaces.
This does not work for interfaces, since they do not have debug information about the parameter names.
For interfaces, either annotations or the `-parameters` approach must be used.
[[customizing-authorization-managers]]
== Customizing Authorization Managers
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.
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].
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`].
For example, let's say you want to allow users with the `ADMIN` role whenever any other role is required.
To do this, you can create a custom implementation for method security as in the following example:
include-code::./CustomMethodInvocationAuthorizationManagerFactory[tag=class,indent=0]
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.
[TIP]
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>>.
Use whichever approach fits best in your situation.
[[authorize-object]]
== Authorizing Arbitrary Objects
+1
View File
@@ -12,6 +12,7 @@ Each section that follows will indicate the more notable removals as well as the
== Core
* Removed `AuthorizationManager#check` in favor of `AuthorizationManager#authorize`
* Added xref:servlet/authorization/architecture.adoc#authz-authorization-manager-factory[`AuthorizationManagerFactory`] for creating `AuthorizationManager` instances in xref:servlet/authorization/authorize-http-requests.adoc#customizing-authorization-managers[request-based] and xref:servlet/authorization/method-security.adoc#customizing-authorization-managers[method-based] authorization components
== Config
@@ -0,0 +1,77 @@
/*
* Copyright 2004-present 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 clients 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.docs.servlet.authorization.authzauthorizationmanagerfactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl;
import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.authorization.AuthorizationManagerFactory;
import org.springframework.security.authorization.DefaultAuthorizationManagerFactory;
/**
* Documentation for {@link AuthorizationManagerFactory}.
*
* @author Steve Riesenberg
*/
@Configuration(proxyBeanMethods = false)
public class AuthorizationManagerFactoryConfiguration {
// tag::config[]
@Bean
<T> AuthorizationManagerFactory<T> authorizationManagerFactory() {
DefaultAuthorizationManagerFactory<T> authorizationManagerFactory =
new DefaultAuthorizationManagerFactory<>();
authorizationManagerFactory.setTrustResolver(getAuthenticationTrustResolver());
authorizationManagerFactory.setRoleHierarchy(getRoleHierarchy());
authorizationManagerFactory.setRolePrefix("role_");
return authorizationManagerFactory;
}
// end::config[]
private static AuthenticationTrustResolverImpl getAuthenticationTrustResolver() {
AuthenticationTrustResolverImpl authenticationTrustResolver =
new AuthenticationTrustResolverImpl();
authenticationTrustResolver.setAnonymousClass(Anonymous.class);
authenticationTrustResolver.setRememberMeClass(RememberMe.class);
return authenticationTrustResolver;
}
private static RoleHierarchyImpl getRoleHierarchy() {
return RoleHierarchyImpl.fromHierarchy("role_admin > role_user");
}
static class Anonymous extends TestingAuthenticationToken {
Anonymous(String principal) {
super(principal, "", "role_anonymous");
}
}
static class RememberMe extends TestingAuthenticationToken {
RememberMe(String principal) {
super(principal, "", "role_rememberMe");
}
}
}
@@ -0,0 +1,208 @@
/*
* Copyright 2004-present 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 clients 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.docs.servlet.authorization.authzauthorizationmanagerfactory;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.core.Authentication;
import org.springframework.security.docs.servlet.authorization.authzauthorizationmanagerfactory.AuthorizationManagerFactoryConfiguration.Anonymous;
import org.springframework.security.docs.servlet.authorization.authzauthorizationmanagerfactory.AuthorizationManagerFactoryConfiguration.RememberMe;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication;
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* Tests for {@link AuthorizationManagerFactoryConfiguration}.
*
* @author Steve Riesenberg
*/
@ExtendWith(SpringTestContextExtension.class)
public class AuthorizationManagerFactoryConfigurationTests {
public final SpringTestContext spring = new SpringTestContext(this);
@Autowired
MockMvc mockMvc;
@Test
void getAnonymousWhenCustomAnonymousClassThenOk() throws Exception {
this.spring.register(AuthorizationManagerFactoryConfiguration.class, SecurityConfiguration.class,
TestController.class).autowire();
Authentication authentication = new Anonymous("anonymous");
// @formatter:off
this.mockMvc.perform(get("/anonymous").with(authentication(authentication)))
.andExpect(status().isOk())
.andExpect(authenticated().withAuthentication(authentication));
// @formatter:on
}
@Test
void getAnonymousWhenAuthenticatedThenForbidden() throws Exception {
this.spring.register(AuthorizationManagerFactoryConfiguration.class, SecurityConfiguration.class,
TestController.class).autowire();
Authentication authentication = new TestingAuthenticationToken("user", "", "role_user");
// @formatter:off
this.mockMvc.perform(get("/anonymous").with(authentication(authentication)))
.andExpect(status().isForbidden())
.andExpect(authenticated().withAuthentication(authentication));
// @formatter:on
}
@Test
void getRememberMeWhenCustomRememberMeClassThenOk() throws Exception {
this.spring.register(AuthorizationManagerFactoryConfiguration.class, SecurityConfiguration.class,
TestController.class).autowire();
Authentication authentication = new RememberMe("rememberMe");
// @formatter:off
this.mockMvc.perform(get("/rememberMe").with(authentication(authentication)))
.andExpect(status().isOk())
.andExpect(authenticated().withAuthentication(authentication));
// @formatter:on
}
@Test
void getRememberMeWhenAuthenticatedThenForbidden() throws Exception {
this.spring.register(AuthorizationManagerFactoryConfiguration.class, SecurityConfiguration.class,
TestController.class).autowire();
Authentication authentication = new TestingAuthenticationToken("user", "", "role_user");
// @formatter:off
this.mockMvc.perform(get("/rememberMe").with(authentication(authentication)))
.andExpect(status().isForbidden())
.andExpect(authenticated().withAuthentication(authentication));
// @formatter:on
}
@Test
void getUserWhenCustomUserRoleThenOk() throws Exception {
this.spring.register(AuthorizationManagerFactoryConfiguration.class, SecurityConfiguration.class,
TestController.class).autowire();
Authentication authentication = new TestingAuthenticationToken("user", "", "role_user");
// @formatter:off
this.mockMvc.perform(get("/user").with(authentication(authentication)))
.andExpect(status().isOk())
.andExpect(authenticated().withAuthentication(authentication));
// @formatter:on
}
@Test
void getUserWhenCustomAdminRoleThenOk() throws Exception {
this.spring.register(AuthorizationManagerFactoryConfiguration.class, SecurityConfiguration.class,
TestController.class).autowire();
Authentication authentication = new TestingAuthenticationToken("admin", "", "role_admin");
// @formatter:off
this.mockMvc.perform(get("/user").with(authentication(authentication)))
.andExpect(status().isOk())
.andExpect(authenticated().withAuthentication(authentication));
// @formatter:on
}
@Test
void getPreAuthorizeWhenCustomUserRoleThenOk() throws Exception {
this.spring.register(AuthorizationManagerFactoryConfiguration.class, SecurityConfiguration.class,
TestController.class).autowire();
Authentication authentication = new TestingAuthenticationToken("user", "", "role_user");
// @formatter:off
this.mockMvc.perform(get("/preAuthorize").with(authentication(authentication)))
.andExpect(status().isOk())
.andExpect(authenticated().withAuthentication(authentication));
// @formatter:on
}
@Test
void getPreAuthorizeWhenCustomAdminRoleThenOk() throws Exception {
this.spring.register(AuthorizationManagerFactoryConfiguration.class, SecurityConfiguration.class,
TestController.class).autowire();
Authentication authentication = new TestingAuthenticationToken("admin", "", "role_admin");
// @formatter:off
this.mockMvc.perform(get("/preAuthorize").with(authentication(authentication)))
.andExpect(status().isOk())
.andExpect(authenticated().withAuthentication(authentication));
// @formatter:on
}
@Test
void getPreAuthorizeWhenOtherRoleThenForbidden() throws Exception {
this.spring.register(AuthorizationManagerFactoryConfiguration.class, SecurityConfiguration.class,
TestController.class).autowire();
Authentication authentication = new TestingAuthenticationToken("other", "", "role_other");
// @formatter:off
this.mockMvc.perform(get("/preAuthorize").with(authentication(authentication)))
.andExpect(status().isForbidden())
.andExpect(authenticated().withAuthentication(authentication));
// @formatter:on
}
@EnableWebMvc
@EnableWebSecurity
@EnableMethodSecurity
@Configuration
static class SecurityConfiguration {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers("/anonymous").anonymous()
.requestMatchers("/rememberMe").rememberMe()
.requestMatchers("/user").hasRole("user")
.requestMatchers("/preAuthorize").permitAll()
.anyRequest().denyAll()
);
// @formatter:on
return http.build();
}
}
@RestController
static class TestController {
@GetMapping({ "/anonymous", "/rememberMe", "/user" })
@ResponseStatus(HttpStatus.OK)
void httpRequest() {
}
@GetMapping("/preAuthorize")
@ResponseStatus(HttpStatus.OK)
@PreAuthorize("hasRole('user')")
void preAuthorize() {
}
}
}
@@ -0,0 +1,48 @@
/*
* Copyright 2004-present 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 clients 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.docs.servlet.authorization.customizingauthorizationmanagers;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.authorization.AuthorizationManagerFactory;
import org.springframework.security.authorization.AuthorizationManagers;
import org.springframework.security.authorization.DefaultAuthorizationManagerFactory;
import org.springframework.security.web.access.intercept.RequestAuthorizationContext;
import org.springframework.stereotype.Component;
/**
* Documentation for {@link AuthorizationManagerFactory}.
*
* @author Steve Riesenberg
*/
// tag::class[]
@Component
public class CustomHttpRequestsAuthorizationManagerFactory
implements AuthorizationManagerFactory<RequestAuthorizationContext> {
private final AuthorizationManagerFactory<RequestAuthorizationContext> delegate =
new DefaultAuthorizationManagerFactory<>();
@Override
public AuthorizationManager<RequestAuthorizationContext> authenticated() {
return AuthorizationManagers.allOf(
this.delegate.authenticated(),
this.delegate.hasRole("USER")
);
}
}
// end::class[]
@@ -0,0 +1,137 @@
/*
* Copyright 2004-present 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 clients 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.docs.servlet.authorization.customizingauthorizationmanagers;
import java.util.Collections;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.anonymous;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication;
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated;
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.unauthenticated;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* Tests for {@link CustomHttpRequestsAuthorizationManagerFactory}.
*
* @author Steve Riesenberg
*/
@ExtendWith(SpringTestContextExtension.class)
public class CustomHttpRequestsAuthorizationManagerFactoryTests {
public final SpringTestContext spring = new SpringTestContext(this);
@Autowired
MockMvc mockMvc;
@Test
void getHelloWhenAnonymousThenForbidden() throws Exception {
this.spring.register(SecurityConfiguration.class, TestController.class).autowire();
// @formatter:off
this.mockMvc.perform(get("/hello").with(anonymous()))
.andExpect(status().isForbidden())
.andExpect(unauthenticated());
// @formatter:on
}
@Test
void getHelloWhenAuthenticatedWithNoRolesThenForbidden() throws Exception {
this.spring.register(SecurityConfiguration.class, TestController.class).autowire();
Authentication authentication = new TestingAuthenticationToken("user", "", Collections.emptyList());
// @formatter:off
this.mockMvc.perform(get("/hello").with(authentication(authentication)))
.andExpect(status().isForbidden())
.andExpect(authenticated().withAuthentication(authentication));
// @formatter:on
}
@Test
void getHelloWhenAuthenticatedWithUserRoleThenOk() throws Exception {
this.spring.register(SecurityConfiguration.class, TestController.class).autowire();
Authentication authentication = new TestingAuthenticationToken("user", "", "ROLE_USER");
// @formatter:off
this.mockMvc.perform(get("/hello").with(authentication(authentication)))
.andExpect(status().isOk())
.andExpect(authenticated().withAuthentication(authentication));
// @formatter:on
}
@Test
void getHelloWhenAuthenticatedWithOtherRoleThenForbidden() throws Exception {
this.spring.register(SecurityConfiguration.class, TestController.class).autowire();
Authentication authentication = new TestingAuthenticationToken("user", "", "ROLE_OTHER");
// @formatter:off
this.mockMvc.perform(get("/hello").with(authentication(authentication)))
.andExpect(status().isForbidden())
.andExpect(authenticated().withAuthentication(authentication));
// @formatter:on
}
@EnableWebMvc
@EnableWebSecurity
@Configuration
static class SecurityConfiguration {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().authenticated()
);
// @formatter:on
return http.build();
}
@Bean
CustomHttpRequestsAuthorizationManagerFactory customHttpRequestsAuthorizationManagerFactory() {
return new CustomHttpRequestsAuthorizationManagerFactory();
}
}
@RestController
static class TestController {
@GetMapping("/**")
@ResponseStatus(HttpStatus.OK)
void ok() {
}
}
}
@@ -0,0 +1,57 @@
/*
* Copyright 2004-present 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 clients 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.docs.servlet.authorization.customizingauthorizationmanagers;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.authorization.AuthorizationManagerFactory;
import org.springframework.security.authorization.AuthorizationManagers;
import org.springframework.security.authorization.DefaultAuthorizationManagerFactory;
import org.springframework.stereotype.Component;
/**
* Documentation for {@link AuthorizationManagerFactory}.
*
* @author Steve Riesenberg
*/
// tag::class[]
@Component
public class CustomMethodInvocationAuthorizationManagerFactory
implements AuthorizationManagerFactory<MethodInvocation> {
private final AuthorizationManagerFactory<MethodInvocation> delegate =
new DefaultAuthorizationManagerFactory<>();
@Override
public AuthorizationManager<MethodInvocation> hasRole(String role) {
return AuthorizationManagers.anyOf(
this.delegate.hasRole(role),
this.delegate.hasRole("ADMIN")
);
}
@Override
public AuthorizationManager<MethodInvocation> hasAnyRole(String... roles) {
return AuthorizationManagers.anyOf(
this.delegate.hasAnyRole(roles),
this.delegate.hasRole("ADMIN")
);
}
}
// end::class[]
@@ -0,0 +1,191 @@
/*
* Copyright 2004-present 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 clients 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.docs.servlet.authorization.customizingauthorizationmanagers;
import java.util.Collections;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.anonymous;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication;
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated;
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.unauthenticated;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* Tests for {@link CustomMethodInvocationAuthorizationManagerFactory}.
*
* @author Steve Riesenberg
*/
@ExtendWith(SpringTestContextExtension.class)
public class CustomMethodInvocationAuthorizationManagerFactoryTests {
public final SpringTestContext spring = new SpringTestContext(this);
@Autowired
MockMvc mockMvc;
@Test
void getUserWhenAnonymousThenForbidden() throws Exception {
this.spring.register(SecurityConfiguration.class, TestController.class).autowire();
// @formatter:off
this.mockMvc.perform(get("/user").with(anonymous()))
.andExpect(status().isForbidden())
.andExpect(unauthenticated());
// @formatter:on
}
@Test
void getUserWhenAuthenticatedWithNoRolesThenForbidden() throws Exception {
this.spring.register(SecurityConfiguration.class, TestController.class).autowire();
Authentication authentication = new TestingAuthenticationToken("user", "", Collections.emptyList());
// @formatter:off
this.mockMvc.perform(get("/user").with(authentication(authentication)))
.andExpect(status().isForbidden())
.andExpect(authenticated().withAuthentication(authentication));
// @formatter:on
}
@Test
void getUserWhenAuthenticatedWithUserRoleThenOk() throws Exception {
this.spring.register(SecurityConfiguration.class, TestController.class).autowire();
Authentication authentication = new TestingAuthenticationToken("user", "", "ROLE_USER");
// @formatter:off
this.mockMvc.perform(get("/user").with(authentication(authentication)))
.andExpect(status().isOk())
.andExpect(authenticated().withAuthentication(authentication));
// @formatter:on
}
@Test
void getUserWhenAuthenticatedWithAdminRoleThenOk() throws Exception {
this.spring.register(SecurityConfiguration.class, TestController.class).autowire();
Authentication authentication = new TestingAuthenticationToken("admin", "", "ROLE_ADMIN");
// @formatter:off
this.mockMvc.perform(get("/user").with(authentication(authentication)))
.andExpect(status().isOk())
.andExpect(authenticated().withAuthentication(authentication));
// @formatter:on
}
@Test
void getUserWhenAuthenticatedWithOtherRoleThenForbidden() throws Exception {
this.spring.register(SecurityConfiguration.class, TestController.class).autowire();
Authentication authentication = new TestingAuthenticationToken("user", "", "ROLE_OTHER");
// @formatter:off
this.mockMvc.perform(get("/user").with(authentication(authentication)))
.andExpect(status().isForbidden())
.andExpect(authenticated().withAuthentication(authentication));
// @formatter:on
}
@Test
void getRolesWhenAuthenticatedWithRole1RoleThenOk() throws Exception {
this.spring.register(SecurityConfiguration.class, TestController.class).autowire();
Authentication authentication = new TestingAuthenticationToken("user", "", "ROLE_ROLE1");
// @formatter:off
this.mockMvc.perform(get("/roles").with(authentication(authentication)))
.andExpect(status().isOk())
.andExpect(authenticated().withAuthentication(authentication));
// @formatter:on
}
@Test
void getRolesWhenAuthenticatedWithAdminRoleThenOk() throws Exception {
this.spring.register(SecurityConfiguration.class, TestController.class).autowire();
Authentication authentication = new TestingAuthenticationToken("admin", "", "ROLE_ADMIN");
// @formatter:off
this.mockMvc.perform(get("/roles").with(authentication(authentication)))
.andExpect(status().isOk())
.andExpect(authenticated().withAuthentication(authentication));
// @formatter:on
}
@Test
void getRolesWhenAuthenticatedWithOtherRoleThenForbidden() throws Exception {
this.spring.register(SecurityConfiguration.class, TestController.class).autowire();
Authentication authentication = new TestingAuthenticationToken("user", "", "ROLE_OTHER");
// @formatter:off
this.mockMvc.perform(get("/roles").with(authentication(authentication)))
.andExpect(status().isForbidden())
.andExpect(authenticated().withAuthentication(authentication));
// @formatter:on
}
@EnableWebMvc
@EnableWebSecurity
@EnableMethodSecurity
@Configuration
static class SecurityConfiguration {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().authenticated()
);
// @formatter:on
return http.build();
}
@Bean
CustomMethodInvocationAuthorizationManagerFactory customMethodInvocationAuthorizationManagerFactory() {
return new CustomMethodInvocationAuthorizationManagerFactory();
}
}
@RestController
static class TestController {
@GetMapping("/user")
@ResponseStatus(HttpStatus.OK)
@PreAuthorize("hasRole('USER')")
void user() {
}
@GetMapping("/roles")
@ResponseStatus(HttpStatus.OK)
@PreAuthorize("hasAnyRole('ROLE1', 'ROLE2')")
void roles() {
}
}
}
@@ -0,0 +1,62 @@
/*
* Copyright 2004-present 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.kt.docs.servlet.authorization.authzauthorizationmanagerfactory
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl
import org.springframework.security.authentication.AuthenticationTrustResolverImpl
import org.springframework.security.authentication.TestingAuthenticationToken
import org.springframework.security.authorization.AuthorizationManagerFactory
import org.springframework.security.authorization.DefaultAuthorizationManagerFactory
/**
* Documentation for [org.springframework.security.authorization.AuthorizationManagerFactory].
*
* @author Steve Riesenberg
*/
@Configuration(proxyBeanMethods = false)
class AuthorizationManagerFactoryConfiguration {
// tag::config[]
@Bean
fun <T> authorizationManagerFactory(): AuthorizationManagerFactory<T> {
val authorizationManagerFactory = DefaultAuthorizationManagerFactory<T>()
authorizationManagerFactory.setTrustResolver(getAuthenticationTrustResolver())
authorizationManagerFactory.setRoleHierarchy(getRoleHierarchy())
authorizationManagerFactory.setRolePrefix("role_")
return authorizationManagerFactory
}
// end::config[]
private fun getAuthenticationTrustResolver(): AuthenticationTrustResolverImpl {
val authenticationTrustResolver = AuthenticationTrustResolverImpl()
authenticationTrustResolver.setAnonymousClass(Anonymous::class.java)
authenticationTrustResolver.setRememberMeClass(RememberMe::class.java)
return authenticationTrustResolver
}
private fun getRoleHierarchy(): RoleHierarchyImpl {
return RoleHierarchyImpl.fromHierarchy("role_admin > role_user")
}
internal class Anonymous(principal: String) :
TestingAuthenticationToken(principal, "", "role_anonymous")
internal class RememberMe(principal: String) :
TestingAuthenticationToken(principal, "", "role_rememberMe")
}
@@ -0,0 +1,229 @@
/*
* Copyright 2004-present 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.kt.docs.servlet.authorization.authzauthorizationmanagerfactory
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.http.HttpStatus
import org.springframework.security.access.prepost.PreAuthorize
import org.springframework.security.authentication.TestingAuthenticationToken
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.test.SpringTestContext
import org.springframework.security.config.test.SpringTestContextExtension
import org.springframework.security.kt.docs.servlet.authorization.authzauthorizationmanagerfactory.AuthorizationManagerFactoryConfiguration.Anonymous
import org.springframework.security.kt.docs.servlet.authorization.authzauthorizationmanagerfactory.AuthorizationManagerFactoryConfiguration.RememberMe
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication
import org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated
import org.springframework.security.web.SecurityFilterChain
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.ResponseStatus
import org.springframework.web.bind.annotation.RestController
import org.springframework.web.servlet.config.annotation.EnableWebMvc
/**
* Tests for [AuthorizationManagerFactoryConfiguration].
*
* @author Steve Riesenberg
*/
@ExtendWith(SpringTestContextExtension::class)
class AuthorizationManagerFactoryConfigurationTests {
@JvmField
val spring = SpringTestContext(this)
@Autowired
lateinit var mockMvc: MockMvc
@Test
@Throws(Exception::class)
fun getAnonymousWhenCustomAnonymousClassThenOk() {
this.spring.register(AuthorizationManagerFactoryConfiguration::class.java, SecurityConfiguration::class.java)
.autowire()
val authentication = Anonymous("anonymous")
// @formatter:off
mockMvc.perform(get("/anonymous").with(authentication(authentication)))
.andExpect(status().isOk())
.andExpect(authenticated().withAuthentication(authentication))
// @formatter:on
}
@Test
@Throws(Exception::class)
fun getAnonymousWhenAuthenticatedThenForbidden() {
this.spring.register(AuthorizationManagerFactoryConfiguration::class.java, SecurityConfiguration::class.java)
.autowire()
val authentication = TestingAuthenticationToken("user", "", "role_user")
// @formatter:off
mockMvc.perform(get("/anonymous").with(authentication(authentication)))
.andExpect(status().isForbidden())
.andExpect(authenticated().withAuthentication(authentication))
// @formatter:on
}
@Test
@Throws(Exception::class)
fun getRememberMeWhenCustomRememberMeClassThenOk() {
this.spring.register(AuthorizationManagerFactoryConfiguration::class.java, SecurityConfiguration::class.java)
.autowire()
val authentication = RememberMe("rememberMe")
// @formatter:off
mockMvc.perform(get("/rememberMe").with(authentication(authentication)))
.andExpect(status().isOk())
.andExpect(authenticated().withAuthentication(authentication))
// @formatter:on
}
@Test
@Throws(Exception::class)
fun getRememberMeWhenAuthenticatedThenForbidden() {
this.spring.register(AuthorizationManagerFactoryConfiguration::class.java, SecurityConfiguration::class.java)
.autowire()
val user = TestingAuthenticationToken("user", "", "role_user")
// @formatter:off
mockMvc.perform(get("/rememberMe").with(authentication(user)))
.andExpect(status().isForbidden())
.andExpect(authenticated().withAuthentication(user))
// @formatter:on
}
@Test
@Throws(Exception::class)
fun getUserWhenCustomUserRoleThenOk() {
this.spring.register(AuthorizationManagerFactoryConfiguration::class.java, SecurityConfiguration::class.java)
.autowire()
val authentication = TestingAuthenticationToken("user", "", "role_user")
// @formatter:off
mockMvc.perform(get("/user").with(authentication(authentication)))
.andExpect(status().isOk())
.andExpect(authenticated().withAuthentication(authentication))
// @formatter:on
}
@Test
@Throws(Exception::class)
fun getUserWhenCustomAdminRoleThenOk() {
this.spring.register(AuthorizationManagerFactoryConfiguration::class.java, SecurityConfiguration::class.java)
.autowire()
val admin = TestingAuthenticationToken("admin", "", "role_admin")
// @formatter:off
mockMvc.perform(get("/user").with(authentication(admin)))
.andExpect(status().isOk())
.andExpect(authenticated().withAuthentication(admin))
// @formatter:on
}
@Test
@Throws(Exception::class)
fun getPreAuthorizeWhenCustomUserRoleThenOk() {
this.spring.register(AuthorizationManagerFactoryConfiguration::class.java, SecurityConfiguration::class.java)
.autowire()
val authentication = TestingAuthenticationToken("user", "", "role_user")
// @formatter:off
mockMvc.perform(get("/preAuthorize").with(authentication(authentication)))
.andExpect(status().isOk())
.andExpect(authenticated().withAuthentication(authentication))
// @formatter:on
}
@Test
@Throws(Exception::class)
fun getPreAuthorizeWhenCustomAdminRoleThenOk() {
this.spring.register(AuthorizationManagerFactoryConfiguration::class.java, SecurityConfiguration::class.java)
.autowire()
val authentication = TestingAuthenticationToken("admin", "", "role_admin")
// @formatter:off
mockMvc.perform(get("/preAuthorize").with(authentication(authentication)))
.andExpect(status().isOk())
.andExpect(authenticated().withAuthentication(authentication))
// @formatter:on
}
@Test
@Throws(Exception::class)
fun getPreAuthorizeWhenOtherRoleThenForbidden() {
this.spring.register(AuthorizationManagerFactoryConfiguration::class.java, SecurityConfiguration::class.java)
.autowire()
val authentication = TestingAuthenticationToken("other", "", "role_other")
// @formatter:off
mockMvc.perform(get("/preAuthorize").with(authentication(authentication)))
.andExpect(status().isForbidden())
.andExpect(authenticated().withAuthentication(authentication))
// @formatter:on
}
@EnableWebMvc
@EnableWebSecurity
@EnableMethodSecurity
@Configuration
internal open class SecurityConfiguration {
@Bean
@Throws(Exception::class)
open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
// @formatter:off
http.authorizeHttpRequests { authorize ->
authorize
.requestMatchers("/anonymous").anonymous()
.requestMatchers("/rememberMe").rememberMe()
.requestMatchers("/user").hasRole("user")
.requestMatchers("/preAuthorize").permitAll()
.anyRequest().denyAll()
}
// @formatter:on
return http.build()
}
@Bean
open fun testController(testService: TestService): TestController {
return TestController(testService())
}
@Bean
open fun testService(): TestService {
return TestServiceImpl()
}
}
@RestController
internal open class TestController(private val testService: TestService) {
@GetMapping(value = ["/anonymous", "/rememberMe", "/user"])
@ResponseStatus(HttpStatus.OK)
fun httpRequest() {
}
@GetMapping("/preAuthorize")
@ResponseStatus(HttpStatus.OK)
fun preAuthorize() {
testService.preAuthorize()
}
}
internal interface TestService {
@PreAuthorize("hasRole('user')")
fun preAuthorize()
}
internal open class TestServiceImpl : TestService {
override fun preAuthorize() {
}
}
}
@@ -0,0 +1,44 @@
/*
* Copyright 2004-present 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.kt.docs.servlet.authorization.customizingauthorizationmanagers
import org.springframework.security.authorization.AuthorizationManager
import org.springframework.security.authorization.AuthorizationManagerFactory
import org.springframework.security.authorization.AuthorizationManagers
import org.springframework.security.authorization.DefaultAuthorizationManagerFactory
import org.springframework.security.web.access.intercept.RequestAuthorizationContext
import org.springframework.stereotype.Component
/**
* Documentation for {@link AuthorizationManagerFactory}.
*
* @author Steve Riesenberg
*/
// tag::class[]
@Component
class CustomHttpRequestsAuthorizationManagerFactory : AuthorizationManagerFactory<RequestAuthorizationContext> {
private val delegate = DefaultAuthorizationManagerFactory<RequestAuthorizationContext>()
override fun authenticated(): AuthorizationManager<RequestAuthorizationContext> {
return AuthorizationManagers.allOf(
delegate.authenticated(),
delegate.hasRole("USER")
)
}
}
// end::class[]
@@ -0,0 +1,131 @@
/*
* Copyright 2004-present 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.kt.docs.servlet.authorization.customizingauthorizationmanagers
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.http.HttpStatus
import org.springframework.security.authentication.TestingAuthenticationToken
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.test.SpringTestContext
import org.springframework.security.config.test.SpringTestContextExtension
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.anonymous
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication
import org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers
import org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated
import org.springframework.security.web.SecurityFilterChain
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.ResponseStatus
import org.springframework.web.bind.annotation.RestController
import org.springframework.web.servlet.config.annotation.EnableWebMvc
/**
* Tests for [CustomHttpRequestsAuthorizationManagerFactory].
*
* @author Steve Riesenberg
*/
@ExtendWith(SpringTestContextExtension::class)
class CustomHttpRequestsAuthorizationManagerFactoryTests {
@JvmField
val spring = SpringTestContext(this)
@Autowired
lateinit var mockMvc: MockMvc
@Test
@Throws(Exception::class)
fun getHelloWhenAnonymousThenForbidden() {
spring.register(SecurityConfiguration::class.java, TestController::class.java).autowire()
// @formatter:off
mockMvc.perform(get("/hello").with(anonymous()))
.andExpect(status().isForbidden())
.andExpect(SecurityMockMvcResultMatchers.unauthenticated())
// @formatter:on
}
@Test
@Throws(Exception::class)
fun getHelloWhenAuthenticatedWithUserRoleThenOk() {
spring.register(SecurityConfiguration::class.java, TestController::class.java).autowire()
val authentication = TestingAuthenticationToken("user", "", "ROLE_USER")
// @formatter:off
mockMvc.perform(get("/hello").with(authentication(authentication)))
.andExpect(status().isOk())
.andExpect(authenticated().withAuthentication(authentication))
// @formatter:on
}
@Test
@Throws(Exception::class)
fun getHelloWhenAuthenticatedWithOtherRoleThenForbidden() {
spring.register(SecurityConfiguration::class.java, TestController::class.java).autowire()
val authentication = TestingAuthenticationToken("user", "", "ROLE_OTHER")
// @formatter:off
mockMvc.perform(get("/hello").with(authentication(authentication)))
.andExpect(status().isForbidden())
.andExpect(authenticated().withAuthentication(authentication))
// @formatter:on
}
@Test
@Throws(Exception::class)
fun getHelloWhenAuthenticatedWithNoRolesThenForbidden() {
spring.register(SecurityConfiguration::class.java, TestController::class.java).autowire()
val authentication = TestingAuthenticationToken("user", "", listOf())
// @formatter:off
mockMvc.perform(get("/hello").with(authentication(authentication)))
.andExpect(status().isForbidden())
.andExpect(authenticated().withAuthentication(authentication))
// @formatter:on
}
@EnableWebMvc
@EnableWebSecurity
@Configuration
internal open class SecurityConfiguration {
@Bean
@Throws(Exception::class)
open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
// @formatter:off
http
.authorizeHttpRequests { authorize ->
authorize.anyRequest().authenticated()
}
// @formatter:on
return http.build()
}
@Bean
open fun customHttpRequestsAuthorizationManagerFactory(): CustomHttpRequestsAuthorizationManagerFactory {
return CustomHttpRequestsAuthorizationManagerFactory()
}
}
@RestController
internal class TestController {
@GetMapping("/**")
@ResponseStatus(HttpStatus.OK)
fun ok() {
}
}
}
@@ -0,0 +1,50 @@
/*
* Copyright 2004-present 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.kt.docs.servlet.authorization.customizingauthorizationmanagers
import org.aopalliance.intercept.MethodInvocation
import org.springframework.security.authorization.AuthorizationManager
import org.springframework.security.authorization.AuthorizationManagerFactory
import org.springframework.security.authorization.AuthorizationManagers
import org.springframework.security.authorization.DefaultAuthorizationManagerFactory
import org.springframework.stereotype.Component
/**
* Documentation for [AuthorizationManagerFactory].
*
* @author Steve Riesenberg
*/
// tag::class[]
@Component
class CustomMethodInvocationAuthorizationManagerFactory : AuthorizationManagerFactory<MethodInvocation> {
private val delegate = DefaultAuthorizationManagerFactory<MethodInvocation>()
override fun hasRole(role: String): AuthorizationManager<MethodInvocation> {
return AuthorizationManagers.anyOf(
delegate.hasRole(role),
delegate.hasRole("ADMIN")
)
}
override fun hasAnyRole(vararg roles: String): AuthorizationManager<MethodInvocation> {
return AuthorizationManagers.anyOf(
delegate.hasAnyRole(*roles),
delegate.hasRole("ADMIN")
)
}
}
// end::class[]
@@ -0,0 +1,215 @@
/*
* Copyright 2004-present 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.kt.docs.servlet.authorization.customizingauthorizationmanagers
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.http.HttpStatus
import org.springframework.security.access.prepost.PreAuthorize
import org.springframework.security.authentication.TestingAuthenticationToken
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.test.SpringTestContext
import org.springframework.security.config.test.SpringTestContextExtension
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.anonymous
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication
import org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated
import org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.unauthenticated
import org.springframework.security.web.SecurityFilterChain
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.ResponseStatus
import org.springframework.web.bind.annotation.RestController
import org.springframework.web.servlet.config.annotation.EnableWebMvc
/**
* Tests for [CustomMethodInvocationAuthorizationManagerFactory].
*
* @author Steve Riesenberg
*/
@ExtendWith(SpringTestContextExtension::class)
class CustomMethodInvocationAuthorizationManagerFactoryTests {
@JvmField
val spring = SpringTestContext(this)
@Autowired
lateinit var mockMvc: MockMvc
@Test
@Throws(Exception::class)
fun getUserWhenAnonymousThenForbidden() {
spring.register(SecurityConfiguration::class.java).autowire()
// @formatter:off
mockMvc.perform(get("/user").with(anonymous()))
.andExpect(status().isForbidden())
.andExpect(unauthenticated())
// @formatter:on
}
@Test
@Throws(Exception::class)
fun getUserWhenAuthenticatedWithNoRolesThenForbidden() {
spring.register(SecurityConfiguration::class.java).autowire()
val authentication = TestingAuthenticationToken("user", "", listOf())
// @formatter:off
mockMvc.perform(get("/user").with(authentication(authentication)))
.andExpect(status().isForbidden())
.andExpect(authenticated().withAuthentication(authentication))
// @formatter:on
}
@Test
@Throws(Exception::class)
fun getUserWhenAuthenticatedWithUserRoleThenOk() {
spring.register(SecurityConfiguration::class.java).autowire()
val authentication = TestingAuthenticationToken("user", "", "ROLE_USER")
// @formatter:off
mockMvc.perform(get("/user").with(authentication(authentication)))
.andExpect(status().isOk())
.andExpect(authenticated().withAuthentication(authentication))
// @formatter:on
}
@Test
@Throws(Exception::class)
fun getUserWhenAuthenticatedWithAdminRoleThenOk() {
spring.register(SecurityConfiguration::class.java).autowire()
val authentication = TestingAuthenticationToken("user", "", "ROLE_ADMIN")
// @formatter:off
mockMvc.perform(get("/user").with(authentication(authentication)))
.andExpect(status().isOk())
.andExpect(authenticated().withAuthentication(authentication))
// @formatter:on
}
@Test
@Throws(Exception::class)
fun getUserWhenAuthenticatedWithOtherRoleThenForbidden() {
spring.register(SecurityConfiguration::class.java).autowire()
val authentication = TestingAuthenticationToken("user", "", "ROLE_OTHER")
// @formatter:off
mockMvc.perform(get("/user").with(authentication(authentication)))
.andExpect(status().isForbidden())
.andExpect(authenticated().withAuthentication(authentication))
// @formatter:on
}
@Test
@Throws(Exception::class)
fun getRolesWhenAuthenticatedWithRole1RoleThenOk() {
spring.register(SecurityConfiguration::class.java).autowire()
val authentication = TestingAuthenticationToken("user", "", "ROLE_ROLE1")
// @formatter:off
mockMvc.perform(get("/roles").with(authentication(authentication)))
.andExpect(status().isOk())
.andExpect(authenticated().withAuthentication(authentication))
// @formatter:on
}
@Test
@Throws(Exception::class)
fun getRolesWhenAuthenticatedWithAdminRoleThenOk() {
spring.register(SecurityConfiguration::class.java).autowire()
val authentication = TestingAuthenticationToken("user", "", "ROLE_ADMIN")
// @formatter:off
mockMvc.perform(get("/roles").with(authentication(authentication)))
.andExpect(status().isOk())
.andExpect(authenticated().withAuthentication(authentication))
// @formatter:on
}
@Test
@Throws(Exception::class)
fun getRolesWhenAuthenticatedWithOtherRoleThenForbidden() {
spring.register(SecurityConfiguration::class.java).autowire()
val authentication = TestingAuthenticationToken("user", "", "ROLE_OTHER")
// @formatter:off
mockMvc.perform(get("/roles").with(authentication(authentication)))
.andExpect(status().isForbidden())
.andExpect(authenticated().withAuthentication(authentication))
// @formatter:on
}
@EnableWebMvc
@EnableWebSecurity
@EnableMethodSecurity
@Configuration
internal open class SecurityConfiguration {
@Bean
@Throws(Exception::class)
open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
// @formatter:off
http
.authorizeHttpRequests { authorize ->
authorize.anyRequest().authenticated()
}
// @formatter:on
return http.build()
}
@Bean
open fun customMethodInvocationAuthorizationManagerFactory(): CustomMethodInvocationAuthorizationManagerFactory {
return CustomMethodInvocationAuthorizationManagerFactory()
}
@Bean
open fun testController(testService: TestService): TestController {
return TestController(testService())
}
@Bean
open fun testService(): TestService {
return TestServiceImpl()
}
}
@RestController
internal open class TestController(private val testService: TestService) {
@GetMapping("/user")
@ResponseStatus(HttpStatus.OK)
fun user() {
testService.user()
}
@GetMapping("/roles")
@ResponseStatus(HttpStatus.OK)
fun roles() {
testService.roles()
}
}
internal interface TestService {
@PreAuthorize("hasRole('USER')")
fun user()
@PreAuthorize("hasAnyRole('ROLE1', 'ROLE2')")
fun roles()
}
internal open class TestServiceImpl : TestService {
override fun user() {
}
override fun roles() {
}
}
}
@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2004-present 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.
-->
<b:beans xmlns:b="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- tag::config[] -->
<b:bean id="authorizationManagerFactory" class="org.springframework.security.authorization.DefaultAuthorizationManagerFactory">
<b:property name="trustResolver" ref="authenticationTrustResolver"/>
<b:property name="roleHierarchy" ref="roleHierarchy"/>
<b:property name="rolePrefix" value="role_"/>
</b:bean>
<!-- end::config[] -->
<b:bean id="authenticationTrustResolver" class="org.springframework.security.authentication.AuthenticationTrustResolverImpl">
<b:property name="anonymousClass" value="org.springframework.security.authentication.TestingAuthenticationToken"/>
</b:bean>
<b:bean id="roleHierarchy" class="org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl" factory-method="fromHierarchy">
<b:constructor-arg name="hierarchy" value="role_admin > role_user"/>
</b:bean>
</b:beans>