Vaadin security enhancements (#1003)
* Removed VaadinManagedSecurity configuration from MgmtUiAutoConfiguration * added SessionFixationProtectionStrategy for additional protection of UI session authentication * added VaadinSessionClosingLogoutHandler to logout from all UI sessions * added AccessDecisionManager to UI security configuration in order to support method security in UI in context of VaadinSharedSecurity * Changed UI push transport from WEBSOCKET to WEBSOCKET_XHR to solve problems with Spring Security Context * Suppressed atmosphere IOUtils false-positive warning * Removed obsolete AsyncVaadinServletConfiguration * Defined Vaadin4SpringServlet bean instead of plain SpringVaadinServlet for configuration flexibility * Removed obsolete SpringSecurityAtmosphereInterceptor because the client does not communicate with the server using websocket protocol anymore * Removed unit test for SpringSecurityAtmosphereInterceptor * Removed obsolete AuthenticationManagerConfigurer coming from Vaadin Managed Security in InMemoryUserManagementAutoConfiguration * Removed SessionFixationProtectionStrategy and VaadinSessionClosingLogoutHandler because all wrapper sessions are invalidated when the session managed by Spring gets invalidated together with configured HttpSessionEventPublisher events * Added call to close the current session before logout redirect * added comment why we used WEBSOCKET_XHR instead of WEBSOCKET Signed-off-by: Bogdan Bondar <Bogdan.Bondar@bosch.io>
This commit is contained in:
@@ -1,87 +0,0 @@
|
||||
/**
|
||||
* 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.ui;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
|
||||
import org.atmosphere.container.JSR356AsyncSupport;
|
||||
import org.atmosphere.cpr.ApplicationConfig;
|
||||
import org.eclipse.hawkbit.ui.utils.VaadinMessageSource;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.boot.web.servlet.ServletRegistrationBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
import com.vaadin.server.VaadinServlet;
|
||||
import com.vaadin.spring.boot.internal.VaadinServletConfiguration;
|
||||
import com.vaadin.spring.boot.internal.VaadinServletConfigurationProperties;
|
||||
import com.vaadin.spring.server.SpringVaadinServlet;
|
||||
|
||||
/**
|
||||
* {@link VaadinServletConfiguration} that sets the context path for
|
||||
* {@link JSR356AsyncSupport} that registers
|
||||
* {@link SpringSecurityAtmosphereInterceptor} for spring security integration.
|
||||
*/
|
||||
@Configuration
|
||||
@EnableConfigurationProperties(VaadinServletConfigurationProperties.class)
|
||||
@Import(VaadinServletConfiguration.class)
|
||||
public class AsyncVaadinServletConfiguration extends VaadinServletConfiguration {
|
||||
|
||||
/**
|
||||
* Localized system message provider bean.
|
||||
*
|
||||
* @param uiProperties
|
||||
* UiProperties
|
||||
* @param i18n
|
||||
* VaadinMessageSource
|
||||
*
|
||||
* @return Localized system message provider
|
||||
*/
|
||||
@Bean
|
||||
public LocalizedSystemMessagesProvider localizedSystemMessagesProvider(final UiProperties uiProperties,
|
||||
final VaadinMessageSource i18n) {
|
||||
return new LocalizedSystemMessagesProvider(uiProperties, i18n);
|
||||
}
|
||||
|
||||
/**
|
||||
* Vaadin servlet bean.
|
||||
*
|
||||
* @param localizedSystemMessagesProvider
|
||||
* LocalizedSystemMessagesProvider
|
||||
*
|
||||
* @return Vaadin servlet service
|
||||
*/
|
||||
@Bean
|
||||
public VaadinServlet vaadinServlet(final LocalizedSystemMessagesProvider localizedSystemMessagesProvider) {
|
||||
return new SpringVaadinServlet() {
|
||||
@Override
|
||||
public void servletInitialized() throws ServletException {
|
||||
super.servletInitialized();
|
||||
getService().setSystemMessagesProvider(localizedSystemMessagesProvider);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
@Bean
|
||||
protected ServletRegistrationBean vaadinServletRegistration() {
|
||||
return createServletRegistrationBean();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addInitParameters(final ServletRegistrationBean servletRegistrationBean) {
|
||||
super.addInitParameters(servletRegistrationBean);
|
||||
|
||||
servletRegistrationBean.addInitParameter(ApplicationConfig.JSR356_MAPPING_PATH, "/UI");
|
||||
servletRegistrationBean.addInitParameter(ApplicationConfig.ATMOSPHERE_INTERCEPTORS,
|
||||
SpringSecurityAtmosphereInterceptor.class.getName());
|
||||
|
||||
}
|
||||
}
|
||||
@@ -16,8 +16,11 @@ import org.springframework.context.MessageSource;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.context.annotation.PropertySource;
|
||||
import org.vaadin.spring.servlet.Vaadin4SpringServlet;
|
||||
|
||||
import com.vaadin.server.SystemMessagesProvider;
|
||||
import com.vaadin.server.VaadinServlet;
|
||||
|
||||
/**
|
||||
* Enables UI components for the Management UI.
|
||||
@@ -25,21 +28,61 @@ import org.springframework.context.annotation.PropertySource;
|
||||
*/
|
||||
@Configuration
|
||||
@ComponentScan
|
||||
@Import(AsyncVaadinServletConfiguration.class)
|
||||
@EnableConfigurationProperties(UiProperties.class)
|
||||
@PropertySource("classpath:/hawkbit-ui-defaults.properties")
|
||||
public class MgmtUiConfiguration {
|
||||
|
||||
/**
|
||||
* Permission checker for UI.
|
||||
*
|
||||
* @param permissionService
|
||||
* PermissionService
|
||||
*
|
||||
* @return Permission checker for UI
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
SpPermissionChecker spPermissionChecker(final PermissionService permissionService) {
|
||||
return new SpPermissionChecker(permissionService);
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility for Vaadin messages source.
|
||||
*
|
||||
* @param source
|
||||
* Delegate MessageSource
|
||||
*
|
||||
* @return Vaadin messages source utility
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
VaadinMessageSource messageSourceVaadin(final MessageSource source) {
|
||||
return new VaadinMessageSource(source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Localized system message provider bean.
|
||||
*
|
||||
* @param uiProperties
|
||||
* UiProperties
|
||||
* @param i18n
|
||||
* VaadinMessageSource
|
||||
*
|
||||
* @return Localized system message provider
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
SystemMessagesProvider systemMessagesProvider(final UiProperties uiProperties, final VaadinMessageSource i18n) {
|
||||
return new LocalizedSystemMessagesProvider(uiProperties, i18n);
|
||||
}
|
||||
|
||||
/**
|
||||
* Vaadin4Spring servlet bean.
|
||||
*
|
||||
* @return Vaadin servlet for Spring
|
||||
*/
|
||||
@Bean
|
||||
public VaadinServlet vaadinServlet() {
|
||||
return new Vaadin4SpringServlet();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
/**
|
||||
* 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.ui;
|
||||
|
||||
import org.atmosphere.config.service.AtmosphereInterceptorService;
|
||||
import org.atmosphere.cpr.Action;
|
||||
import org.atmosphere.cpr.AtmosphereInterceptor;
|
||||
import org.atmosphere.cpr.AtmosphereInterceptorAdapter;
|
||||
import org.atmosphere.cpr.AtmosphereResource;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
|
||||
|
||||
/**
|
||||
* An {@link AtmosphereInterceptor} implementation which retrieves the
|
||||
* {@link SecurityContext} from the http-session and set in into the
|
||||
* {@link SecurityContextHolder}. This is necessary due that websocket requests
|
||||
* are not going through the spring security filter chain and the
|
||||
* {@link SecurityContext} will not be present in the current Thread.
|
||||
*/
|
||||
@AtmosphereInterceptorService
|
||||
public class SpringSecurityAtmosphereInterceptor extends AtmosphereInterceptorAdapter {
|
||||
|
||||
@Override
|
||||
public Action inspect(final AtmosphereResource r) {
|
||||
final SecurityContext context = (SecurityContext) r.getRequest().getSession()
|
||||
.getAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY);
|
||||
SecurityContextHolder.setContext(context);
|
||||
return Action.CONTINUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postInspect(final AtmosphereResource r) {
|
||||
SecurityContextHolder.clearContext();
|
||||
}
|
||||
}
|
||||
@@ -91,7 +91,7 @@ public abstract class AbstractEntityGridHeader extends AbstractGridHeader {
|
||||
this.resizeHeaderSupport = new ResizeHeaderSupport(i18n, getMaxMinIconId(), this::maximizeTable,
|
||||
this::minimizeTable, this::onLoadIsTableMaximized);
|
||||
|
||||
addHeaderSupports(Arrays.asList(getSearchHeaderSupport(), filterButtonsHeaderSupport, resizeHeaderSupport));
|
||||
addHeaderSupports(Arrays.asList(searchHeaderSupport, filterButtonsHeaderSupport, resizeHeaderSupport));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -219,8 +219,10 @@ public final class DashboardMenu extends CustomComponent {
|
||||
|
||||
final String logoutUrl = generateLogoutUrl();
|
||||
|
||||
settingsItem.addItem(i18n.getMessage("label.sign.out"),
|
||||
selectedItem -> Page.getCurrent().setLocation(logoutUrl));
|
||||
settingsItem.addItem(i18n.getMessage("label.sign.out"), selectedItem -> {
|
||||
getUI().getSession().close();
|
||||
Page.getCurrent().setLocation(logoutUrl);
|
||||
});
|
||||
return settings;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
/**
|
||||
* 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.push;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
import org.atmosphere.cpr.AtmosphereRequest;
|
||||
import org.atmosphere.cpr.AtmosphereResource;
|
||||
import org.eclipse.hawkbit.ui.SpringSecurityAtmosphereInterceptor;
|
||||
import org.junit.After;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
|
||||
|
||||
import io.qameta.allure.Description;
|
||||
import io.qameta.allure.Feature;
|
||||
import io.qameta.allure.Story;
|
||||
|
||||
@Feature("Unit Tests - Management UI")
|
||||
@Story("Push Security")
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class SpringSecurityAtmosphereInterceptorTest {
|
||||
|
||||
@Mock
|
||||
private AtmosphereResource atmosphereResourceMock;
|
||||
@Mock
|
||||
private AtmosphereRequest atmosphereRequestMock;
|
||||
@Mock
|
||||
private SecurityContext sessionSecurityContextMock;
|
||||
@Mock
|
||||
private HttpSession httpSessionMock;
|
||||
|
||||
private final SpringSecurityAtmosphereInterceptor underTest = new SpringSecurityAtmosphereInterceptor();
|
||||
|
||||
@After
|
||||
public void after() {
|
||||
SecurityContextHolder.clearContext();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Verify that Security Context is set from Request to thread local when calling inspect")
|
||||
public void inspectRetrievesSetsSecurityContextFromRequestToThreadLocal() {
|
||||
|
||||
when(atmosphereResourceMock.getRequest()).thenReturn(atmosphereRequestMock);
|
||||
when(atmosphereRequestMock.getSession()).thenReturn(httpSessionMock);
|
||||
when(httpSessionMock.getAttribute(Mockito.eq(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY)))
|
||||
.thenReturn(sessionSecurityContextMock);
|
||||
underTest.inspect(atmosphereResourceMock);
|
||||
// verify
|
||||
assertThat(SecurityContextHolder.getContext()).isEqualTo(sessionSecurityContextMock);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Verify that security Context gets cleared after atmosphere request")
|
||||
public void afterAtmosphereRequestSecurityContextGetsCleared() {
|
||||
SecurityContextHolder.setContext(sessionSecurityContextMock);
|
||||
|
||||
underTest.postInspect(atmosphereResourceMock);
|
||||
|
||||
assertThat(SecurityContextHolder.getContext()).isNotEqualTo(sessionSecurityContextMock);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user