Skip to content

Commit

Permalink
Merge pull request apache#4966 from demery-pivotal/geode-7851/logout
Browse files Browse the repository at this point in the history
GEODE-7851: Pulse logout requests end of OAuth session
  • Loading branch information
jmelchio authored Apr 20, 2020
2 parents 509240f + 410ffca commit d09b26a
Show file tree
Hide file tree
Showing 12 changed files with 191 additions and 163 deletions.
5 changes: 5 additions & 0 deletions geode-docs/tools_modules/pulse/pulse-auth.html.md.erb
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,10 @@ After you set up the authentication provider properly, create a properties file
The URI for your OAuth provider's JSON Web Key (JWK) Set endpoint.
- **pulse.oauth.endSessionEndpoint**
The URI for your OAuth provider's endpoint to request that the end user be logged out. See the `end_session_endpoint` parameter of the OpenID Provider Discovery Metadata standard proposal.
- **pulse.oauth.userNameAttributeName**
The attribute name used to access the user's name from your OAuth provider's user info response.
Expand All @@ -169,6 +173,7 @@ pulse.oauth.authorizationUri=http://example.com/uaa/oauth/authorize
pulse.oauth.tokenUri=http://example.com/uaa/oauth/token
pulse.oauth.userInfoUri=http://example.com/uaa/userinfo
pulse.oauth.jwkSetUri=http://example.com/uaa/token_keys
pulse.oauth.endSessionEndpoint=http://example.com/uaa/profile
pulse.oauth.userNameAttributeName=user_name
```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,19 +59,18 @@ public class Repository {
Locale locale =
new Locale(PulseConstants.APPLICATION_LANGUAGE, PulseConstants.APPLICATION_COUNTRY);

private ResourceBundle resourceBundle =
private final ResourceBundle resourceBundle =
ResourceBundle.getBundle(PulseConstants.LOG_MESSAGES_FILE, locale);

private PulseConfig pulseConfig = new PulseConfig();
private final PulseConfig pulseConfig = new PulseConfig();

@Autowired(required = false)
public Repository() {
this(null);
}

// The authorizedClientService is required only when using OAuth2 security.
@Autowired
public Repository(
@Autowired(required = false) OAuth2AuthorizedClientService authorizedClientService) {
@Autowired(required = false)
public Repository(OAuth2AuthorizedClientService authorizedClientService) {
this(authorizedClientService, Cluster::new);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,26 @@
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;


/**
* Configures Pulse to use the authentication manager defined by the
* {@code pulse-authentication-custom.xml} file, which <em>must</em> define an authentication
* manager. This configuration is applied when the {@code pulse.authentication.custom} profile is
* active.
*/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
@Profile("pulse.authentication.custom")
@ImportResource("classpath:pulse-authentication-custom.xml")
public class CustomSecurityConfig extends DefaultSecurityConfig {
// the pulse-authentication-custom.xml should configure an <authentication-manager>
private final AuthenticationManager authenticationManager;

@Autowired
private AuthenticationManager authenticationManager;
CustomSecurityConfig(AuthenticationManager authenticationManager,
RepositoryLogoutHandler repositoryLogoutHandler) {
super(repositoryLogoutHandler);
this.authenticationManager = authenticationManager;
}

@Override
protected void configure(AuthenticationManagerBuilder authenticationManagerBuilder) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import java.util.HashMap;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
Expand All @@ -33,14 +34,20 @@
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.ExceptionMappingAuthenticationFailureHandler;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
@Profile("pulse.authentication.default")
public class DefaultSecurityConfig extends WebSecurityConfigurerAdapter {

private final RepositoryLogoutHandler repositoryLogoutHandler;

@Autowired
DefaultSecurityConfig(RepositoryLogoutHandler repositoryLogoutHandler) {
this.repositoryLogoutHandler = repositoryLogoutHandler;
}

@Bean
public AuthenticationFailureHandler failureHandler() {
ExceptionMappingAuthenticationFailureHandler exceptionMappingAuthenticationFailureHandler =
Expand All @@ -55,11 +62,6 @@ public AuthenticationFailureHandler failureHandler() {
return exceptionMappingAuthenticationFailureHandler;
}

@Bean
public LogoutSuccessHandler logoutHandler() {
return new LogoutHandler("/login.html");
}

@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.authorizeRequests(authorize -> authorize
Expand All @@ -78,7 +80,8 @@ protected void configure(HttpSecurity httpSecurity) throws Exception {
.defaultSuccessUrl("/clusterDetail.html", true))
.logout(logout -> logout
.logoutUrl("/clusterLogout")
.logoutSuccessHandler(logoutHandler()))
.addLogoutHandler(repositoryLogoutHandler)
.logoutSuccessUrl("/login.html"))
.exceptionHandling(exception -> exception
.accessDeniedPage("/accessDenied.html"))
.headers(header -> header
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,9 @@
*/
public class GemFireAuthentication extends UsernamePasswordAuthenticationToken {

private JMXConnector jmxc = null;

public GemFireAuthentication(Object principal, Object credentials,
Collection<GrantedAuthority> list, JMXConnector jmxc) {
Collection<GrantedAuthority> list) {
super(principal, credentials, list);
this.jmxc = jmxc;
}

private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,15 @@

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Profile;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Component;

import org.apache.geode.tools.pulse.internal.data.Repository;

Expand All @@ -36,11 +39,13 @@
*
* @since GemFire version 9.0
*/
@Component
@Profile("pulse.authentication.gemfire")
public class GemFireAuthenticationProvider implements AuthenticationProvider {

private static final Logger logger = LogManager.getLogger();
private final Repository repository;

@Autowired
public GemFireAuthenticationProvider(Repository repository) {
this.repository = repository;
}
Expand All @@ -49,8 +54,9 @@ public GemFireAuthenticationProvider(Repository repository) {
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
if (authentication instanceof GemFireAuthentication) {
GemFireAuthentication gemAuth = (GemFireAuthentication) authentication;
if (gemAuth.isAuthenticated())
if (gemAuth.isAuthenticated()) {
return gemAuth;
}
}

String name = authentication.getName();
Expand All @@ -65,7 +71,7 @@ public Authentication authenticate(Authentication authentication) throws Authent

Collection<GrantedAuthority> list = GemFireAuthentication.populateAuthorities(jmxc);
GemFireAuthentication auth = new GemFireAuthentication(authentication.getPrincipal(),
authentication.getCredentials(), list, jmxc);
authentication.getCredentials(), list);
logger.debug("For user " + name + " authList=" + list);
return auth;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,28 +18,27 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;

import org.apache.geode.tools.pulse.internal.data.Repository;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
@Profile("pulse.authentication.gemfire")
public class GemfireSecurityConfig extends DefaultSecurityConfig {

private final Repository repository;
private final AuthenticationProvider authenticationProvider;

@Autowired
public GemfireSecurityConfig(Repository repository) {
this.repository = repository;
public GemfireSecurityConfig(GemFireAuthenticationProvider gemFireAuthenticationProvider,
RepositoryLogoutHandler repositoryLogoutHandler) {
super(repositoryLogoutHandler);
authenticationProvider = gemFireAuthenticationProvider;
}

@Override
protected void configure(AuthenticationManagerBuilder authenticationManagerBuilder) {
authenticationManagerBuilder
.authenticationProvider(new GemFireAuthenticationProvider(repository));
authenticationManagerBuilder.authenticationProvider(authenticationProvider);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more contributor license
* agreements. See the NOTICE file distributed with this work for additional information regarding
* copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License. You may obtain a
* copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/

package org.apache.geode.tools.pulse.internal.security;

import static java.util.Collections.singletonMap;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.context.annotation.PropertySource;
import org.springframework.security.oauth2.client.InMemoryOAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.oidc.web.logout.OidcClientInitiatedLogoutSuccessHandler;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
import org.springframework.security.oauth2.client.web.AuthenticatedPrincipalOAuth2AuthorizedClientRepository;
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
import org.springframework.security.oauth2.core.AuthorizationGrantType;

/**
* Configures Pulse to use the OAuth 2 provider defined by properties in {@code pulse.properties}.
*/
@Configuration
@Profile("pulse.authentication.oauth")
@PropertySource("classpath:pulse.properties")
public class OAuthClientConfig {
@Value("${pulse.oauth.providerId}")
private String providerId;
@Value("${pulse.oauth.providerName}")
private String providerName;
@Value("${pulse.oauth.clientId}")
private String clientId;
@Value("${pulse.oauth.clientSecret}")
private String clientSecret;
@Value("${pulse.oauth.authorizationUri}")
private String authorizationUri;
@Value("${pulse.oauth.tokenUri}")
private String tokenUri;
@Value("${pulse.oauth.userInfoUri}")
private String userInfoUri;
@Value("${pulse.oauth.jwkSetUri}")
private String jwkSetUri;
@Value("${pulse.oauth.endSessionEndpoint}")
private String endSessionEndpoint;
@Value("${pulse.oauth.userNameAttributeName}")
private String userNameAttributeName;

@Bean
ClientRegistration clientRegistration() {
return ClientRegistration.withRegistrationId(providerId)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.redirectUriTemplate("{baseUrl}/login/oauth2/code/{registrationId}")
.clientId(clientId)
.clientSecret(clientSecret)
.scope("openid", "CLUSTER:READ", "CLUSTER:WRITE", "DATA:READ", "DATA:WRITE")
.authorizationUri(authorizationUri)
.tokenUri(tokenUri)
.userInfoUri(userInfoUri)
.jwkSetUri(jwkSetUri)
.providerConfigurationMetadata(
singletonMap("end_session_endpoint", endSessionEndpoint))
// When Spring shows the login page, it displays a link to the OAuth provider's
// authorization URI. Spring uses the value passed to clientName() as the text for that
// link. We pass the providerName property here, to let the user know which OAuth provider
// they will be redirected to.
.clientName(providerName)
.userNameAttributeName(userNameAttributeName)
.build();
}

@Bean
public ClientRegistrationRepository clientRegistrationRepository(
ClientRegistration clientRegistration) {
return new InMemoryClientRegistrationRepository(clientRegistration);
}

@Bean
public OAuth2AuthorizedClientService authorizedClientService(
ClientRegistrationRepository clientRegistrationRepository) {
return new InMemoryOAuth2AuthorizedClientService(clientRegistrationRepository);
}

@Bean
public OAuth2AuthorizedClientRepository authorizedClientRepository(
OAuth2AuthorizedClientService authorizedClientService) {
return new AuthenticatedPrincipalOAuth2AuthorizedClientRepository(authorizedClientService);
}

@Bean
public OidcClientInitiatedLogoutSuccessHandler oidcLogoutHandler(
ClientRegistrationRepository clientRegistrationRepository) {
return new OidcClientInitiatedLogoutSuccessHandler(clientRegistrationRepository);
}
}
Loading

0 comments on commit d09b26a

Please sign in to comment.