Polish MFA Samples
This commit removes unneeded AuthorizationManagerFactory implementations, simplifies the custom AuthorizationManagerFactory example, and updates usage of hasAllAuthorities. Issue gh-17934
This commit is contained in:
parent
f652920bb3
commit
ad6fe4fdc3
|
@ -13,10 +13,6 @@ import org.springframework.security.web.SecurityFilterChain;
|
||||||
import org.springframework.security.web.authentication.ott.OneTimeTokenGenerationSuccessHandler;
|
import org.springframework.security.web.authentication.ott.OneTimeTokenGenerationSuccessHandler;
|
||||||
import org.springframework.security.web.authentication.ott.RedirectOneTimeTokenGenerationSuccessHandler;
|
import org.springframework.security.web.authentication.ott.RedirectOneTimeTokenGenerationSuccessHandler;
|
||||||
|
|
||||||
import static org.springframework.security.authorization.AuthorityAuthorizationManager.hasAuthority;
|
|
||||||
import static org.springframework.security.authorization.AuthorityAuthorizationManager.hasRole;
|
|
||||||
import static org.springframework.security.authorization.AuthorizationManagers.allOf;
|
|
||||||
|
|
||||||
@EnableWebSecurity
|
@EnableWebSecurity
|
||||||
@Configuration(proxyBeanMethods = false)
|
@Configuration(proxyBeanMethods = false)
|
||||||
public class ListAuthoritiesEverywhereConfiguration {
|
public class ListAuthoritiesEverywhereConfiguration {
|
||||||
|
@ -27,8 +23,8 @@ public class ListAuthoritiesEverywhereConfiguration {
|
||||||
// @formatter:off
|
// @formatter:off
|
||||||
http
|
http
|
||||||
.authorizeHttpRequests((authorize) -> authorize
|
.authorizeHttpRequests((authorize) -> authorize
|
||||||
.requestMatchers("/admin/**").access(allOf(hasAuthority(GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY), hasAuthority(GrantedAuthorities.FACTOR_OTT_AUTHORITY), hasRole("ADMIN"))) // <1>
|
.requestMatchers("/admin/**").hasAllAuthorities(GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY, GrantedAuthorities.FACTOR_OTT_AUTHORITY, "ROLE_ADMIN") // <1>
|
||||||
.anyRequest().access(allOf(hasAuthority(GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY), hasAuthority(GrantedAuthorities.FACTOR_OTT_AUTHORITY)))
|
.anyRequest().hasAllAuthorities(GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY, GrantedAuthorities.FACTOR_OTT_AUTHORITY)
|
||||||
)
|
)
|
||||||
.formLogin(Customizer.withDefaults())
|
.formLogin(Customizer.withDefaults())
|
||||||
.oneTimeTokenLogin(Customizer.withDefaults());
|
.oneTimeTokenLogin(Customizer.withDefaults());
|
||||||
|
|
|
@ -1,16 +1,12 @@
|
||||||
package org.springframework.security.docs.servlet.authentication.customauthorizationmanagerfactory;
|
package org.springframework.security.docs.servlet.authentication.customauthorizationmanagerfactory;
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
import org.jspecify.annotations.NullMarked;
|
|
||||||
import org.jspecify.annotations.Nullable;
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.security.access.expression.SecurityExpressionOperations;
|
import org.springframework.security.authorization.AuthorityAuthorizationManager;
|
||||||
import org.springframework.security.access.expression.SecurityExpressionRoot;
|
|
||||||
import org.springframework.security.authorization.AuthorityAuthorizationDecision;
|
|
||||||
import org.springframework.security.authorization.AuthorizationDecision;
|
import org.springframework.security.authorization.AuthorizationDecision;
|
||||||
import org.springframework.security.authorization.AuthorizationManager;
|
import org.springframework.security.authorization.AuthorizationManager;
|
||||||
import org.springframework.security.authorization.AuthorizationManagerFactory;
|
import org.springframework.security.authorization.AuthorizationManagerFactory;
|
||||||
|
@ -21,10 +17,9 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.core.GrantedAuthorities;
|
import org.springframework.security.core.GrantedAuthorities;
|
||||||
import org.springframework.security.core.GrantedAuthority;
|
import org.springframework.security.core.userdetails.PasswordEncodedUser;
|
||||||
import org.springframework.security.core.authority.AuthorityUtils;
|
|
||||||
import org.springframework.security.core.userdetails.UserDetails;
|
|
||||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||||
|
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
|
||||||
import org.springframework.security.web.SecurityFilterChain;
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
import org.springframework.security.web.authentication.ott.OneTimeTokenGenerationSuccessHandler;
|
import org.springframework.security.web.authentication.ott.OneTimeTokenGenerationSuccessHandler;
|
||||||
import org.springframework.security.web.authentication.ott.RedirectOneTimeTokenGenerationSuccessHandler;
|
import org.springframework.security.web.authentication.ott.RedirectOneTimeTokenGenerationSuccessHandler;
|
||||||
|
@ -51,50 +46,31 @@ class CustomAuthorizationManagerFactory {
|
||||||
|
|
||||||
// tag::authorizationManager[]
|
// tag::authorizationManager[]
|
||||||
@Component
|
@Component
|
||||||
class OptInToMfaAuthorizationManager implements AuthorizationManager<Object> {
|
class UserBasedOttAuthorizationManager implements AuthorizationManager<Object> {
|
||||||
@Override
|
@Override
|
||||||
public AuthorizationResult authorize(Supplier<? extends @Nullable Authentication> authentication, Object context) {
|
public AuthorizationResult authorize(Supplier<? extends @Nullable Authentication> authentication, Object context) {
|
||||||
MyPrincipal principal = (MyPrincipal) authentication.get().getPrincipal();
|
if ("admin".equals(authentication.get().getName())) {
|
||||||
if (principal.optedIn()) {
|
return AuthorityAuthorizationManager.hasAuthority(GrantedAuthorities.FACTOR_OTT_AUTHORITY)
|
||||||
SecurityExpressionOperations sec = new SecurityExpressionRoot<>(authentication, context) {};
|
.authorize(authentication, context);
|
||||||
return new AuthorityAuthorizationDecision(sec.hasAuthority(GrantedAuthorities.FACTOR_OTT_AUTHORITY),
|
} else {
|
||||||
AuthorityUtils.createAuthorityList(GrantedAuthorities.FACTOR_OTT_AUTHORITY));
|
|
||||||
}
|
|
||||||
return new AuthorizationDecision(true);
|
return new AuthorizationDecision(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// end::authorizationManager[]
|
// end::authorizationManager[]
|
||||||
|
|
||||||
// tag::authorizationManagerFactory[]
|
// tag::authorizationManagerFactory[]
|
||||||
@Bean
|
@Bean
|
||||||
AuthorizationManagerFactory<Object> authorizationManagerFactory(OptInToMfaAuthorizationManager optIn) {
|
AuthorizationManagerFactory<Object> authorizationManagerFactory(UserBasedOttAuthorizationManager optIn) {
|
||||||
DefaultAuthorizationManagerFactory<Object> defaults = new DefaultAuthorizationManagerFactory<>();
|
DefaultAuthorizationManagerFactory<Object> defaults = new DefaultAuthorizationManagerFactory<>();
|
||||||
defaults.setAdditionalAuthorization(optIn);
|
defaults.setAdditionalAuthorization(optIn);
|
||||||
return defaults;
|
return defaults;
|
||||||
}
|
}
|
||||||
// end::authorizationManagerFactory[]
|
// end::authorizationManagerFactory[]
|
||||||
|
|
||||||
@NullMarked
|
|
||||||
record MyPrincipal(String username, boolean optedIn) implements UserDetails {
|
|
||||||
@Override
|
|
||||||
public Collection<? extends GrantedAuthority> getAuthorities() {
|
|
||||||
return AuthorityUtils.createAuthorityList("app");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @Nullable String getPassword() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getUsername() {
|
|
||||||
return this.username;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
UserDetailsService users() {
|
public UserDetailsService users() {
|
||||||
return (username) -> new MyPrincipal(username, username.equals("optedin"));
|
return new InMemoryUserDetailsManager(PasswordEncodedUser.user(), PasswordEncodedUser.admin());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
|
|
|
@ -20,19 +20,18 @@ import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
|
||||||
import org.springframework.security.config.test.SpringTestContext;
|
import org.springframework.security.config.test.SpringTestContext;
|
||||||
import org.springframework.security.config.test.SpringTestContextExtension;
|
import org.springframework.security.config.test.SpringTestContextExtension;
|
||||||
import org.springframework.security.core.GrantedAuthorities;
|
import org.springframework.security.core.GrantedAuthorities;
|
||||||
import org.springframework.security.core.userdetails.UserDetails;
|
|
||||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
|
||||||
import org.springframework.security.docs.servlet.authentication.servletx509config.CustomX509Configuration;
|
import org.springframework.security.docs.servlet.authentication.servletx509config.CustomX509Configuration;
|
||||||
|
import org.springframework.security.test.context.support.WithMockUser;
|
||||||
|
import org.springframework.security.test.context.support.WithSecurityContextTestExecutionListener;
|
||||||
|
import org.springframework.test.context.TestExecutionListeners;
|
||||||
|
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||||
import org.springframework.test.web.servlet.MockMvc;
|
import org.springframework.test.web.servlet.MockMvc;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication;
|
|
||||||
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;
|
|
||||||
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated;
|
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.request.MockMvcRequestBuilders.get;
|
||||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
|
||||||
|
@ -43,7 +42,8 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
|
||||||
*
|
*
|
||||||
* @author Rob Winch
|
* @author Rob Winch
|
||||||
*/
|
*/
|
||||||
@ExtendWith(SpringTestContextExtension.class)
|
@ExtendWith({SpringExtension.class, SpringTestContextExtension.class})
|
||||||
|
@TestExecutionListeners(WithSecurityContextTestExecutionListener.class)
|
||||||
public class CustomAuthorizationManagerFactoryTests {
|
public class CustomAuthorizationManagerFactoryTests {
|
||||||
|
|
||||||
public final SpringTestContext spring = new SpringTestContext(this);
|
public final SpringTestContext spring = new SpringTestContext(this);
|
||||||
|
@ -51,40 +51,36 @@ public class CustomAuthorizationManagerFactoryTests {
|
||||||
@Autowired
|
@Autowired
|
||||||
MockMvc mockMvc;
|
MockMvc mockMvc;
|
||||||
|
|
||||||
@Autowired
|
|
||||||
UserDetailsService users;
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void getWhenOptedInThenRedirectsToOtt() throws Exception {
|
@WithMockUser(username = "admin")
|
||||||
|
void getWhenAdminThenRedirectsToOtt() throws Exception {
|
||||||
this.spring.register(CustomAuthorizationManagerFactory.class, Http200Controller.class).autowire();
|
this.spring.register(CustomAuthorizationManagerFactory.class, Http200Controller.class).autowire();
|
||||||
UserDetails user = this.users.loadUserByUsername("optedin");
|
|
||||||
// @formatter:off
|
// @formatter:off
|
||||||
this.mockMvc.perform(get("/").with(user(user)))
|
this.mockMvc.perform(get("/"))
|
||||||
.andExpect(status().is3xxRedirection())
|
.andExpect(status().is3xxRedirection())
|
||||||
.andExpect(redirectedUrl("http://localhost/login?factor=ott"));
|
.andExpect(redirectedUrl("http://localhost/login?factor=ott"));
|
||||||
// @formatter:on
|
// @formatter:on
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void getWhenNotOptedInThenAllows() throws Exception {
|
@WithMockUser
|
||||||
|
void getWhenNotAdminThenAllows() throws Exception {
|
||||||
this.spring.register(CustomAuthorizationManagerFactory.class, Http200Controller.class).autowire();
|
this.spring.register(CustomAuthorizationManagerFactory.class, Http200Controller.class).autowire();
|
||||||
UserDetails user = this.users.loadUserByUsername("user");
|
|
||||||
// @formatter:off
|
// @formatter:off
|
||||||
this.mockMvc.perform(get("/").with(user(user)))
|
this.mockMvc.perform(get("/"))
|
||||||
.andExpect(status().isOk())
|
.andExpect(status().isOk())
|
||||||
.andExpect(authenticated().withUsername("user"));
|
.andExpect(authenticated().withUsername("user"));
|
||||||
// @formatter:on
|
// @formatter:on
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void getWhenOptedAndHasFactorThenAllows() throws Exception {
|
@WithMockUser(username = "admin", authorities = GrantedAuthorities.FACTOR_OTT_AUTHORITY)
|
||||||
|
void getWhenAdminAndHasFactorThenAllows() throws Exception {
|
||||||
this.spring.register(CustomAuthorizationManagerFactory.class, Http200Controller.class).autowire();
|
this.spring.register(CustomAuthorizationManagerFactory.class, Http200Controller.class).autowire();
|
||||||
UserDetails user = this.users.loadUserByUsername("optedin");
|
|
||||||
TestingAuthenticationToken token = new TestingAuthenticationToken(user, "", GrantedAuthorities.FACTOR_OTT_AUTHORITY);
|
|
||||||
// @formatter:off
|
// @formatter:off
|
||||||
this.mockMvc.perform(get("/").with(authentication(token)))
|
this.mockMvc.perform(get("/"))
|
||||||
.andExpect(status().isOk())
|
.andExpect(status().isOk())
|
||||||
.andExpect(authenticated().withUsername("optedin"));
|
.andExpect(authenticated().withUsername("admin"));
|
||||||
// @formatter:on
|
// @formatter:on
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,9 +13,6 @@ import org.springframework.security.web.SecurityFilterChain;
|
||||||
import org.springframework.security.web.authentication.ott.OneTimeTokenGenerationSuccessHandler;
|
import org.springframework.security.web.authentication.ott.OneTimeTokenGenerationSuccessHandler;
|
||||||
import org.springframework.security.web.authentication.ott.RedirectOneTimeTokenGenerationSuccessHandler;
|
import org.springframework.security.web.authentication.ott.RedirectOneTimeTokenGenerationSuccessHandler;
|
||||||
|
|
||||||
import static org.springframework.security.authorization.AuthorityAuthorizationManager.hasAuthority;
|
|
||||||
import static org.springframework.security.authorization.AuthorizationManagers.allOf;
|
|
||||||
|
|
||||||
@EnableWebSecurity
|
@EnableWebSecurity
|
||||||
@Configuration(proxyBeanMethods = false)
|
@Configuration(proxyBeanMethods = false)
|
||||||
class ListAuthoritiesConfiguration {
|
class ListAuthoritiesConfiguration {
|
||||||
|
@ -26,7 +23,7 @@ class ListAuthoritiesConfiguration {
|
||||||
// @formatter:off
|
// @formatter:off
|
||||||
http
|
http
|
||||||
.authorizeHttpRequests((authorize) -> authorize
|
.authorizeHttpRequests((authorize) -> authorize
|
||||||
.anyRequest().access(allOf(hasAuthority(GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY), hasAuthority(GrantedAuthorities.FACTOR_OTT_AUTHORITY))) // <1>
|
.anyRequest().hasAllAuthorities(GrantedAuthorities.FACTOR_PASSWORD_AUTHORITY, GrantedAuthorities.FACTOR_OTT_AUTHORITY) // <1>
|
||||||
)
|
)
|
||||||
.formLogin(Customizer.withDefaults())
|
.formLogin(Customizer.withDefaults())
|
||||||
.oneTimeTokenLogin(Customizer.withDefaults());
|
.oneTimeTokenLogin(Customizer.withDefaults());
|
||||||
|
@ -36,7 +33,7 @@ class ListAuthoritiesConfiguration {
|
||||||
// end::httpSecurity[]
|
// end::httpSecurity[]
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
UserDetailsService userDetailsService() {
|
UserDetailsService users() {
|
||||||
return new InMemoryUserDetailsManager(
|
return new InMemoryUserDetailsManager(
|
||||||
User.withDefaultPasswordEncoder()
|
User.withDefaultPasswordEncoder()
|
||||||
.username("user")
|
.username("user")
|
||||||
|
|
|
@ -8,8 +8,6 @@ import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.security.authorization.AuthorizationDecision;
|
|
||||||
import org.springframework.security.authorization.AuthorizationManager;
|
|
||||||
import org.springframework.security.authorization.AuthorizationManagerFactory;
|
import org.springframework.security.authorization.AuthorizationManagerFactory;
|
||||||
import org.springframework.security.authorization.DefaultAuthorizationManagerFactory;
|
import org.springframework.security.authorization.DefaultAuthorizationManagerFactory;
|
||||||
import org.springframework.security.config.Customizer;
|
import org.springframework.security.config.Customizer;
|
||||||
|
@ -22,12 +20,8 @@ import org.springframework.security.oauth2.client.registration.InMemoryClientReg
|
||||||
import org.springframework.security.oauth2.client.registration.TestClientRegistrations;
|
import org.springframework.security.oauth2.client.registration.TestClientRegistrations;
|
||||||
import org.springframework.security.web.AuthenticationEntryPoint;
|
import org.springframework.security.web.AuthenticationEntryPoint;
|
||||||
import org.springframework.security.web.SecurityFilterChain;
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
import org.springframework.security.web.access.intercept.RequestAuthorizationContext;
|
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import static org.springframework.security.authorization.AllAuthoritiesAuthorizationManager.hasAllAuthorities;
|
|
||||||
import static org.springframework.security.authorization.AuthorizationManagers.allOf;
|
|
||||||
|
|
||||||
@EnableWebSecurity
|
@EnableWebSecurity
|
||||||
@Configuration(proxyBeanMethods = false)
|
@Configuration(proxyBeanMethods = false)
|
||||||
class MissingAuthorityConfiguration {
|
class MissingAuthorityConfiguration {
|
||||||
|
@ -53,83 +47,13 @@ class MissingAuthorityConfiguration {
|
||||||
|
|
||||||
// tag::authorizationManagerFactoryBean[]
|
// tag::authorizationManagerFactoryBean[]
|
||||||
@Bean
|
@Bean
|
||||||
AuthorizationManagerFactory<RequestAuthorizationContext> authz() {
|
AuthorizationManagerFactory<Object> authz() {
|
||||||
return new FactorAuthorizationManagerFactory(hasAllAuthorities(GrantedAuthorities.FACTOR_X509_AUTHORITY, GrantedAuthorities.FACTOR_AUTHORIZATION_CODE_AUTHORITY));
|
return DefaultAuthorizationManagerFactory.builder()
|
||||||
|
.requireAdditionalAuthorities(GrantedAuthorities.FACTOR_X509_AUTHORITY, GrantedAuthorities.FACTOR_AUTHORIZATION_CODE_AUTHORITY)
|
||||||
|
.build();
|
||||||
}
|
}
|
||||||
// end::authorizationManagerFactoryBean[]
|
// end::authorizationManagerFactoryBean[]
|
||||||
|
|
||||||
// tag::authorizationManagerFactory[]
|
|
||||||
class FactorAuthorizationManagerFactory implements AuthorizationManagerFactory<RequestAuthorizationContext> {
|
|
||||||
private final AuthorizationManager<RequestAuthorizationContext> hasAuthorities;
|
|
||||||
private final DefaultAuthorizationManagerFactory<RequestAuthorizationContext> delegate =
|
|
||||||
new DefaultAuthorizationManagerFactory<>();
|
|
||||||
|
|
||||||
FactorAuthorizationManagerFactory(AuthorizationManager<RequestAuthorizationContext> hasAuthorities) {
|
|
||||||
this.hasAuthorities = hasAuthorities;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public AuthorizationManager<RequestAuthorizationContext> permitAll() {
|
|
||||||
return this.delegate.permitAll();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public AuthorizationManager<RequestAuthorizationContext> denyAll() {
|
|
||||||
return this.delegate.denyAll();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public AuthorizationManager<RequestAuthorizationContext> hasRole(String role) {
|
|
||||||
return hasAnyRole(role);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public AuthorizationManager<RequestAuthorizationContext> hasAnyRole(String... roles) {
|
|
||||||
return allOf(new AuthorizationDecision(false), this.hasAuthorities, this.delegate.hasAnyRole(roles));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public AuthorizationManager<RequestAuthorizationContext> hasAllRoles(String... roles) {
|
|
||||||
return allOf(new AuthorizationDecision(false), this.hasAuthorities, this.delegate.hasAllRoles(roles));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public AuthorizationManager<RequestAuthorizationContext> hasAuthority(String authority) {
|
|
||||||
return hasAnyAuthority(authority);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public AuthorizationManager<RequestAuthorizationContext> hasAnyAuthority(String... authorities) {
|
|
||||||
return allOf(new AuthorizationDecision(false), this.hasAuthorities, this.delegate.hasAnyAuthority(authorities));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public AuthorizationManager<RequestAuthorizationContext> hasAllAuthorities(String... authorities) {
|
|
||||||
return allOf(new AuthorizationDecision(false), this.hasAuthorities, this.delegate.hasAllAuthorities(authorities));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public AuthorizationManager<RequestAuthorizationContext> authenticated() {
|
|
||||||
return allOf(new AuthorizationDecision(false), this.hasAuthorities, this.delegate.authenticated());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public AuthorizationManager<RequestAuthorizationContext> fullyAuthenticated() {
|
|
||||||
return allOf(new AuthorizationDecision(false), this.hasAuthorities, this.delegate.fullyAuthenticated());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public AuthorizationManager<RequestAuthorizationContext> rememberMe() {
|
|
||||||
return allOf(new AuthorizationDecision(false), this.hasAuthorities, this.delegate.rememberMe());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public AuthorizationManager<RequestAuthorizationContext> anonymous() {
|
|
||||||
return this.delegate.anonymous();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// end::authorizationManagerFactory[]
|
|
||||||
|
|
||||||
// tag::authenticationEntryPoint[]
|
// tag::authenticationEntryPoint[]
|
||||||
@Component
|
@Component
|
||||||
class ScopeRetrievingAuthenticationEntryPoint implements AuthenticationEntryPoint {
|
class ScopeRetrievingAuthenticationEntryPoint implements AuthenticationEntryPoint {
|
||||||
|
|
|
@ -1,19 +1,16 @@
|
||||||
package org.springframework.security.kt.docs.servlet.authentication.customauthorizationmanagerfactory
|
package org.springframework.security.kt.docs.servlet.authentication.customauthorizationmanagerfactory
|
||||||
|
|
||||||
import org.jspecify.annotations.NullMarked
|
|
||||||
import org.springframework.context.annotation.Bean
|
import org.springframework.context.annotation.Bean
|
||||||
import org.springframework.context.annotation.Configuration
|
import org.springframework.context.annotation.Configuration
|
||||||
import org.springframework.security.access.expression.SecurityExpressionRoot
|
|
||||||
import org.springframework.security.authorization.*
|
import org.springframework.security.authorization.*
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity
|
||||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
|
||||||
import org.springframework.security.config.annotation.web.invoke
|
import org.springframework.security.config.annotation.web.invoke
|
||||||
import org.springframework.security.core.Authentication
|
import org.springframework.security.core.Authentication
|
||||||
import org.springframework.security.core.GrantedAuthorities
|
import org.springframework.security.core.GrantedAuthorities
|
||||||
import org.springframework.security.core.GrantedAuthority
|
import org.springframework.security.core.userdetails.PasswordEncodedUser
|
||||||
import org.springframework.security.core.authority.AuthorityUtils
|
|
||||||
import org.springframework.security.core.userdetails.UserDetails
|
|
||||||
import org.springframework.security.core.userdetails.UserDetailsService
|
import org.springframework.security.core.userdetails.UserDetailsService
|
||||||
|
import org.springframework.security.provisioning.InMemoryUserDetailsManager
|
||||||
import org.springframework.security.web.SecurityFilterChain
|
import org.springframework.security.web.SecurityFilterChain
|
||||||
import org.springframework.security.web.authentication.ott.OneTimeTokenGenerationSuccessHandler
|
import org.springframework.security.web.authentication.ott.OneTimeTokenGenerationSuccessHandler
|
||||||
import org.springframework.security.web.authentication.ott.RedirectOneTimeTokenGenerationSuccessHandler
|
import org.springframework.security.web.authentication.ott.RedirectOneTimeTokenGenerationSuccessHandler
|
||||||
|
@ -43,50 +40,31 @@ internal class CustomAuthorizationManagerFactory {
|
||||||
|
|
||||||
// tag::authorizationManager[]
|
// tag::authorizationManager[]
|
||||||
@Component
|
@Component
|
||||||
internal open class OptInToMfaAuthorizationManager : AuthorizationManager<Object> {
|
internal open class UserBasedOttAuthorizationManager : AuthorizationManager<Object> {
|
||||||
override fun authorize(
|
override fun authorize(
|
||||||
authentication: Supplier<out Authentication?>, context: Object): AuthorizationResult {
|
authentication: Supplier<out Authentication?>, context: Object): AuthorizationResult {
|
||||||
val principal = authentication.get().getPrincipal() as MyPrincipal?
|
return if ("admin" == authentication.get().name) {
|
||||||
if (principal!!.optedIn) {
|
AuthorityAuthorizationManager.hasAuthority<Object>(GrantedAuthorities.FACTOR_OTT_AUTHORITY)
|
||||||
val root = object : SecurityExpressionRoot<Object>(authentication, context) { }
|
.authorize(authentication, context)
|
||||||
return AuthorityAuthorizationDecision(
|
} else {
|
||||||
root.hasAuthority(GrantedAuthorities.FACTOR_OTT_AUTHORITY),
|
AuthorizationDecision(true)
|
||||||
AuthorityUtils.createAuthorityList(GrantedAuthorities.FACTOR_OTT_AUTHORITY)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
return AuthorizationDecision(true)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// end::authorizationManager[]
|
// end::authorizationManager[]
|
||||||
|
|
||||||
// tag::authorizationManagerFactory[]
|
// tag::authorizationManagerFactory[]
|
||||||
@Bean
|
@Bean
|
||||||
fun authorizationManagerFactory(optIn: OptInToMfaAuthorizationManager?): AuthorizationManagerFactory<Object> {
|
fun authorizationManagerFactory(optIn: UserBasedOttAuthorizationManager?): AuthorizationManagerFactory<Object> {
|
||||||
val defaults = DefaultAuthorizationManagerFactory<Object>()
|
val defaults = DefaultAuthorizationManagerFactory<Object>()
|
||||||
defaults.setAdditionalAuthorization(optIn)
|
defaults.setAdditionalAuthorization(optIn)
|
||||||
return defaults
|
return defaults
|
||||||
}
|
}
|
||||||
// end::authorizationManagerFactory[]
|
// end::authorizationManagerFactory[]
|
||||||
|
|
||||||
@NullMarked
|
|
||||||
class MyPrincipal(val user: String, val optedIn: Boolean) : UserDetails {
|
|
||||||
override fun getAuthorities(): MutableCollection<out GrantedAuthority> {
|
|
||||||
return AuthorityUtils.createAuthorityList("app")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getPassword(): String? {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getUsername(): String {
|
|
||||||
return this.user
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
fun users(): UserDetailsService {
|
fun users(): UserDetailsService {
|
||||||
return UserDetailsService { username: String? -> MyPrincipal(username!!, username == "optedin") }
|
return InMemoryUserDetailsManager(PasswordEncodedUser.user(), PasswordEncodedUser.admin())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
|
|
|
@ -18,16 +18,18 @@ package org.springframework.security.kt.docs.servlet.authentication.customauthor
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
import org.junit.jupiter.api.extension.ExtendWith
|
import org.junit.jupiter.api.extension.ExtendWith
|
||||||
import org.springframework.beans.factory.annotation.Autowired
|
import org.springframework.beans.factory.annotation.Autowired
|
||||||
import org.springframework.security.authentication.TestingAuthenticationToken
|
|
||||||
import org.springframework.security.config.test.SpringTestContext
|
import org.springframework.security.config.test.SpringTestContext
|
||||||
import org.springframework.security.config.test.SpringTestContextExtension
|
import org.springframework.security.config.test.SpringTestContextExtension
|
||||||
import org.springframework.security.core.GrantedAuthorities
|
import org.springframework.security.core.GrantedAuthorities
|
||||||
import org.springframework.security.core.userdetails.UserDetailsService
|
import org.springframework.security.test.context.support.WithMockUser
|
||||||
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors
|
import org.springframework.security.test.context.support.WithSecurityContextTestExecutionListener
|
||||||
import org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers
|
import org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated
|
||||||
|
import org.springframework.test.context.TestExecutionListeners
|
||||||
|
import org.springframework.test.context.junit.jupiter.SpringExtension
|
||||||
import org.springframework.test.web.servlet.MockMvc
|
import org.springframework.test.web.servlet.MockMvc
|
||||||
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders
|
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
|
||||||
import org.springframework.test.web.servlet.result.MockMvcResultMatchers
|
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl
|
||||||
|
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
|
||||||
import org.springframework.web.bind.annotation.GetMapping
|
import org.springframework.web.bind.annotation.GetMapping
|
||||||
import org.springframework.web.bind.annotation.RestController
|
import org.springframework.web.bind.annotation.RestController
|
||||||
|
|
||||||
|
@ -36,7 +38,8 @@ import org.springframework.web.bind.annotation.RestController
|
||||||
*
|
*
|
||||||
* @author Rob Winch
|
* @author Rob Winch
|
||||||
*/
|
*/
|
||||||
@ExtendWith(SpringTestContextExtension::class)
|
@ExtendWith(SpringExtension::class, SpringTestContextExtension::class)
|
||||||
|
@TestExecutionListeners(WithSecurityContextTestExecutionListener::class)
|
||||||
class CustomAuthorizationManagerFactoryTests {
|
class CustomAuthorizationManagerFactoryTests {
|
||||||
@JvmField
|
@JvmField
|
||||||
val spring: SpringTestContext = SpringTestContext(this)
|
val spring: SpringTestContext = SpringTestContext(this)
|
||||||
|
@ -44,43 +47,39 @@ class CustomAuthorizationManagerFactoryTests {
|
||||||
@Autowired
|
@Autowired
|
||||||
var mockMvc: MockMvc? = null
|
var mockMvc: MockMvc? = null
|
||||||
|
|
||||||
@Autowired
|
|
||||||
var users: UserDetailsService? = null
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Throws(Exception::class)
|
@Throws(Exception::class)
|
||||||
fun getWhenOptedInThenRedirectsToOtt() {
|
@WithMockUser(username = "admin")
|
||||||
|
fun getWhenAdminThenRedirectsToOtt() {
|
||||||
this.spring.register(CustomAuthorizationManagerFactory::class.java, Http200Controller::class.java).autowire()
|
this.spring.register(CustomAuthorizationManagerFactory::class.java, Http200Controller::class.java).autowire()
|
||||||
val user = this.users!!.loadUserByUsername("optedin")
|
|
||||||
// @formatter:off
|
// @formatter:off
|
||||||
this.mockMvc!!.perform(MockMvcRequestBuilders.get("/").with(SecurityMockMvcRequestPostProcessors.user(user)))
|
this.mockMvc!!.perform(get("/"))
|
||||||
.andExpect(MockMvcResultMatchers.status().is3xxRedirection())
|
.andExpect(status().is3xxRedirection())
|
||||||
.andExpect(MockMvcResultMatchers.redirectedUrl("http://localhost/login?factor=ott"))
|
.andExpect(redirectedUrl("http://localhost/login?factor=ott"))
|
||||||
// @formatter:on
|
// @formatter:on
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Throws(Exception::class)
|
@Throws(Exception::class)
|
||||||
fun getWhenNotOptedInThenAllows() {
|
@WithMockUser
|
||||||
|
fun getWhenNotAdminThenAllows() {
|
||||||
this.spring.register(CustomAuthorizationManagerFactory::class.java, Http200Controller::class.java).autowire()
|
this.spring.register(CustomAuthorizationManagerFactory::class.java, Http200Controller::class.java).autowire()
|
||||||
val user = this.users!!.loadUserByUsername("user")
|
|
||||||
// @formatter:off
|
// @formatter:off
|
||||||
this.mockMvc!!.perform(MockMvcRequestBuilders.get("/").with(SecurityMockMvcRequestPostProcessors.user(user)))
|
this.mockMvc!!.perform(get("/"))
|
||||||
.andExpect(MockMvcResultMatchers.status().isOk())
|
.andExpect(status().isOk())
|
||||||
.andExpect(SecurityMockMvcResultMatchers.authenticated().withUsername("user"))
|
.andExpect(authenticated().withUsername("user"))
|
||||||
// @formatter:on
|
// @formatter:on
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Throws(Exception::class)
|
@Throws(Exception::class)
|
||||||
fun getWhenOptedAndHasFactorThenAllows() {
|
@WithMockUser(username = "admin", authorities = [GrantedAuthorities.FACTOR_OTT_AUTHORITY])
|
||||||
|
fun getWhenAdminAndHasFactorThenAllows() {
|
||||||
this.spring.register(CustomAuthorizationManagerFactory::class.java, Http200Controller::class.java).autowire()
|
this.spring.register(CustomAuthorizationManagerFactory::class.java, Http200Controller::class.java).autowire()
|
||||||
val user = this.users!!.loadUserByUsername("optedin")
|
|
||||||
val token = TestingAuthenticationToken(user, "", GrantedAuthorities.FACTOR_OTT_AUTHORITY)
|
|
||||||
// @formatter:off
|
// @formatter:off
|
||||||
this.mockMvc!!.perform(MockMvcRequestBuilders.get("/").with(SecurityMockMvcRequestPostProcessors.authentication(token)))
|
this.mockMvc!!.perform(get("/"))
|
||||||
.andExpect(MockMvcResultMatchers.status().isOk())
|
.andExpect(status().isOk())
|
||||||
.andExpect(SecurityMockMvcResultMatchers.authenticated().withUsername("optedin"))
|
.andExpect(authenticated().withUsername("admin"))
|
||||||
// @formatter:on
|
// @formatter:on
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -58,70 +58,13 @@ internal class MissingAuthorityConfiguration {
|
||||||
|
|
||||||
// tag::authorizationManagerFactoryBean[]
|
// tag::authorizationManagerFactoryBean[]
|
||||||
@Bean
|
@Bean
|
||||||
fun authz(): AuthorizationManagerFactory<RequestAuthorizationContext> {
|
fun authz(): AuthorizationManagerFactory<Object> {
|
||||||
return FactorAuthorizationManagerFactory(hasAllAuthorities(GrantedAuthorities.FACTOR_X509_AUTHORITY, GrantedAuthorities.FACTOR_AUTHORIZATION_CODE_AUTHORITY))
|
return DefaultAuthorizationManagerFactory.builder<Object>()
|
||||||
|
.requireAdditionalAuthorities(GrantedAuthorities.FACTOR_X509_AUTHORITY, GrantedAuthorities.FACTOR_AUTHORIZATION_CODE_AUTHORITY)
|
||||||
|
.build()
|
||||||
}
|
}
|
||||||
// end::authorizationManagerFactoryBean[]
|
// end::authorizationManagerFactoryBean[]
|
||||||
|
|
||||||
// tag::authorizationManagerFactory[]
|
|
||||||
internal inner class FactorAuthorizationManagerFactory(private val hasAuthorities: AuthorizationManager<RequestAuthorizationContext>) :
|
|
||||||
AuthorizationManagerFactory<RequestAuthorizationContext> {
|
|
||||||
private val delegate = DefaultAuthorizationManagerFactory<RequestAuthorizationContext>()
|
|
||||||
|
|
||||||
override fun permitAll(): AuthorizationManager<RequestAuthorizationContext> {
|
|
||||||
return this.delegate.permitAll()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun denyAll(): AuthorizationManager<RequestAuthorizationContext> {
|
|
||||||
return this.delegate.denyAll()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hasRole(role: String): AuthorizationManager<RequestAuthorizationContext> {
|
|
||||||
return hasAnyRole(role)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hasAnyRole(vararg roles: String): AuthorizationManager<RequestAuthorizationContext> {
|
|
||||||
return addFactors(this.delegate.hasAnyRole(*roles))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hasAllRoles(vararg roles: String): AuthorizationManager<RequestAuthorizationContext> {
|
|
||||||
return addFactors(this.delegate.hasAllRoles(*roles))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hasAuthority(authority: String): AuthorizationManager<RequestAuthorizationContext> {
|
|
||||||
return hasAnyAuthority(authority)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hasAnyAuthority(vararg authorities: String): AuthorizationManager<RequestAuthorizationContext> {
|
|
||||||
return addFactors(this.delegate.hasAnyAuthority(*authorities))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hasAllAuthorities(vararg authorities: String): AuthorizationManager<RequestAuthorizationContext> {
|
|
||||||
return addFactors(this.delegate.hasAllAuthorities(*authorities))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun authenticated(): AuthorizationManager<RequestAuthorizationContext> {
|
|
||||||
return addFactors(this.delegate.authenticated())
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun fullyAuthenticated(): AuthorizationManager<RequestAuthorizationContext> {
|
|
||||||
return addFactors(this.delegate.fullyAuthenticated())
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun rememberMe(): AuthorizationManager<RequestAuthorizationContext> {
|
|
||||||
return addFactors(this.delegate.rememberMe())
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun anonymous(): AuthorizationManager<RequestAuthorizationContext> {
|
|
||||||
return this.delegate.anonymous()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun addFactors(delegate: AuthorizationManager<RequestAuthorizationContext>): AuthorizationManager<RequestAuthorizationContext> {
|
|
||||||
return allOf(AuthorizationDecision(false), this.hasAuthorities, delegate)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// end::authorizationManagerFactory[]
|
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
fun clients(): ClientRegistrationRepository {
|
fun clients(): ClientRegistrationRepository {
|
||||||
return InMemoryClientRegistrationRepository(TestClientRegistrations.clientRegistration().build())
|
return InMemoryClientRegistrationRepository(TestClientRegistrations.clientRegistration().build())
|
||||||
|
|
Loading…
Reference in New Issue