Skip to content

Commit

Permalink
feat(server, ui): SSO OAuth2 with mock oidc-provider, authenticate SS…
Browse files Browse the repository at this point in the history
…O Opaque token and generate session on server side
  • Loading branch information
DelaunayAlex committed Oct 10, 2024
1 parent 886c167 commit f6b1432
Show file tree
Hide file tree
Showing 14 changed files with 360 additions and 135 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,34 @@
spring:
security:
oauth2:
client:
registration:
my-provider:
provider: my-provider
client-id: my-client
client-secret: my-client-secret
authorization-grant-type: authorization_code
redirect-uri: "https://localhost:4200/login/oauth2/code/{registrationId}"
scope: openid, profile, email
client-name: My Provider
provider:
my-provider:
issuer-uri: http://localhost:3000
authorization-uri: http://localhost:3000/auth
token-uri: http://localhost:3000/token
user-info-uri: http://localhost:3000/me
user-name-attribute: sub
jwk-set-uri: http://localhost:3000/jwks
resourceserver:
jwt:
issuer-uri: http://localhost:3000 # URL du serveur OIDC
opaque-token:
introspection-uri: http://localhost:3000/token/introspection
client-id: 'my-client'
client-secret: 'my-client-secret'
authorizationserver:
issuer: http://localhost:3000
endpoint:
oidc:
user-info-uri: http://localhost:3000/userinfo

auth:
sso:
Expand Down
24 changes: 0 additions & 24 deletions chutney/packaging/local-dev/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,30 +56,6 @@ spring:
- mem-auth
- db-sqlite
- sso-auth
security:
oauth2:
resourceserver:
jwt:
issuer-uri: http://localhost:3000 # URL du serveur OIDC
client:
registration:
my-client: # Identifiant pour le client dans Spring Security
client-id: my-client # Le client_id que vous avez défini dans votre serveur OIDC
client-secret: my-client-secret # Le client_secret du serveur OIDC
scope: openid, profile, email # Scopes requis (OpenID Connect nécessite "openid")
authorization-grant-type: authorization_code # Type de grant (flux d'autorisation)
redirect-uri: "{baseUrl}/auth-callback" # URL de redirection après authentification
provider: my-provider # Le nom du provider associé (référence à la section provider)
provider:
my-provider: # Configuration du fournisseur OIDC
issuer-uri: http://localhost:3000 # URI de l'issuer (serveur OIDC)
user-info-uri: http://localhost:3000/userinfo # URI pour obtenir les informations utilisateur
jwk-set-uri: http://localhost:3000/.well-known/openid-configuration/jwks # URI pour obtenir la clé publique pour valider les tokens JWT
token-uri: http://localhost:3000/token # URI pour échanger le code d'autorisation contre un token
authorization-uri: http://localhost:3000/auth # URI pour l'authentification et obtenir le code d'autorisation
introspection-uri: http://localhost:3000/introspect # URI d'introspection du token (facultatif)



chutney:
configuration-folder: .chutney/conf
Expand Down
6 changes: 5 additions & 1 deletion chutney/server/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,11 @@
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
<artifactId>spring-boot-starter-oauth2-authorization-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

package com.chutneytesting.security;


import com.chutneytesting.admin.api.InfoController;
import com.chutneytesting.security.api.SsoOpenIdConnectController;
import com.chutneytesting.security.api.UserController;
Expand All @@ -16,7 +17,6 @@
import com.chutneytesting.security.infra.handlers.Http401FailureHandler;
import com.chutneytesting.security.infra.handlers.HttpEmptyLogoutSuccessHandler;
import com.chutneytesting.security.infra.handlers.HttpLoginSuccessHandler;
import com.chutneytesting.security.infra.sso.OAuth2UserDetailsService;
import com.chutneytesting.security.infra.sso.SsoOpenIdConnectConfig;
import com.chutneytesting.server.core.domain.security.Authorization;
import com.chutneytesting.server.core.domain.security.User;
Expand All @@ -25,6 +25,8 @@
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
Expand All @@ -40,14 +42,15 @@
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
@Profile("!sso-auth")
public class ChutneyWebSecurityConfig {

public static final String LOGIN_URL = UserController.BASE_URL + "/login";
public static final String LOGOUT_URL = UserController.BASE_URL + "/logout";
public static final String API_BASE_URL_PATTERN = "/api/**";

@Value("${management.endpoints.web.base-path:/actuator}")
String actuatorBaseUrl;
public static String ACTUATOR_BASE_URL;

@Value("${server.ssl.enabled:true}")
Boolean sslEnabled;
Expand All @@ -63,9 +66,12 @@ public SsoOpenIdConnectConfig emptySsoOpenIdConnectConfig() {
return new SsoOpenIdConnectConfig();
}


@Bean
@Order()
@ConditionalOnMissingBean(value = SecurityFilterChain.class)
public SecurityFilterChain securityFilterChain(final HttpSecurity http) throws Exception {
configureBaseHttpSecurity(http);
configureBaseHttpSecurity(http, sslEnabled);
UserDto anonymous = anonymous();
http
.anonymous(anonymousConfigurer -> anonymousConfigurer
Expand All @@ -80,20 +86,18 @@ public SecurityFilterChain securityFilterChain(final HttpSecurity http) throws E
.requestMatchers(new MvcRequestMatcher(introspector, SsoOpenIdConnectController.BASE_URL)).permitAll()
.requestMatchers(new MvcRequestMatcher(introspector, SsoOpenIdConnectController.BASE_URL + "/**")).permitAll()
.requestMatchers(new MvcRequestMatcher(introspector, API_BASE_URL_PATTERN)).authenticated()
.requestMatchers(new MvcRequestMatcher(introspector, actuatorBaseUrl + "/**")).hasAuthority(Authorization.ADMIN_ACCESS.name())
.requestMatchers(new MvcRequestMatcher(introspector, ACTUATOR_BASE_URL + "/**")).hasAuthority(Authorization.ADMIN_ACCESS.name())
.anyRequest().permitAll();
})
.oauth2ResourceServer(oauth2ResourceServerCustomizer -> oauth2ResourceServerCustomizer.jwt(Customizer.withDefaults()))
.httpBasic(Customizer.withDefaults());

return http.build();
}

protected void configureBaseHttpSecurity(final HttpSecurity http) throws Exception {
public void configureBaseHttpSecurity(final HttpSecurity http, Boolean sslEnabled) throws Exception {
http
.csrf(AbstractHttpConfigurer::disable)
.exceptionHandling(httpSecurityExceptionHandlingConfigurer -> httpSecurityExceptionHandlingConfigurer.authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED)))
.requiresChannel(this.requireChannel())
.requiresChannel(this.requireChannel(sslEnabled))
.formLogin(httpSecurityFormLoginConfigurer -> httpSecurityFormLoginConfigurer
.loginProcessingUrl(LOGIN_URL)
.successHandler(new HttpLoginSuccessHandler())
Expand All @@ -103,15 +107,15 @@ protected void configureBaseHttpSecurity(final HttpSecurity http) throws Excepti
.logoutSuccessHandler(new HttpEmptyLogoutSuccessHandler()));
}

protected UserDto anonymous() {
public UserDto anonymous() {
UserDto anonymous = new UserDto();
anonymous.setId(User.ANONYMOUS.id);
anonymous.setName(User.ANONYMOUS.id);
anonymous.grantAuthority("ANONYMOUS");
return anonymous;
}

private Customizer<ChannelSecurityConfigurer<HttpSecurity>.ChannelRequestMatcherRegistry> requireChannel() {
private Customizer<ChannelSecurityConfigurer<HttpSecurity>.ChannelRequestMatcherRegistry> requireChannel(Boolean sslEnabled) {
if (sslEnabled) {
return channelRequestMatcherRegistry -> channelRequestMatcherRegistry.anyRequest().requiresSecure();
} else {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* SPDX-FileCopyrightText: 2017-2024 Enedis
*
* SPDX-License-Identifier: Apache-2.0
*
*/

package com.chutneytesting.security.infra.sso;

import com.chutneytesting.security.api.UserDto;
import com.chutneytesting.security.domain.AuthenticationService;
import com.chutneytesting.security.infra.UserDetailsServiceHelper;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.user.DefaultOAuth2User;
import org.springframework.security.oauth2.core.user.OAuth2User;

public class CustomOAuth2UserService implements OAuth2UserService<OAuth2UserRequest, OAuth2User> {

private final AuthenticationService authenticationService;

public CustomOAuth2UserService(AuthenticationService authenticationService) {
this.authenticationService = authenticationService;
}

@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
OAuth2UserService<OAuth2UserRequest, OAuth2User> delegate = new DefaultOAuth2UserService();
OAuth2User oAuth2User = delegate.loadUser(userRequest);
Map<String, Object> oAuth2UserAttributes = oAuth2User.getAttributes();
String username = (String) oAuth2UserAttributes.get("sub");
UserDto user = new UserDto();
user.setId(username);
user.setName(username);
user.setRoles(Collections.emptySet());
user = UserDetailsServiceHelper.grantAuthoritiesFromUserRole(user, authenticationService);
Map<String, Object> attributes = new HashMap<>(oAuth2UserAttributes);
attributes.put("user", user);
return new DefaultOAuth2User(user.getAuthorities(), attributes, "sub");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,68 @@

package com.chutneytesting.security.infra.sso;

import static com.chutneytesting.security.ChutneyWebSecurityConfig.API_BASE_URL_PATTERN;
import static com.chutneytesting.security.ChutneyWebSecurityConfig.LOGIN_URL;
import static com.chutneytesting.security.ChutneyWebSecurityConfig.LOGOUT_URL;

import com.chutneytesting.admin.api.InfoController;
import com.chutneytesting.security.ChutneyWebSecurityConfig;
import com.chutneytesting.security.api.SsoOpenIdConnectController;
import com.chutneytesting.security.api.UserDto;
import com.chutneytesting.security.domain.AuthenticationService;
import com.chutneytesting.security.infra.memory.InMemoryUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import com.chutneytesting.security.domain.Authorizations;
import com.chutneytesting.server.core.domain.security.Authorization;
import java.util.ArrayList;
import java.util.Collections;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.security.oauth2.server.servlet.OAuth2AuthorizationServerProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Profile;
import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher;
import org.springframework.web.servlet.handler.HandlerMappingIntrospector;

@Configuration
@Profile("sso-auth")
@EnableWebSecurity
@EnableMethodSecurity
@EnableConfigurationProperties(OAuth2AuthorizationServerProperties.class)
public class OAuth2SsoSecurityConfiguration {

@Value("${management.endpoints.web.base-path:/actuator}")
public static String ACTUATOR_BASE_URL;

@Value("${server.ssl.enabled:true}")
Boolean sslEnabled;

@Bean
public AuthenticationService authenticationService(Authorizations authorizations) {
return new AuthenticationService(authorizations);
}

@Bean
@ConditionalOnMissingBean
public SsoOpenIdConnectConfig emptySsoOpenIdConnectConfig() {
return new SsoOpenIdConnectConfig();
}

@Bean
@Primary
SsoOpenIdConnectConfig ssoOpenIdConnectConfig(
Expand All @@ -47,27 +92,47 @@ SsoOpenIdConnectConfig ssoOpenIdConnectConfig(
}

@Bean
public OAuth2UserDetailsService oAuth2UserDetailsService(AuthenticationService authenticationService) {
return new OAuth2UserDetailsService(authenticationService);
public OAuth2UserService<OAuth2UserRequest, OAuth2User> customOAuth2UserService(AuthenticationService authenticationService) {
return new CustomOAuth2UserService(authenticationService);
}

@Configuration
@Profile("sso-auth")
public static class OAuth2SsoConfiguration {
@Bean
public TokenAuthenticationProvider tokenAuthenticationProvider(AuthenticationService authenticationService, ClientRegistrationRepository clientRegistrationRepository) {
return new TokenAuthenticationProvider(customOAuth2UserService(authenticationService), clientRegistrationRepository.findByRegistrationId("my-provider"));
}

@Autowired
protected void configure(
final AuthenticationManagerBuilder auth,
final InMemoryUserDetailsService authService
) throws Exception {
auth.userDetailsService(authService);
}
@Bean
public AuthenticationManager authenticationManager(TokenAuthenticationProvider tokenAuthenticationProvider) {
return new ProviderManager(Collections.singletonList(tokenAuthenticationProvider));
}

@Bean
public SecurityFilterChain securityFilterChainOAuth2Sso(final HttpSecurity http, OAuth2UserDetailsService oAuth2UserDetailsService) throws Exception {
return http.oauth2ResourceServer(oauth2ResourceServerCustomizer -> oauth2ResourceServerCustomizer.jwt(Customizer.withDefaults()))
.userDetailsService(oAuth2UserDetailsService)
.build();
}
@Bean
@Order(1)
public SecurityFilterChain securityFilterChainOAuth2Sso(final HttpSecurity http, TokenAuthenticationProvider tokenAuthenticationProvider, AuthenticationManager authenticationManager) throws Exception {
ChutneyWebSecurityConfig chutneyWebSecurityConfig = new ChutneyWebSecurityConfig();
TokenAuthenticationFilter tokenFilter = new TokenAuthenticationFilter(authenticationManager);
chutneyWebSecurityConfig.configureBaseHttpSecurity(http, sslEnabled);
UserDto anonymous = chutneyWebSecurityConfig.anonymous();
http
.authenticationProvider(tokenAuthenticationProvider)
.addFilterBefore(tokenFilter, BasicAuthenticationFilter.class)
.anonymous(anonymousConfigurer -> anonymousConfigurer
.principal(anonymous)
.authorities(new ArrayList<>(anonymous.getAuthorities())))
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED))
.authorizeHttpRequests(httpRequest -> {
HandlerMappingIntrospector introspector = new HandlerMappingIntrospector();
httpRequest
.requestMatchers(new MvcRequestMatcher(introspector, LOGIN_URL)).permitAll()
.requestMatchers(new MvcRequestMatcher(introspector, LOGOUT_URL)).permitAll()
.requestMatchers(new MvcRequestMatcher(introspector, InfoController.BASE_URL + "/**")).permitAll()
.requestMatchers(new MvcRequestMatcher(introspector, SsoOpenIdConnectController.BASE_URL)).permitAll()
.requestMatchers(new MvcRequestMatcher(introspector, SsoOpenIdConnectController.BASE_URL + "/**")).permitAll()
.requestMatchers(new MvcRequestMatcher(introspector, API_BASE_URL_PATTERN)).authenticated()
.requestMatchers(new MvcRequestMatcher(introspector, ACTUATOR_BASE_URL + "/**")).hasAuthority(Authorization.ADMIN_ACCESS.name())
.anyRequest().permitAll();
})
.httpBasic(Customizer.withDefaults());
return http.build();
}
}
Loading

0 comments on commit f6b1432

Please sign in to comment.