All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.gravitee.rest.api.management.security.config.BasicSecurityConfigurerAdapter Maven / Gradle / Ivy

There is a newer version: 4.5.3
Show newest version
/*
 * Copyright © 2015 The Gravitee team (http://gravitee.io)
 *
 * Licensed 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 io.gravitee.rest.api.management.security.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import io.gravitee.apim.core.access_point.query_service.AccessPointQueryService;
import io.gravitee.apim.core.installation.domain_service.InstallationTypeDomainService;
import io.gravitee.apim.core.installation.query_service.InstallationAccessQueryService;
import io.gravitee.common.event.EventManager;
import io.gravitee.rest.api.idp.api.IdentityProvider;
import io.gravitee.rest.api.idp.api.authentication.AuthenticationProvider;
import io.gravitee.rest.api.idp.core.plugin.IdentityProviderManager;
import io.gravitee.rest.api.model.parameters.ParameterReferenceType;
import io.gravitee.rest.api.security.authentication.AuthenticationProviderManager;
import io.gravitee.rest.api.security.authentication.GraviteeAuthenticationDetails;
import io.gravitee.rest.api.security.cookies.CookieGenerator;
import io.gravitee.rest.api.security.csrf.CookieCsrfSignedTokenRepository;
import io.gravitee.rest.api.security.csrf.CsrfRequestMatcher;
import io.gravitee.rest.api.security.filter.CsrfIncludeFilter;
import io.gravitee.rest.api.security.filter.GraviteeContextAuthorizationFilter;
import io.gravitee.rest.api.security.filter.GraviteeContextFilter;
import io.gravitee.rest.api.security.filter.RecaptchaFilter;
import io.gravitee.rest.api.security.filter.TokenAuthenticationFilter;
import io.gravitee.rest.api.security.listener.AuthenticationFailureListener;
import io.gravitee.rest.api.security.listener.AuthenticationSuccessListener;
import io.gravitee.rest.api.security.utils.AuthoritiesProvider;
import io.gravitee.rest.api.service.EnvironmentService;
import io.gravitee.rest.api.service.ParameterService;
import io.gravitee.rest.api.service.ReCaptchaService;
import io.gravitee.rest.api.service.TokenService;
import io.gravitee.rest.api.service.UserService;
import jakarta.servlet.http.HttpServletRequest;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationDetailsSource;
import org.springframework.security.config.annotation.SecurityConfigurer;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.intercept.AuthorizationFilter;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.security.web.csrf.CsrfFilter;
import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;

/**
 * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
 * @author Nicolas GERAUD (nicolas.geraud at graviteesource.com)
 * @author GraviteeSource Team
 */
@Configuration
@Profile("basic")
@EnableWebSecurity
public class BasicSecurityConfigurerAdapter {

    private static final Logger LOGGER = LoggerFactory.getLogger(BasicSecurityConfigurerAdapter.class);

    @Autowired
    private ConfigurableEnvironment environment;

    @Autowired
    private IdentityProviderManager identityProviderManager;

    @Autowired
    private AuthenticationProviderManager authenticationProviderManager;

    @Autowired
    private CookieGenerator cookieGenerator;

    @Autowired
    private ReCaptchaService reCaptchaService;

    @Autowired
    private ObjectMapper objectMapper;

    @Autowired
    private UserService userService;

    @Autowired
    private TokenService tokenService;

    @Autowired
    private AuthoritiesProvider authoritiesProvider;

    @Autowired
    private ParameterService parameterService;

    @Autowired
    private EventManager eventManager;

    @Autowired
    private AccessPointQueryService accessPointQueryService;

    @Autowired
    private InstallationAccessQueryService installationAccessQueryService;

    @Autowired
    private InstallationTypeDomainService installationTypeDomainService;

    @Autowired
    private EnvironmentService environmentService;

    @Bean
    public AuthenticationSuccessListener authenticationSuccessListener() {
        return new AuthenticationSuccessListener();
    }

    @Bean
    public AuthenticationFailureListener authenticationFailureListener() {
        return new AuthenticationFailureListener();
    }

    @Bean
    public CookieCsrfSignedTokenRepository cookieCsrfSignedTokenRepository() {
        return new CookieCsrfSignedTokenRepository();
    }

    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        return new GraviteeUrlBasedCorsConfigurationSource(
            environment,
            parameterService,
            installationAccessQueryService,
            eventManager,
            ParameterReferenceType.ORGANIZATION
        );
    }

    /*
     * We don't want sonar to warn us about the hard coded jwt secret string as it is used to provide a warning
     * and encourage users to use a personal secret instead of the default one
     */
    @Bean
    @SuppressWarnings("java:S6418")
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        final String jwtSecret = environment.getProperty("jwt.secret");
        if (jwtSecret == null || jwtSecret.isEmpty()) {
            throw new IllegalStateException("JWT secret is mandatory");
        }

        //Warning if the secret is still the default one
        if ("myJWT4Gr4v1t33_S3cr3t".equals(jwtSecret)) {
            LOGGER.warn("");
            LOGGER.warn("##############################################################");
            LOGGER.warn("#                      SECURITY WARNING                      #");
            LOGGER.warn("##############################################################");
            LOGGER.warn("");
            LOGGER.warn("You still use the default jwt secret.");
            LOGGER.warn("This known secret can be used to impersonate anyone.");
            LOGGER.warn("Please change this value, or ask your administrator to do it !");
            LOGGER.warn("");
            LOGGER.warn("##############################################################");
            LOGGER.warn("");
        }

        authenticationManager(http);
        authentication(http);
        session(http);
        authorizations(http);
        hsts(http);
        csrf(http);
        cors(http);

        http.addFilterBefore(
            new TokenAuthenticationFilter(jwtSecret, cookieGenerator, userService, tokenService, authoritiesProvider),
            BasicAuthenticationFilter.class
        );
        http.addFilterBefore(new RecaptchaFilter(reCaptchaService, objectMapper), TokenAuthenticationFilter.class);
        http.addFilterBefore(
            new GraviteeContextFilter(installationTypeDomainService, accessPointQueryService, environmentService),
            CorsFilter.class
        );
        http.addFilterAfter(new GraviteeContextAuthorizationFilter(), AuthorizationFilter.class);

        return http.build();
    }

    private void authenticationManager(HttpSecurity http) throws Exception {
        final AuthenticationManagerBuilder auth = http.getSharedObject(AuthenticationManagerBuilder.class);

        LOGGER.info("--------------------------------------------------------------");
        LOGGER.info("Management API BasicSecurity Config");
        LOGGER.info("Loading authentication identity providers for Basic authentication");

        List providers = authenticationProviderManager
            .getIdentityProviders()
            .stream()
            .filter(authenticationProvider -> !authenticationProvider.external())
            .collect(Collectors.toList());

        for (io.gravitee.rest.api.security.authentication.AuthenticationProvider provider : providers) {
            LOGGER.info("Loading authentication provider of type {} at position {}", provider.type(), provider.index());

            boolean found = false;
            Collection identityProviders = identityProviderManager.getAll();
            for (IdentityProvider identityProvider : identityProviders) {
                if (identityProvider.type().equalsIgnoreCase(provider.type())) {
                    AuthenticationProvider authenticationProviderPlugin = identityProviderManager.loadIdentityProvider(
                        identityProvider.type(),
                        provider.configuration()
                    );

                    if (authenticationProviderPlugin != null) {
                        Object authenticationProvider = authenticationProviderPlugin.configure();

                        if (authenticationProvider instanceof org.springframework.security.authentication.AuthenticationProvider) {
                            auth.authenticationProvider(
                                (org.springframework.security.authentication.AuthenticationProvider) authenticationProvider
                            );
                        } else if (authenticationProvider instanceof SecurityConfigurer) {
                            auth.apply((SecurityConfigurer) authenticationProvider);
                        }
                        found = true;
                        break;
                    }
                }
            }

            if (!found) {
                LOGGER.error("No authentication provider found for type: {}", provider.type());
            }
        }
        LOGGER.info("--------------------------------------------------------------");
    }

    private HttpSecurity authentication(HttpSecurity security) throws Exception {
        return security
            .httpBasic()
            .authenticationDetailsSource(authenticationDetailsSource())
            .realmName("Gravitee.io Management API")
            .and();
    }

    private HttpSecurity session(HttpSecurity security) throws Exception {
        return security.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and();
    }

    private HttpSecurity authorizations(HttpSecurity security) throws Exception {
        String uriOrgPrefix = "/organizations/**";
        String uriPrefix = uriOrgPrefix + "/environments/**";

        return security
            .authorizeHttpRequests()
            /*
             * Swagger
             */
            // ---------------------------
            // TODO: DELETE THIS IN 3.18.0
            .requestMatchers(HttpMethod.GET, "/swagger.json")
            .permitAll()
            // ---------------------------

            .requestMatchers(HttpMethod.GET, "/openapi.*")
            .permitAll()
            .requestMatchers(HttpMethod.OPTIONS, "**")
            .permitAll()
            /*
             * Global auth resources.
             */
            .requestMatchers(HttpMethod.GET, "/auth/cockpit")
            .permitAll()
            /*
             * Global external auth resources.
             */
            .requestMatchers(HttpMethod.GET, "/auth/external")
            .permitAll()
            /*
             * organizations resources
             */
            // Console resource
            .requestMatchers(HttpMethod.GET, uriOrgPrefix + "/console/**")
            .permitAll()
            // Auth resource
            .requestMatchers(HttpMethod.POST, uriOrgPrefix + "/auth/**")
            .permitAll()
            .requestMatchers(HttpMethod.GET, uriOrgPrefix + "/social-identities")
            .permitAll()
            // Current user
            .requestMatchers(HttpMethod.POST, uriOrgPrefix + "/user/login")
            .permitAll()
            .requestMatchers(HttpMethod.GET, uriOrgPrefix + "/user/**")
            .authenticated()
            // Users management
            .requestMatchers(HttpMethod.GET, uriOrgPrefix + "/users/custom-fields")
            .permitAll()
            .requestMatchers(HttpMethod.POST, uriOrgPrefix + "/users/registration/**")
            .permitAll()
            .requestMatchers(HttpMethod.POST, uriOrgPrefix + "/users/**/changePassword")
            .permitAll()
            .requestMatchers(HttpMethod.GET, uriOrgPrefix + "/users")
            .authenticated()
            .requestMatchers(HttpMethod.GET, uriOrgPrefix + "/users/**")
            .authenticated()
            .requestMatchers(HttpMethod.PUT, uriOrgPrefix + "/users/**")
            .authenticated()
            .requestMatchers(HttpMethod.DELETE, uriOrgPrefix + "/users/**")
            .authenticated()
            // Organization configuration
            .requestMatchers(HttpMethod.GET, uriOrgPrefix + "/configuration/rolescopes/**")
            .permitAll()
            .requestMatchers(HttpMethod.GET, uriOrgPrefix + "/configuration/custom-user-fields")
            .permitAll()
            .requestMatchers(uriOrgPrefix + "/configuration/**")
            .authenticated()
            // Search for users
            .requestMatchers(HttpMethod.GET, uriOrgPrefix + "/search/users")
            .authenticated()
            /*
             * environments resources
             */
            // DEPRECATED
            // Auth resource
            .requestMatchers(HttpMethod.POST, uriPrefix + "/auth/**")
            .permitAll()
            // DEPRECATED
            // Current user
            .requestMatchers(HttpMethod.POST, uriPrefix + "/user/login")
            .permitAll()
            .requestMatchers(HttpMethod.GET, uriPrefix + "/user/**")
            .authenticated()
            // DEPRECATED
            // Users management
            .requestMatchers(HttpMethod.GET, uriPrefix + "/users/custom-fields")
            .permitAll()
            .requestMatchers(HttpMethod.POST, uriPrefix + "/users/registration/**")
            .permitAll()
            .requestMatchers(HttpMethod.GET, uriPrefix + "/users")
            .authenticated()
            .requestMatchers(HttpMethod.GET, uriPrefix + "/users/**")
            .authenticated()
            .requestMatchers(HttpMethod.PUT, uriPrefix + "/users/**")
            .authenticated()
            .requestMatchers(HttpMethod.DELETE, uriPrefix + "/users/**")
            .authenticated()
            // DEPRECATED
            // configuration
            .requestMatchers(HttpMethod.GET, uriPrefix + "/configuration/rolescopes/**")
            .permitAll()
            // DEPRECATED
            // Search for users
            .requestMatchers(HttpMethod.GET, uriPrefix + "/search/users")
            .authenticated()
            /*
             * environments resources
             */
            // API requests
            .requestMatchers(HttpMethod.GET, uriPrefix + "/apis/hooks")
            .authenticated()
            .requestMatchers(HttpMethod.GET, uriPrefix + "/apis/**")
            .authenticated()
            .requestMatchers(HttpMethod.POST, uriPrefix + "/apis")
            .authenticated()
            .requestMatchers(HttpMethod.POST, uriPrefix + "/apis/**")
            .authenticated()
            .requestMatchers(HttpMethod.PUT, uriPrefix + "/apis/**")
            .authenticated()
            .requestMatchers(HttpMethod.DELETE, uriPrefix + "/apis/**")
            .authenticated()
            // Application requests
            .requestMatchers(HttpMethod.POST, uriPrefix + "/applications")
            .authenticated()
            .requestMatchers(HttpMethod.POST, uriPrefix + "/applications/**")
            .authenticated()
            .requestMatchers(HttpMethod.PUT, uriPrefix + "/applications/**")
            .authenticated()
            .requestMatchers(HttpMethod.DELETE, uriPrefix + "/applications/**")
            .authenticated()
            // Subscriptions
            .requestMatchers(HttpMethod.GET, uriPrefix + "/subscriptions/**")
            .authenticated()
            // Instance requests
            .requestMatchers(HttpMethod.GET, uriPrefix + "/instances/**")
            .authenticated()
            // Platform requests
            .requestMatchers(HttpMethod.GET, uriPrefix + "/platform/**")
            .authenticated()
            // Configuration Groups
            .requestMatchers(HttpMethod.GET, uriPrefix + "/configuration/groups/**")
            .permitAll()
            // Configuration Categories
            .requestMatchers(HttpMethod.GET, uriPrefix + "/configuration/categories/**")
            .permitAll()
            // Configuration Tags
            .requestMatchers(HttpMethod.GET, uriPrefix + "/configuration/tags/**")
            .permitAll()
            // Configuration Tenants
            .requestMatchers(HttpMethod.GET, uriPrefix + "/configuration/tenants/**")
            .permitAll()
            // Configuration
            .requestMatchers(uriPrefix + "/configuration/**")
            .authenticated()
            // Entrypoints
            .requestMatchers(HttpMethod.GET, uriPrefix + "/entrypoints/**")
            .permitAll()
            .anyRequest()
            .authenticated()
            .and();
    }

    private AuthenticationDetailsSource authenticationDetailsSource() {
        return GraviteeAuthenticationDetails::new;
    }

    private HttpSecurity hsts(HttpSecurity security) throws Exception {
        HeadersConfigurer.HstsConfig hstsConfig = security.headers().httpStrictTransportSecurity();

        Boolean hstsEnabled = environment.getProperty("http.hsts.enabled", Boolean.class, true);
        if (hstsEnabled) {
            return hstsConfig
                .includeSubDomains(environment.getProperty("http.hsts.include-sub-domains", Boolean.class, true))
                .maxAgeInSeconds(environment.getProperty("http.hsts.max-age", Long.class, 31536000L))
                .and()
                .and();
        }

        return hstsConfig.disable().and();
    }

    private HttpSecurity csrf(HttpSecurity security) throws Exception {
        if (environment.getProperty("http.csrf.enabled", Boolean.class, false)) {
            // Don't use deferred csrf (see https://docs.spring.io/spring-security/reference/5.8/migration/servlet/exploits.html#_i_need_to_opt_out_of_deferred_tokens_for_another_reason)
            final CsrfTokenRequestAttributeHandler requestHandler = new CsrfTokenRequestAttributeHandler();
            requestHandler.setCsrfRequestAttributeName(null);

            final CookieCsrfSignedTokenRepository csrfTokenRepository = cookieCsrfSignedTokenRepository();
            return security
                .csrf(csrf ->
                    csrf
                        .csrfTokenRepository(csrfTokenRepository)
                        .requireCsrfProtectionMatcher(new CsrfRequestMatcher())
                        .csrfTokenRequestHandler(requestHandler)
                        .sessionAuthenticationStrategy((authentication, request, response) -> {
                            // Force the csrf cookie to be pushed back in the response cookies to keep it across subsequent request.
                            csrfTokenRepository.saveToken((CsrfToken) request.getAttribute(CsrfToken.class.getName()), request, response);
                        })
                )
                .addFilterAfter(new CsrfIncludeFilter(), CsrfFilter.class);
        } else {
            // deepcode ignore DisablesCSRFProtection: CSRF Protection is disabled here to match configuration set by the user (via gravitee.yml)
            return security.csrf(AbstractHttpConfigurer::disable);
        }
    }

    private HttpSecurity cors(HttpSecurity security) throws Exception {
        return security.cors().and();
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy