Add CAS Gateway sample
Closes gh-174
This commit is contained in:
@@ -17,6 +17,8 @@ services:
|
|||||||
--server.port=8080
|
--server.port=8080
|
||||||
--cas.service-registry.core.init-from-json=true
|
--cas.service-registry.core.init-from-json=true
|
||||||
--cas.service-registry.json.location=file:/etc/cas/services
|
--cas.service-registry.json.location=file:/etc/cas/services
|
||||||
|
--cas.tgc.secure=false
|
||||||
|
--cas.tgc.sameSitePolicy=Lax
|
||||||
volumes:
|
volumes:
|
||||||
- ./services/http-1.json:/etc/cas/services/http-1.json
|
- ./services/http-1.json:/etc/cas/services/http-1.json
|
||||||
networks:
|
networks:
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ plugins {
|
|||||||
id 'java'
|
id 'java'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ext['spring-security.version'] = '6.3.0-SNAPSHOT'
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
maven { url "https://repo.spring.io/milestone" }
|
maven { url "https://repo.spring.io/milestone" }
|
||||||
|
|||||||
@@ -38,4 +38,9 @@ public class IndexController {
|
|||||||
return "loggedout";
|
return "loggedout";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("/public")
|
||||||
|
String publicPage() {
|
||||||
|
return "public";
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ import org.springframework.security.cas.ServiceProperties;
|
|||||||
import org.springframework.security.cas.authentication.CasAuthenticationProvider;
|
import org.springframework.security.cas.authentication.CasAuthenticationProvider;
|
||||||
import org.springframework.security.cas.web.CasAuthenticationEntryPoint;
|
import org.springframework.security.cas.web.CasAuthenticationEntryPoint;
|
||||||
import org.springframework.security.cas.web.CasAuthenticationFilter;
|
import org.springframework.security.cas.web.CasAuthenticationFilter;
|
||||||
|
import org.springframework.security.cas.web.CasGatewayAuthenticationRedirectFilter;
|
||||||
|
import org.springframework.security.cas.web.CasGatewayResolverRequestMatcher;
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
import org.springframework.security.core.userdetails.User;
|
import org.springframework.security.core.userdetails.User;
|
||||||
import org.springframework.security.core.userdetails.UserDetails;
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
@@ -38,6 +40,9 @@ import org.springframework.security.core.userdetails.UserDetailsByNameServiceWra
|
|||||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||||
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
|
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
|
||||||
import org.springframework.security.web.SecurityFilterChain;
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
|
import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher;
|
||||||
|
import org.springframework.security.web.util.matcher.AndRequestMatcher;
|
||||||
|
import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
public class SecurityConfig {
|
public class SecurityConfig {
|
||||||
@@ -52,14 +57,30 @@ public class SecurityConfig {
|
|||||||
private ServletWebServerApplicationContext context;
|
private ServletWebServerApplicationContext context;
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public SecurityFilterChain filterChain(HttpSecurity http, UserDetailsService userDetailsService) throws Exception {
|
public SecurityFilterChain filterChain(HttpSecurity http, UserDetailsService userDetailsService,
|
||||||
http.authorizeHttpRequests((authorize) -> authorize.requestMatchers(HttpMethod.GET, "/loggedout").permitAll()
|
MvcRequestMatcher.Builder builder) throws Exception {
|
||||||
.anyRequest().authenticated())
|
// @formatter:off
|
||||||
|
CasGatewayAuthenticationRedirectFilter casGatewayAuthenticationRedirectFilter = new CasGatewayAuthenticationRedirectFilter(this.casLoginUrl, serviceProperties());
|
||||||
|
casGatewayAuthenticationRedirectFilter.setRequestMatcher(new AndRequestMatcher(
|
||||||
|
builder.pattern("/public"), new CasGatewayResolverRequestMatcher(serviceProperties())));
|
||||||
|
http
|
||||||
|
.authorizeHttpRequests((authorize) -> authorize
|
||||||
|
.requestMatchers(HttpMethod.GET, "/loggedout").permitAll()
|
||||||
|
.requestMatchers("/public").permitAll()
|
||||||
|
.anyRequest().authenticated()
|
||||||
|
)
|
||||||
.exceptionHandling((exceptions) -> exceptions.authenticationEntryPoint(casAuthenticationEntryPoint()))
|
.exceptionHandling((exceptions) -> exceptions.authenticationEntryPoint(casAuthenticationEntryPoint()))
|
||||||
.logout((logout) -> logout.logoutSuccessUrl("/loggedout"))
|
.logout((logout) -> logout.logoutSuccessUrl("/loggedout"))
|
||||||
.addFilter(casAuthenticationFilter(userDetailsService))
|
.addFilter(casAuthenticationFilter(userDetailsService))
|
||||||
.addFilterBefore(new SingleSignOutFilter(), CasAuthenticationFilter.class);
|
.addFilterBefore(new SingleSignOutFilter(), CasAuthenticationFilter.class)
|
||||||
|
.addFilterAfter(casGatewayAuthenticationRedirectFilter, CasAuthenticationFilter.class);
|
||||||
return http.build();
|
return http.build();
|
||||||
|
// @formatter:on
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
MvcRequestMatcher.Builder mvcRequestMatcherBuilder(HandlerMappingIntrospector introspector) {
|
||||||
|
return new MvcRequestMatcher.Builder(introspector);
|
||||||
}
|
}
|
||||||
|
|
||||||
public CasAuthenticationProvider casAuthenticationProvider(UserDetailsService userDetailsService) {
|
public CasAuthenticationProvider casAuthenticationProvider(UserDetailsService userDetailsService) {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
cas.base.url=http://localhost:8090/cas
|
server.port=8081
|
||||||
|
cas.base.url=http://localhost.example:8090/cas
|
||||||
cas.login.url=${cas.base.url}/login
|
cas.login.url=${cas.base.url}/login
|
||||||
cas.logout.url=${cas.base.url}/logout
|
cas.logout.url=${cas.base.url}/logout
|
||||||
service.base.url=http://localhost:8080
|
service.base.url=http://localhost:8081
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org" xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
|
||||||
|
<head>
|
||||||
|
<title>Spring Security - CAS Login & Logout</title>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<style>
|
||||||
|
span, dt {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<main role="main" class="container-fluid">
|
||||||
|
<h1 class="mt-5">This is a public page that performs a CAS Gateway Authentication</h1>
|
||||||
|
<p class="lead">You are successfully logged in as <span sec:authentication="name"></span></p>
|
||||||
|
|
||||||
|
<h6>Visit the <a href="https://github.com/apereo/cas" target="_blank">Apereo CAS</a> documentation for more details.</h6>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -29,8 +29,10 @@ import org.testcontainers.junit.jupiter.Container;
|
|||||||
import org.testcontainers.junit.jupiter.Testcontainers;
|
import org.testcontainers.junit.jupiter.Testcontainers;
|
||||||
import org.testcontainers.utility.DockerImageName;
|
import org.testcontainers.utility.DockerImageName;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.boot.test.context.SpringBootTest;
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
import org.springframework.boot.test.web.server.LocalServerPort;
|
import org.springframework.boot.test.web.server.LocalServerPort;
|
||||||
|
import org.springframework.core.env.Environment;
|
||||||
import org.springframework.test.context.DynamicPropertyRegistry;
|
import org.springframework.test.context.DynamicPropertyRegistry;
|
||||||
import org.springframework.test.context.DynamicPropertySource;
|
import org.springframework.test.context.DynamicPropertySource;
|
||||||
|
|
||||||
@@ -43,11 +45,15 @@ class CasLoginApplicationTests {
|
|||||||
@LocalServerPort
|
@LocalServerPort
|
||||||
int port;
|
int port;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
Environment environment;
|
||||||
|
|
||||||
@Container
|
@Container
|
||||||
static GenericContainer<?> casServer = new GenericContainer<>(DockerImageName.parse("apereo/cas:6.6.6"))
|
static GenericContainer<?> casServer = new GenericContainer<>(DockerImageName.parse("apereo/cas:6.6.6"))
|
||||||
.withCommand("--cas.standalone.configuration-directory=/etc/cas/config", "--server.ssl.enabled=false",
|
.withCommand("--cas.standalone.configuration-directory=/etc/cas/config", "--server.ssl.enabled=false",
|
||||||
"--server.port=8080", "--cas.service-registry.core.init-from-json=true",
|
"--server.port=8080", "--cas.service-registry.core.init-from-json=true",
|
||||||
"--cas.service-registry.json.location=file:/etc/cas/services")
|
"--cas.service-registry.json.location=file:/etc/cas/services", "--cas.tgc.secure=false",
|
||||||
|
"--cas.tgc.sameSitePolicy=Lax")
|
||||||
.withExposedPorts(8080).withClasspathResourceMapping("cas/services/https-1.json",
|
.withExposedPorts(8080).withClasspathResourceMapping("cas/services/https-1.json",
|
||||||
"/etc/cas/services/https-1.json", BindMode.READ_WRITE)
|
"/etc/cas/services/https-1.json", BindMode.READ_WRITE)
|
||||||
.waitingFor(Wait.forLogMessage(".*Ready to process requests.*", 1));
|
.waitingFor(Wait.forLogMessage(".*Ready to process requests.*", 1));
|
||||||
@@ -92,4 +98,19 @@ class CasLoginApplicationTests {
|
|||||||
assertThat(logoutMsg).isEqualTo("You are successfully logged out of the app, but not CAS");
|
assertThat(logoutMsg).isEqualTo("You are successfully logged out of the app, but not CAS");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void publicPageWhenCasGatewayAuthenticationThenAuthenticated() {
|
||||||
|
doCasLogin();
|
||||||
|
Selenide.open("http://localhost:" + this.port + "/public");
|
||||||
|
String lead = Selenide.$(By.className("lead")).text();
|
||||||
|
assertThat(lead).isEqualTo("You are successfully logged in as casuser");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void doCasLogin() {
|
||||||
|
Selenide.open(this.environment.getProperty("cas.login.url"));
|
||||||
|
Selenide.$(By.name("username")).setValue("casuser");
|
||||||
|
Selenide.$(By.name("password")).setValue("Mellon");
|
||||||
|
Selenide.$(By.name("submitBtn")).click();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user