Spring Security Kerberos 环境搭建

2019年初,领导刘哥给了我一个很棘手的任务,让研究下Kerberos认证原理,因为半年后有个项目会依赖Kerberos实现SSO。我心里暗暗骂了一句,我这么忙能不能让那些每天吹牛的人研究下呀,我激动的拍了下桌子,满脸笑容的答应了。

Kerberos是啥呀,我问了问百度,翻掉前两页的广告之后,我终于查到了一些资料。

  1. Spring Security Kerberos 配置IWA的关键步骤备忘:https://blog.csdn.net/wwwcomy/article/details/84883086
  2. Kerberos基本概念及原理汇总:https://cloud.tencent.com/developer/article/1381306
  3. 第十二章 Spring Security扩展:https://www.iteye.com/blog/lengyun3566-1404943
  4. spring-security-kerberos官网:https://spring.io/projects/spring-security-kerberos

以上内容讲的都挺好,头一个礼拜我Google了各种网站,终于在我的努力下懂得了原来这就是Kerberos,我真的懂了,但我应该怎么做呢。就好像我懂得了这就是涡轮增压的原理,行了,那你去设计台车吧,我拿着手中的锤子不知道该砸向谁。其实网上的内容大部分是讲解Kerberos的认证原理,都是很底层的内容,当然对于开发人员来说掌握其中的原理是必要的,但只需要简单了解即可,不需要深入研究,因为有很多框架已经把内部的逻辑实现了,我们只要按照相关的配置信息简单配置即可。针对Kerberos认证原理,我们只需了解:

  1. 大致的认证过程
  2. 所需要的机器种类
  3. 所需要的机器环境。

如何将Kerberos用到具体的项目中才是我们真正需要掌握的,而网上能够查到的资料又非常稀少,为了将我2个月的研究过程进行整理,并希望能给恰巧遇到同样问题的你提供一点帮助,特整理了本篇文章,当然内容肯定会由很多错误的地方,还望嘴下留情,我们才好共同探讨。

环境介绍

先说下甲方爸爸的机器环境:

  • 服务器操作系统:Windows Server2016 R2,服务器已经入域
  • 部署中间件:Tomcat 8.5,运行环境Java,框架Spring Boot 2B/S框架
  • 客户端操作系统:Windows 10,客户端同样已经入域、客户端浏览器为万恶的IE9(当时微软还没有丢弃它)
    再说下我的需求:

我们未来要做一个系统,这个系统支持两种登录方式:

  1. 用户名+密码。访问地址为http://www.jiafangbaba.com/login之后跳转到登录页面,输入用户名和密码,之后登录进入主页。这种实现过程就不说了,每个公司都有自己的一套权限认证体系。
  2. 域认证。访问地址http://www.jiafangbaba.com之后,根据当前的域用户信息直接跳转到系统首页。
    本篇内容只是关于如何实现SSO的,对于上述两种登录方式的逻辑实现,以后再写。

实施步骤

整个实现步骤大致分为下面几步:

  1. 域环境准备
  2. 服务器端口配置
  3. 编写测试程序
  4. 浏览器配置
  5. 验证成果
域环境准备

先在VM上创建三台虚机,相关配置如下:

KDC/DNS Server Client
OS Windows Server 2016 Windows Server 2016 Win10
IP 192.168.150.138 192.168.150.139 192.168.150.140
DNS 127.0.0.1 192.168.150.138 192.168.150.138
AD jiafangbaba.com jiafangbaba.com jiafangbaba.com
JDK ----- Jdk1.8 -----
Tomcat ----- apache-tomcat-8.5.42 -----
计算机名 kdc.jiafangbaba.com server.jiafangbaba.com client.jiafangbaba.com
备注 AD域控制器 应用服务器 客户端

虚机的创建步骤大家自己找找吧,到处都是:这个不会有人不知道吧。

搭建域环境的步骤大家找找吧,也到处都是:https://www.jb51.net/article/252392.htm

KDC/DNS端环境搭建详细步骤
  1. 修改计算机名为kdc
  2. 修改本机IP为静态IP(192.168.150.138),DNS地址为127.0.0.1
  3. 检查登录用户Administrator是否有密码,检查密码是否符合密码策略(默认策略密码要有大小写字母和数字)
  4. 搭建域服务器,域名为jiafangbaba.com
  5. 创建两个域用户
  • 用户名:user2(用于client端登录),密码:Tomcat2019
  • 用户名:tomcat1(用于生成keytab文件)密码:Tomcat2019
Server端
  1. 修改计算机名为server
  2. 修改本机IP为静态IP(192.168.150.139),DNS地址为192.168.150.138KDC的IP
  3. 加入域(jiafangbaba.com
Client端
  1. DNS地址为192.168.150.138(KDC的IP)
  2. 加入域(jiafangbaba.com),并用域用户(user2)登录

服务器端口配置

我没Google到端口配置的相关内容,这也是我在调试的过程中遇到的很头疼的一个问题。同样的包在我本地行,在用户现场死活跑不出来,研究了好长一段时间才明白,我本地都是把防火墙禁掉的,而现场环境是打开的,所以需要提前将相关端口策略开通,才能保证服务器之间的Kerberos验证是没有问题的。我通过抓包工具抓到了需要开放的端口。

相关端口见下表

本端 本端端口 本端类型 对端 对端端口 对端类型 协议 功能
Client 随机 客户端 KDC 53 服务端 UDP DNS解析
Client 随机 客户端 KDC 88 服务端 TCP KerberosV5:AS Request
Client 随机 客户端 KDC 389 服务端 UDP 访问LDAP服务
Client 随机 客户端 KDC 389 服务端 TCP 访问LDAP服务
Server 随机 客户端 KDC 53 服务端 UDP DNS解析
Server 随机 客户端 KDC 88 服务端 UDP KerberosV5:AS Request
Server 随机 客户端 KDC 389 服务端 TCP 访问LDAP服务
Client 随机 客户端 Server 80 服务端 TCP HTTP请求
备注:此处对端端口为Tomcat的端口,默认为8080,测试环境中已修改为80.

在测试环境中通过telnet命令确定机器之间是互通的,这一步非常关键。上表中的Client相关的端口大家可以忽略,因为本身Client对上面的端口是放开的,特殊情况下才需要考虑Client的上述配置。

编写测试程序

按照上述内容配置相关服务器信息之后,下面就到了真正的测试环节,首先我们要准备测试包部署到Server服务器上的Tomcat中。由于我使用的是SpringBoot框架,而spring-security-kerberosSpring对Kerbeors的实现,对于Spring Security,上来肯定是先来个WebSecurityConfig

此类的目的有两个:1、实现WebSecurityConfigurerAdapter的自动装载,其中下图红框中的adDomainadServer等参数是我们实现Kerberos认证的关键参数。

  1. 实现权限的拦截。
    我为了测试简单,把/homeall-new两个URL添加到了忽略列表,其中/home是为了验证kerberos认证是否通过,all-new是为了获取所有域用户,本篇文章不谈域用户的获取。
package com.example.demowin.config;

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.ldap.core.LdapTemplate;
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.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.servlet.configuration.EnableWebMvcSecurity;
import org.springframework.security.kerberos.authentication.KerberosServiceAuthenticationProvider;
import org.springframework.security.kerberos.authentication.sun.SunJaasKerberosTicketValidator;
import org.springframework.security.kerberos.client.config.SunJaasKrb5LoginConfig;
import org.springframework.security.kerberos.client.ldap.KerberosLdapContextSource;
import org.springframework.security.kerberos.web.authentication.SpnegoAuthenticationProcessingFilter;
import org.springframework.security.kerberos.web.authentication.SpnegoEntryPoint;
import org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;

@Configuration
@EnableWebMvcSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

	@Value("${app.ad-domain}")
	private String adDomain;

	@Value("${app.ad-server}")
	private String adServer;

	@Value("${app.service-principal}")
	private String servicePrincipal;

	@Value("${app.keytab-location}")
	private String keytabLocation;

	@Value("${app.ldap-search-filter}")
	private String ldapSearchFilter;
	
	
	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http
			.exceptionHandling()
				.authenticationEntryPoint(spnegoEntryPoint())
				.and()
			.authorizeRequests()
				.antMatchers( "/all-new/").permitAll()
				.and()
			.authorizeRequests()
				.antMatchers("/", "/home").permitAll()
				.anyRequest().authenticated()
				.and()
			.formLogin()
				.loginPage("/login").permitAll()
				.and()
			.logout()
				.permitAll()
				.and()
			.addFilterBefore(
					spnegoAuthenticationProcessingFilter(authenticationManagerBean()),
					BasicAuthenticationFilter.class);
		
		//初始化krb5config文件位置
//		initKrb5Config();
	}

	@Override
	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
		auth
			.authenticationProvider(activeDirectoryLdapAuthenticationProvider())
			.authenticationProvider(kerberosServiceAuthenticationProvider());
	}

	@Bean
	public ActiveDirectoryLdapAuthenticationProvider activeDirectoryLdapAuthenticationProvider() 		{
		return new ActiveDirectoryLdapAuthenticationProvider(adDomain, adServer);
	}

	@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() throws Exception {
		KerberosServiceAuthenticationProvider provider = new KerberosServiceAuthenticationProvider();
		provider.setTicketValidator(sunJaasKerberosTicketValidator());
		provider.setUserDetailsService(ldapUserDetailsService());
		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 KerberosLdapContextSource kerberosLdapContextSource() throws Exception {
		KerberosLdapContextSource contextSource = new KerberosLdapContextSource(adServer);
		contextSource.setLoginConfig(loginConfig());
		return contextSource;
	}
	
	@Bean
	public LdapTemplate ldapTemplate() throws Exception {
		LdapTemplate template = new LdapTemplate(kerberosLdapContextSource());
		return template;
	}

	public SunJaasKrb5LoginConfig loginConfig() throws Exception {
		SunJaasKrb5LoginConfig loginConfig = new SunJaasKrb5LoginConfig();
		loginConfig.setKeyTabLocation(new FileSystemResource(keytabLocation));
		loginConfig.setServicePrincipal(servicePrincipal);
		loginConfig.setDebug(true);
		loginConfig.setIsInitiator(true);
		loginConfig.afterPropertiesSet();
		return loginConfig;
	}

	@Bean
	@Override
	public AuthenticationManager authenticationManagerBean() throws Exception {
		return super.authenticationManagerBean();
	}
	
	@Bean
    public KerberosServiceAuthenticationProvider kerberosServiceAuthenticationProvider() {
        KerberosServiceAuthenticationProvider provider = new KerberosServiceAuthenticationProvider();
        provider.setTicketValidator(sunJaasKerberosTicketValidator());
        provider.setUserDetailsService(dummyUserDetailsService());
        return provider;
    }
	
	@Bean
    public DummyUserDetailsService dummyUserDetailsService() {
        return new DummyUserDetailsService();
    }
}

相关配置信息说明如下:

  • ad-domain:該參數表示的是域名,可在入域的“計算機屬性”中查找,建議統一使用大寫字母表示,為JIANGFANGBABA.COM
  • ad-server:該參數表示的是ldap服務地址,對於windows server系統,我們在創建DC的時,會自動啓用該服務,所以此處地址應為域控服務器計算機名稱(包含域名),為:ldap://kdc.jiafangbaba.com;小写即可
  • service-principal:該參數表示的是認證主體的名稱,即部署應用程序的計算機名稱(包含域名),為:HTTP/[email protected]
  • keytab-location:該參數表示的keytab文件位置,此处为/tmp/server.keytab,代表Tomcat所在的磁盘下的/tmp/server.keytab文件,假如Tomcat在D盘,那么需要将keytab文件拷贝到D:/tmp/下,具體路徑和名稱根據情況而定,此文件根據service-principal生成;
  • ldap-search-filter:ldap目錄查詢過濾條件,不需要修改。
创建keytab文件

上面的配置中有一步是需要创建keytab文件,此文件为Server服务器上的秘钥文件,我们知道kerberos认证中涉及到三方认证,分别是Client-KDCServer-KDCClient-Server之间的认证,Client-KDCClient-Server之间的认证其实是登录Client的域用户与KDC之间的认证,而Server为一台具体的服务器并没有具体的域用户登录信息,所以我们需要将Server上部署的应用程序作为一个域用户即principal进行权限的认证,那么就需要建立Server上的应用程序和域用户的映射即Server.jiafangbaba.comtomcat1域用户之间的映射关系。简单来说Client需要有个域用户,Server上同样需要一个域用户,并通过此域用户创建一个keytab秘钥文件,每次Server进行kerberos认证时,以此秘钥文件代替域用户名、密码进行相关的校验。

KDC中执行下列代码:

  1. 执行
setspn –a HTTP/server.jiafangbaba.com tomcat1
  1. 执行
ktpass /out c:\tmp\server.keytab /mapuser [email protected] /princ HTTP/[email protected] /pass Tomcat2019 /ptype KRB5_NT_PRINCIPAL /crypto ALL`
  1. 将生成的keytab文件拷贝到server端
    注意:keytab文件所在的目录一定已经被创建好
  • /out;指定keytab文件生成的位置
  • /mapuser 用户名@域名(大写)
  • /princ setspn设置的认证主体名@域名(大写)
  • /pass 用户密码
  • /ptype 认证主体类型(写这个就行)
  • /crypto 支持的加密类型

“/home”等其他地址的详细页面如下:

package com.example.demowin.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

@Configuration
public class MvcConfig extends WebMvcConfigurerAdapter {

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/home").setViewName("home.html");
        registry.addViewController("/").setViewName("home.html");
        registry.addViewController("/hello").setViewName("hello.html");
        registry.addViewController("/login").setViewName("login.html");
    }

}

home.html页面代码如下:

DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
    <head>
        <title>Spring Security Kerberos Exampletitle>
    head>
    <body>
        <h1>Welcome!h1>
        <p>Click <a th:href="@{/hello}">herea> to see a greeting.p>
        <p>Click <a th:href="@{/all-new/}">all domain usersa> to see all domain users.p>
    body>
html>

点击home.htmlClick here超链接,会跳转到/hello页面,因为我们在WebSecurityConfig类中并没有将/hello页面添加到permitAll,所以页面进入/hello之前会进行kerberos认证,认证通过之后会跳转到/hello页面,返回hello.html。同时Spring Security会将当前登录的域用户信息封装到后端HttpServletRequest对象中,在hello.html页面通过thymeleaf语法 [[${#httpServletRequest.remoteUser}]]就可将登录的域用户名打印出来。

DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
    <head>
        <title>Spring Security Kerberos Exampletitle>
    head>
    <body>
        <h1 th:inline="text">Hello [[${#httpServletRequest.remoteUser}]]!h1>
        <h1 th:inline="text">User Principal is [[${#httpServletRequest.userPrincipal}]]!h1>
    body>
html>

浏览器配置

在IE浏览器中,将要访问的域名server.jiafangbaba.com路径添加到本地信任站点,否则浏览器会弹出Windows窗口输入用户名密码。

如果使用的是EDGEChrome浏览器,同样需要在IE浏览器下设置本地信任站点,因为浏览器都会使用IE中的信任站点进行权限的认证。

验证成果

Spring Security Kerberos 环境搭建_第1张图片

Spring Security Kerberos 环境搭建_第2张图片

你可能感兴趣的:(Kerberos,spring,java,后端)