Skip to content

Commit e85691e

Browse files
committed
Support dual stateful/stateless security configs
Introduce four SecurityFilterChain beans: - JWT (stateless): handles JWT-based requests - Header (stateless): handles x-forwarded-user based requests - refactored to be moved out from stateful - Stateful (old): handles session-based requests - Fallback: applies when no chain matches and always returns 401. Meant to block if no other authentication is set up
1 parent 00a82d2 commit e85691e

File tree

7 files changed

+180
-99
lines changed

7 files changed

+180
-99
lines changed
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package com.box.l10n.mojito.security;
2+
3+
import com.box.l10n.mojito.security.SecurityConfig.AuthenticationType;
4+
import java.util.Arrays;
5+
import java.util.Set;
6+
import java.util.stream.Collectors;
7+
import org.springframework.context.annotation.Condition;
8+
import org.springframework.context.annotation.ConditionContext;
9+
import org.springframework.core.type.AnnotatedTypeMetadata;
10+
11+
public class AuthTypesCondition implements Condition {
12+
13+
private static final String KEY = "l10n.security.authenticationType";
14+
15+
@Override
16+
public boolean matches(ConditionContext ctx, AnnotatedTypeMetadata md) {
17+
String raw = ctx.getEnvironment().getProperty(KEY, "");
18+
Set<String> types =
19+
Arrays.stream(raw.split(","))
20+
.map(String::trim)
21+
.filter(s -> !s.isEmpty())
22+
.map(String::toUpperCase)
23+
.collect(Collectors.toSet());
24+
25+
var attrs = md.getAnnotationAttributes(ConditionalOnAuthTypes.class.getName());
26+
AuthenticationType[] required = (AuthenticationType[]) attrs.get("anyOf");
27+
boolean matchIfMissing = (boolean) attrs.get("matchIfMissing");
28+
29+
if (types.isEmpty()) {
30+
return matchIfMissing;
31+
}
32+
return Arrays.stream(required).map(Enum::name).anyMatch(types::contains);
33+
}
34+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.box.l10n.mojito.security;
2+
3+
import java.lang.annotation.*;
4+
import org.springframework.context.annotation.Conditional;
5+
6+
@Target({ElementType.TYPE, ElementType.METHOD})
7+
@Retention(RetentionPolicy.RUNTIME)
8+
@Documented
9+
@Conditional(AuthTypesCondition.class)
10+
public @interface ConditionalOnAuthTypes {
11+
SecurityConfig.AuthenticationType[] anyOf() default {};
12+
13+
boolean matchIfMissing() default false;
14+
}

webapp/src/main/java/com/box/l10n/mojito/security/SecurityConfig.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,8 @@ public enum AuthenticationType {
6464
DATABASE,
6565
AD,
6666
HEADER,
67-
OAUTH2
67+
OAUTH2,
68+
JWT
6869
}
6970

7071
public static class OAuth2 {

webapp/src/main/java/com/box/l10n/mojito/security/WebSecurityConfig.java

Lines changed: 9 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,15 @@
22

33
import com.box.l10n.mojito.ActuatorHealthLegacyConfig;
44
import com.box.l10n.mojito.service.security.user.UserService;
5-
import com.google.common.base.Preconditions;
65
import java.util.ArrayList;
76
import java.util.List;
87
import org.slf4j.Logger;
98
import org.slf4j.LoggerFactory;
109
import org.springframework.beans.factory.annotation.Autowired;
11-
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
1210
import org.springframework.context.annotation.AdviceMode;
1311
import org.springframework.context.annotation.Bean;
1412
import org.springframework.context.annotation.Configuration;
13+
import org.springframework.core.annotation.Order;
1514
import org.springframework.http.HttpMethod;
1615
import org.springframework.http.HttpStatus;
1716
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
@@ -25,9 +24,6 @@
2524
import org.springframework.security.web.SecurityFilterChain;
2625
import org.springframework.security.web.authentication.HttpStatusEntryPoint;
2726
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
28-
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider;
29-
import org.springframework.security.web.authentication.preauth.RequestHeaderAuthenticationFilter;
30-
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
3127
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
3228

3329
/**
@@ -37,9 +33,13 @@
3733
// TOOD(spring2) we don't use method level security, do we? remove?
3834
@EnableGlobalMethodSecurity(securedEnabled = true, mode = AdviceMode.ASPECTJ)
3935
@Configuration
40-
@ConditionalOnProperty(
41-
value = "l10n.security.stateless.enabled",
42-
havingValue = "false",
36+
@ConditionalOnAuthTypes(
37+
anyOf = {
38+
SecurityConfig.AuthenticationType.AD,
39+
SecurityConfig.AuthenticationType.DATABASE,
40+
SecurityConfig.AuthenticationType.LDAP,
41+
SecurityConfig.AuthenticationType.OAUTH2
42+
},
4343
matchIfMissing = true)
4444
public class WebSecurityConfig {
4545

@@ -60,12 +60,6 @@ public class WebSecurityConfig {
6060

6161
@Autowired UserDetailsContextMapperImpl userDetailsContextMapperImpl;
6262

63-
@Autowired(required = false)
64-
RequestHeaderAuthenticationFilter requestHeaderAuthenticationFilter;
65-
66-
@Autowired(required = false)
67-
PreAuthenticatedAuthenticationProvider preAuthenticatedAuthenticationProvider;
68-
6963
@Autowired UserService userService;
7064

7165
@Autowired UserDetailsServiceImpl userDetailsService;
@@ -84,9 +78,6 @@ public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception
8478
case AD:
8579
configureActiveDirectory(auth);
8680
break;
87-
case HEADER:
88-
configureHeaderAuth(auth);
89-
break;
9081
}
9182
}
9283
}
@@ -134,14 +125,6 @@ void configureLdap(AuthenticationManagerBuilder auth) throws Exception {
134125
.ldif(ldapConfig.getLdif());
135126
}
136127

137-
void configureHeaderAuth(AuthenticationManagerBuilder auth) {
138-
Preconditions.checkNotNull(
139-
preAuthenticatedAuthenticationProvider,
140-
"The preAuthenticatedAuthenticationProvider must be configured");
141-
logger.debug("Configuring in pre authentication");
142-
auth.authenticationProvider(preAuthenticatedAuthenticationProvider);
143-
}
144-
145128
static void setAuthorizationRequests(HttpSecurity http, List<String> extraPermitAllPatterns)
146129
throws Exception {
147130

@@ -216,6 +199,7 @@ static void setAuthorizationRequests(HttpSecurity http, List<String> extraPermit
216199
}
217200

218201
@Bean
202+
@Order(3)
219203
public SecurityFilterChain configure(HttpSecurity http) throws Exception {
220204
logger.debug("Configuring web security");
221205

@@ -249,15 +233,6 @@ public SecurityFilterChain configure(HttpSecurity http) throws Exception {
249233
for (SecurityConfig.AuthenticationType authenticationType :
250234
securityConfig.getAuthenticationType()) {
251235
switch (authenticationType) {
252-
case HEADER:
253-
Preconditions.checkNotNull(
254-
requestHeaderAuthenticationFilter,
255-
"The requestHeaderAuthenticationFilter must be configured");
256-
logger.debug("Add request header Auth filter");
257-
requestHeaderAuthenticationFilter.setAuthenticationManager(
258-
authenticationConfiguration.getAuthenticationManager());
259-
http.addFilterBefore(requestHeaderAuthenticationFilter, BasicAuthenticationFilter.class);
260-
break;
261236
case OAUTH2:
262237
logger.debug("Configure OAuth2");
263238
http.oauth2Login(
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package com.box.l10n.mojito.security;
2+
3+
import org.slf4j.Logger;
4+
import org.slf4j.LoggerFactory;
5+
import org.springframework.context.annotation.Bean;
6+
import org.springframework.context.annotation.Configuration;
7+
import org.springframework.core.annotation.Order;
8+
import org.springframework.http.HttpStatus;
9+
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
10+
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
11+
import org.springframework.security.web.SecurityFilterChain;
12+
import org.springframework.security.web.authentication.HttpStatusEntryPoint;
13+
14+
@EnableWebSecurity
15+
@Configuration
16+
public class WebSecurityFallbackConfig {
17+
18+
/** logger */
19+
static Logger logger = LoggerFactory.getLogger(WebSecurityFallbackConfig.class);
20+
21+
@Bean
22+
@Order(99)
23+
SecurityFilterChain securityFallbackBlock(HttpSecurity http) throws Exception {
24+
WebSecurityJWTConfig.applyStatelessSharedConfig(http);
25+
HttpStatusEntryPoint httpStatusEntryPoint = new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED);
26+
http.exceptionHandling(
27+
e -> {
28+
e.authenticationEntryPoint(httpStatusEntryPoint);
29+
});
30+
return http.build();
31+
}
32+
}
Lines changed: 44 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,66 @@
11
package com.box.l10n.mojito.security;
22

3-
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
43
import org.springframework.context.annotation.Bean;
54
import org.springframework.context.annotation.Configuration;
6-
import org.springframework.security.authentication.AuthenticationManager;
7-
import org.springframework.security.core.Authentication;
8-
import org.springframework.security.core.AuthenticationException;
5+
import org.springframework.core.annotation.Order;
6+
import org.springframework.security.authentication.ProviderManager;
7+
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
98
import org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper;
9+
import org.springframework.security.oauth2.server.resource.web.authentication.BearerTokenAuthenticationFilter;
10+
import org.springframework.security.web.SecurityFilterChain;
1011
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider;
12+
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
1113
import org.springframework.security.web.authentication.preauth.RequestHeaderAuthenticationFilter;
1214

13-
// This must in sync with {@link
14-
// com.box.l10n.mojito.security.SecurityConfig.AuthenticationType#HEADER}
15-
@ConditionalOnExpression("'${l10n.security.authenticationType:}'.toUpperCase().contains('HEADER')")
15+
@ConditionalOnAuthTypes(anyOf = SecurityConfig.AuthenticationType.HEADER)
1616
@Configuration
1717
class WebSecurityHeaderConfig {
1818

19+
public static final String X_FORWARDED_USER = "x-forwarded-user";
20+
1921
@Bean
20-
RequestHeaderAuthenticationFilter requestHeaderAuthenticationFilter() throws Exception {
21-
RequestHeaderAuthenticationFilter requestHeaderAuthenticationFilter =
22-
new RequestHeaderAuthenticationFilter();
23-
requestHeaderAuthenticationFilter.setPrincipalRequestHeader("x-forwarded-user");
24-
requestHeaderAuthenticationFilter.setExceptionIfHeaderMissing(false);
25-
requestHeaderAuthenticationFilter.setAuthenticationManager(
26-
new AuthenticationManager() {
27-
@Override
28-
public Authentication authenticate(Authentication authentication)
29-
throws AuthenticationException {
30-
throw new RuntimeException(
31-
"This must be overridden with the actual authentication manager of the app - it is not injectable yet with this config style");
32-
}
33-
});
34-
35-
return requestHeaderAuthenticationFilter;
22+
PreAuthenticatedAuthenticationProvider preAuthenticatedAuthenticationProvider(
23+
UserDetailsServiceCreatePartialImpl uds) {
24+
25+
UserDetailsByNameServiceWrapper<PreAuthenticatedAuthenticationToken> wrapper =
26+
new UserDetailsByNameServiceWrapper<>(uds);
27+
28+
PreAuthenticatedAuthenticationProvider p = new PreAuthenticatedAuthenticationProvider();
29+
p.setPreAuthenticatedUserDetailsService(wrapper);
30+
return p;
3631
}
3732

3833
@Bean
39-
PreAuthenticatedAuthenticationProvider preAuthenticatedAuthenticationProvider() {
40-
PreAuthenticatedAuthenticationProvider preAuthenticatedAuthenticationProvider =
41-
new PreAuthenticatedAuthenticationProvider();
42-
UserDetailsByNameServiceWrapper userDetailsByNameServiceWrapper =
43-
new UserDetailsByNameServiceWrapper(getUserDetailsServiceCreatePartial());
44-
preAuthenticatedAuthenticationProvider.setPreAuthenticatedUserDetailsService(
45-
userDetailsByNameServiceWrapper);
46-
return preAuthenticatedAuthenticationProvider;
34+
RequestHeaderAuthenticationFilter requestHeaderAuthenticationFilter(
35+
PreAuthenticatedAuthenticationProvider preAuthenticatedAuthenticationProvider) {
36+
RequestHeaderAuthenticationFilter f = new RequestHeaderAuthenticationFilter();
37+
f.setPrincipalRequestHeader(X_FORWARDED_USER);
38+
f.setCredentialsRequestHeader(X_FORWARDED_USER);
39+
f.setExceptionIfHeaderMissing(false);
40+
f.setAuthenticationManager(new ProviderManager(preAuthenticatedAuthenticationProvider));
41+
return f;
4742
}
4843

4944
@Bean
5045
protected UserDetailsServiceCreatePartialImpl getUserDetailsServiceCreatePartial() {
5146
return new UserDetailsServiceCreatePartialImpl();
5247
}
48+
49+
@Bean
50+
@Order(1)
51+
SecurityFilterChain headerPreAuthenticated(
52+
HttpSecurity http,
53+
RequestHeaderAuthenticationFilter headerFilter,
54+
PreAuthenticatedAuthenticationProvider preauthProvider)
55+
throws Exception {
56+
57+
http.securityMatcher(req -> req.getHeader(X_FORWARDED_USER) != null);
58+
59+
WebSecurityJWTConfig.applyStatelessSharedConfig(http);
60+
61+
http.authenticationProvider(preauthProvider);
62+
http.addFilterBefore(headerFilter, BearerTokenAuthenticationFilter.class);
63+
64+
return http.build();
65+
}
5366
}

0 commit comments

Comments
 (0)