Client
private Connection getConnection(ConnectionId remoteId,
Call call, int serviceClass, AtomicBoolean fallbackToSimpleAuth)
-->
connection.setupIOstreams(fallbackToSimpleAuth);
--> 切换realUser的UGI建立Sasl连接
UserGroupInformation ticket = remoteId.getTicket();
if (ticket != null) {
final UserGroupInformation realUser = ticket.getRealUser();
if (realUser != null) {
ticket = realUser;
}
}
--> 创建socket
setupConnection()
ipcStreams = new IpcStreams(socket, maxResponseLength);
先发送connection header
/**
* Write the connection header - this is sent when connection is established
* +----------------------------------+
* | "hrpc" 4 bytes |
* +----------------------------------+
* | Version (1 byte) |
* +----------------------------------+
* | Service Class (1 byte) |
* +----------------------------------+
* | AuthProtocol (1 byte) |
* +----------------------------------+
*/
writeConnectionHeader(ipcStreams);
如果kerbos enabled, 使用Sasl
//boolean trySasl = UserGroupInformation.isSecurityEnabled() ||
// (ticket != null && !ticket.getTokens().isEmpty());
//this.authProtocol = trySasl ? AuthProtocol.SASL : AuthProtocol.NONE;
if (authProtocol == AuthProtocol.SASL) {
try {
authMethod = ticket
.doAs(new PrivilegedExceptionAction<AuthMethod>() {
@Override
public AuthMethod run()
throws IOException, InterruptedException {
创建SaslConnection
return setupSaslConnection(ipcStreams);
}
});
private synchronized AuthMethod setupSaslConnection(IpcStreams streams)
throws IOException {
// Do not use Client.conf here! We must use ConnectionId.conf, since the
// Client object is cached and shared between all RPC clients, even those
// for separate services.
saslRpcClient = new SaslRpcClient(remoteId.getTicket(),
remoteId.getProtocol(), remoteId.getAddress(), remoteId.conf);
return saslRpcClient.saslConnect(streams);
}
类SaslRpcClient
/**
* Do client side SASL authentication with server via the given IpcStreams.
*
* @param ipcStreams
* @return AuthMethod used to negotiate the connection
* @throws IOException
*/
public AuthMethod saslConnect(IpcStreams ipcStreams) throws IOException {
// redefined if/when a SASL negotiation starts, can be queried if the
// negotiation fails
authMethod = AuthMethod.SIMPLE;
发送一个negotiateRequest, 主要目的是确认服务端也是Sasl AuthProtocol
sendSaslMessage(ipcStreams.out, negotiateRequest);
// loop until sasl is complete or a rpc error occurs
boolean done = false;
//在一个循环里,直到建立好连接
do {
ByteBuffer bb = ipcStreams.readResponse();
RpcWritable.Buffer saslPacket = RpcWritable.Buffer.wrap(bb);
RpcResponseHeaderProto header =
saslPacket.getValue(RpcResponseHeaderProto.getDefaultInstance());
switch (header.getStatus()) {
case ERROR: // might get a RPC error during
case FATAL:
throw new RemoteException(header.getExceptionClassName(),
header.getErrorMsg());
default: break;
}
如果不是SASL的,就报错,通过特殊的callId判定。 -33
if (header.getCallId() != AuthProtocol.SASL.callId) {
throw new SaslException("Non-SASL response during negotiation");
}
RpcSaslProto saslMessage =
saslPacket.getValue(RpcSaslProto.getDefaultInstance());
if (saslPacket.remaining() > 0) {
throw new SaslException("Received malformed response length");
}
// handle sasl negotiation process
RpcSaslProto.Builder response = null;
switch (saslMessage.getState()) {
case NEGOTIATE: {
选择一个可用的Auth, 如果NN是kerbos,那么会返回 TOKEN和KERBOS两种
如果第一次连接,UserGroupInformation的tokens是空的,所以第一次选择的saslAuthType 就是KERBOS
当客户端获取了Token, 就会设置到UserGroupInformation里,再次建立连接的时候,就能选择 TOKEN
参考后面对selectSaslClient 方法的解读
// create a compatible SASL client, throws if no supported auths,
SaslAuth saslAuthType = selectSaslClient(saslMessage.getAuthsList());
// define auth being attempted, caller can query if connect fails
authMethod = AuthMethod.valueOf(saslAuthType.getMethod());
byte[] responseToken = null;
if (authMethod == AuthMethod.SIMPLE) { // switching to SIMPLE
done = true; // not going to wait for success ack
} else {
byte[] challengeToken = null;
if (saslAuthType.hasChallenge()) {
// server provided the first challenge
challengeToken = saslAuthType.getChallenge().toByteArray();
saslAuthType =
SaslAuth.newBuilder(saslAuthType).clearChallenge().build();
} else if (saslClient.hasInitialResponse()) {
challengeToken = new byte[0];
}
responseToken = (challengeToken != null)
? saslClient.evaluateChallenge(challengeToken)
: new byte[0];
}
response = createSaslReply(SaslState.INITIATE, responseToken);
response.addAuths(saslAuthType);
break;
}
case CHALLENGE: {
if (saslClient == null) {
// should probably instantiate a client to allow a server to
// demand a specific negotiation
throw new SaslException("Server sent unsolicited challenge");
}
byte[] responseToken = saslEvaluateToken(saslMessage, false);
response = createSaslReply(SaslState.RESPONSE, responseToken);
break;
}
case SUCCESS: {
// simple server sends immediate success to a SASL client for
// switch to simple
if (saslClient == null) {
authMethod = AuthMethod.SIMPLE;
} else {
saslEvaluateToken(saslMessage, true);
}
done = true;
break;
}
default: {
throw new SaslException(
"RPC client doesn't support SASL " + saslMessage.getState());
}
}
if (response != null) {
sendSaslMessage(ipcStreams.out, response.build());
}
} while (!done);
return authMethod;
}
/**
* Instantiate a sasl client for the first supported auth type in the
* given list. The auth type must be defined, enabled, and the user
* must possess the required credentials, else the next auth is tried.
*
* @param authTypes to attempt in the given order
* @return SaslAuth of instantiated client
* @throws AccessControlException - client doesn't support any of the auths
* @throws IOException - misc errors
*/
private SaslAuth selectSaslClient(List<SaslAuth> authTypes)
throws SaslException, AccessControlException, IOException {
SaslAuth selectedAuthType = null;
boolean switchToSimple = false;
for (SaslAuth authType : authTypes) {
if (!isValidAuthType(authType)) {
continue; // don't know what it is, try next
}
AuthMethod authMethod = AuthMethod.valueOf(authType.getMethod());
if (authMethod == AuthMethod.SIMPLE) {
switchToSimple = true;
} else {
*******************重点在这个方法里*************************
saslClient = createSaslClient(authType);
if (saslClient == null) { // client lacks credentials, try next
continue;
}
}
selectedAuthType = authType;
break;
}
if (saslClient == null && !switchToSimple) {
List<String> serverAuthMethods = new ArrayList<String>();
for (SaslAuth authType : authTypes) {
serverAuthMethods.add(authType.getMethod());
}
throw new AccessControlException(
"Client cannot authenticate via:" + serverAuthMethods);
}
if (LOG.isDebugEnabled() && selectedAuthType != null) {
LOG.debug("Use " + selectedAuthType.getMethod() +
" authentication for protocol " + protocol.getSimpleName());
}
return selectedAuthType;
}
/**
* Try to create a SaslClient for an authentication type. May return
* null if the type isn't supported or the client lacks the required
* credentials.
*
* @param authType - the requested authentication method
* @return SaslClient for the authType or null
* @throws SaslException - error instantiating client
* @throws IOException - misc errors
*/
private SaslClient createSaslClient(SaslAuth authType)
throws SaslException, IOException {
String saslUser = null;
// SASL requires the client and server to use the same proto and serverId
// if necessary, auth types below will verify they are valid
final String saslProtocol = authType.getProtocol();
final String saslServerName = authType.getServerId();
Map<String, String> saslProperties =
saslPropsResolver.getClientProperties(serverAddr.getAddress());
CallbackHandler saslCallback = null;
final AuthMethod method = AuthMethod.valueOf(authType.getMethod());
switch (method) {
优先尝试TOKEN,
case TOKEN: {
//如果UserGroupInformaiton的subject中已经有了对应server的token了,那么久会返回一个合法的token。
否则没有token
Token<?> token = getServerToken(authType);
if (token == null) {
LOG.debug("tokens aren't supported for this protocol" +
" or user doesn't have one");
************************* 重点,如果token为空,返回一个null, 会重新尝试 KERBOS的方式创建***********************
return null;
}
saslCallback = new SaslClientCallbackHandler(token);
break;
}
case KERBEROS: {
if (ugi.getRealAuthenticationMethod().getAuthMethod() !=
AuthMethod.KERBEROS) {
LOG.debug("client isn't using kerberos");
return null;
}
String serverPrincipal = getServerPrincipal(authType);
if (serverPrincipal == null) {
LOG.debug("protocol doesn't use kerberos");
return null;
}
if (LOG.isDebugEnabled()) {
LOG.debug("RPC Server's Kerberos principal name for protocol="
+ protocol.getCanonicalName() + " is " + serverPrincipal);
}
break;
}
default:
throw new IOException("Unknown authentication method " + method);
}
String mechanism = method.getMechanismName();
if (LOG.isDebugEnabled()) {
LOG.debug("Creating SASL " + mechanism + "(" + method + ") "
+ " client to authenticate to service at " + saslServerName);
}
return saslFactory.createSaslClient(
new String[] {mechanism}, saslUser, saslProtocol, saslServerName,
saslProperties, saslCallback);
}