Skip to content

Commit 2660eb4

Browse files
committed
Migrate kdc testing
- Migrate test kdc system to org.apache.kerby:kerb-simplekdc - Add simple tests for KerberosRestTemplate - Fixes #175
1 parent 1f932ed commit 2660eb4

File tree

15 files changed

+397
-1240
lines changed

15 files changed

+397
-1240
lines changed

gradle.properties

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,5 @@ mockitoVersion=4.8.1
77
assertjVersion=3.23.1
88
servletApiVersion=6.0.0
99
httpclient5Version=5.1.4
10+
kerbyVersion=2.0.3
11+
okhttp3Version=3.14.9

settings.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ include 'spring-security-kerberos-bom'
3030
include 'spring-security-kerberos-core'
3131
include 'spring-security-kerberos-client'
3232
include 'spring-security-kerberos-web'
33+
include 'spring-security-kerberos-test'
3334
include 'spring-security-kerberos-samples:sec-client-rest-template'
3435
include 'spring-security-kerberos-samples:sec-server-client-auth'
3536
include 'spring-security-kerberos-samples:sec-server-spnego-form-auth'

spring-security-kerberos-client/spring-security-kerberos-client.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,11 @@ dependencies {
1414
optional 'org.springframework.security:spring-security-ldap'
1515
// api('org.springframework.security:spring-security-web')
1616
// api('jakarta.servlet:jakarta.servlet-api')
17+
testImplementation project(':spring-security-kerberos-test')
1718
testImplementation 'org.springframework:spring-test'
1819
testImplementation 'org.springframework.security:spring-security-config'
1920
testImplementation 'org.junit.jupiter:junit-jupiter'
2021
testImplementation 'org.mockito:mockito-junit-jupiter'
2122
testImplementation 'org.assertj:assertj-core'
23+
testImplementation 'com.squareup.okhttp3:mockwebserver'
2224
}
Lines changed: 112 additions & 246 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
/*
2-
* Copyright 2015 the original author or authors.
2+
* Copyright 2023 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
66
* You may obtain a copy of the License at
77
*
8-
* https://www.apache.org/licenses/LICENSE-2.0
8+
* https://www.apache.org/licenses/LICENSE-2.0
99
*
1010
* Unless required by applicable law or agreed to in writing, software
1111
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -15,249 +15,115 @@
1515
*/
1616
package org.springframework.security.kerberos.client;
1717

18-
// import static org.hamcrest.CoreMatchers.is;
19-
// import static org.junit.Assert.assertThat;
20-
21-
// import java.io.File;
22-
// import java.io.IOException;
23-
// import java.lang.annotation.Documented;
24-
// import java.lang.annotation.ElementType;
25-
// import java.lang.annotation.Retention;
26-
// import java.lang.annotation.RetentionPolicy;
27-
// import java.lang.annotation.Target;
28-
// import java.net.InetAddress;
29-
// import java.util.concurrent.CountDownLatch;
30-
// import java.util.concurrent.TimeUnit;
31-
32-
// import org.junit.After;
33-
// import org.junit.Test;
34-
// import org.springframework.boot.SpringApplication;
35-
// import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration;
36-
// import org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration;
37-
// import org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration;
38-
// import org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration;
39-
// import org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration;
40-
// import org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration;
41-
// import org.springframework.boot.autoconfigure.web.ServerPropertiesAutoConfiguration;
42-
// import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration;
43-
// import org.springframework.boot.context.embedded.EmbeddedServletContainerInitializedEvent;
44-
// import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
45-
// import org.springframework.context.ApplicationListener;
46-
// import org.springframework.context.ConfigurableApplicationContext;
47-
// import org.springframework.context.annotation.Bean;
48-
// import org.springframework.context.annotation.Configuration;
49-
// import org.springframework.context.annotation.Import;
50-
// import org.springframework.http.client.ClientHttpResponse;
51-
// import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
52-
// import org.springframework.security.kerberos.client.KerberosRestTemplate;
53-
// import org.springframework.security.kerberos.test.KerberosSecurityTestcase;
54-
// import org.springframework.security.kerberos.test.MiniKdc;
55-
// import org.springframework.stereotype.Controller;
56-
// import org.springframework.web.bind.annotation.RequestMapping;
57-
// import org.springframework.web.bind.annotation.RequestMethod;
58-
// import org.springframework.web.bind.annotation.ResponseBody;
59-
// import org.springframework.web.client.DefaultResponseErrorHandler;
60-
// import org.springframework.web.client.RestTemplate;
61-
62-
public class KerberosRestTemplateTests /*extends KerberosSecurityTestcase */{
63-
64-
// private ConfigurableApplicationContext context;
65-
66-
// @After
67-
// public void close() {
68-
// if (context != null) {
69-
// context.close();
70-
// }
71-
// context = null;
72-
// }
73-
74-
// @Test
75-
// public void testSpnego() throws Exception {
76-
77-
// MiniKdc kdc = getKdc();
78-
// File workDir = getWorkDir();
79-
// String host = InetAddress.getLocalHost().getCanonicalHostName();
80-
81-
// String serverPrincipal = "HTTP/" + host;
82-
// File serverKeytab = new File(workDir, "server.keytab");
83-
// kdc.createPrincipal(serverKeytab, serverPrincipal);
84-
85-
// String clientPrincipal = "client/" + host;
86-
// File clientKeytab = new File(workDir, "client.keytab");
87-
// kdc.createPrincipal(clientKeytab, clientPrincipal);
88-
89-
90-
// context = SpringApplication.run(new Object[] { WebSecurityConfig.class, VanillaWebConfiguration.class,
91-
// WebConfiguration.class }, new String[] { "--security.basic.enabled=true",
92-
// "--security.user.name=username", "--security.user.password=password",
93-
// "--serverPrincipal=" + serverPrincipal, "--serverKeytab=" + serverKeytab.getAbsolutePath() });
94-
95-
// PortInitListener portInitListener = context.getBean(PortInitListener.class);
96-
// assertThat(portInitListener.latch.await(10, TimeUnit.SECONDS), is(true));
97-
// int port = portInitListener.port;
98-
99-
// KerberosRestTemplate restTemplate = new KerberosRestTemplate(clientKeytab.getAbsolutePath(), clientPrincipal);
100-
101-
// String response = restTemplate.getForObject("http://" + host + ":" + port + "/hello", String.class);
102-
// assertThat(response, is("home"));
103-
// }
104-
105-
// @Test
106-
// public void testSpnegoWithPassword() throws Exception {
107-
108-
// MiniKdc kdc = getKdc();
109-
// File workDir = getWorkDir();
110-
// String host = InetAddress.getLocalHost().getCanonicalHostName();
111-
112-
// String serverPrincipal = "HTTP/" + host;
113-
// File serverKeytab = new File(workDir, "server.keytab");
114-
// kdc.createPrincipal(serverKeytab, serverPrincipal);
115-
116-
// String userPrincipal = "testuser";
117-
// String password = "testpassword";
118-
// kdc.createPrincipal(userPrincipal, password);
119-
120-
121-
// context = SpringApplication.run(new Object[] { WebSecurityConfig.class, VanillaWebConfiguration.class,
122-
// WebConfiguration.class }, new String[] { "--security.basic.enabled=true",
123-
// "--security.user.name=username", "--security.user.password=password",
124-
// "--serverPrincipal=" + serverPrincipal, "--serverKeytab=" + serverKeytab.getAbsolutePath() });
125-
126-
// PortInitListener portInitListener = context.getBean(PortInitListener.class);
127-
// assertThat(portInitListener.latch.await(10, TimeUnit.SECONDS), is(true));
128-
// int port = portInitListener.port;
129-
130-
// KerberosRestTemplate restTemplate = new KerberosRestTemplate(null, userPrincipal, password, null);
131-
132-
// String response = restTemplate.getForObject("http://" + host + ":" + port + "/hello", String.class);
133-
// assertThat(response, is("home"));
134-
// }
135-
136-
// @Test
137-
// public void testSpnegoWithForward() throws Exception {
138-
139-
// MiniKdc kdc = getKdc();
140-
// File workDir = getWorkDir();
141-
// String host = InetAddress.getLocalHost().getCanonicalHostName();
142-
143-
// String serverPrincipal = "HTTP/" + host;
144-
// File serverKeytab = new File(workDir, "server.keytab");
145-
// kdc.createPrincipal(serverKeytab, serverPrincipal);
146-
147-
// context = SpringApplication.run(new Object[] { WebSecurityConfigSpnegoForward.class, VanillaWebConfiguration.class,
148-
// WebConfiguration.class }, new String[] { "--security.basic.enabled=true",
149-
// "--security.user.name=username", "--security.user.password=password",
150-
// "--serverPrincipal=" + serverPrincipal, "--serverKeytab=" + serverKeytab.getAbsolutePath() });
151-
152-
// PortInitListener portInitListener = context.getBean(PortInitListener.class);
153-
// assertThat(portInitListener.latch.await(10, TimeUnit.SECONDS), is(true));
154-
// int port = portInitListener.port;
155-
156-
// // TODO: should tweak minikdc so that we can use kerberos principals
157-
// // which are not valid, for now just use plain RestTemplate
158-
159-
// // just checking that we get 401 which we skip and
160-
// // get login page content
161-
// RestTemplate restTemplate = new RestTemplate(new HttpComponentsClientHttpRequestFactory());
162-
// restTemplate.setErrorHandler(new DefaultResponseErrorHandler() {
163-
// @Override
164-
// public void handleError(ClientHttpResponse response) throws IOException {
165-
// }
166-
// });
167-
168-
// String response = restTemplate.getForObject("http://" + host + ":" + port + "/hello", String.class);
169-
// assertThat(response, is("login"));
170-
// }
171-
172-
// @Test
173-
// public void testSpnegoWithSuccessHandler() throws Exception {
174-
175-
// MiniKdc kdc = getKdc();
176-
// File workDir = getWorkDir();
177-
// String host = InetAddress.getLocalHost().getCanonicalHostName();
178-
179-
// String serverPrincipal = "HTTP/" + host;
180-
// File serverKeytab = new File(workDir, "server.keytab");
181-
// kdc.createPrincipal(serverKeytab, serverPrincipal);
182-
183-
// String clientPrincipal = "client/" + host;
184-
// File clientKeytab = new File(workDir, "client.keytab");
185-
// kdc.createPrincipal(clientKeytab, clientPrincipal);
186-
187-
188-
// context = SpringApplication.run(new Object[] { WebSecurityConfigSuccessHandler.class, VanillaWebConfiguration.class,
189-
// WebConfiguration.class }, new String[] { "--security.basic.enabled=true",
190-
// "--security.user.name=username", "--security.user.password=password",
191-
// "--serverPrincipal=" + serverPrincipal, "--serverKeytab=" + serverKeytab.getAbsolutePath() });
192-
193-
// PortInitListener portInitListener = context.getBean(PortInitListener.class);
194-
// assertThat(portInitListener.latch.await(10, TimeUnit.SECONDS), is(true));
195-
// int port = portInitListener.port;
196-
197-
// KerberosRestTemplate restTemplate = new KerberosRestTemplate(clientKeytab.getAbsolutePath(), clientPrincipal);
198-
199-
// String response = restTemplate.getForObject("http://" + host + ":" + port + "/hello", String.class);
200-
// assertThat(response, is("home"));
201-
// }
202-
203-
// protected static class PortInitListener implements ApplicationListener<EmbeddedServletContainerInitializedEvent> {
204-
205-
// public int port;
206-
// public CountDownLatch latch = new CountDownLatch(1);
207-
208-
// @Override
209-
// public void onApplicationEvent(EmbeddedServletContainerInitializedEvent event) {
210-
// port = event.getEmbeddedServletContainer().getPort();
211-
// latch.countDown();
212-
// }
213-
214-
// }
215-
216-
// @Configuration
217-
// protected static class VanillaWebConfiguration {
218-
219-
// @Bean
220-
// public PortInitListener portListener() {
221-
// return new PortInitListener();
222-
// }
223-
224-
// @Bean
225-
// public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() {
226-
// TomcatEmbeddedServletContainerFactory factory = new TomcatEmbeddedServletContainerFactory();
227-
// factory.setPort(0);
228-
// return factory;
229-
// }
230-
// }
231-
232-
// @MinimalWebConfiguration
233-
// @Import(SecurityAutoConfiguration.class)
234-
// @Controller
235-
// protected static class WebConfiguration {
236-
237-
// @RequestMapping(method = RequestMethod.GET)
238-
// @ResponseBody
239-
// public String home() {
240-
// return "home";
241-
// }
242-
243-
// @RequestMapping(method = RequestMethod.GET, value = "/login")
244-
// @ResponseBody
245-
// public String login() {
246-
// return "login";
247-
// }
248-
249-
// }
250-
251-
// @Configuration
252-
// @Target(ElementType.TYPE)
253-
// @Retention(RetentionPolicy.RUNTIME)
254-
// @Documented
255-
// @Import({ EmbeddedServletContainerAutoConfiguration.class,
256-
// ServerPropertiesAutoConfiguration.class,
257-
// DispatcherServletAutoConfiguration.class, WebMvcAutoConfiguration.class,
258-
// HttpMessageConvertersAutoConfiguration.class,
259-
// ErrorMvcAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class })
260-
// protected static @interface MinimalWebConfiguration {
261-
// }
18+
import java.io.File;
19+
import java.nio.charset.StandardCharsets;
20+
import java.util.Collections;
21+
22+
import okhttp3.mockwebserver.Dispatcher;
23+
import okhttp3.mockwebserver.MockResponse;
24+
import okhttp3.mockwebserver.MockWebServer;
25+
import okhttp3.mockwebserver.RecordedRequest;
26+
import okio.Buffer;
27+
import org.junit.jupiter.api.AfterEach;
28+
import org.junit.jupiter.api.BeforeEach;
29+
import org.junit.jupiter.api.Test;
30+
31+
import org.springframework.http.MediaType;
32+
import org.springframework.security.kerberos.test.KerberosSecurityTestcase;
33+
import org.springframework.security.kerberos.test.MiniKdc;
34+
35+
import static org.assertj.core.api.Assertions.assertThat;
36+
import static org.springframework.http.HttpHeaders.AUTHORIZATION;
37+
import static org.springframework.http.HttpHeaders.CONTENT_LENGTH;
38+
import static org.springframework.http.HttpHeaders.CONTENT_TYPE;
39+
import static org.springframework.http.HttpHeaders.WWW_AUTHENTICATE;
40+
41+
class KerberosRestTemplateTests extends KerberosSecurityTestcase {
42+
43+
private final MockWebServer server = new MockWebServer();
44+
private static final String helloWorld = "Hello World";
45+
private static final MediaType textContentType =
46+
new MediaType("text", "plain", Collections.singletonMap("charset", "UTF-8"));
47+
private int port;
48+
private String baseUrl;
49+
private KerberosRestTemplate restTemplate;
50+
private String clientPrincipal;
51+
private File clientKeytab;
52+
53+
@BeforeEach
54+
void setUp() throws Exception {
55+
this.server.setDispatcher(new TestDispatcher());
56+
this.server.start();
57+
this.port = this.server.getPort();
58+
this.baseUrl = "http://localhost:" + this.port;
59+
60+
MiniKdc kdc = getKdc();
61+
File workDir = getWorkDir();
62+
63+
clientPrincipal = "client/localhost";
64+
clientKeytab = new File(workDir, "client.keytab");
65+
kdc.createPrincipal(clientKeytab, clientPrincipal);
66+
67+
String serverPrincipal = "HTTP/localhost";
68+
File serverKeytab = new File(workDir, "server.keytab");
69+
kdc.createPrincipal(serverKeytab, serverPrincipal);
70+
}
71+
72+
@AfterEach
73+
void tearDown() throws Exception {
74+
this.server.shutdown();
75+
}
76+
77+
@Test
78+
void sendsNegotiateHeader() {
79+
setUpClient();
80+
String s = restTemplate.getForObject(baseUrl + "/get", String.class);
81+
assertThat(s).isEqualTo(helloWorld);
82+
}
83+
84+
private void setUpClient() {
85+
restTemplate = new KerberosRestTemplate(clientKeytab.getAbsolutePath(), clientPrincipal);
86+
}
87+
88+
private MockResponse getRequest(RecordedRequest request, byte[] body, String contentType) {
89+
if (request.getMethod().equals("OPTIONS")) {
90+
return new MockResponse().setResponseCode(200).setHeader("Allow", "GET, OPTIONS, HEAD, TRACE");
91+
}
92+
Buffer buf = new Buffer();
93+
buf.write(body);
94+
MockResponse response = new MockResponse()
95+
.setHeader(CONTENT_LENGTH, body.length)
96+
.setBody(buf)
97+
.setResponseCode(200);
98+
if (contentType != null) {
99+
response = response.setHeader(CONTENT_TYPE, contentType);
100+
}
101+
return response;
102+
}
103+
104+
protected class TestDispatcher extends Dispatcher {
105+
106+
@Override
107+
public MockResponse dispatch(RecordedRequest request) throws InterruptedException {
108+
try {
109+
byte[] helloWorldBytes = helloWorld.getBytes(StandardCharsets.UTF_8);
110+
111+
if (request.getPath().equals("/get")) {
112+
String header = request.getHeader(AUTHORIZATION);
113+
if (header == null) {
114+
return new MockResponse().setResponseCode(401).addHeader(WWW_AUTHENTICATE, "Negotiate");
115+
}
116+
else if (header != null && header.startsWith("Negotiate ")) {
117+
return getRequest(request, helloWorldBytes, textContentType.toString());
118+
}
119+
}
120+
return new MockResponse().setResponseCode(404);
121+
}
122+
catch (Throwable ex) {
123+
return new MockResponse().setResponseCode(500).setBody(ex.toString());
124+
}
125+
126+
}
127+
}
262128

263129
}

0 commit comments

Comments
 (0)