Feature hawkbit uaa extension (#317)

* use UserPrincipal to determine tenant at runtime

Signed-off-by: Michael Hirsch <michael.hirsch@bosch-si.com>

* add hawkbit-uaa extension

Signed-off-by: Michael Hirsch <michael.hirsch@bosch-si.com>

* adapt WithSpringAuthorityRule with UserPrincipal for determine tenant

Signed-off-by: Michael Hirsch <michael.hirsch@bosch-si.com>

* fix String principal in DDI download resource

Signed-off-by: Michael Hirsch <michael.hirsch@bosch-si.com>

* merge the email to the UserPrincipal from the master manually

Signed-off-by: Michael Hirsch <michael.hirsch@bosch-si.com>

* Fixed some grammar issues and typos

Signed-off-by: Dominic Schabel <dominic.schabel@bosch-si.com>
This commit is contained in:
Michael Hirsch
2016-10-31 13:16:03 +01:00
committed by GitHub
parent b7f5bf3d79
commit 22272ba3c1
15 changed files with 654 additions and 59 deletions

View File

@@ -0,0 +1,46 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.security.uaa;
import org.springframework.boot.autoconfigure.security.oauth2.resource.ResourceServerProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.NestedConfigurationProperty;
import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeResourceDetails;
/**
* Configuration properties for setting the UAA OAUTH2 client-properties and
* resource-properties.
*
* <pre>
uaa.client.clientId=app
uaa.client.clientSecret=appsecret
uaa.client.accessTokenUri=http://localhost:8080/uaa/oauth/token
uaa.client.userAuthorizationUri=http://localhost:8080/uaa/oauth/authorize
uaa.client.clientAuthenticationScheme=form
uaa.resource.userInfoUri=http://localhost:8080/uaa/userinfo
uaa.resource.jwt.keyValue=abc
* </pre>
*/
@ConfigurationProperties("uaa")
public class UaaClientProperties {
@NestedConfigurationProperty
private final AuthorizationCodeResourceDetails client = new AuthorizationCodeResourceDetails();
@NestedConfigurationProperty
private final ResourceServerProperties resource = new ResourceServerProperties();
public AuthorizationCodeResourceDetails getClient() {
return client;
}
public ResourceServerProperties getResource() {
return resource;
}
}

View File

@@ -0,0 +1,199 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.security.uaa;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.eclipse.hawkbit.im.authentication.UserAuthenticationFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.embedded.FilterRegistrationBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.oauth2.client.OAuth2ClientContext;
import org.springframework.security.oauth2.client.OAuth2RestTemplate;
import org.springframework.security.oauth2.client.filter.OAuth2ClientAuthenticationProcessingFilter;
import org.springframework.security.oauth2.client.filter.OAuth2ClientContextFilter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableOAuth2Client;
import org.springframework.security.oauth2.provider.authentication.BearerTokenExtractor;
import org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationManager;
import org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationProcessingFilter;
import org.springframework.security.oauth2.provider.token.DefaultAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import com.google.common.base.Throwables;
/**
* The Spring-Auto-Configuration implementation for integrating the UAA
* (https://github.com/cloudfoundry/uaa) as an identity management.
*
* To use the OAUTH2 redirect login flow the
* {@link OAuth2ClientAuthenticationProcessingFilter} is listing on the path
* {@code /uaalogin}. This will then re-direct to the configured UAA login form.
*
* The {@link UserAuthenticationFilter} implementation delegates to the
* {@link OAuth2AuthenticationProcessingFilter} which validates given bearer
* tokens in the {@code Authorization} header '
* {@code Authorization: bearer eyJhbGciOiJIUzI1NiIsImtpZCI6Imx}' to
* authenticate bearer tokens for the REST API. Only the signed token is
* verified, there is no extra round-trip back to the OAUTH2-Server (UAA).
*
* </p>
* Example configuration:
*
* <pre>
uaa.client.clientId=app
uaa.client.clientSecret=appsecret
uaa.client.accessTokenUri=http://localhost:8080/uaa/oauth/token
uaa.client.userAuthorizationUri=http://localhost:8080/uaa/oauth/authorize
uaa.client.clientAuthenticationScheme=form
uaa.resource.userInfoUri=http://localhost:8080/uaa/userinfo
uaa.resource.jwt.keyValue=abc
* </pre>
*
*/
@EnableOAuth2Client
@EnableConfigurationProperties(UaaClientProperties.class)
@Order(Ordered.HIGHEST_PRECEDENCE)
public class UaaOAuthAutoConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
private OAuth2ClientContext oauth2ClientContext;
@Autowired
private UaaClientProperties uaaClientResources;
@Override
protected void configure(final HttpSecurity http) throws Exception {
http.regexMatcher("\\/uaalogin.*").addFilterBefore(ssoFilter("/uaalogin"), BasicAuthenticationFilter.class);
}
/**
* @return The {@link UserPrincipalInfoTokenServices} which extract
* authentication, principal and authorities information from an JWT
* access token.
*/
@Bean
public UserPrincipalInfoTokenServices userPrincipalInfoTokenServices() {
return new UserPrincipalInfoTokenServices(uaaClientResources.getResource().getUserInfoUri(),
uaaClientResources.getClient().getClientId(), oauth2ClientContext);
}
/**
* @return The {@link JwtTokenStore} verifies access tokens and extract
* authentication and authorities from it.
*/
@Bean
public JwtTokenStore jwtTokenStore() {
final DefaultAccessTokenConverter accessTokenConverter = new DefaultAccessTokenConverter();
accessTokenConverter.setUserTokenConverter(userPrincipalInfoTokenServices());
final JwtAccessTokenConverter jwtTokenEnhancer = new JwtAccessTokenConverter();
jwtTokenEnhancer.setAccessTokenConverter(accessTokenConverter);
jwtTokenEnhancer.setSigningKey(uaaClientResources.getResource().getJwt().getKeyValue());
jwtTokenEnhancer.setVerifierKey(uaaClientResources.getResource().getJwt().getKeyValue());
try {
jwtTokenEnhancer.afterPropertiesSet();
} catch (final Exception e) {
throw Throwables.propagate(e);
}
return new JwtTokenStore(jwtTokenEnhancer);
}
/**
* @param filter
* the {@link OAuth2ClientContextFilter} to register.
* @return the Spring {@link FilterRegistrationBean} to register a filter in
* the spring filter-chain
*/
@Bean
public FilterRegistrationBean oauth2ClientFilterRegistration(final OAuth2ClientContextFilter filter) {
final FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(filter);
registration.setOrder(-100);
return registration;
}
/**
* @return The adapter for the hawkBit {@link UserAuthenticationFilter}
* which delegates to the oAuth-filter mechanism to authenticate JWT
* bearer tokens in the hawkBit security filter chain.
*/
@Bean
public UserAuthenticationFilter userAuthenticationFilter() {
return new UserAuthenticationFilterAdapter(resourceOAuthFilter());
}
private Filter resourceOAuthFilter() {
final DefaultTokenServices remoteTokenService = new DefaultTokenServices();
remoteTokenService.setTokenStore(jwtTokenStore());
final OAuth2AuthenticationManager oauth2Manager = new OAuth2AuthenticationManager();
oauth2Manager.setTokenServices(remoteTokenService);
final OAuth2AuthenticationProcessingFilter oAuth2AuthenticationProcessingFilter = new OAuth2AuthenticationProcessingFilter();
oAuth2AuthenticationProcessingFilter.setTokenExtractor(new BearerTokenExtractor());
oAuth2AuthenticationProcessingFilter.setAuthenticationManager(oauth2Manager);
return oAuth2AuthenticationProcessingFilter;
}
private Filter ssoFilter(final String path) {
final OAuth2ClientAuthenticationProcessingFilter oAuth2ClientAuthenticationFilter = new OAuth2ClientAuthenticationProcessingFilter(
path);
final SavedRequestAwareAuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();
oAuth2ClientAuthenticationFilter.setAuthenticationSuccessHandler(successHandler);
successHandler.setAlwaysUseDefaultTargetUrl(true);
successHandler.setDefaultTargetUrl("/UI");
final OAuth2RestTemplate oAuth2RestTemplate = new OAuth2RestTemplate(uaaClientResources.getClient(),
oauth2ClientContext);
oAuth2ClientAuthenticationFilter.setRestTemplate(oAuth2RestTemplate);
final UserPrincipalInfoTokenServices tokenServices = new UserPrincipalInfoTokenServices(
uaaClientResources.getResource().getUserInfoUri(), uaaClientResources.getClient().getClientId(),
oauth2ClientContext);
tokenServices.setRestTemplate(oAuth2RestTemplate);
tokenServices.setAuthoritiesExtractor(tokenServices);
oAuth2ClientAuthenticationFilter.setTokenServices(tokenServices);
return oAuth2ClientAuthenticationFilter;
}
private static final class UserAuthenticationFilterAdapter implements UserAuthenticationFilter {
private final Filter delegate;
private UserAuthenticationFilterAdapter(final Filter delegate) {
this.delegate = delegate;
}
@Override
public void init(final FilterConfig filterConfig) throws ServletException {
delegate.init(filterConfig);
}
@Override
public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain)
throws IOException, ServletException {
delegate.doFilter(request, response, chain);
}
@Override
public void destroy() {
delegate.destroy();
}
}
}

View File

@@ -0,0 +1,105 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.security.uaa;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.eclipse.hawkbit.im.authentication.UserPrincipal;
import org.springframework.boot.autoconfigure.security.oauth2.resource.AuthoritiesExtractor;
import org.springframework.boot.autoconfigure.security.oauth2.resource.UserInfoTokenServices;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.jwt.Jwt;
import org.springframework.security.jwt.JwtHelper;
import org.springframework.security.oauth2.client.OAuth2ClientContext;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.util.JsonParser;
import org.springframework.security.oauth2.common.util.JsonParserFactory;
import org.springframework.security.oauth2.provider.token.UserAuthenticationConverter;
/**
* Implementation which maps principals from the OAUTH2 access tokens to the
* hawkBit {@link UserPrincipal}, so hawkBit is able to work with the user and
* tenant information. Additional extracting the {@code scope}-list from the JWT
* into {@link GrantedAuthority}.
*
* This implementation concentrates all necessary authentication, principal and
* authorities mapping within the OAUTH2 workflow, e.g. using redirect login
* form or using the REST-API with a access bearer token.
*/
public class UserPrincipalInfoTokenServices extends UserInfoTokenServices
implements UserAuthenticationConverter, AuthoritiesExtractor {
private final OAuth2ClientContext oauth2ClientContext;
private final JsonParser jsonParser = JsonParserFactory.create();
/**
* Constructor.
*
* @param userInfoEndpointUrl
* the OAUTH2 info endpoint to retrieve user information
* @param clientId
* the OAUTH2 client-id to execute the user info endpoint
* @param oauth2ClientContext
* the spring {@link OAuth2ClientContext}
*/
public UserPrincipalInfoTokenServices(final String userInfoEndpointUrl, final String clientId,
final OAuth2ClientContext oauth2ClientContext) {
super(userInfoEndpointUrl, clientId);
this.oauth2ClientContext = oauth2ClientContext;
}
@Override
protected Object getPrincipal(final Map<String, Object> map) {
final String username = String.valueOf(map.get("user_name"));
final String firstname = String.valueOf(map.get("given_name"));
final String lastname = String.valueOf(map.get("family_name"));
final String email = String.valueOf(map.get("email"));
final String zoneId = String.valueOf(getAccessTokenMap().get("zid"));
return new UserPrincipal(username, firstname, lastname, username, email, zoneId);
}
@Override
public Map<String, ?> convertUserAuthentication(final Authentication userAuthentication) {
throw new UnsupportedOperationException("converting an authentication object to a map is not implemented");
}
@Override
@SuppressWarnings("unchecked")
public Authentication extractAuthentication(final Map<String, ?> map) {
final Object principal = getPrincipal((Map<String, Object>) map);
return new UsernamePasswordAuthenticationToken(principal, "N/A", extractAuthorities((Map<String, Object>) map));
}
@Override
public List<GrantedAuthority> extractAuthorities(final Map<String, Object> map) {
final Map<String, Object> accessTokenMap;
if (map.containsKey("scope")) {
accessTokenMap = map;
} else {
accessTokenMap = getAccessTokenMap();
}
@SuppressWarnings("unchecked")
final List<String> scopes = (List<String>) accessTokenMap.get("scope");
return scopes.stream().map(scope -> new SimpleGrantedAuthority(scope)).collect(Collectors.toList());
}
private Map<String, Object> getAccessTokenMap() {
final Map<String, Object> accessTokenMap;
final OAuth2AccessToken accessToken = oauth2ClientContext.getAccessToken();
final Jwt decode = JwtHelper.decode(accessToken.getValue());
accessTokenMap = jsonParser.parseMap(decode.getClaims());
return accessTokenMap;
}
}

View File

@@ -0,0 +1,3 @@
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.eclipse.hawkbit.security.uaa.UaaOAuthAutoConfiguration