LoginUtil.java 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554
  1. package com.primeton.dsp.datarelease.data.bdata;
  2. import org.apache.hadoop.conf.Configuration;
  3. import org.apache.hadoop.security.UserGroupInformation;
  4. import org.apache.hadoop.security.authentication.util.KerberosUtil;
  5. import org.apache.log4j.Logger;
  6. import javax.security.auth.login.AppConfigurationEntry;
  7. import javax.security.auth.login.AppConfigurationEntry.LoginModuleControlFlag;
  8. import java.io.File;
  9. import java.io.FileWriter;
  10. import java.io.IOException;
  11. import java.util.HashMap;
  12. import java.util.Map;
  13. /**
  14. *
  15. * Hadoop 认证实现类
  16. *
  17. * <pre>
  18. *
  19. * Created by zhaopx.
  20. * User: zhaopx
  21. * Date: 2020/4/20
  22. * Time: 17:57
  23. *
  24. * </pre>
  25. *
  26. * @author zhaopx
  27. */
  28. public class LoginUtil {
  29. public enum Module {
  30. STORM("StormClient"), KAFKA("KafkaClient"), ZOOKEEPER("Client");
  31. private String name;
  32. private Module(String name) {
  33. this.name = name;
  34. }
  35. public String getName() {
  36. return name;
  37. }
  38. }
  39. private static final Logger LOG = Logger.getLogger(LoginUtil.class);
  40. /**
  41. * line operator string
  42. */
  43. private static final String LINE_SEPARATOR = System
  44. .getProperty("line.separator");
  45. /**
  46. * jaas file postfix
  47. */
  48. private static final String JAAS_POSTFIX = ".jaas.conf";
  49. /**
  50. * IBM jdk login module
  51. */
  52. private static final String IBM_LOGIN_MODULE = "com.ibm.security.auth.module.Krb5LoginModule required";
  53. /**
  54. * oracle jdk login module
  55. */
  56. private static final String SUN_LOGIN_MODULE = "com.sun.security.auth.module.Krb5LoginModule required";
  57. /**
  58. * java security login file path
  59. */
  60. public static final String JAVA_SECURITY_LOGIN_CONF_KEY = "java.security.auth.login.config";
  61. private static final String JAVA_SECURITY_KRB5_CONF_KEY = "java.security.krb5.conf";
  62. private static final String ZOOKEEPER_SERVER_PRINCIPAL_KEY = "zookeeper.server.principal";
  63. private static final String LOGIN_FAILED_CAUSE_PASSWORD_WRONG = "(wrong password) keytab file and user not match, you can kinit -k -t keytab user in client server to check";
  64. private static final String LOGIN_FAILED_CAUSE_TIME_WRONG = "(clock skew) time of local server and remote server not match, please check ntp to remote server";
  65. private static final String LOGIN_FAILED_CAUSE_AES256_WRONG = "(aes256 not support) aes256 not support by default jdk/jre, need copy local_policy.jar and US_export_policy.jar from remote server in path /opt/Bigdata/jdk/jre/lib/security";
  66. private static final String LOGIN_FAILED_CAUSE_PRINCIPAL_WRONG = "(no rule) principal format not support by default, need add property hadoop.security.auth_to_local(in core-site.xml) value RULE:[1:$1] RULE:[2:$1]";
  67. private static final String LOGIN_FAILED_CAUSE_TIME_OUT = "(time out) can not connect to kdc server or there is fire wall in the network";
  68. private static final boolean IS_IBM_JDK = System.getProperty("java.vendor")
  69. .contains("IBM");
  70. public synchronized static void login(String userPrincipal,
  71. String userKeytabPath, String krb5ConfPath, Configuration conf)
  72. throws IOException {
  73. // 1.check input parameters
  74. if ((userPrincipal == null) || (userPrincipal.length() <= 0)) {
  75. LOG.error("input userPrincipal is invalid.");
  76. throw new IOException("input userPrincipal is invalid.");
  77. }
  78. if ((userKeytabPath == null) || (userKeytabPath.length() <= 0)) {
  79. LOG.error("input userKeytabPath is invalid.");
  80. throw new IOException("input userKeytabPath is invalid.");
  81. }
  82. if ((krb5ConfPath == null) || (krb5ConfPath.length() <= 0)) {
  83. LOG.error("input krb5ConfPath is invalid.");
  84. throw new IOException("input krb5ConfPath is invalid.");
  85. }
  86. if ((conf == null)) {
  87. LOG.error("input conf is invalid.");
  88. throw new IOException("input conf is invalid.");
  89. }
  90. // 2.check file exsits
  91. File userKeytabFile = new File(userKeytabPath);
  92. if (!userKeytabFile.exists()) {
  93. LOG.error("userKeytabFile(" + userKeytabFile.getAbsolutePath()
  94. + ") does not exsit.");
  95. throw new IOException("userKeytabFile("
  96. + userKeytabFile.getAbsolutePath() + ") does not exsit.");
  97. }
  98. if (!userKeytabFile.isFile()) {
  99. LOG.error("userKeytabFile(" + userKeytabFile.getAbsolutePath()
  100. + ") is not a file.");
  101. throw new IOException("userKeytabFile("
  102. + userKeytabFile.getAbsolutePath() + ") is not a file.");
  103. }
  104. File krb5ConfFile = new File(krb5ConfPath);
  105. if (!krb5ConfFile.exists()) {
  106. LOG.error("krb5ConfFile(" + krb5ConfFile.getAbsolutePath()
  107. + ") does not exsit.");
  108. throw new IOException("krb5ConfFile("
  109. + krb5ConfFile.getAbsolutePath() + ") does not exsit.");
  110. }
  111. if (!krb5ConfFile.isFile()) {
  112. LOG.error("krb5ConfFile(" + krb5ConfFile.getAbsolutePath()
  113. + ") is not a file.");
  114. throw new IOException("krb5ConfFile("
  115. + krb5ConfFile.getAbsolutePath() + ") is not a file.");
  116. }
  117. // 3.set and check krb5config
  118. setKrb5Config(krb5ConfFile.getAbsolutePath());
  119. setConfiguration(conf);
  120. // 4.login and check for hadoop
  121. loginHadoop(userPrincipal, userKeytabFile.getAbsolutePath());
  122. LOG.info("Login success!!!!!!!!!!!!!!");
  123. }
  124. private static void setConfiguration(Configuration conf) throws IOException {
  125. UserGroupInformation.setConfiguration(conf);
  126. }
  127. private static boolean checkNeedLogin(String principal) throws IOException {
  128. if (!UserGroupInformation.isSecurityEnabled()) {
  129. LOG.error("UserGroupInformation is not SecurityEnabled, please check if core-site.xml exists in classpath.");
  130. throw new IOException(
  131. "UserGroupInformation is not SecurityEnabled, please check if core-site.xml exists in classpath.");
  132. }
  133. UserGroupInformation currentUser = UserGroupInformation
  134. .getCurrentUser();
  135. if ((currentUser != null) && (currentUser.hasKerberosCredentials())) {
  136. if (checkCurrentUserCorrect(principal)) {
  137. LOG.info("current user is " + currentUser + "has logined.");
  138. if (!currentUser.isFromKeytab()) {
  139. LOG.error("current user is not from keytab.");
  140. throw new IOException("current user is not from keytab.");
  141. }
  142. return false;
  143. } else {
  144. LOG.error("current user is "
  145. + currentUser
  146. + "has logined. please check your enviroment , especially when it used IBM JDK or kerberos for OS count login!!");
  147. throw new IOException("current user is " + currentUser
  148. + " has logined. And please check your enviroment!!");
  149. }
  150. }
  151. return true;
  152. }
  153. public static void setKrb5Config(String krb5ConfFile) throws IOException {
  154. System.setProperty(JAVA_SECURITY_KRB5_CONF_KEY, krb5ConfFile);
  155. String ret = System.getProperty(JAVA_SECURITY_KRB5_CONF_KEY);
  156. if (ret == null) {
  157. LOG.error(JAVA_SECURITY_KRB5_CONF_KEY + " is null.");
  158. throw new IOException(JAVA_SECURITY_KRB5_CONF_KEY + " is null.");
  159. }
  160. if (!ret.equals(krb5ConfFile)) {
  161. LOG.error(JAVA_SECURITY_KRB5_CONF_KEY + " is " + ret + " is not "
  162. + krb5ConfFile + ".");
  163. throw new IOException(JAVA_SECURITY_KRB5_CONF_KEY + " is " + ret
  164. + " is not " + krb5ConfFile + ".");
  165. }
  166. }
  167. public static void setJaasFile(String principal, String keytabPath)
  168. throws IOException {
  169. String jaasPath = new File(System.getProperty("java.io.tmpdir"))
  170. + File.separator + System.getProperty("user.name")
  171. + JAAS_POSTFIX;
  172. // windows路径下分隔符替换
  173. jaasPath = jaasPath.replace("\\", "\\\\");
  174. keytabPath = keytabPath.replace("\\", "\\\\");
  175. // 删除jaas文件
  176. deleteJaasFile(jaasPath);
  177. writeJaasFile(jaasPath, principal, keytabPath);
  178. System.setProperty(JAVA_SECURITY_LOGIN_CONF_KEY, jaasPath);
  179. }
  180. private static void writeJaasFile(String jaasPath, String principal,
  181. String keytabPath) throws IOException {
  182. FileWriter writer = new FileWriter(new File(jaasPath));
  183. try {
  184. writer.write(getJaasConfContext(principal, keytabPath));
  185. writer.flush();
  186. } catch (IOException e) {
  187. throw new IOException("Failed to create jaas.conf File");
  188. } finally {
  189. writer.close();
  190. }
  191. }
  192. private static void deleteJaasFile(String jaasPath) throws IOException {
  193. File jaasFile = new File(jaasPath);
  194. if (jaasFile.exists()) {
  195. if (!jaasFile.delete()) {
  196. throw new IOException("Failed to delete exists jaas file.");
  197. }
  198. }
  199. }
  200. private static String getJaasConfContext(String principal, String keytabPath) {
  201. Module[] allModule = Module.values();
  202. StringBuilder builder = new StringBuilder();
  203. for (Module modlue : allModule) {
  204. builder.append(getModuleContext(principal, keytabPath, modlue));
  205. }
  206. return builder.toString();
  207. }
  208. private static String getModuleContext(String userPrincipal,
  209. String keyTabPath, Module module) {
  210. StringBuilder builder = new StringBuilder();
  211. if (IS_IBM_JDK) {
  212. builder.append(module.getName()).append(" {")
  213. .append(LINE_SEPARATOR);
  214. builder.append(IBM_LOGIN_MODULE).append(LINE_SEPARATOR);
  215. builder.append("credsType=both").append(LINE_SEPARATOR);
  216. builder.append("principal=\"" + userPrincipal + "\"").append(
  217. LINE_SEPARATOR);
  218. builder.append("useKeytab=\"" + keyTabPath + "\"").append(
  219. LINE_SEPARATOR);
  220. builder.append("debug=true;").append(LINE_SEPARATOR);
  221. builder.append("};").append(LINE_SEPARATOR);
  222. } else {
  223. builder.append(module.getName()).append(" {")
  224. .append(LINE_SEPARATOR);
  225. builder.append(SUN_LOGIN_MODULE).append(LINE_SEPARATOR);
  226. builder.append("useKeyTab=true").append(LINE_SEPARATOR);
  227. builder.append("keyTab=\"" + keyTabPath + "\"").append(
  228. LINE_SEPARATOR);
  229. builder.append("principal=\"" + userPrincipal + "\"").append(
  230. LINE_SEPARATOR);
  231. builder.append("useTicketCache=false").append(LINE_SEPARATOR);
  232. builder.append("storeKey=true").append(LINE_SEPARATOR);
  233. builder.append("debug=true;").append(LINE_SEPARATOR);
  234. builder.append("};").append(LINE_SEPARATOR);
  235. }
  236. return builder.toString();
  237. }
  238. public static void setJaasConf(String loginContextName, String principal,
  239. String keytabFile) throws IOException {
  240. if ((loginContextName == null) || (loginContextName.length() <= 0)) {
  241. LOG.error("input loginContextName is invalid.");
  242. throw new IOException("input loginContextName is invalid.");
  243. }
  244. if ((principal == null) || (principal.length() <= 0)) {
  245. LOG.error("input principal is invalid.");
  246. throw new IOException("input principal is invalid.");
  247. }
  248. if ((keytabFile == null) || (keytabFile.length() <= 0)) {
  249. LOG.error("input keytabFile is invalid.");
  250. throw new IOException("input keytabFile is invalid.");
  251. }
  252. File userKeytabFile = new File(keytabFile);
  253. if (!userKeytabFile.exists()) {
  254. LOG.error("userKeytabFile(" + userKeytabFile.getAbsolutePath()
  255. + ") does not exsit.");
  256. throw new IOException("userKeytabFile("
  257. + userKeytabFile.getAbsolutePath() + ") does not exsit.");
  258. }
  259. javax.security.auth.login.Configuration
  260. .setConfiguration(new JaasConfiguration(loginContextName,
  261. principal, userKeytabFile.getAbsolutePath()));
  262. javax.security.auth.login.Configuration conf = javax.security.auth.login.Configuration
  263. .getConfiguration();
  264. if (!(conf instanceof JaasConfiguration)) {
  265. LOG.error("javax.security.auth.login.Configuration is not JaasConfiguration.");
  266. throw new IOException(
  267. "javax.security.auth.login.Configuration is not JaasConfiguration.");
  268. }
  269. AppConfigurationEntry[] entrys = conf
  270. .getAppConfigurationEntry(loginContextName);
  271. if (entrys == null) {
  272. LOG.error("javax.security.auth.login.Configuration has no AppConfigurationEntry named "
  273. + loginContextName + ".");
  274. throw new IOException(
  275. "javax.security.auth.login.Configuration has no AppConfigurationEntry named "
  276. + loginContextName + ".");
  277. }
  278. boolean checkPrincipal = false;
  279. for (int i = 0; i < entrys.length; i++) {
  280. if (entrys[i].getOptions().get("principal").equals(principal)) {
  281. checkPrincipal = true;
  282. }
  283. }
  284. if (!checkPrincipal) {
  285. LOG.error("AppConfigurationEntry named " + loginContextName
  286. + " does not have principal value of " + principal + ".");
  287. throw new IOException("AppConfigurationEntry named "
  288. + loginContextName + " does not have principal value of "
  289. + principal + ".");
  290. }
  291. }
  292. public static void setZookeeperServerPrincipal(String zkServerPrincipal)
  293. throws IOException {
  294. System.setProperty(ZOOKEEPER_SERVER_PRINCIPAL_KEY, zkServerPrincipal);
  295. String ret = System.getProperty(ZOOKEEPER_SERVER_PRINCIPAL_KEY);
  296. if (ret == null) {
  297. LOG.error(ZOOKEEPER_SERVER_PRINCIPAL_KEY + " is null.");
  298. throw new IOException(ZOOKEEPER_SERVER_PRINCIPAL_KEY + " is null.");
  299. }
  300. if (!ret.equals(zkServerPrincipal)) {
  301. LOG.error(ZOOKEEPER_SERVER_PRINCIPAL_KEY + " is " + ret
  302. + " is not " + zkServerPrincipal + ".");
  303. throw new IOException(ZOOKEEPER_SERVER_PRINCIPAL_KEY + " is " + ret
  304. + " is not " + zkServerPrincipal + ".");
  305. }
  306. }
  307. public static void setZookeeperServerPrincipal(String zkServerPrincipalKey,
  308. String zkServerPrincipal) throws IOException {
  309. System.setProperty(zkServerPrincipalKey, zkServerPrincipal);
  310. String ret = System.getProperty(zkServerPrincipalKey);
  311. if (ret == null) {
  312. LOG.error(zkServerPrincipalKey + " is null.");
  313. throw new IOException(zkServerPrincipalKey + " is null.");
  314. }
  315. if (!ret.equals(zkServerPrincipal)) {
  316. LOG.error(zkServerPrincipalKey + " is " + ret + " is not "
  317. + zkServerPrincipal + ".");
  318. throw new IOException(zkServerPrincipalKey + " is " + ret
  319. + " is not " + zkServerPrincipal + ".");
  320. }
  321. }
  322. private static void loginHadoop(String principal, String keytabFile)
  323. throws IOException {
  324. try {
  325. UserGroupInformation.loginUserFromKeytab(principal, keytabFile);
  326. } catch (IOException e) {
  327. LOG.error("login failed with " + principal + " and " + keytabFile
  328. + ".");
  329. LOG.error("perhaps cause 1 is " + LOGIN_FAILED_CAUSE_PASSWORD_WRONG
  330. + ".");
  331. LOG.error("perhaps cause 2 is " + LOGIN_FAILED_CAUSE_TIME_WRONG
  332. + ".");
  333. LOG.error("perhaps cause 3 is " + LOGIN_FAILED_CAUSE_AES256_WRONG
  334. + ".");
  335. LOG.error("perhaps cause 4 is "
  336. + LOGIN_FAILED_CAUSE_PRINCIPAL_WRONG + ".");
  337. LOG.error("perhaps cause 5 is " + LOGIN_FAILED_CAUSE_TIME_OUT + ".");
  338. throw e;
  339. }
  340. }
  341. private static void checkAuthenticateOverKrb() throws IOException {
  342. UserGroupInformation loginUser = UserGroupInformation.getLoginUser();
  343. UserGroupInformation currentUser = UserGroupInformation
  344. .getCurrentUser();
  345. if (loginUser == null) {
  346. LOG.error("current user is " + currentUser
  347. + ", but loginUser is null.");
  348. throw new IOException("current user is " + currentUser
  349. + ", but loginUser is null.");
  350. }
  351. if (!loginUser.equals(currentUser)) {
  352. LOG.error("current user is " + currentUser + ", but loginUser is "
  353. + loginUser + ".");
  354. throw new IOException("current user is " + currentUser
  355. + ", but loginUser is " + loginUser + ".");
  356. }
  357. if (!loginUser.hasKerberosCredentials()) {
  358. LOG.error("current user is " + currentUser
  359. + " has no Kerberos Credentials.");
  360. throw new IOException("current user is " + currentUser
  361. + " has no Kerberos Credentials.");
  362. }
  363. if (!UserGroupInformation.isLoginKeytabBased()) {
  364. LOG.error("current user is " + currentUser
  365. + " is not Login Keytab Based.");
  366. throw new IOException("current user is " + currentUser
  367. + " is not Login Keytab Based.");
  368. }
  369. }
  370. private static boolean checkCurrentUserCorrect(String principal)
  371. throws IOException {
  372. UserGroupInformation ugi = UserGroupInformation.getCurrentUser();
  373. if (ugi == null) {
  374. LOG.error("current user still null.");
  375. throw new IOException("current user still null.");
  376. }
  377. String defaultRealm = null;
  378. try {
  379. defaultRealm = KerberosUtil.getDefaultRealm();
  380. } catch (Exception e) {
  381. LOG.warn("getDefaultRealm failed.");
  382. throw new IOException(e);
  383. }
  384. if ((defaultRealm != null) && (defaultRealm.length() > 0)) {
  385. StringBuilder realm = new StringBuilder();
  386. StringBuilder principalWithRealm = new StringBuilder();
  387. realm.append("@").append(defaultRealm);
  388. if (!principal.endsWith(realm.toString())) {
  389. principalWithRealm.append(principal).append(realm);
  390. principal = principalWithRealm.toString();
  391. }
  392. }
  393. return principal.equals(ugi.getUserName());
  394. }
  395. /**
  396. * copy from hbase zkutil 0.94&0.98 A JAAS configuration that defines the
  397. * login modules that we want to use for login.
  398. */
  399. private static class JaasConfiguration extends
  400. javax.security.auth.login.Configuration {
  401. private static final Map<String, String> BASIC_JAAS_OPTIONS = new HashMap<String, String>();
  402. static {
  403. String jaasEnvVar = System.getenv("HBASE_JAAS_DEBUG");
  404. if (jaasEnvVar != null && "true".equalsIgnoreCase(jaasEnvVar)) {
  405. BASIC_JAAS_OPTIONS.put("debug", "true");
  406. }
  407. }
  408. private static final Map<String, String> KEYTAB_KERBEROS_OPTIONS = new HashMap<String, String>();
  409. static {
  410. if (IS_IBM_JDK) {
  411. KEYTAB_KERBEROS_OPTIONS.put("credsType", "both");
  412. } else {
  413. KEYTAB_KERBEROS_OPTIONS.put("useKeyTab", "true");
  414. KEYTAB_KERBEROS_OPTIONS.put("useTicketCache", "false");
  415. KEYTAB_KERBEROS_OPTIONS.put("doNotPrompt", "true");
  416. KEYTAB_KERBEROS_OPTIONS.put("storeKey", "true");
  417. }
  418. KEYTAB_KERBEROS_OPTIONS.putAll(BASIC_JAAS_OPTIONS);
  419. }
  420. private static final AppConfigurationEntry KEYTAB_KERBEROS_LOGIN = new AppConfigurationEntry(
  421. KerberosUtil.getKrb5LoginModuleName(),
  422. LoginModuleControlFlag.REQUIRED, KEYTAB_KERBEROS_OPTIONS);
  423. private static final AppConfigurationEntry[] KEYTAB_KERBEROS_CONF = new AppConfigurationEntry[] { KEYTAB_KERBEROS_LOGIN };
  424. private javax.security.auth.login.Configuration baseConfig;
  425. private final String loginContextName;
  426. private final boolean useTicketCache;
  427. private final String keytabFile;
  428. private final String principal;
  429. public JaasConfiguration(String loginContextName, String principal,
  430. String keytabFile) throws IOException {
  431. this(loginContextName, principal, keytabFile, keytabFile == null
  432. || keytabFile.length() == 0);
  433. }
  434. private JaasConfiguration(String loginContextName, String principal,
  435. String keytabFile, boolean useTicketCache) throws IOException {
  436. try {
  437. this.baseConfig = javax.security.auth.login.Configuration
  438. .getConfiguration();
  439. } catch (SecurityException e) {
  440. this.baseConfig = null;
  441. }
  442. this.loginContextName = loginContextName;
  443. this.useTicketCache = useTicketCache;
  444. this.keytabFile = keytabFile;
  445. this.principal = principal;
  446. initKerberosOption();
  447. LOG.info("JaasConfiguration loginContextName=" + loginContextName
  448. + " principal=" + principal + " useTicketCache="
  449. + useTicketCache + " keytabFile=" + keytabFile);
  450. }
  451. private void initKerberosOption() throws IOException {
  452. if (!useTicketCache) {
  453. if (IS_IBM_JDK) {
  454. KEYTAB_KERBEROS_OPTIONS.put("useKeytab", keytabFile);
  455. } else {
  456. KEYTAB_KERBEROS_OPTIONS.put("keyTab", keytabFile);
  457. KEYTAB_KERBEROS_OPTIONS.put("useKeyTab", "true");
  458. KEYTAB_KERBEROS_OPTIONS.put("useTicketCache",
  459. useTicketCache ? "true" : "false");
  460. }
  461. }
  462. KEYTAB_KERBEROS_OPTIONS.put("principal", principal);
  463. }
  464. @Override
  465. public AppConfigurationEntry[] getAppConfigurationEntry(String appName) {
  466. if (loginContextName.equals(appName)) {
  467. return KEYTAB_KERBEROS_CONF;
  468. }
  469. if (baseConfig != null) {
  470. return baseConfig.getAppConfigurationEntry(appName);
  471. }
  472. return (null);
  473. }
  474. }
  475. }