Skip to content

Commit 7f919df

Browse files
Add CAS Gateway sample
Closes gh-174
1 parent e1565dc commit 7f919df

File tree

7 files changed

+82
-7
lines changed

7 files changed

+82
-7
lines changed

docker/cas/docker-compose.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ services:
1717
--server.port=8080
1818
--cas.service-registry.core.init-from-json=true
1919
--cas.service-registry.json.location=file:/etc/cas/services
20+
--cas.tgc.secure=false
21+
--cas.tgc.sameSitePolicy=Lax
2022
volumes:
2123
- ./services/http-1.json:/etc/cas/services/http-1.json
2224
networks:

servlet/spring-boot/java/cas/login/build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ plugins {
55
id 'java'
66
}
77

8+
ext['spring-security.version'] = '6.3.0-SNAPSHOT'
9+
810
repositories {
911
mavenCentral()
1012
maven { url "https://repo.spring.io/milestone" }

servlet/spring-boot/java/cas/login/src/main/java/cas/example/IndexController.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,9 @@ public String loggedout(Model model) {
3838
return "loggedout";
3939
}
4040

41+
@GetMapping("/public")
42+
String publicPage() {
43+
return "public";
44+
}
45+
4146
}

servlet/spring-boot/java/cas/login/src/main/java/cas/example/SecurityConfig.java

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,18 @@
3131
import org.springframework.security.cas.authentication.CasAuthenticationProvider;
3232
import org.springframework.security.cas.web.CasAuthenticationEntryPoint;
3333
import org.springframework.security.cas.web.CasAuthenticationFilter;
34+
import org.springframework.security.cas.web.CasGatewayAuthenticationRedirectFilter;
35+
import org.springframework.security.cas.web.CasGatewayResolverRequestMatcher;
3436
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
3537
import org.springframework.security.core.userdetails.User;
3638
import org.springframework.security.core.userdetails.UserDetails;
3739
import org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper;
3840
import org.springframework.security.core.userdetails.UserDetailsService;
3941
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
4042
import org.springframework.security.web.SecurityFilterChain;
43+
import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher;
44+
import org.springframework.security.web.util.matcher.AndRequestMatcher;
45+
import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
4146

4247
@Configuration
4348
public class SecurityConfig {
@@ -52,14 +57,30 @@ public class SecurityConfig {
5257
private ServletWebServerApplicationContext context;
5358

5459
@Bean
55-
public SecurityFilterChain filterChain(HttpSecurity http, UserDetailsService userDetailsService) throws Exception {
56-
http.authorizeHttpRequests((authorize) -> authorize.requestMatchers(HttpMethod.GET, "/loggedout").permitAll()
57-
.anyRequest().authenticated())
60+
public SecurityFilterChain filterChain(HttpSecurity http, UserDetailsService userDetailsService,
61+
MvcRequestMatcher.Builder builder) throws Exception {
62+
// @formatter:off
63+
CasGatewayAuthenticationRedirectFilter casGatewayAuthenticationRedirectFilter = new CasGatewayAuthenticationRedirectFilter(this.casLoginUrl, serviceProperties());
64+
casGatewayAuthenticationRedirectFilter.setRequestMatcher(new AndRequestMatcher(
65+
builder.pattern("/public"), new CasGatewayResolverRequestMatcher(serviceProperties())));
66+
http
67+
.authorizeHttpRequests((authorize) -> authorize
68+
.requestMatchers(HttpMethod.GET, "/loggedout").permitAll()
69+
.requestMatchers("/public").permitAll()
70+
.anyRequest().authenticated()
71+
)
5872
.exceptionHandling((exceptions) -> exceptions.authenticationEntryPoint(casAuthenticationEntryPoint()))
5973
.logout((logout) -> logout.logoutSuccessUrl("/loggedout"))
6074
.addFilter(casAuthenticationFilter(userDetailsService))
61-
.addFilterBefore(new SingleSignOutFilter(), CasAuthenticationFilter.class);
75+
.addFilterBefore(new SingleSignOutFilter(), CasAuthenticationFilter.class)
76+
.addFilterAfter(casGatewayAuthenticationRedirectFilter, CasAuthenticationFilter.class);
6277
return http.build();
78+
// @formatter:on
79+
}
80+
81+
@Bean
82+
MvcRequestMatcher.Builder mvcRequestMatcherBuilder(HandlerMappingIntrospector introspector) {
83+
return new MvcRequestMatcher.Builder(introspector);
6384
}
6485

6586
public CasAuthenticationProvider casAuthenticationProvider(UserDetailsService userDetailsService) {
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
cas.base.url=http://localhost:8090/cas
1+
server.port=8081
2+
cas.base.url=http://localhost.example:8090/cas
23
cas.login.url=${cas.base.url}/login
34
cas.logout.url=${cas.base.url}/logout
4-
service.base.url=http://localhost:8080
5+
service.base.url=http://localhost:8081
56

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<!doctype html>
2+
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org" xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
3+
<head>
4+
<title>Spring Security - CAS Login & Logout</title>
5+
<meta charset="utf-8" />
6+
<style>
7+
span, dt {
8+
font-weight: bold;
9+
}
10+
</style>
11+
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
12+
</head>
13+
<body>
14+
<div class="container">
15+
<main role="main" class="container-fluid">
16+
<h1 class="mt-5">This is a public page that performs a CAS Gateway Authentication</h1>
17+
<p class="lead">You are successfully logged in as <span sec:authentication="name"></span></p>
18+
19+
<h6>Visit the <a href="https://github.com/apereo/cas" target="_blank">Apereo CAS</a> documentation for more details.</h6>
20+
</main>
21+
</div>
22+
</body>
23+
</html>

servlet/spring-boot/java/cas/login/src/test/java/cas/example/CasLoginApplicationTests.java

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,10 @@
2929
import org.testcontainers.junit.jupiter.Testcontainers;
3030
import org.testcontainers.utility.DockerImageName;
3131

32+
import org.springframework.beans.factory.annotation.Autowired;
3233
import org.springframework.boot.test.context.SpringBootTest;
3334
import org.springframework.boot.test.web.server.LocalServerPort;
35+
import org.springframework.core.env.Environment;
3436
import org.springframework.test.context.DynamicPropertyRegistry;
3537
import org.springframework.test.context.DynamicPropertySource;
3638

@@ -43,11 +45,15 @@ class CasLoginApplicationTests {
4345
@LocalServerPort
4446
int port;
4547

48+
@Autowired
49+
Environment environment;
50+
4651
@Container
4752
static GenericContainer<?> casServer = new GenericContainer<>(DockerImageName.parse("apereo/cas:6.6.6"))
4853
.withCommand("--cas.standalone.configuration-directory=/etc/cas/config", "--server.ssl.enabled=false",
4954
"--server.port=8080", "--cas.service-registry.core.init-from-json=true",
50-
"--cas.service-registry.json.location=file:/etc/cas/services")
55+
"--cas.service-registry.json.location=file:/etc/cas/services", "--cas.tgc.secure=false",
56+
"--cas.tgc.sameSitePolicy=Lax")
5157
.withExposedPorts(8080).withClasspathResourceMapping("cas/services/https-1.json",
5258
"/etc/cas/services/https-1.json", BindMode.READ_WRITE)
5359
.waitingFor(Wait.forLogMessage(".*Ready to process requests.*", 1));
@@ -92,4 +98,19 @@ void loginAndLogout() {
9298
assertThat(logoutMsg).isEqualTo("You are successfully logged out of the app, but not CAS");
9399
}
94100

101+
@Test
102+
void publicPageWhenCasGatewayAuthenticationThenAuthenticated() {
103+
doCasLogin();
104+
Selenide.open("http://localhost:" + this.port + "/public");
105+
String lead = Selenide.$(By.className("lead")).text();
106+
assertThat(lead).isEqualTo("You are successfully logged in as casuser");
107+
}
108+
109+
private void doCasLogin() {
110+
Selenide.open(this.environment.getProperty("cas.login.url"));
111+
Selenide.$(By.name("username")).setValue("casuser");
112+
Selenide.$(By.name("password")).setValue("Mellon");
113+
Selenide.$(By.name("submitBtn")).click();
114+
}
115+
95116
}

0 commit comments

Comments
 (0)