kakawait / cas-security-spring-boot-starter

Spring boot starter for Apereo CAS client fully integrated with Spring security
MIT License
153 stars 45 forks source link
apereo cas spring spring-boot spring-security

Spring Security CAS starter

Travis Maven Central License Codecov SonarQube Tech Debt Twitter Follow

A Spring boot starter that will help you configure Spring Security Cas within the application security context.

Features

Setup

Add the Spring boot starter to your project

<dependency>
  <groupId>com.kakawait</groupId>
  <artifactId>cas-security-spring-boot-starter</artifactId>
  <version>1.1.0</version>
</dependency>

But be careful 1.x.x version has some breaking changes if you comes from 0.x.x version. But be careful 2.x.x version will have some breaking changes if you comes from 1.x.x version.

Please checkout CHANGELOG.md, in particular breaking changes sections.

* breaking changes should be only possible between two major version, example:

Usage

In order to trigger auto-configuration you must fill, at least, the following properties regarding the resolution mode you want to use

static (classic) resolution mode

static resolution mode is classic and default mode that you could find if you're using plain old Apereo Java client or Spring Security CAS.

Thus you have to fill at least the following mandatory properties:

security:
  cas:
    server:
      base-url: http://your.cas.server/cas
    service:
      base-url: http://localhost:8080
Property Apereo Java client equivalent Description
security.cas.server.base-url casServerUrlPrefix The start of the CAS server url, i.e. https://localhost:8443/cas
security.cas.service.base-url serviceName The name of the server this application is hosted on. Service URL will be dynamically constructed using this, i.e. https://localhost:8443 (you must include the protocol, but port is optional if it's a standard port).

dynamic resolution mode:

dynamic resolution mode is a novel mode from that starter that will allow you to do not hard-code service url in your configuration. Thereby your configuration will be more portable and easy to use.

ATTENTION dynamic resolution mode use information from HttpServletRequest to build service url, that can be a security breach if you do not control headers like Host or X-Forwarded-* that why dynamic resolution mode is not the default mode and you must activate it as described in below properties.

security:
  cas:
    server:
      base-url: http://your.cas.server/cas
    service:
      resolution-mode: dynamic
Property Apereo Java client equivalent Description
security.cas.server.base-url casServerUrlPrefix the start of the CAS server url, i.e. https://localhost:8443/cas
security.cas.service.resolution-mode Not implemented Resolution modes can be static or dynamic, by default is static and you must fill security.cas.service.base-url whereas in dynamic mode service url will be generated from receiving HttpServletRequest

if you're using X-Forwarding-Prefix header I will strongly recommend you to use ForwardedHeaderFilter since Tomcat RemoteIpValve used when setting up server.use-forward-headers=true does not support prefix/context-path.

@Bean
FilterRegistrationBean forwardedHeaderFilter() {
    FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
    filterRegistrationBean.setFilter(new ForwardedHeaderFilter());
    filterRegistrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE);
    return filterRegistrationBean;
}

Properties

The supported properties are:

Property Default value Description
security.cas.enabled true Enable CAS security
security.cas.key UUID.randomUUID().toString() An id used by the CasAuthenticationProvider
security.cas.paths /** Comma-separated list of paths to secure (works the same way as security.basic.path)
security.cas.user.default-roles USER Comma-separated list of default user roles. If roles have been found from security.cas.user.roles-attributes default roles will be append to the list of users roles
security.cas.user.roles-attributes Comma-separated list of CAS attributes to be used to determine user roles
security.cas.proxy-validation.enabled true Defines if proxy should be checked again chains security.cas.proxy-validation.chains
security.cas.proxy-validation.chains Defines proxy chains. Each acceptable proxy chain should include a comma-separated list of URLs (for exact match) or regular expressions of URLs (starting by the ^ character)
security.cas.server.protocol-version 3 Determine which CAS protocol version to be used, only protocol version 1, 2 or 3 is supported.
security.cas.server.base-url The start of the CAS server url, i.e. https://localhost:8443/cas
security.cas.server.validation-base-url Optional, security.cas.server.base-url is used if missing. The start of the CAS server url (similar to security.cas.server.base-url) used during ticket validation flow. Could be useful when server (your service) to server (CAS server) network is different from your external/browser network (i.e. docker environment, see docker profile properties).
security.cas.server.paths.login /login Defines the location of the CAS server login path that will be append to the existing security.cas.server.base-url url
security.cas.server.paths.logout /logout Defines the location of the CAS server logout path that will be appended to the existing security.cas.server.base-url url
security.cas.service.resolution-mode static Resolution modes can be static or dynamic, the default is static and you must fill security.cas.service.base-url whereas in dynamic mode service url will be generated from receiving HttpServletRequest. Attention will not override security.cas.server.validation-base-url and security.cas.service.callback-base-url if defined, see docker profile properties to get an example.
security.cas.service.base-url The name of the server this application is hosted on. Service URL will be dynamically constructed using this, i.e. https://localhost:8443 (you must include the protocol, but port is optional if it's a standard port). Skipped if resolution mode is dynamic.
security.cas.service.callback-base-url Optional, security.cas.service.base-url is used if missing. Represents the base url that will be used to compute Proxy granting ticket callback (see security.cas.service.paths.proxy-callback). It could be useful to be different from security.cas.service.base-url when server (CAS server) to server (your service) network is different from your external/browser network (i.e. docker environment, see see docker profile properties).
security.cas.service.paths.login /login Defines the application login path that will be appended to the existing security.cas.service.base-url url
security.cas.service.paths.logout /logout Defines the application logout path that will be appended to the existing security.cas.service.base-url url
security.cas.service.paths.proxy-callback The callback path that will be, if present, appended to the security.cas.service.callback-base-url or security.cas.service.base-url and added to as parameter inside request validation. It must be set if you want to receive Proxy Granting Ticket PGT.

Otherwise you can checkout CasSecurityProperties class.

Additional configuration

If you need to set additional configuration options simply register within Spring application context instance of CasSecurityConfigurerAdapter

@Configuration
class CustomCasSecurityConfiguration extends CasSecurityConfigurerAdapter {
    @Override
    public void configure(CasAuthenticationFilterConfigurer filter) {
        // Here you can configure CasAuthenticationFilter
    }

    @Override
    public void configure(CasSingleSignOutFilterConfigurer filter) {
        // Here you can configure SingleSignOutFilter
    }

    @Override
    public void configure(CasAuthenticationProviderSecurityBuilder provider) {
        // Here  you can configure CasAuthenticationProvider
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        // Here you can configure Spring Security HttpSecurity object during init configure
    }

    @Override
    public void configure(CasTicketValidatorBuilder ticketValidator) {
        // Here you can configure CasTicketValidator
    }
}

Otherwise many beans defined in that starter are annotated with @ConditionOnMissingBean thus you can override default bean definitions.

Proxy granting storage

Starter does not provide any additional proxy granting storage (yet), by default an in memory storage is used ProxyGrantingTicketStorageImpl.

To override it you can expose a ProxyGrantingTicketStorage bean like following:

@Bean
ProxyGrantingTicketStorage proxyGrantingTicketStorage() {
    return new MyCustomProxyGrantingTicketStorage();
}

Or use configurer but a bit longer since you must report ProxyGrantingTicketStorage in both CasAuthenticationFilter and TicketValidator

@Configuration
class CustomCasSecurityConfiguration extends CasSecurityConfigurerAdapter {
    @Override
    public void configure(CasAuthenticationFilterConfigurer filter) {
        filter.proxyGrantingTicketStorage(new MyCustomProxyGrantingStorage());
    }

    @Override
    public void configure(CasTicketValidatorBuilder ticketValidator) {
        ticketValidator.proxyGrantingTicketStorage(new MyCustomProxyGrantingStorage());
    }
}

Logout & SLO

By default starter will configure both logout and single logout (SLO).

ATTENTION default logout (on /logout) behavior will:

  1. Logout from application and also logout from CAS server that will logout any other applications.
  2. Keep default Spring security behavior concerning CSRF and logging out to summarize if CSRF is enabled logout will only mapped on POST, see https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#csrf-logout for more details

If you want to change those behaviors, for example by adding a logout page that will propose user to logout from other application, you may configure like following:

@Configuration
class CasCustomLogoutConfiguration extends CasSecurityConfigurerAdapter {
    private final CasSecurityProperties casSecurityProperties;

    private final LogoutSuccessHandler casLogoutSuccessHandler;

    public CustomLogoutConfiguration(LogoutSuccessHandler casLogoutSuccessHandler) {
        this.casLogoutSuccessHandler = casLogoutSuccessHandler;
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.logout()
            .permitAll()
            // Add null logoutSuccessHandler to disable CasLogoutSuccessHandler
            .logoutSuccessHandler(null)
            .logoutSuccessUrl("/logout.html")
            .logoutRequestMatcher(new AntPathRequestMatcher("/logout"));
        LogoutFilter filter = new LogoutFilter(casLogoutSuccessHandler, new SecurityContextLogoutHandler());
        filter.setFilterProcessesUrl("/cas/logout");
        http.addFilterBefore(filter, LogoutFilter.class);
    }
}

@Configuration
class WebMvcConfiguration extends WebMvcConfigurerAdapter {
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/logout.html").setViewName("logout");
        registry.setOrder(Ordered.HIGHEST_PRECEDENCE);
    }
}

With possible logout.html like following

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8" />
    <title>Logout page</title>
</head>
<body>
    <h2>Do you want to log out of CAS?</h2>
    <p>You have logged out of this application, but may still have an active single-sign on session with CAS.</p>
    <p><a href="https://github.com/kakawait/cas-security-spring-boot-starter/blob/master/cas/logout" th:href="https://github.com/kakawait/cas-security-spring-boot-starter/blob/master/@{/cas/logout}">Logout of CAS</a></p>
</body>
</html>

You can checkout & run sample module cas-security-spring-boot-sample with profile custom-logout.

Proxy chains validation

By default client configuration is security.cas.proxy-validation.enabled = true with empty proxy chains (security.cas.proxy-validation.chains). That means you will not be able to validate proxy ticket since proxy chains is empty.

You should disable proxy validation using:

security:
  cas:
    proxy-validation:
      enabled: false

But is not recommended for production environment, or define your own proxy chains:

security
  cas:
    proxy-validation:
      chains:
        - http://localhost:8180, http://localhost:8181
        - - http://localhost:8280
          - http://localhost:8281
        - ^http://my\\.domain\\..*

As you can see there is multiple syntaxes for yml format to define collection of collection:

  1. Using comma-separated list
  2. Using double - - syntax

If you are using properties format you could translate like following:

security.cas.proxy-validation.chains[0] = http://localhost:8180, http://localhost:8181
security.cas.proxy-validation.chains[1] = http://localhost:8280, http://localhost:8281
security.cas.proxy-validation.chains[2] = ^http://my\\.domain\\..*

RestTemplate integration with Proxy ticket

Since 0.7.0 version, there is a simple integration with RestTemplate but not enabled by default.

In order to enable it you must create your own RestTemplate bean and add an interceptor

@Bean
RestTemplate casRestTemplate(ServiceProperties serviceProperties, ProxyTicketProvider proxyTicketProvider) {
    RestTemplate restTemplate = new RestTemplate();
    restTemplate.getInterceptors().add(new CasAuthorizationInterceptor(serviceProperties, proxyTicketProvider));
    return restTemplate;
}

This interceptor is pretty simple, it will simply ask a new proxy ticket for each request and append it to request query parameter. For example with: http://httpbin.org/get interceptor will modify request uri to become http://httpbin.org/get?ticket=PT-XX-YYYYYYYYYY.

ATTENTION if interceptor gets any issue to get proxy ticket from CAS server, it will throw an IllegalStateException.

Please checkout You can find sample usage for both on CasSecuritySpringBootSampleApplication

AssertionProvider and ProxyTicketProvider

In addition to RestTemplate integration, since 0.7.0 there are now two new autoconfigured beans:

  1. AssertionProvider that will provide you a way to retrieve the current (bounded to current authenticated request) org.jasig.cas.client.validation.Assertion
  2. ProxyTicketProvider that will provide you a simple way to ask a proxy ticket for a given service (regarding the current authenticated request)

You can find sample usage for both on CasSecuritySpringBootSampleApplication

License

MIT License