BAEL-1931 Kerberos integration in Java (#6559)

* BAEL-1931 kerberos integration in java

* add javadoc to manual test

* use list in mini kdc builder, constructor injection in service

* remove unused constant
This commit is contained in:
Denis 2019-03-31 22:08:40 +02:00 committed by Grzegorz Piwowarek
parent 33e7f0d8e3
commit 295b78661a
21 changed files with 612 additions and 1 deletions

View File

@ -18,12 +18,15 @@
<module>spring-security-sso-auth-server</module>
<module>spring-security-sso-ui</module>
<module>spring-security-sso-ui-2</module>
<module>spring-security-sso-kerberos</module>
</modules>
<properties>
<rest-assured.version>3.1.0</rest-assured.version>
<oauth.version>2.3.3.RELEASE</oauth.version>
<oauth-auto.version>2.1.1.RELEASE</oauth-auto.version>
<oauth-auto.version>2.1.1.RELEASE</oauth-auto.version>
<spring-security-kerberos.version>1.0.1.RELEASE</spring-security-kerberos.version>
<apacheds-jdbm1.version>2.0.0-M2</apacheds-jdbm1.version>
</properties>
</project>

View File

@ -0,0 +1,2 @@
krb-test-workdir/
/bin/

View File

@ -0,0 +1,94 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-security-sso-kerberos</artifactId>
<parent>
<groupId>org.baeldung</groupId>
<artifactId>spring-security-sso</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<build>
<resources>
<resource>
<directory>${basedir}/src/main/resources</directory>
<filtering>true</filtering>
<includes>
<include>**/*</include>
</includes>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>2.7</version>
<configuration>
<delimiters>
<delimiter>@</delimiter>
</delimiters>
<useDefaultDelimiters>false</useDefaultDelimiters>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.kerberos</groupId>
<artifactId>spring-security-kerberos-web</artifactId>
<version>${spring-security-kerberos.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.security.kerberos</groupId>
<artifactId>spring-security-kerberos-client</artifactId>
<version>${spring-security-kerberos.version}</version>
</dependency>
<dependency>
<groupId>org.apache.directory.jdbm</groupId>
<artifactId>apacheds-jdbm1</artifactId>
<version>${apacheds-jdbm1.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.security.kerberos</groupId>
<artifactId>spring-security-kerberos-test</artifactId>
<exclusions>
<exclusion>
<groupId>org.apache.directory.jdbm</groupId>
<artifactId>apacheds-jdbm1</artifactId>
</exclusion>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
</exclusions>
<version>${spring-security-kerberos.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,20 @@
package kerberos.client;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import java.nio.file.Paths;
@SpringBootApplication
class KerberosClientApp {
static {
System.setProperty("java.security.krb5.conf",
Paths.get(".\\krb-test-workdir\\krb5.conf").normalize().toAbsolutePath().toString());
System.setProperty("sun.security.krb5.debug", "true");
}
public static void main(String[] args) {
SpringApplication.run(KerberosClientApp.class, args);
}
}

View File

@ -0,0 +1,26 @@
package kerberos.client;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
@Service
class SampleService {
@Value("${app.access-url}")
private String endpoint;
private RestTemplate restTemplate;
public SampleService(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
void setRestTemplate(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
String getData() {
return restTemplate.getForObject(endpoint, String.class);
}
}

View File

@ -0,0 +1,10 @@
package kerberos.client.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@Configuration
@Import(KerberosConfig.class)
class AppConfig {
}

View File

@ -0,0 +1,22 @@
package kerberos.client.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.kerberos.client.KerberosRestTemplate;
import org.springframework.web.client.RestTemplate;
@Configuration
class KerberosConfig {
@Value("${app.user-principal}")
private String principal;
@Value("${app.keytab-location}")
private String keytabLocation;
@Bean
public RestTemplate restTemplate() {
return new KerberosRestTemplate(keytabLocation, principal);
}
}

View File

@ -0,0 +1,35 @@
package kerberos.kdc;
import org.apache.commons.io.FileUtils;
import org.springframework.security.kerberos.test.MiniKdc;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
class KerberosMiniKdc {
private static final String KRB_WORK_DIR = ".\\spring-security-sso\\spring-security-sso-kerberos\\krb-test-workdir";
public static void main(String[] args) throws Exception {
String[] config = MiniKdcConfigBuilder.builder()
.workDir(prepareWorkDir())
.confDir("minikdc-krb5.conf")
.keytabName("example.keytab")
.principals("client/localhost", "HTTP/localhost")
.build();
MiniKdc.main(config);
}
private static String prepareWorkDir() throws IOException {
Path dir = Paths.get(KRB_WORK_DIR);
File directory = dir.normalize().toFile();
FileUtils.deleteQuietly(directory);
FileUtils.forceMkdir(directory);
return dir.toString();
}
}

View File

@ -0,0 +1,64 @@
package kerberos.kdc;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Objects;
class MiniKdcConfigBuilder {
private String workDir;
private String confDir;
private String keytabName;
private Collection<String> principals;
private MiniKdcConfigBuilder() {
// desired
}
static MiniKdcConfigBuilder builder() {
return new MiniKdcConfigBuilder();
}
MiniKdcConfigBuilder workDir(String workDir) {
this.workDir = workDir;
return this;
}
MiniKdcConfigBuilder confDir(String cfg) {
try {
URL resource = Thread.currentThread().getContextClassLoader().getResource(cfg);
URI uri = Objects.requireNonNull(resource).toURI();
this.confDir = Paths.get(uri).toString();
} catch (URISyntaxException cause) {
throw new IllegalStateException("Could not resolve path for: " + cfg, cause);
}
return this;
}
MiniKdcConfigBuilder keytabName(String keytabName) {
this.keytabName = Paths.get(workDir).resolve(keytabName).toString();
return this;
}
MiniKdcConfigBuilder principals(String... principals) {
this.principals = Arrays.asList(principals);
return this;
}
String[] build() {
Collection<String> miniKdcConfig = new ArrayList<>();
miniKdcConfig.add(workDir);
miniKdcConfig.add(confDir);
miniKdcConfig.add(keytabName);
miniKdcConfig.addAll(principals);
return miniKdcConfig.toArray(new String[0]);
}
}

View File

@ -0,0 +1,22 @@
package kerberos.server;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import java.nio.file.Paths;
@SpringBootApplication
public class KerberizedServerApp {
static {
System.setProperty("java.security.krb5.conf",
Paths.get(".\\spring-security-sso\\spring-security-sso-kerberos\\krb-test-workdir\\krb5.conf")
.normalize().toAbsolutePath().toString());
System.setProperty("sun.security.krb5.debug", "true");
}
public static void main(String[] args) {
SpringApplication.run(KerberizedServerApp.class, args);
}
}

View File

@ -0,0 +1,18 @@
package kerberos.server.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
@Configuration
class MvcConfig extends WebMvcConfigurerAdapter {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/home").setViewName("home");
registry.addViewController("/").setViewName("home");
registry.addViewController("/hello").setViewName("hello");
registry.addViewController("/login").setViewName("login");
}
}

View File

@ -0,0 +1,103 @@
package kerberos.server.config;
import kerberos.server.service.DummyUserDetailsService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.FileSystemResource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.kerberos.authentication.KerberosAuthenticationProvider;
import org.springframework.security.kerberos.authentication.KerberosServiceAuthenticationProvider;
import org.springframework.security.kerberos.authentication.sun.SunJaasKerberosClient;
import org.springframework.security.kerberos.authentication.sun.SunJaasKerberosTicketValidator;
import org.springframework.security.kerberos.web.authentication.SpnegoAuthenticationProcessingFilter;
import org.springframework.security.kerberos.web.authentication.SpnegoEntryPoint;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
@Configuration
@EnableWebSecurity
class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Value("${app.service-principal}")
private String servicePrincipal;
@Value("${app.keytab-location}")
private String keytabLocation;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.exceptionHandling()
.authenticationEntryPoint(spnegoEntryPoint())
.and()
.authorizeRequests().antMatchers("/", "/home").permitAll()
.anyRequest().authenticated()
.and()
.formLogin().loginPage("/login").permitAll()
.and()
.logout().permitAll()
.and()
.addFilterBefore(spnegoAuthenticationProcessingFilter(authenticationManagerBean()),
BasicAuthenticationFilter.class);
}
@Bean
public AuthenticationManager anAuthenticationManager() throws Exception {
return authenticationManager();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(kerberosAuthenticationProvider())
.authenticationProvider(kerberosServiceAuthenticationProvider());
}
@Bean
public KerberosAuthenticationProvider kerberosAuthenticationProvider() {
KerberosAuthenticationProvider provider = new KerberosAuthenticationProvider();
SunJaasKerberosClient client = new SunJaasKerberosClient();
client.setDebug(true);
provider.setKerberosClient(client);
provider.setUserDetailsService(dummyUserDetailsService());
return provider;
}
@Bean
public SpnegoEntryPoint spnegoEntryPoint() {
return new SpnegoEntryPoint("/login");
}
@Bean
public SpnegoAuthenticationProcessingFilter spnegoAuthenticationProcessingFilter(
AuthenticationManager authenticationManager) {
SpnegoAuthenticationProcessingFilter filter = new SpnegoAuthenticationProcessingFilter();
filter.setAuthenticationManager(authenticationManager);
return filter;
}
@Bean
public KerberosServiceAuthenticationProvider kerberosServiceAuthenticationProvider() {
KerberosServiceAuthenticationProvider provider = new KerberosServiceAuthenticationProvider();
provider.setTicketValidator(sunJaasKerberosTicketValidator());
provider.setUserDetailsService(dummyUserDetailsService());
return provider;
}
@Bean
public SunJaasKerberosTicketValidator sunJaasKerberosTicketValidator() {
SunJaasKerberosTicketValidator ticketValidator = new SunJaasKerberosTicketValidator();
ticketValidator.setServicePrincipal(servicePrincipal);
ticketValidator.setKeyTabLocation(new FileSystemResource(keytabLocation));
ticketValidator.setDebug(true);
return ticketValidator;
}
@Bean
public DummyUserDetailsService dummyUserDetailsService() {
return new DummyUserDetailsService();
}
}

View File

@ -0,0 +1,15 @@
package kerberos.server.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/endpoint")
class SampleController {
@GetMapping
String getIt() {
return "data from kerberized server";
}
}

View File

@ -0,0 +1,16 @@
package kerberos.server.service;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
public class DummyUserDetailsService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return new User(username, "notUsed", true, true, true, true, AuthorityUtils.createAuthorityList("ROLE_USER"));
}
}

View File

@ -0,0 +1,6 @@
# make sure the same data is configured in KerberosMiniKdc
# otherwise configuration/communication error will occur
app.service-principal=HTTP/localhost
app.user-principal=client/localhost
app.keytab-location=@project.basedir@\\krb-test-workdir\\example.keytab
app.access-url=http://localhost:8080/endpoint

View File

@ -0,0 +1,25 @@
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
[libdefaults]
default_realm = {0}
udp_preference_limit = 1
[realms]
{0} = '{'
kdc = {1}:{2}
'}'

View File

@ -0,0 +1,47 @@
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
dn: ou=users,dc=${0},dc=${1}
objectClass: organizationalUnit
objectClass: top
ou: users
dn: uid=krbtgt,ou=users,dc=${0},dc=${1}
objectClass: top
objectClass: person
objectClass: inetOrgPerson
objectClass: krb5principal
objectClass: krb5kdcentry
cn: KDC Service
sn: Service
uid: krbtgt
userPassword: secret
krb5PrincipalName: krbtgt/${2}.${3}@${2}.${3}
krb5KeyVersionNumber: 0
dn: uid=ldap,ou=users,dc=${0},dc=${1}
objectClass: top
objectClass: person
objectClass: inetOrgPerson
objectClass: krb5principal
objectClass: krb5kdcentry
cn: LDAP
sn: Service
uid: ldap
userPassword: secret
krb5PrincipalName: ldap/${4}@${2}.${3}
krb5KeyVersionNumber: 0

View File

@ -0,0 +1,10 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
>
<head>
<title>Spring Security Kerberos Example</title>
</head>
<body>
<h1 th:inline="text">Hello [[${#httpServletRequest.remoteUser}]]!</h1>
</body>
</html>

View File

@ -0,0 +1,10 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
<title>Spring Security Kerberos Example</title>
</head>
<body>
<h1>Welcome!</h1>
<p>Click <a th:href="@{/hello}">here</a> to see a greeting.</p>
</body>
</html>

View File

@ -0,0 +1,20 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
>
<head>
<title>Spring Security Kerberos Example</title>
</head>
<body>
<div th:if="${param.error}">
Invalid username and password.
</div>
<div th:if="${param.logout}">
You have been logged out.
</div>
<form th:action="@{/login}" method="post">
<div><label> User Name : <input type="text" name="username"/> </label></div>
<div><label> Password: <input type="password" name="password"/> </label></div>
<div><input type="submit" value="Sign In"/></div>
</form>
</body>
</html>

View File

@ -0,0 +1,43 @@
package kerberos.client;
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;
import static org.junit.Assert.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
/**
* Procedure to run this manual test:
* <ol>
* <li>Start {@code KerberosMiniKdc}</li>
* <li>Start {@code KerberizedServerApp}</li>
* <li>Run the test</li>
* </ol>
*/
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@FixMethodOrder
public class SampleServiceManualTest {
@Autowired
private SampleService sampleService;
@Test
public void a_givenKerberizedRestTemplate_whenServiceCall_thenSuccess() {
assertNotNull(sampleService);
assertEquals("data from kerberized server", sampleService.getData());
}
@Test
public void b_givenRestTemplate_whenServiceCall_thenFail() {
sampleService.setRestTemplate(new RestTemplate());
assertThrows(RestClientException.class, sampleService::getData);
}
}