Skip to content

Commit a282f6a

Browse files
Merge branch 'main' of https://github.com/spring-projects/spring-security into fix-typos
Signed-off-by: Tran Ngoc Nhan <ngocnhan.tran1996@gmail.com> # Conflicts: # web/src/main/java/org/springframework/security/web/server/authentication/AuthenticationWebFilter.java
2 parents 30be740 + 9dde697 commit a282f6a

24 files changed

Lines changed: 668 additions & 152 deletions

File tree

config/src/main/java/org/springframework/security/config/annotation/authorization/GlobalMultiFactorAuthenticationConfiguration.java renamed to config/src/main/java/org/springframework/security/config/annotation/authorization/AuthorizationManagerFactoryConfiguration.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,14 @@
2727
import org.springframework.security.authorization.DefaultAuthorizationManagerFactory;
2828

2929
/**
30-
* Uses {@link EnableGlobalMultiFactorAuthentication} to configure a
30+
* Uses {@link EnableMultiFactorAuthentication} to configure a
3131
* {@link DefaultAuthorizationManagerFactory}.
3232
*
3333
* @author Rob Winch
3434
* @since 7.0
35-
* @see EnableGlobalMultiFactorAuthentication
35+
* @see EnableMultiFactorAuthentication
3636
*/
37-
class GlobalMultiFactorAuthenticationConfiguration implements ImportAware {
37+
class AuthorizationManagerFactoryConfiguration implements ImportAware {
3838

3939
private String[] authorities;
4040

@@ -49,9 +49,9 @@ DefaultAuthorizationManagerFactory authorizationManagerFactory(ObjectProvider<Ro
4949
@Override
5050
public void setImportMetadata(AnnotationMetadata importMetadata) {
5151
Map<String, Object> multiFactorAuthenticationAttrs = importMetadata
52-
.getAnnotationAttributes(EnableGlobalMultiFactorAuthentication.class.getName());
52+
.getAnnotationAttributes(EnableMultiFactorAuthentication.class.getName());
5353

54-
this.authorities = (String[]) multiFactorAuthenticationAttrs.get("authorities");
54+
this.authorities = (String[]) multiFactorAuthenticationAttrs.getOrDefault("authorities", new String[0]);
5555
}
5656

5757
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
* Copyright 2002-present the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.config.annotation.authorization;
18+
19+
import org.jspecify.annotations.Nullable;
20+
21+
import org.springframework.beans.BeansException;
22+
import org.springframework.beans.factory.config.BeanPostProcessor;
23+
import org.springframework.context.annotation.Bean;
24+
import org.springframework.context.annotation.Configuration;
25+
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
26+
import org.springframework.security.web.authentication.AuthenticationFilter;
27+
import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter;
28+
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
29+
30+
@Configuration(proxyBeanMethods = false)
31+
class EnableMfaFiltersConfiguration {
32+
33+
@Bean
34+
BeanPostProcessor mfaBeanPostProcessor() {
35+
return new EnableMfaFiltersPostProcessor();
36+
}
37+
38+
/**
39+
* A {@link BeanPostProcessor} that enables MFA on authentication filters.
40+
*
41+
* @author Rob Winch
42+
* @since 7.0
43+
*/
44+
private static class EnableMfaFiltersPostProcessor implements BeanPostProcessor {
45+
46+
@Override
47+
public @Nullable Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
48+
if (bean instanceof AbstractAuthenticationProcessingFilter filter) {
49+
filter.setMfaEnabled(true);
50+
}
51+
if (bean instanceof AuthenticationFilter filter) {
52+
filter.setMfaEnabled(true);
53+
}
54+
if (bean instanceof AbstractPreAuthenticatedProcessingFilter filter) {
55+
filter.setMfaEnabled(true);
56+
}
57+
if (bean instanceof BasicAuthenticationFilter filter) {
58+
filter.setMfaEnabled(true);
59+
}
60+
return bean;
61+
}
62+
63+
}
64+
65+
}

config/src/main/java/org/springframework/security/config/annotation/authorization/EnableGlobalMultiFactorAuthentication.java renamed to config/src/main/java/org/springframework/security/config/annotation/authorization/EnableMultiFactorAuthentication.java

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,17 +26,20 @@
2626
import org.springframework.security.authorization.DefaultAuthorizationManagerFactory;
2727

2828
/**
29-
* Exposes a {@link DefaultAuthorizationManagerFactory} as a Bean with the
30-
* {@link #authorities()} specified as additional required authorities. The configuration
31-
* will be picked up by both
29+
* Enables Multi-Factor Authentication (MFA) support within Spring Security.
30+
*
31+
* When {@link #authorities()} is specified creates a
32+
* {@link DefaultAuthorizationManagerFactory} as a Bean with the {@link #authorities()}
33+
* specified as additional required authorities. The configuration will be picked up by
34+
* both
3235
* {@link org.springframework.security.config.annotation.web.configuration.EnableWebSecurity}
3336
* and
3437
* {@link org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity}.
3538
*
3639
* <pre>
3740
3841
* &#64;Configuration
39-
* &#64;EnableGlobalMultiFactorAuthentication(authorities = { GrantedAuthorities.FACTOR_OTT, GrantedAuthorities.FACTOR_PASSWORD })
42+
* &#64;EnableMultiFactorAuthentication(authorities = { GrantedAuthorities.FACTOR_OTT, GrantedAuthorities.FACTOR_PASSWORD })
4043
* public class MyConfiguration {
4144
* // ...
4245
* }
@@ -51,13 +54,15 @@
5154
@Retention(RetentionPolicy.RUNTIME)
5255
@Target(ElementType.TYPE)
5356
@Documented
54-
@Import(GlobalMultiFactorAuthenticationConfiguration.class)
55-
public @interface EnableGlobalMultiFactorAuthentication {
57+
@Import(MultiFactorAuthenticationSelector.class)
58+
public @interface EnableMultiFactorAuthentication {
5659

5760
/**
5861
* The additional authorities that are required.
5962
* @return the additional authorities that are required (e.g. {
60-
* FactorGrantedAuthority.FACTOR_OTT, FactorGrantedAuthority.FACTOR_PASSWORD })
63+
* FactorGrantedAuthority.FACTOR_OTT, FactorGrantedAuthority.FACTOR_PASSWORD }). Can
64+
* be null or an empty array if no additional authorities are required (if
65+
* authorization rules are not globally requiring MFA).
6166
* @see org.springframework.security.core.authority.FactorGrantedAuthority
6267
*/
6368
String[] authorities();
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* Copyright 2004-present the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.config.annotation.authorization;
18+
19+
import java.util.ArrayList;
20+
import java.util.List;
21+
import java.util.Map;
22+
23+
import org.springframework.context.annotation.ImportSelector;
24+
import org.springframework.core.type.AnnotationMetadata;
25+
import org.springframework.security.authorization.DefaultAuthorizationManagerFactory;
26+
27+
/**
28+
* Uses {@link EnableMultiFactorAuthentication} to configure a
29+
* {@link DefaultAuthorizationManagerFactory}.
30+
*
31+
* @author Rob Winch
32+
* @since 7.0
33+
* @see EnableMultiFactorAuthentication
34+
*/
35+
class MultiFactorAuthenticationSelector implements ImportSelector {
36+
37+
@Override
38+
public String[] selectImports(AnnotationMetadata metadata) {
39+
Map<String, Object> multiFactorAuthenticationAttrs = metadata
40+
.getAnnotationAttributes(EnableMultiFactorAuthentication.class.getName());
41+
String[] authorities = (String[]) multiFactorAuthenticationAttrs.getOrDefault("authorities", new String[0]);
42+
List<String> imports = new ArrayList<>(2);
43+
if (authorities.length > 0) {
44+
imports.add(AuthorizationManagerFactoryConfiguration.class.getName());
45+
}
46+
imports.add(EnableMfaFiltersConfiguration.class.getName());
47+
return imports.toArray(new String[imports.size()]);
48+
}
49+
50+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
/*
2+
* Copyright 2004-present the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.config.annotation.authorization;
18+
19+
import jakarta.servlet.Filter;
20+
import jakarta.servlet.http.HttpServletRequest;
21+
import org.jspecify.annotations.Nullable;
22+
import org.junit.jupiter.api.Test;
23+
import org.junit.jupiter.api.extension.ExtendWith;
24+
25+
import org.springframework.beans.factory.annotation.Autowired;
26+
import org.springframework.context.annotation.Bean;
27+
import org.springframework.context.annotation.Configuration;
28+
import org.springframework.mock.web.MockFilterChain;
29+
import org.springframework.mock.web.MockHttpServletRequest;
30+
import org.springframework.mock.web.MockHttpServletResponse;
31+
import org.springframework.mock.web.MockServletContext;
32+
import org.springframework.security.authentication.AuthenticationManager;
33+
import org.springframework.security.authentication.TestingAuthenticationToken;
34+
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
35+
import org.springframework.security.core.Authentication;
36+
import org.springframework.security.core.GrantedAuthority;
37+
import org.springframework.security.core.authority.FactorGrantedAuthority;
38+
import org.springframework.security.core.context.SecurityContextHolder;
39+
import org.springframework.security.test.context.support.WithMockUser;
40+
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
41+
import org.springframework.security.web.authentication.AuthenticationFilter;
42+
import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter;
43+
import org.springframework.security.web.authentication.www.BasicAuthenticationConverter;
44+
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
45+
import org.springframework.security.web.util.matcher.AnyRequestMatcher;
46+
import org.springframework.test.context.junit.jupiter.SpringExtension;
47+
import org.springframework.test.context.web.WebAppConfiguration;
48+
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
49+
50+
import static org.assertj.core.api.Assertions.assertThat;
51+
import static org.mockito.ArgumentMatchers.any;
52+
import static org.mockito.BDDMockito.given;
53+
import static org.mockito.Mockito.mock;
54+
55+
/**
56+
* Tests for {@link EnableMultiFactorAuthentication}.
57+
*
58+
* @author Rob Winch
59+
*/
60+
@ExtendWith(SpringExtension.class)
61+
@WebAppConfiguration
62+
@WithMockUser(authorities = FactorGrantedAuthority.PASSWORD_AUTHORITY)
63+
public class EnableMultiFactorAuthenticationFiltersSetTests {
64+
65+
@Autowired
66+
private AuthenticationManager manager;
67+
68+
private TestingAuthenticationToken newAuthn = new TestingAuthenticationToken("user", "password", "ROLE_USER",
69+
FactorGrantedAuthority.OTT_AUTHORITY);
70+
71+
@Test
72+
void preAuthenticationFilter(@Autowired AbstractAuthenticationProcessingFilter filter) throws Exception {
73+
assertMfaEnabled(filter);
74+
}
75+
76+
@Test
77+
void authenticationFilter(@Autowired AuthenticationFilter filter) throws Exception {
78+
assertMfaEnabled(filter);
79+
}
80+
81+
@Test
82+
void preAuthnFilter(@Autowired AbstractPreAuthenticatedProcessingFilter filter) throws Exception {
83+
assertMfaEnabled(filter);
84+
}
85+
86+
@Test
87+
void basicAuthnFilter(@Autowired BasicAuthenticationFilter filter) throws Exception {
88+
assertMfaEnabled(filter);
89+
}
90+
91+
private void assertMfaEnabled(Filter filter) throws Exception {
92+
given(this.manager.authenticate(any())).willReturn(this.newAuthn);
93+
MockHttpServletRequest request = MockMvcRequestBuilders.get("/")
94+
.headers((headers) -> headers.setBasicAuth("u", "p"))
95+
.buildRequest(new MockServletContext());
96+
MockHttpServletResponse response = new MockHttpServletResponse();
97+
MockFilterChain chain = new MockFilterChain();
98+
filter.doFilter(request, response, chain);
99+
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
100+
assertThat(authentication).isNotNull();
101+
assertThat(authentication.getAuthorities()).extracting(GrantedAuthority::getAuthority)
102+
.containsExactlyInAnyOrder(FactorGrantedAuthority.OTT_AUTHORITY, FactorGrantedAuthority.PASSWORD_AUTHORITY,
103+
"ROLE_USER");
104+
}
105+
106+
@EnableWebSecurity
107+
@Configuration
108+
@EnableMultiFactorAuthentication(
109+
authorities = { FactorGrantedAuthority.OTT_AUTHORITY, FactorGrantedAuthority.PASSWORD_AUTHORITY })
110+
static class Config {
111+
112+
@Bean
113+
AuthenticationManager authenticationManager() {
114+
return mock(AuthenticationManager.class);
115+
}
116+
117+
@Bean
118+
static AbstractAuthenticationProcessingFilter authnProcessingFilter(
119+
AuthenticationManager authenticationManager) {
120+
AbstractAuthenticationProcessingFilter result = new AbstractAuthenticationProcessingFilter(
121+
AnyRequestMatcher.INSTANCE, authenticationManager) {
122+
};
123+
result.setAuthenticationConverter(new BasicAuthenticationConverter());
124+
return result;
125+
}
126+
127+
@Bean
128+
static AuthenticationFilter authenticationFilter(AuthenticationManager authenticationManager) {
129+
return new AuthenticationFilter(authenticationManager, new BasicAuthenticationConverter());
130+
}
131+
132+
@Bean
133+
static AbstractPreAuthenticatedProcessingFilter preAuthenticatedProcessingFilter(
134+
AuthenticationManager authenticationManager) {
135+
AbstractPreAuthenticatedProcessingFilter result = new AbstractPreAuthenticatedProcessingFilter() {
136+
@Override
137+
protected @Nullable Object getPreAuthenticatedCredentials(HttpServletRequest request) {
138+
return "password";
139+
}
140+
141+
@Override
142+
protected @Nullable Object getPreAuthenticatedPrincipal(HttpServletRequest request) {
143+
return "user";
144+
}
145+
};
146+
result.setRequiresAuthenticationRequestMatcher(AnyRequestMatcher.INSTANCE);
147+
result.setAuthenticationManager(authenticationManager);
148+
return result;
149+
}
150+
151+
@Bean
152+
static BasicAuthenticationFilter basicAuthenticationFilter(AuthenticationManager authenticationManager) {
153+
return new BasicAuthenticationFilter(authenticationManager);
154+
}
155+
156+
}
157+
158+
}

0 commit comments

Comments
 (0)