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

org.apereo.cas.web.security.CasWebSecurityConfigurerAdapter Maven / Gradle / Ivy

There is a newer version: 7.2.0-RC4
Show newest version
package org.apereo.cas.web.security;

import org.apereo.cas.configuration.CasConfigurationProperties;
import org.apereo.cas.configuration.model.core.monitor.ActuatorEndpointProperties;
import org.apereo.cas.configuration.model.core.monitor.JaasSecurityActuatorEndpointsMonitorProperties;
import org.apereo.cas.util.function.FunctionUtils;
import org.apereo.cas.util.spring.beans.BeanSupplier;
import org.apereo.cas.web.CasWebSecurityConfigurer;
import org.apereo.cas.web.CasWebSecurityConstants;
import org.apereo.cas.web.security.authentication.IpAddressAuthorizationManager;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.jooq.lambda.Unchecked;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties;
import org.springframework.boot.actuate.autoconfigure.security.servlet.EndpointRequest;
import org.springframework.boot.actuate.endpoint.web.PathMappedEndpoints;
import org.springframework.boot.autoconfigure.security.servlet.PathRequest;
import org.springframework.boot.autoconfigure.web.WebProperties;
import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.jaas.JaasAuthenticationProvider;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.web.authentication.www.BasicAuthenticationConverter;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.security.web.context.SecurityContextRepository;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.ResourceUtils;
import jakarta.servlet.http.HttpServletRequest;
import java.io.File;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

/**
 * This is {@link CasWebSecurityConfigurerAdapter}.
 *
 * @author Misagh Moayyed
 * @since 6.0.0
 */
@Slf4j
@Order(CasWebSecurityConstants.SECURITY_CONFIGURATION_ORDER)
@RequiredArgsConstructor
public class CasWebSecurityConfigurerAdapter {

    private final ObjectPostProcessor basicAuthFilterPostProcessor = new ObjectPostProcessor<>() {
        @Override
        public  O postProcess(final O object) {
            val patternsToIgnore = getAllowedPatternsToIgnore()
                .stream().map(AntPathRequestMatcher::new).collect(Collectors.toSet());
            object.setAuthenticationConverter(new BasicAuthenticationConverter() {
                @Override
                public UsernamePasswordAuthenticationToken convert(final HttpServletRequest request) {
                    val requestIsNotIgnored = patternsToIgnore.stream().noneMatch(requestMatcher -> requestMatcher.matches(request));
                    return requestIsNotIgnored ? super.convert(request) : null;
                }
            });
            return object;
        }
    };

    private final CasConfigurationProperties casProperties;

    private final WebEndpointProperties webEndpointProperties;

    private final ObjectProvider pathMappedEndpoints;

    private final List webSecurityConfigurers;

    private final SecurityContextRepository securityContextRepository;

    private final WebProperties webProperties;

    private static List prepareProtocolEndpoint(final String endpoint) {
        val baseEndpoint = StringUtils.prependIfMissing(endpoint, "/");
        return List.of(baseEndpoint.concat("**"), StringUtils.appendIfMissing(endpoint, "/").concat("**"));
    }

    private static void configureJaasAuthenticationProvider(final HttpSecurity http,
                                                            final JaasSecurityActuatorEndpointsMonitorProperties jaas) throws Exception {
        val provider = new JaasAuthenticationProvider();
        provider.setLoginConfig(jaas.getLoginConfig());
        provider.setLoginContextName(jaas.getLoginContextName());
        provider.setRefreshConfigurationOnStartup(jaas.isRefreshConfigurationOnStartup());
        provider.afterPropertiesSet();
        http.authenticationProvider(provider);
    }

    /**
     * Configure web security for spring security.
     *
     * @param web web security
     */
    public void configureWebSecurity(final WebSecurity web) {
    }

    /**
     * Configure http security.
     *
     * @param http the http
     * @return the http security
     * @throws Exception the exception
     */
    public HttpSecurity configureHttpSecurity(final HttpSecurity http) throws Exception {
        http
            .cors(Customizer.withDefaults())
            .csrf(AbstractHttpConfigurer::disable)
            .headers(AbstractHttpConfigurer::disable)
            .logout(AbstractHttpConfigurer::disable)
            .requiresChannel(customizer -> customizer.requestMatchers(r -> r.getHeader("X-Forwarded-Proto") != null).requiresSecure());

        val patterns = getAllowedPatternsToIgnore();
        LOGGER.debug("Configuring protocol endpoints [{}] to exclude/ignore from http security", patterns);
        var requests = http.authorizeHttpRequests(customizer -> {
            val matchers = patterns.stream().map(AntPathRequestMatcher::new).toList().toArray(new RequestMatcher[0]);
            customizer.requestMatchers(matchers).permitAll();
        });
        webSecurityConfigurers
            .stream()
            .filter(BeanSupplier::isNotProxy)
            .forEach(Unchecked.consumer(cfg -> cfg.configure(http)));

        val endpoints = casProperties.getMonitor().getEndpoints().getEndpoint();
        endpoints.forEach(Unchecked.biConsumer((key, endpointProps) -> {
            val endpoint = EndpointRequest.to(key);
            endpointProps.getAccess().forEach(Unchecked.consumer(
                access -> configureEndpointAccess(requests, access, endpointProps, endpoint)));
        }));
        configureEndpointAccessToDenyUndefined(requests);
        configureEndpointAccessForStaticResources(requests);
        configureEndpointAccessByFormLogin(requests);

        val jaas = casProperties.getMonitor().getEndpoints().getJaas();
        FunctionUtils.doIfNotNull(jaas.getLoginConfig(), __ -> configureJaasAuthenticationProvider(http, jaas));

        http.securityContext(securityContext -> securityContext.securityContextRepository(securityContextRepository));
        webSecurityConfigurers
            .stream()
            .filter(BeanSupplier::isNotProxy)
            .forEach(Unchecked.consumer(cfg -> cfg.finish(http)));
        return http;
    }

    protected List getAllowedPatternsToIgnore() {
        val patterns = webSecurityConfigurers
            .stream()
            .filter(BeanSupplier::isNotProxy)
            .map(CasWebSecurityConfigurer::getIgnoredEndpoints)
            .flatMap(List::stream)
            .map(CasWebSecurityConfigurerAdapter::prepareProtocolEndpoint)
            .flatMap(List::stream)
            .collect(Collectors.toList());
        patterns.add("/webjars/**");
        patterns.add("/themes/**");
        patterns.add("/js/**");
        patterns.add("/css/**");
        patterns.add("/images/**");
        patterns.add("/static/**");
        patterns.add("/public/**");
        patterns.add("/error");
        patterns.add("/favicon.ico");
        patterns.add(CasWebSecurityConfigurer.ENDPOINT_URL_ADMIN_FORM_LOGIN);
        patterns.add("/");
        patterns.add(webEndpointProperties.getBasePath());
        patterns.addAll(casProperties.getMonitor().getEndpoints().getIgnoredEndpoints());
        return patterns;
    }

    protected void configureEndpointAccessToDenyUndefined(
        final HttpSecurity http) {
        val endpoints = casProperties.getMonitor().getEndpoints().getEndpoint().keySet();
        val endpointDefaults = casProperties.getMonitor().getEndpoints().getDefaultEndpointProperties();
        pathMappedEndpoints.getObject()
            .stream()
            .filter(BeanSupplier::isNotProxy)
            .forEach(endpoint -> {
                val rootPath = endpoint.getRootPath();
                if (endpoints.contains(rootPath)) {
                    LOGGER.trace("Endpoint security is defined for endpoint [{}]", rootPath);
                } else {
                    val defaultAccessRules = endpointDefaults.getAccess();
                    LOGGER.trace("Endpoint security is NOT defined for endpoint [{}]. Using default security rules [{}]", rootPath, endpointDefaults);
                    val endpointRequest = EndpointRequest.to(rootPath).excludingLinks();
                    defaultAccessRules.forEach(Unchecked.consumer(access ->
                        configureEndpointAccess(http, access, endpointDefaults, endpointRequest)));
                }
            });
    }

    protected void configureEndpointAccessForStaticResources(final HttpSecurity requests) throws Exception {
        requests.authorizeHttpRequests(customizer -> {
            customizer.requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll();
            customizer.requestMatchers(new AntPathRequestMatcher("/resources/**")).permitAll();
            customizer.requestMatchers(new AntPathRequestMatcher("/static/**")).permitAll();
            customizer.requestMatchers(new AntPathRequestMatcher("/public/**")).permitAll();
            Arrays.stream(webProperties.getResources().getStaticLocations())
                .forEach(location -> {
                    if (location.startsWith(ResourceUtils.FILE_URL_PREFIX)) {
                        val file = new File(StringUtils.remove(location, ResourceUtils.FILE_URL_PREFIX));
                        if (file.exists() && file.isDirectory()) {
                            val directories = Arrays.stream(file.listFiles(File::isDirectory)).toList();
                            LOGGER.info("Directories to authorize for static public resources are [{}]", directories);
                            directories.forEach(directory -> customizer.requestMatchers(new AntPathRequestMatcher('/' + directory.getName() + "/**")).permitAll());
                        }
                    }
                });
        });
    }

    protected void configureEndpointAccessByFormLogin(final HttpSecurity http) throws Exception {
        if (casProperties.getMonitor().getEndpoints().isFormLoginEnabled()) {
            http.formLogin(customize -> customize.loginPage(CasWebSecurityConfigurer.ENDPOINT_URL_ADMIN_FORM_LOGIN));
        } else {
            http.formLogin(AbstractHttpConfigurer::disable);
        }
    }

    protected void configureEndpointAccess(final HttpSecurity httpSecurity,
                                           final ActuatorEndpointProperties.EndpointAccessLevel access,
                                           final ActuatorEndpointProperties properties,
                                           final EndpointRequest.EndpointRequestMatcher endpoint) throws Exception {
        switch (access) {
            case AUTHORITY -> configureEndpointAccessByAuthority(httpSecurity, properties, endpoint);
            case ROLE -> configureEndpointAccessByRole(httpSecurity, properties, endpoint);
            case AUTHENTICATED -> configureEndpointAccessAuthenticated(httpSecurity, endpoint);
            case IP_ADDRESS -> configureEndpointAccessByIpAddress(httpSecurity, properties, endpoint);
            case PERMIT -> configureEndpointAccessPermitAll(httpSecurity, endpoint);
            case ANONYMOUS -> configureEndpointAccessAnonymously(httpSecurity, endpoint);
            default -> configureEndpointAccessToDenyAll(httpSecurity, endpoint);
        }
    }

    protected void configureEndpointAccessPermitAll(final HttpSecurity requests,
                                                    final EndpointRequest.EndpointRequestMatcher endpoint) throws Exception {
        requests.authorizeHttpRequests(customizer -> customizer.requestMatchers(endpoint).permitAll());
    }

    protected void configureEndpointAccessToDenyAll(final HttpSecurity requests,
                                                    final EndpointRequest.EndpointRequestMatcher endpoint) throws Exception {
        requests.authorizeHttpRequests(customizer -> customizer.requestMatchers(endpoint).denyAll());
    }

    protected void configureEndpointAccessAnonymously(final HttpSecurity requests,
                                                      final EndpointRequest.EndpointRequestMatcher endpoint) throws Exception {

        requests.authorizeHttpRequests(customizer -> customizer.requestMatchers(endpoint).permitAll());
    }

    protected void configureEndpointAccessByIpAddress(final HttpSecurity requests,
                                                      final ActuatorEndpointProperties properties,
                                                      final EndpointRequest.EndpointRequestMatcher endpoint) throws Exception {
        requests.authorizeHttpRequests(customizer -> customizer.requestMatchers(endpoint)
            .access(new IpAddressAuthorizationManager(casProperties, properties)));
    }

    protected void configureEndpointAccessAuthenticated(
        final HttpSecurity http,
        final EndpointRequest.EndpointRequestMatcher endpoint) throws Exception {
        http.authorizeHttpRequests(customizer -> customizer.requestMatchers(endpoint).authenticated())
            .httpBasic(customizer -> customizer.withObjectPostProcessor(basicAuthFilterPostProcessor));
    }

    protected void configureEndpointAccessByRole(
        final HttpSecurity http,
        final ActuatorEndpointProperties properties,
        final EndpointRequest.EndpointRequestMatcher endpoint) throws Exception {
        http.authorizeHttpRequests(customizer -> customizer.requestMatchers(endpoint)
                .hasAnyRole(properties.getRequiredRoles().toArray(ArrayUtils.EMPTY_STRING_ARRAY)))
            .httpBasic(customizer -> customizer.withObjectPostProcessor(basicAuthFilterPostProcessor));
    }

    protected void configureEndpointAccessByAuthority(
        final HttpSecurity http,
        final ActuatorEndpointProperties properties,
        final EndpointRequest.EndpointRequestMatcher endpoint) throws Exception {
        http.authorizeHttpRequests(customizer -> customizer.requestMatchers(endpoint)
                .hasAnyAuthority(properties.getRequiredAuthorities().toArray(ArrayUtils.EMPTY_STRING_ARRAY)))
            .httpBasic(customizer -> customizer.withObjectPostProcessor(basicAuthFilterPostProcessor));
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy