Spring Security CAS starter
A Spring boot starter that will help you configure Spring Security Cas within the application security context.
Features
- Spring boot 1 and 2 support.
- Configures CAS authentication and authorization
- Support dynamic service resolution based on current
HttpServletRequest
- Advance configuration through CasSecurityConfigurerAdapter
- Integration with Basic authentication if
security.basic.enabled=true
will allow you to authenticate using headerAuthorization: Basic ...
in addition to CAS RestTemplate
integration
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:
- from
0.x.x
to1.x.x
- from
1.x.x
to2.x.x
- ...
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:
- Logout from application and also logout from CAS server that will logout any other applications.
- 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="/cas/logout" th:href="@{/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:
- Using comma-separated list
- 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:
AssertionProvider
that will provide you a way to retrieve the current (bounded to current authenticated request)org.jasig.cas.client.validation.Assertion
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