Explorar o código

Add CAS Gateway sample

Closes gh-174
Marcus Hert Da Coregio hai 1 ano
pai
achega
7f919df0cc

+ 2 - 0
docker/cas/docker-compose.yml

@@ -17,6 +17,8 @@ services:
       --server.port=8080
       --cas.service-registry.core.init-from-json=true
       --cas.service-registry.json.location=file:/etc/cas/services
+      --cas.tgc.secure=false
+      --cas.tgc.sameSitePolicy=Lax
     volumes:
       - ./services/http-1.json:/etc/cas/services/http-1.json
     networks:

+ 2 - 0
servlet/spring-boot/java/cas/login/build.gradle

@@ -5,6 +5,8 @@ plugins {
     id 'java'
 }
 
+ext['spring-security.version'] = '6.3.0-SNAPSHOT'
+
 repositories {
     mavenCentral()
     maven { url "https://repo.spring.io/milestone" }

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

@@ -38,4 +38,9 @@ public class IndexController {
 		return "loggedout";
 	}
 
+	@GetMapping("/public")
+	String publicPage() {
+		return "public";
+	}
+
 }

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

@@ -31,6 +31,8 @@ import org.springframework.security.cas.ServiceProperties;
 import org.springframework.security.cas.authentication.CasAuthenticationProvider;
 import org.springframework.security.cas.web.CasAuthenticationEntryPoint;
 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.core.userdetails.User;
 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.provisioning.InMemoryUserDetailsManager;
 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
 public class SecurityConfig {
@@ -52,14 +57,30 @@ public class SecurityConfig {
 	private ServletWebServerApplicationContext context;
 
 	@Bean
-	public SecurityFilterChain filterChain(HttpSecurity http, UserDetailsService userDetailsService) throws Exception {
-		http.authorizeHttpRequests((authorize) -> authorize.requestMatchers(HttpMethod.GET, "/loggedout").permitAll()
-				.anyRequest().authenticated())
+	public SecurityFilterChain filterChain(HttpSecurity http, UserDetailsService userDetailsService,
+			MvcRequestMatcher.Builder builder) throws Exception {
+		// @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()))
 				.logout((logout) -> logout.logoutSuccessUrl("/loggedout"))
 				.addFilter(casAuthenticationFilter(userDetailsService))
-				.addFilterBefore(new SingleSignOutFilter(), CasAuthenticationFilter.class);
+				.addFilterBefore(new SingleSignOutFilter(), CasAuthenticationFilter.class)
+				.addFilterAfter(casGatewayAuthenticationRedirectFilter, CasAuthenticationFilter.class);
 		return http.build();
+		// @formatter:on
+	}
+
+	@Bean
+	MvcRequestMatcher.Builder mvcRequestMatcherBuilder(HandlerMappingIntrospector introspector) {
+		return new MvcRequestMatcher.Builder(introspector);
 	}
 
 	public CasAuthenticationProvider casAuthenticationProvider(UserDetailsService userDetailsService) {

+ 3 - 2
servlet/spring-boot/java/cas/login/src/main/resources/application.properties

@@ -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.logout.url=${cas.base.url}/logout
-service.base.url=http://localhost:8080
+service.base.url=http://localhost:8081
 

+ 23 - 0
servlet/spring-boot/java/cas/login/src/main/resources/templates/public.html

@@ -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>

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

@@ -29,8 +29,10 @@ import org.testcontainers.junit.jupiter.Container;
 import org.testcontainers.junit.jupiter.Testcontainers;
 import org.testcontainers.utility.DockerImageName;
 
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
 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.DynamicPropertySource;
 
@@ -43,11 +45,15 @@ class CasLoginApplicationTests {
 	@LocalServerPort
 	int port;
 
+	@Autowired
+	Environment environment;
+
 	@Container
 	static GenericContainer<?> casServer = new GenericContainer<>(DockerImageName.parse("apereo/cas:6.6.6"))
 			.withCommand("--cas.standalone.configuration-directory=/etc/cas/config", "--server.ssl.enabled=false",
 					"--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",
 					"/etc/cas/services/https-1.json", BindMode.READ_WRITE)
 			.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");
 	}
 
+	@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();
+	}
+
 }