1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.syncope.sra;
20
21 import java.io.InputStream;
22 import java.security.KeyStore;
23 import java.security.PrivateKey;
24 import java.security.cert.X509Certificate;
25 import java.text.ParseException;
26 import java.util.Map;
27 import org.apache.commons.lang3.StringUtils;
28 import org.apache.syncope.common.lib.types.IdRepoEntitlement;
29 import org.apache.syncope.common.lib.types.SAML2BindingType;
30 import org.apache.syncope.sra.security.CsrfRouteMatcher;
31 import org.apache.syncope.sra.security.LogoutRouteMatcher;
32 import org.apache.syncope.sra.security.PublicRouteMatcher;
33 import org.apache.syncope.sra.security.cas.CASSecurityConfigUtils;
34 import org.apache.syncope.sra.security.oauth2.OAuth2SecurityConfigUtils;
35 import org.apache.syncope.sra.security.pac4j.NoOpLogoutHandler;
36 import org.apache.syncope.sra.security.saml2.SAML2MetadataEndpoint;
37 import org.apache.syncope.sra.security.saml2.SAML2SecurityConfigUtils;
38 import org.apache.syncope.sra.security.saml2.SAML2WebSsoAuthenticationWebFilter;
39 import org.pac4j.core.http.callback.NoParameterCallbackUrlResolver;
40 import org.pac4j.saml.client.SAML2Client;
41 import org.pac4j.saml.config.SAML2Configuration;
42 import org.pac4j.saml.metadata.keystore.BaseSAML2KeystoreGenerator;
43 import org.springframework.beans.factory.ObjectProvider;
44 import org.springframework.beans.factory.annotation.Qualifier;
45 import org.springframework.boot.actuate.autoconfigure.security.reactive.EndpointRequest;
46 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
47 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
48 import org.springframework.cache.CacheManager;
49 import org.springframework.context.ConfigurableApplicationContext;
50 import org.springframework.context.annotation.Bean;
51 import org.springframework.context.annotation.Configuration;
52 import org.springframework.core.annotation.Order;
53 import org.springframework.core.convert.converter.Converter;
54 import org.springframework.core.io.FileUrlResource;
55 import org.springframework.core.io.support.ResourcePatternResolver;
56 import org.springframework.http.HttpMethod;
57 import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
58 import org.springframework.security.config.web.server.ServerHttpSecurity;
59 import org.springframework.security.core.userdetails.MapReactiveUserDetailsService;
60 import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
61 import org.springframework.security.core.userdetails.User;
62 import org.springframework.security.core.userdetails.UserDetails;
63 import org.springframework.security.oauth2.client.registration.ClientRegistration;
64 import org.springframework.security.oauth2.client.registration.ClientRegistrations;
65 import org.springframework.security.oauth2.client.registration.InMemoryReactiveClientRegistrationRepository;
66 import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
67 import org.springframework.security.oauth2.core.AuthorizationGrantType;
68 import org.springframework.security.oauth2.core.OAuth2TokenValidator;
69 import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
70 import org.springframework.security.oauth2.jwt.Jwt;
71 import org.springframework.security.oauth2.jwt.JwtValidators;
72 import org.springframework.security.oauth2.jwt.MappedJwtClaimSetConverter;
73 import org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder;
74 import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
75 import org.springframework.security.web.server.SecurityWebFilterChain;
76 import org.springframework.security.web.server.util.matcher.NegatedServerWebExchangeMatcher;
77 import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher;
78 import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers;
79 import reactor.core.publisher.Mono;
80
81 @EnableWebFluxSecurity
82 @Configuration(proxyBeanMethods = false)
83 public class SecurityConfig {
84
85 @Bean
86 @Order(0)
87 @ConditionalOnProperty(prefix = SRAProperties.PREFIX, name = SRAProperties.AM_TYPE, havingValue = "SAML2")
88 public SecurityWebFilterChain saml2SecurityFilterChain(final ServerHttpSecurity http) {
89 ServerWebExchangeMatcher metadataMatcher =
90 ServerWebExchangeMatchers.pathMatchers(HttpMethod.GET, SAML2MetadataEndpoint.METADATA_URL);
91 return http.securityMatcher(metadataMatcher).
92 authorizeExchange().anyExchange().permitAll().
93 and().csrf().requireCsrfProtectionMatcher(new NegatedServerWebExchangeMatcher(metadataMatcher)).
94 and().build();
95 }
96
97 @ConditionalOnMissingBean
98 @Bean
99 @Order(1)
100 public SecurityWebFilterChain actuatorSecurityFilterChain(final ServerHttpSecurity http) {
101 ServerWebExchangeMatcher actuatorMatcher = EndpointRequest.toAnyEndpoint();
102 return http.securityMatcher(actuatorMatcher).
103 authorizeExchange().anyExchange().authenticated().
104 and().httpBasic().
105 and().csrf().requireCsrfProtectionMatcher(new NegatedServerWebExchangeMatcher(actuatorMatcher)).
106 and().build();
107 }
108
109 @ConditionalOnMissingBean
110 @Bean
111 public ReactiveUserDetailsService actuatorUserDetailsService(final SRAProperties props) {
112 UserDetails user = User.builder().
113 username(props.getAnonymousUser()).
114 password("{noop}" + props.getAnonymousKey()).
115 roles(IdRepoEntitlement.ANONYMOUS).
116 build();
117 return new MapReactiveUserDetailsService(user);
118 }
119
120 @Bean
121 @ConditionalOnProperty(prefix = SRAProperties.PREFIX, name = SRAProperties.AM_TYPE, havingValue = "OIDC")
122 public ClientRegistration oidcClientRegistration(final SRAProperties props) {
123 return ClientRegistrations.fromOidcIssuerLocation(props.getOidc().getConfiguration()).
124 registrationId(SRAProperties.AMType.OIDC.name()).
125 clientId(props.getOidc().getClientId()).
126 clientSecret(props.getOidc().getClientSecret()).
127 scope(props.getOidc().getScopes().toArray(String[]::new)).
128 build();
129 }
130
131 @Bean
132 @ConditionalOnProperty(prefix = SRAProperties.PREFIX, name = SRAProperties.AM_TYPE, havingValue = "OIDC")
133 public ReactiveClientRegistrationRepository oidcClientRegistrationRepository(
134 @Qualifier("oidcClientRegistration") final ClientRegistration oidcClientRegistration) {
135 return new InMemoryReactiveClientRegistrationRepository(oidcClientRegistration);
136 }
137
138 @Bean
139 @ConditionalOnMissingBean
140 @ConditionalOnProperty(prefix = SRAProperties.PREFIX, name = SRAProperties.AM_TYPE, havingValue = "OIDC")
141 public OAuth2TokenValidator<Jwt> oidcJWTValidator(final SRAProperties props) {
142 return JwtValidators.createDefaultWithIssuer(props.getOidc().getConfiguration());
143 }
144
145 @Bean
146 @ConditionalOnMissingBean
147 public Converter<Map<String, Object>, Map<String, Object>> jwtClaimSetConverter() {
148 return MappedJwtClaimSetConverter.withDefaults(Map.of());
149 }
150
151 @Bean
152 @ConditionalOnMissingBean
153 @ConditionalOnProperty(prefix = SRAProperties.PREFIX, name = SRAProperties.AM_TYPE, havingValue = "OIDC")
154 public ReactiveJwtDecoder oidcJWTDecoder(
155 @Qualifier("oidcClientRegistration")
156 final ClientRegistration oidcClientRegistration,
157 @Qualifier("oidcJWTValidator")
158 final OAuth2TokenValidator<Jwt> oidcJWTValidator,
159 @Qualifier("jwtClaimSetConverter")
160 final Converter<Map<String, Object>, Map<String, Object>> jwtClaimSetConverter) {
161 String jwkSetUri = oidcClientRegistration.getProviderDetails().getJwkSetUri();
162 NimbusReactiveJwtDecoder jwtDecoder = NimbusReactiveJwtDecoder.withJwkSetUri(jwkSetUri)
163 .jwsAlgorithm(SignatureAlgorithm.RS256)
164 .jwsAlgorithm(SignatureAlgorithm.RS512)
165 .build();
166 jwtDecoder.setJwtValidator(oidcJWTValidator);
167 jwtDecoder.setClaimSetConverter(jwtClaimSetConverter);
168 return jwtDecoder;
169 }
170
171 @Bean
172 @ConditionalOnProperty(prefix = SRAProperties.PREFIX, name = SRAProperties.AM_TYPE, havingValue = "OAUTH2")
173 public ClientRegistration oauth2ClientRegistration(final SRAProperties props) {
174 return ClientRegistration.withRegistrationId(SRAProperties.AMType.OAUTH2.name()).
175 redirectUri("{baseUrl}/{action}/oauth2/code/{registrationId}").
176 tokenUri(props.getOauth2().getTokenUri()).
177 authorizationUri(props.getOauth2().getAuthorizationUri()).
178 userInfoUri(props.getOauth2().getUserInfoUri()).
179 userNameAttributeName(props.getOauth2().getUserNameAttributeName()).
180 clientId(props.getOauth2().getClientId()).
181 clientSecret(props.getOauth2().getClientSecret()).
182 scope(props.getOauth2().getScopes().toArray(String[]::new)).
183 authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE).
184 jwkSetUri(props.getOauth2().getJwkSetUri()).
185 build();
186 }
187
188 @Bean
189 @ConditionalOnProperty(prefix = SRAProperties.PREFIX, name = SRAProperties.AM_TYPE, havingValue = "OAUTH2")
190 public ReactiveClientRegistrationRepository oauth2ClientRegistrationRepository(
191 @Qualifier("oauth2ClientRegistration") final ClientRegistration oauth2ClientRegistration) {
192 return new InMemoryReactiveClientRegistrationRepository(oauth2ClientRegistration);
193 }
194
195 @Bean
196 @ConditionalOnMissingBean
197 @ConditionalOnProperty(prefix = SRAProperties.PREFIX, name = SRAProperties.AM_TYPE, havingValue = "OAUTH2")
198 public OAuth2TokenValidator<Jwt> oauth2JWTValidator(final SRAProperties props) {
199 return props.getOauth2().getIssuer() == null
200 ? JwtValidators.createDefault()
201 : JwtValidators.createDefaultWithIssuer(props.getOauth2().getIssuer());
202 }
203
204 @Bean
205 @ConditionalOnMissingBean
206 @ConditionalOnProperty(prefix = SRAProperties.PREFIX, name = SRAProperties.AM_TYPE, havingValue = "OAUTH2")
207 public ReactiveJwtDecoder oauth2JWTDecoder(
208 @Qualifier("oauth2ClientRegistration")
209 final ClientRegistration oauth2ClientRegistration,
210 @Qualifier("oauth2JWTValidator")
211 final OAuth2TokenValidator<Jwt> oauth2JWTValidator,
212 @Qualifier("jwtClaimSetConverter")
213 final Converter<Map<String, Object>, Map<String, Object>> jwtClaimSetConverter) {
214
215 String jwkSetUri = oauth2ClientRegistration.getProviderDetails().getJwkSetUri();
216 NimbusReactiveJwtDecoder jwtDecoder;
217 if (StringUtils.isBlank(jwkSetUri)) {
218 jwtDecoder = new NimbusReactiveJwtDecoder(jwt -> {
219 try {
220 return Mono.just(jwt.getJWTClaimsSet());
221 } catch (ParseException e) {
222 return Mono.error(e);
223 }
224 });
225 } else {
226 jwtDecoder = NimbusReactiveJwtDecoder.withJwkSetUri(jwkSetUri).build();
227 }
228 jwtDecoder.setJwtValidator(oauth2JWTValidator);
229 jwtDecoder.setClaimSetConverter(jwtClaimSetConverter);
230 return jwtDecoder;
231 }
232
233 @Bean
234 @ConditionalOnMissingBean
235 @ConditionalOnProperty(prefix = SRAProperties.PREFIX, name = SRAProperties.AM_TYPE, havingValue = "SAML2")
236 public SAML2Client saml2Client(final ResourcePatternResolver resourceResolver,
237 final SRAProperties props) {
238 SAML2Configuration cfg = new SAML2Configuration(
239 resourceResolver.getResource(props.getSaml2().getKeystore()),
240 props.getSaml2().getKeystoreStorePass(),
241 props.getSaml2().getKeystoreKeypass(),
242 resourceResolver.getResource(props.getSaml2().getIdpMetadata()));
243
244 cfg.setKeystoreType(props.getSaml2().getKeystoreType());
245 if (cfg.getKeystoreResource() instanceof FileUrlResource) {
246 cfg.setKeystoreGenerator(new BaseSAML2KeystoreGenerator(cfg) {
247
248 @Override
249 protected void store(
250 final KeyStore ks,
251 final X509Certificate certificate,
252 final PrivateKey privateKey) throws Exception {
253
254
255 }
256
257 @Override
258 public InputStream retrieve() throws Exception {
259 return cfg.getKeystoreResource().getInputStream();
260 }
261 });
262 }
263
264 cfg.setAuthnRequestBindingType(props.getSaml2().getAuthnRequestBinding().getUri());
265 cfg.setResponseBindingType(SAML2BindingType.POST.getUri());
266 cfg.setSpLogoutRequestBindingType(props.getSaml2().getLogoutRequestBinding().getUri());
267 cfg.setSpLogoutResponseBindingType(props.getSaml2().getLogoutResponseBinding().getUri());
268
269 cfg.setServiceProviderEntityId(props.getSaml2().getEntityId());
270
271 cfg.setWantsAssertionsSigned(true);
272 cfg.setAuthnRequestSigned(true);
273 cfg.setSpLogoutRequestSigned(true);
274 cfg.setServiceProviderMetadataResourceFilepath(props.getSaml2().getSpMetadataFilePath());
275 cfg.setMaximumAuthenticationLifetime(props.getSaml2().getMaximumAuthenticationLifetime());
276 cfg.setAcceptedSkew(props.getSaml2().getAcceptedSkew());
277
278 cfg.setLogoutHandler(new NoOpLogoutHandler());
279
280 SAML2Client saml2Client = new SAML2Client(cfg);
281 saml2Client.setName(SRAProperties.AMType.SAML2.name());
282 saml2Client.setCallbackUrl(props.getSaml2().getEntityId()
283 + SAML2WebSsoAuthenticationWebFilter.FILTER_PROCESSES_URI);
284 saml2Client.setCallbackUrlResolver(new NoParameterCallbackUrlResolver());
285 saml2Client.init();
286
287 return saml2Client;
288 }
289
290 @Bean
291 @Order(2)
292 @ConditionalOnProperty(prefix = SRAProperties.PREFIX, name = SRAProperties.AM_TYPE)
293 public SecurityWebFilterChain routesSecurityFilterChain(
294 @Qualifier("saml2Client") final ObjectProvider<SAML2Client> saml2Client,
295 final SRAProperties props,
296 final ServerHttpSecurity http,
297 final CacheManager cacheManager,
298 final LogoutRouteMatcher logoutRouteMatcher,
299 final PublicRouteMatcher publicRouteMatcher,
300 final CsrfRouteMatcher csrfRouteMatcher,
301 final ConfigurableApplicationContext ctx) {
302
303 ServerHttpSecurity.AuthorizeExchangeSpec builder = http.authorizeExchange().
304 matchers(publicRouteMatcher).permitAll().
305 anyExchange().authenticated();
306
307 switch (props.getAmType()) {
308 case OIDC:
309 case OAUTH2:
310 OAuth2SecurityConfigUtils.forLogin(http, props.getAmType(), ctx);
311 OAuth2SecurityConfigUtils.forLogout(builder, props.getAmType(), cacheManager, logoutRouteMatcher, ctx);
312 http.oauth2ResourceServer().jwt().jwtDecoder(ctx.getBean(ReactiveJwtDecoder.class));
313 break;
314
315 case SAML2:
316 saml2Client.ifAvailable(client -> {
317 SAML2SecurityConfigUtils.forLogin(http, client, publicRouteMatcher);
318 SAML2SecurityConfigUtils.forLogout(builder, client, cacheManager, logoutRouteMatcher, ctx);
319 });
320 break;
321
322 case CAS:
323 CASSecurityConfigUtils.forLogin(
324 http,
325 props.getCas().getProtocol(),
326 props.getCas().getServerPrefix(),
327 publicRouteMatcher);
328 CASSecurityConfigUtils.forLogout(
329 builder,
330 cacheManager,
331 props.getCas().getServerPrefix(),
332 logoutRouteMatcher,
333 ctx);
334 break;
335
336 default:
337 }
338
339 return builder.and().csrf().requireCsrfProtectionMatcher(csrfRouteMatcher).and().build();
340 }
341 }