kerberos是一种安全协议,它涉及到三个部分:KDC、 Server端 与 Client端的代码。对于Kerberos 的整个认证过程,有很多介绍。但是对于如何在一个已有TCP程序中进行kerberos认证,一直不知道如何使用。前段时间在oracle的文件中,找到一个server与client的样例,结合这个用例,将它进行改写,便于理解。
前置说明:
package com.test.gssapi.sample1;
import org.ietf.jgss.*;
import java.io.*;
import java.net.Socket;
import java.net.ServerSocket;
public class TestServer {
private int localPort;
private ServerSocket ss;
private boolean iskerberos;
private Socket socket = null;
private GSSContext context = null;
public TestServer(int port) {
this.localPort = port;
this.iskerberos = false;
}
public TestServer(int port, boolean iskerberos) {
this.localPort = port;
this.iskerberos = iskerberos;
}
public void receive() throws IOException, GSSException {
this.ss = new ServerSocket(localPort);
while(true) {
socket = ss.accept();
DataInputStream inStream = new DataInputStream(socket.getInputStream());
DataOutputStream outStream = new DataOutputStream(socket.getOutputStream());
this.initKerberos(inStream, outStream);
int length = inStream.readInt();
byte[] token = new byte[length];
token = new byte[length];
System.out.println("Will read token of size " + token.length);
inStream.readFully(token);
String s = new String(token);
System.out.println(s);
//1. 发送回去的消息
byte[] token1 = "Receive Client Message".getBytes();
outStream.writeInt(token1.length);
outStream.write(token1);
outStream.flush();
this.destroy();
}
}
private void initKerberos( DataInputStream inStream, DataOutputStream outStream) throws GSSException, IOException {
if(!this.iskerberos) {
return;
}
GSSManager manager = GSSManager.getInstance();
context = manager.createContext((GSSCredential) null);
byte[] token = null;
while (!context.isEstablished()) {
token = new byte[inStream.readInt()];
System.out.println(
"Will read input token of size " + token.length + " for processing by acceptSecContext");
inStream.readFully(token);
token = context.acceptSecContext(token, 0, token.length);
// Send a token to the peer if one was generated by
// acceptSecContext
if (token != null) {
System.out.println("Will send token of size " + token.length + " from acceptSecContext.");
outStream.writeInt(token.length);
outStream.write(token);
outStream.flush();
}
}
System.out.print("Context Established! ");
System.out.println("Client is " + context.getSrcName());
System.out.println("Server is " + context.getTargName());
}
private void destroy() {
if(this.socket != null) {
try {
this.socket.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
this.socket = null;
}
if(this.context != null) {
try {
this.context.dispose();
} catch (GSSException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
this.context = null;
}
}
public static void main(String[] args) throws IOException, GSSException {
int localPort = Integer.parseInt(args[0]);
boolean iskerberos = false;
if(args.length >= 2) {
iskerberos = Boolean.parseBoolean(args[1]);
}
TestServer server = new TestServer(localPort, iskerberos);
server.receive();
}
}
package com.test.gssapi.sample1;
import org.ietf.jgss.*;
import java.net.Socket;
import java.net.UnknownHostException;
import java.io.IOException;
import java.io.DataInputStream;
import java.io.DataOutputStream;
public class TestClient {
private String srvPrincal;
private String srvIP;
private int srvPort;
private boolean isKerberos;
private Socket socket;
private DataInputStream inStream;
private DataOutputStream outStream;
private GSSContext context;
public TestClient(String srvPrincal, String srvIp, int srvPort, boolean iskerberos) {
this.srvPrincal = srvPrincal;
this.srvIP = srvIp;
this.srvPort = srvPort;
this.context = null;
this.isKerberos = iskerberos;
try {
this.initSocket();
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
public TestClient(String srvPrincal, String srvIp, int srvPort) {
this.srvPrincal = srvPrincal;
this.srvIP = srvIp;
this.srvPort = srvPort;
this.context = null;
this.isKerberos = false;
try {
this.initSocket();
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
public void destroy() throws IOException, GSSException {
if (this.inStream != null) {
this.inStream.close();
}
if (this.outStream != null) {
this.outStream.close();
}
if (this.socket != null) {
this.socket.close();
}
if (this.context != null) {
this.context.dispose();
}
}
private void initSocket() throws UnknownHostException, IOException {
this.socket = new Socket(srvIP, srvPort);
this.inStream = new DataInputStream(socket.getInputStream());
this.outStream = new DataOutputStream(socket.getOutputStream());
System.out.println("Connected to server: " + this.socket.getInetAddress());
}
private void initKerberos() throws GSSException, IOException {
if (!isKerberos) {
return;
}
/**
* Oid用来表示GSS-API这种机制。 这个值是协议中定义的(RFC 1964),这一个部分是固定的
*/
Oid krb5Oid = new Oid("1.2.840.113554.1.2.2");
GSSManager manager = GSSManager.getInstance();
/*
* Create a GSSName out of the server's name. The null indicates that this
* application does not wish to make any claims about the syntax of this name
* and that the underlying mechanism should try to parse it as per whatever
* default syntax it chooses.
*/
/**
* 创建一个GSSName,需要注意的是第一个参数srvPrincal,服务器收到消息后,会与自己的princal进行比较,
* 如果不正确,就会抛出异常.
*/
GSSName serverName = manager.createName(srvPrincal, null);
/*
* Create a GSSContext for mutual authentication with the server. - serverName
* is the GSSName that represents the server. - krb5Oid is the Oid that
* represents the mechanism to use. The client chooses the mechanism to use. -
* null is passed in for client credentials - DEFAULT_LIFETIME lets the
* mechanism decide how long the context can remain valid. Note: Passing in null
* for the credentials asks GSS-API to use the default credentials. This means
* that the mechanism will look among the credentials stored in the current
* Subject to find the right kind of credentials that it needs.
*/
System.out.println("init kerberos .. 1");
GSSContext context = manager.createContext(serverName, krb5Oid, null, GSSContext.DEFAULT_LIFETIME);
// Set the desired optional features on the context. The client
// chooses these options.
context.requestMutualAuth(true); // Mutual authentication
context.requestConf(true); // Will use confidentiality later
context.requestInteg(true); // Will use integrity later
System.out.println("init kerberos .. 2");
// Do the context eastablishment loop
byte[] token = new byte[0];
while (!context.isEstablished()) {
// token is ignored on the first call
// 这里进行kerberos认证,即调用JaaS的代码进行认证。认证完成后,返回
// 这里我们可以把它GSS-api将JaaS的代码集成了。kerberos信息的确认是在此函数中进行的
token = context.initSecContext(token, 0, token.length);
// Send a token to the server if one was generated by
// initSecContext
if (token != null) {
System.out.println("Will send token of size " + token.length + " from initSecContext.");
outStream.writeInt(token.length);
outStream.write(token);
outStream.flush();
}
// If the client is done with context establishment
// then there will be no more tokens to read in this loop
if (!context.isEstablished()) {
token = new byte[inStream.readInt()];
System.out
.println("Will read input token of size " + token.length + " for processing by initSecContext");
inStream.readFully(token);
}
}
System.out.println("Context Established! ");
System.out.println("Client is " + context.getSrcName());
System.out.println("Server is " + context.getTargName());
}
public void sendMessage() throws UnknownHostException, IOException, GSSException {
// Obtain the command-line arguments and parse the port number
System.setProperty("java.security.krb5.conf", "/etc/krb5.conf");
//判断是不是需要初始化kerberos
this.initKerberos();
//发送消息
String msg = "Hello Server ";
byte[] messageBytes = msg.getBytes();
outStream.writeInt(messageBytes.length);
outStream.write(messageBytes);
outStream.flush();
//收到服务端传回来的消息
byte[] token = new byte[0];
token = new byte[inStream.readInt()];
System.out.println("Will read token of size " + token.length);
inStream.readFully(token);
String s = new String(token);
System.out.println(s);
System.out.println("Exiting... ");
this.destroy();
}
public static void main(String[] args) throws IOException, GSSException {
String princal = args[0];
String ip = args[1];
int port = Integer.parseInt(args[2]);
boolean iskerberos = false;
if(args.length >= 4) {
iskerberos = Boolean.parseBoolean(args[3]);
}
TestClient client = new TestClient(princal, ip, port, iskerberos);
client.sendMessage();
}
}
还管是服务端还是客户端都需要使用到jaas的配置文件,这个配置文件,在下面结合程序的执行来进行说明
需要特别指出的,对于客户端/服务端,需要保证KDC正常可用,而且其中包含了正常的princal信息。下面介绍它的执行过程
服务端执行命令:
java -classpath kerberosTest.jar com.test.gssapi.sample1.TestServer 9111
客户端执行命令:
java -classpath kerberosTest.jar com.test.gssapi.sample1.TestClient admin 192.168.1.56 9111
此时就是一个普通的TCP通信协议
服务端的命令:
[root@freeipa56 testKerberos]# java -classpath kerberosTest.jar -Djava.security.krb5.realm=EXAMPLE.COM -Djava.security.krb5.kdc=freeipa56.example.com -Djavax.security.auth.useSubjectCredsOnly=false -Djava.security.auth.login.config=server.conf com.test.gssapi.sample1.TestServer 9111 true
参数说明: 服务端一共有两个参数, 9111表示端口号, true表示启用kerberos,如果不输入,则表明不启用kerberos
客户端命令:
java -classpath kerberosTest.jar -Djava.security.krb5.realm=EXAMPLE.COM -Djava.security.krb5.kdc=freeipa56.example.com -Djavax.security.auth.useSubjectCredsOnly=false -Djava.security.auth.login.config=client.conf com.test.gssapi.sample1.TestClient admin 192.168.1.56 9111 true
参数说明:
客户端参数一共四个参数,
admin: 表示服务端对应的princal为admin, 服务端认证的princal必须为这个
192.168.1.56: 表明服务端对应的IP
9111: 为服务端监听的端口
true: 表明启用kerberos
Kerberos提供了多种认证方式,这些认证方式都是通过java.security.auth.login.config对应的配置文件来设置的。下面对于分几种情况说明.
服务端配置文件(即server.conf与client.conf):
[root@freeipa56 testKerberos]# cat server.conf
com.sun.security.jgss.accept {
com.sun.security.auth.module.Krb5LoginModule required storeKey=true;
};
客户端的配置文件client.conf
[root@freeipa56 testKerberos]# cat client.conf
com.sun.security.jgss.initiate {
com.sun.security.auth.module.Krb5LoginModule required;
};
此时客户端的打屏信息:
[lch@freeipa56 testKerberos]$ java -classpath kerberosTest.jar -Djava.security.krb5.realm=EXAMPLE.COM -Djava.security.krb5.kdc=freeipa56.example.com -Djavax.security.auth.useSubjectCredsOnly=false -Djava.security.auth.login.config=client.conf com.test.gssapi.sample1.TestClient admin 192.168.1.56 9111 true
Connected to server: /192.168.1.56
Kerberos username [lch]: lch <------ 提示输入princal
Kerberos password for lch: <----- 提示输入相应的密码
Will send token of size 558 from initSecContext.
Will read input token of size 108 for processing by initSecContext
Context Established!
Client is lch@EXAMPLE.COM
Server is admin
Will read token of size 22
Receive Client Message
Exiting...
而服务端:
[root@freeipa56 testKerberos]# java -classpath kerberosTest.jar -Djava.security.krb5.realm=EXAMPLE.COM -Djava.security.krb5.kdc=freeipa56.example.com -Djavax.security.auth.useSubjectCredsOnly=false -Djava.security.auth.login.config=bcsLogin.conf com.test.gssapi.sample1.TestServer 9111 true
Will read input token of size 579 for processing by acceptSecContext
Kerberos username [root]: admin <---- 提示输入服务端的princal名称
Kerberos password for admin: <--- 提示输入相应的密码
Will send token of size 108 from acceptSecContext.
Context Established! Client is [email protected]
Server is [email protected]
Will read token of size 13
Hello Server
服务端的配置与2.4.2.1相同,client通过获取ticket的方式来进行认证
服务端的配置文件:
[root@freeipa56 testKerberos]# cat server.conf
com.sun.security.jgss.accept {
com.sun.security.auth.module.Krb5LoginModule required storeKey=true;
};
客户端配置文件:
[lch@freeipa56 testKerberos]$ cat client.conf
com.sun.security.jgss.initiate {
com.sun.security.auth.module.Krb5LoginModule required
useKeyTab=false
useTicketCache=true;
};
查看当前环境的kerberos信息,
[lch@freeipa56 testKerberos]$ klist
Ticket cache: FILE:/tmp/krb5cc_500
Default principal: lch@EXAMPLE.COM
Valid starting Expires Service principal
11/15/17 11:08:47 11/16/17 11:08:46 krbtgt/EXAMPLE.COM@EXAMPLE.COM
执行客户端命令:
[lch@freeipa56 testKerberos]$ java -classpath kerberosTest.jar -Djava.security.krb5.realm=EXAMPLE.COM -Djava.security.krb5.kdc=freeipa56.example.com -Djavax.security.auth.useSubjectCredsOnly=false -Djava.security.auth.login.config=client.conf com.test.gssapi.sample1.TestClient admin 192.168.1.56 9111 true
Connected to server: /192.168.1.56
Will send token of size 579 from initSecContext.
Will read input token of size 108 for processing by initSecContext
Context Established!
Client is lch@EXAMPLE.COM
Server is admin
Will read token of size 22
Receive Client Message
Exiting...
客户端程序不需要输入用户名密码(服务端与)
服务端的配置文件
[root@freeipa56 testKerberos]# cat server.conf
com.sun.security.jgss.accept {
com.sun.security.auth.module.Krb5LoginModule required
useKeyTab=true
storeKey=true
useTicketCache=false
keyTab="/root/admin.headless.keytab"
principal="[email protected]";
};
客户端的配置文件:
[lch@freeipa56 testKerberos]$ cat client.conf
com.sun.security.jgss.initiate {
com.sun.security.auth.module.Krb5LoginModule required
useKeyTab=false
useTicketCache=true;
};
执行服务端程序与客户端程序就会发现服务端不再需要输入认证信息了。
Kerberos提供多种认证方式,而我们可以通过java.security.auth.login.config对应的配置参数进行认证