Skip to content
This repository was archived by the owner on May 14, 2025. It is now read-only.

Commit 88a422e

Browse files
ghillertilayaperumalg
authored andcommitted
gh-1294 Add support for OAUTH authorization
* Add role support to OAuth2-enabled security - Uses same rules (matchers) as traditional security * Add `DefaultDataflowAuthoritiesExtractor` - Assigns all roles by default - Allows for user-customization, if providing own `AuthoritiesExtractor` bean * Add tests resolves #1294 gh-1294 Add documentation gh-1294 Polishing gh-1294 Polishing
1 parent e406a98 commit 88a422e

File tree

10 files changed

+325
-103
lines changed

10 files changed

+325
-103
lines changed

spring-cloud-dataflow-docs/src/main/asciidoc/configuration.adoc

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -699,10 +699,6 @@ security:
699699

700700
<1> Providing the Client Id in the OAuth Configuration Section will activate OAuth2 security
701701

702-
NOTE: As of the current version, Spring Cloud Data Flow does not provide
703-
finer-grained authorization when OAUTH is used as authentication mechanism. Thus,
704-
once you are logged in, you have full access to all functionality.
705-
706702
You can verify that basic authentication is working properly using _curl_:
707703

708704
[source,bash]
@@ -721,6 +717,28 @@ the REST Api using the **Authorization** Http header:
721717
$ curl -H "Authorization: Bearer <ACCESS_TOKEN>" http://localhost:9393/
722718
```
723719

720+
[[configuration-security-oauth2-authorization]]
721+
==== OAuth REST Endpoint Authorization
722+
723+
The OAuth2 authentication option uses the same authorization rules as used by the
724+
<<configuration-security-basic-authentication, Traditional Authentication>> option.
725+
726+
[TIP]
727+
====
728+
The authorization rules are defined in `dataflow-server-defaults.yml` (Part of
729+
the Spring Cloud Data Flow Core Module). Please see the chapter on
730+
<<customizing-authorization, customizing authorization>> for more details.
731+
====
732+
733+
Due to fact that the determination of security roles is very environment-specific,
734+
_Spring Cloud Data Flow_ will by default assign all roles to authenticated OAuth2
735+
users using the `DefaultDataflowAuthoritiesExtractor` class.
736+
737+
You can customize that behavior by providing your own Spring bean definition that
738+
extends Spring Security OAuth's `AuthoritiesExtractor` interface. In that case,
739+
the custom bean definition will take precedence over the default one provided by
740+
_Spring Cloud Data Flow_
741+
724742
[[configuration-security-oauth2-shell]]
725743
==== OAuth Authentication using the Spring Cloud Data Flow Shell
726744

spring-cloud-dataflow-server-core/src/main/java/org/springframework/cloud/dataflow/server/config/DataFlowControllerAutoConfiguration.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
import org.springframework.cloud.dataflow.registry.RdbmsUriRegistry;
4343
import org.springframework.cloud.dataflow.server.config.apps.CommonApplicationProperties;
4444
import org.springframework.cloud.dataflow.server.config.features.FeaturesProperties;
45-
import org.springframework.cloud.dataflow.server.config.security.BasicAuthSecurityConfiguration.AuthorizationConfig;
45+
import org.springframework.cloud.dataflow.server.config.security.AuthorizationConfig;
4646
import org.springframework.cloud.dataflow.server.config.security.support.OnSecurityEnabledAndOAuth2Disabled;
4747
import org.springframework.cloud.dataflow.server.config.security.support.SecurityStateBean;
4848
import org.springframework.cloud.dataflow.server.controller.AboutController;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
* Copyright 2016-2017 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+
* http://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+
package org.springframework.cloud.dataflow.server.config.security;
17+
18+
import java.util.ArrayList;
19+
import java.util.List;
20+
21+
import org.springframework.boot.context.properties.ConfigurationProperties;
22+
import org.springframework.cloud.dataflow.core.DataFlowPropertyKeys;
23+
24+
/**
25+
* Holds configuration for the authorization aspects of security.
26+
*
27+
* @author Eric Bottard
28+
* @author Gunnar Hillert
29+
*/
30+
@ConfigurationProperties(prefix = DataFlowPropertyKeys.PREFIX + "security.authorization")
31+
public class AuthorizationConfig {
32+
33+
private boolean enabled = true;
34+
35+
private List<String> rules = new ArrayList<>();
36+
37+
public List<String> getRules() {
38+
return rules;
39+
}
40+
41+
public void setRules(List<String> rules) {
42+
this.rules = rules;
43+
}
44+
45+
public boolean isEnabled() {
46+
return enabled;
47+
}
48+
49+
public void setEnabled(boolean enabled) {
50+
this.enabled = enabled;
51+
}
52+
53+
}

spring-cloud-dataflow-server-core/src/main/java/org/springframework/cloud/dataflow/server/config/security/BasicAuthSecurityConfiguration.java

Lines changed: 2 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -15,23 +15,14 @@
1515
*/
1616
package org.springframework.cloud.dataflow.server.config.security;
1717

18-
import java.util.ArrayList;
19-
import java.util.List;
20-
import java.util.regex.Matcher;
21-
import java.util.regex.Pattern;
22-
23-
import org.slf4j.LoggerFactory;
24-
2518
import org.springframework.beans.factory.annotation.Autowired;
2619
import org.springframework.boot.autoconfigure.security.SecurityProperties;
27-
import org.springframework.boot.context.properties.ConfigurationProperties;
28-
import org.springframework.cloud.dataflow.core.DataFlowPropertyKeys;
2920
import org.springframework.cloud.dataflow.server.config.security.support.OnSecurityEnabledAndOAuth2Disabled;
21+
import org.springframework.cloud.dataflow.server.config.security.support.SecurityConfigUtils;
3022
import org.springframework.cloud.dataflow.server.config.security.support.SecurityStateBean;
3123
import org.springframework.context.annotation.Bean;
3224
import org.springframework.context.annotation.Conditional;
3325
import org.springframework.context.annotation.Configuration;
34-
import org.springframework.http.HttpMethod;
3526
import org.springframework.http.MediaType;
3627
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
3728
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
@@ -50,8 +41,6 @@
5041
import org.springframework.session.SessionRepository;
5142
import org.springframework.session.web.http.HeaderHttpSessionStrategy;
5243
import org.springframework.session.web.http.SessionRepositoryFilter;
53-
import org.springframework.util.Assert;
54-
import org.springframework.util.StringUtils;
5544
import org.springframework.web.accept.ContentNegotiationStrategy;
5645

5746
import static org.springframework.cloud.dataflow.server.controller.UiController.dashboard;
@@ -71,15 +60,6 @@
7160
@EnableWebSecurity
7261
public class BasicAuthSecurityConfiguration extends WebSecurityConfigurerAdapter {
7362

74-
public static final Pattern AUTHORIZATION_RULE;
75-
76-
private static final org.slf4j.Logger logger = LoggerFactory.getLogger(BasicAuthSecurityConfiguration.class);
77-
78-
static {
79-
String methodsRegex = StringUtils.arrayToDelimitedString(HttpMethod.values(), "|");
80-
AUTHORIZATION_RULE = Pattern.compile("(" + methodsRegex + ")\\s+(.+)\\s+=>\\s+(.+)");
81-
}
82-
8363
@Autowired
8464
private ContentNegotiationStrategy contentNegotiationStrategy;
8565

@@ -114,7 +94,7 @@ protected void configure(HttpSecurity http) throws Exception {
11494
.permitAll();
11595

11696
if (authorizationConfig.isEnabled()) {
117-
security = configureSimpleSecurity(security);
97+
security = SecurityConfigUtils.configureSimpleSecurity(security, authorizationConfig);
11898
}
11999

120100
security.and().formLogin().loginPage(loginPage).loginProcessingUrl(dashboard("/login"))
@@ -143,55 +123,4 @@ protected void configure(HttpSecurity http) throws Exception {
143123
securityStateBean.setAuthorizationEnabled(true);
144124
}
145125

146-
/**
147-
* Read the configuration for "simple" (that is, not ACL based) security and apply it.
148-
*/
149-
private ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry configureSimpleSecurity(
150-
ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry security) {
151-
for (String rule : authorizationConfig.getRules()) {
152-
Matcher matcher = AUTHORIZATION_RULE.matcher(rule);
153-
Assert.isTrue(matcher.matches(),
154-
String.format("Unable to parse security rule [%s], expected format is 'HTTP_METHOD ANT_PATTERN => "
155-
+ "SECURITY_ATTRIBUTE(S)'", rule));
156-
157-
HttpMethod method = HttpMethod.valueOf(matcher.group(1).trim());
158-
String urlPattern = matcher.group(2).trim();
159-
String attribute = matcher.group(3).trim();
160-
161-
logger.info("Authorization '{}' | '{}' | '{}'", method, attribute, urlPattern);
162-
security = security.antMatchers(method, urlPattern).access(attribute);
163-
}
164-
return security;
165-
}
166-
167-
/**
168-
* Holds configuration for the authorization aspects of security.
169-
*
170-
* @author Eric Bottard
171-
* @author Gunnar Hillert
172-
*/
173-
@ConfigurationProperties(prefix = DataFlowPropertyKeys.PREFIX + "security.authorization")
174-
public static class AuthorizationConfig {
175-
176-
private boolean enabled = true;
177-
178-
private List<String> rules = new ArrayList<>();
179-
180-
public List<String> getRules() {
181-
return rules;
182-
}
183-
184-
public void setRules(List<String> rules) {
185-
this.rules = rules;
186-
}
187-
188-
public boolean isEnabled() {
189-
return enabled;
190-
}
191-
192-
public void setEnabled(boolean enabled) {
193-
this.enabled = enabled;
194-
}
195-
196-
}
197126
}

spring-cloud-dataflow-server-core/src/main/java/org/springframework/cloud/dataflow/server/config/security/OAuthSecurityConfiguration.java

Lines changed: 34 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,9 @@
2828
import org.springframework.boot.autoconfigure.security.SecurityProperties;
2929
import org.springframework.boot.autoconfigure.security.oauth2.resource.ResourceServerProperties;
3030
import org.springframework.boot.autoconfigure.security.oauth2.resource.UserInfoTokenServices;
31+
import org.springframework.cloud.dataflow.server.config.security.support.DefaultDataflowAuthoritiesExtractor;
3132
import org.springframework.cloud.dataflow.server.config.security.support.OnSecurityEnabledAndOAuth2Enabled;
33+
import org.springframework.cloud.dataflow.server.config.security.support.SecurityConfigUtils;
3234
import org.springframework.cloud.dataflow.server.config.security.support.SecurityStateBean;
3335
import org.springframework.cloud.dataflow.server.service.impl.ManualOAuthAuthenticationProvider;
3436
import org.springframework.context.ApplicationEventPublisher;
@@ -44,6 +46,7 @@
4446
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
4547
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
4648
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
49+
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
4750
import org.springframework.security.oauth2.client.OAuth2ClientContext;
4851
import org.springframework.security.oauth2.client.OAuth2RestTemplate;
4952
import org.springframework.security.oauth2.client.filter.OAuth2AuthenticationFailureEvent;
@@ -97,6 +100,9 @@ public class OAuthSecurityConfiguration extends WebSecurityConfigurerAdapter {
97100
@Autowired
98101
private ApplicationEventPublisher applicationEventPublisher;
99102

103+
@Autowired
104+
private AuthorizationConfig authorizationConfig;
105+
100106
@Override
101107
protected void configure(HttpSecurity http) throws Exception {
102108

@@ -116,13 +122,28 @@ protected void configure(HttpSecurity http) throws Exception {
116122
http.addFilterBefore(basicAuthenticationFilter, oauthFilter.getClass());
117123
http.addFilterBefore(oAuth2AuthenticationProcessingFilter(), basicAuthenticationFilter.getClass());
118124

119-
http.authorizeRequests()
120-
.antMatchers(
121-
"/security/info**", "/login**", dashboard("/logout-success-oauth.html"),
122-
dashboard("/styles/**"), dashboard("/images/**"), dashboard("/fonts/**"), dashboard("/lib/**"))
123-
.permitAll().anyRequest()
124-
.authenticated().and()
125-
.httpBasic().and()
125+
ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry security =
126+
127+
http.authorizeRequests()
128+
.antMatchers(
129+
"/favicon.ico",
130+
"/security/info**", "/login**", dashboard("/logout-success-oauth.html"),
131+
dashboard("/styles/**"), dashboard("/images/**"), "/assets/**", dashboard("/fonts/**"),
132+
dashboard("/lib/**"))
133+
.permitAll()
134+
.antMatchers("/", dashboard("/**"), "/dashboard", "/features").authenticated();
135+
136+
if (authorizationConfig.isEnabled()) {
137+
security = SecurityConfigUtils.configureSimpleSecurity(security, authorizationConfig);
138+
security.anyRequest().denyAll();
139+
securityStateBean.setAuthorizationEnabled(true);
140+
}
141+
else {
142+
security.anyRequest().authenticated();
143+
securityStateBean.setAuthorizationEnabled(false);
144+
}
145+
146+
http.httpBasic().and()
126147
.logout()
127148
.logoutSuccessUrl(dashboard("/logout-success-oauth.html"))
128149
.and().csrf().disable()
@@ -131,16 +152,15 @@ protected void configure(HttpSecurity http) throws Exception {
131152
.defaultAuthenticationEntryPointFor(basicAuthenticationEntryPoint, AnyRequestMatcher.INSTANCE);
132153

133154
securityStateBean.setAuthenticationEnabled(true);
134-
securityStateBean.setAuthorizationEnabled(false);
135155
}
136156

137157
@Bean
138158
public UserInfoTokenServices tokenServices() {
139159
final UserInfoTokenServices tokenServices = new UserInfoTokenServices(resourceServerProperties.getUserInfoUri(),
140160
authorizationCodeResourceDetails.getClientId());
141161
tokenServices.setRestTemplate(oAuth2RestTemplate());
162+
tokenServices.setAuthoritiesExtractor(new DefaultDataflowAuthoritiesExtractor());
142163
return tokenServices;
143-
144164
}
145165

146166
@Bean
@@ -189,12 +209,14 @@ public AuthenticationManager oauthAuthenticationManager() {
189209
@EventListener
190210
public void handleOAuth2AuthenticationFailureEvent(
191211
OAuth2AuthenticationFailureEvent oAuth2AuthenticationFailureEvent) {
192-
final int throwableIdex = ExceptionUtils.indexOfThrowable(oAuth2AuthenticationFailureEvent.getException(),
193-
ResourceAccessException.class);
194-
if (throwableIdex > -1) {
212+
final int throwableIdexForResourceAccessException = ExceptionUtils
213+
.indexOfThrowable(oAuth2AuthenticationFailureEvent.getException(), ResourceAccessException.class);
214+
215+
if (throwableIdexForResourceAccessException > -1) {
195216
logger.error("An error ocurred while accessing an authentication REST resource.",
196217
oAuth2AuthenticationFailureEvent.getException());
197218
}
219+
198220
}
199221

200222
private static class BrowserDetectingContentNegotiationStrategy extends HeaderContentNegotiationStrategy {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
* Copyright 2017 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+
* http://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+
package org.springframework.cloud.dataflow.server.config.security.support;
17+
18+
import java.util.ArrayList;
19+
import java.util.List;
20+
import java.util.Map;
21+
import java.util.stream.Collectors;
22+
import java.util.stream.Stream;
23+
24+
import org.slf4j.LoggerFactory;
25+
26+
import org.springframework.boot.autoconfigure.security.oauth2.resource.AuthoritiesExtractor;
27+
import org.springframework.security.config.core.GrantedAuthorityDefaults;
28+
import org.springframework.security.core.GrantedAuthority;
29+
import org.springframework.security.core.authority.SimpleGrantedAuthority;
30+
import org.springframework.util.Assert;
31+
import org.springframework.util.StringUtils;
32+
33+
/**
34+
* Default Spring Cloud Data Flow {@link AuthoritiesExtractor}. Will assign ALL
35+
* {@link CoreSecurityRoles} to the authenticated OAuth2 user.
36+
*
37+
* @author Gunnar Hillert
38+
*
39+
*/
40+
public class DefaultDataflowAuthoritiesExtractor implements AuthoritiesExtractor {
41+
42+
private static final org.slf4j.Logger logger = LoggerFactory.getLogger(DefaultDataflowAuthoritiesExtractor.class);
43+
44+
/**
45+
* The returned {@link List} of {@link GrantedAuthority}s contains all roles from
46+
* {@link CoreSecurityRoles}. The roles are prefixed with the value specified in
47+
* {@link GrantedAuthorityDefaults}.
48+
*
49+
*
50+
* @param Must not be null. Is only used for logging
51+
*/
52+
@Override
53+
public List<GrantedAuthority> extractAuthorities(Map<String, Object> map) {
54+
Assert.notNull(map, "The map argument must not be null.");
55+
56+
final List<String> rolesAsStrings = new ArrayList<>();
57+
final List<GrantedAuthority> grantedAuthorities =
58+
Stream.of(CoreSecurityRoles.values())
59+
.map(roleEnum -> {
60+
final String roleName = SecurityConfigUtils.ROLE_PREFIX + roleEnum.getKey();
61+
rolesAsStrings.add(roleName);
62+
return new SimpleGrantedAuthority(roleName);
63+
})
64+
.collect(Collectors.toList());
65+
logger.info("Adding ALL roles {} to user {}", StringUtils.collectionToCommaDelimitedString(rolesAsStrings), map);
66+
return grantedAuthorities;
67+
}
68+
}

0 commit comments

Comments
 (0)