HBase分析之用戶機制

HBase的用戶機制和Hadoop的用戶機制是一樣的。但對剛接觸的人來說,相當的隱蔽,啟動HBase不用設置用戶名、密碼,連接HBase也不需要設置用戶名、密碼。但HBase(實質上是Hadoop)提供了默認的用戶來執行操作。

  1. 超級用戶
    如果沒有特意配置,那么HBase會選擇啟動HBase的系統用戶作為超級用戶。如果需要改變超級用戶,可通過修改hbase-site.xml來配置,加入hbase.superuser。
<property>
  <name>hbase.superuser</name>
  <value>admin</value>
</property>
  1. 默認用戶
    默認用戶也類似,在沒有特意配置時,HBase會選擇當前的系統用戶作為HBase的用戶,改變默認用戶隱藏的比較深,我們從代碼來看。

分析源碼

在創建Connection時,會判斷是否已經創建了用戶,如果沒有,會調用LoginContext的login()方法來創建。中間的調用就直接跳過了,想詳細看的可以參照以下堆棧信息。

在login方法中,按順序反射調用了LOGIN_METHOD(login())和COMMIT_METHOD(commit()),中間啰嗦的代碼就...跳過了,抓住重點看:

public void login() throws LoginException {
    ...
    try {
        // 分別反射調用了login和commit方法
        invokePriv(LOGIN_METHOD);
        invokePriv(COMMIT_METHOD);
        ...
    } catch (LoginException le) {
        ...
    }
}

invokePriv方法是invoke方法的帶權限執行,主要看invoke方法。在invoke方法中,遍歷module stack中的元素,對里面的每個元素反射執行login和commit方法。Module Stack中有兩個元素,UnixLoginModuleUserGroupInformation$HadoopLoginModule。實際執行的順序就是:

  1. UnixLoginModule#login
  2. UserGroupInformation$HadoopLoginModule#login
  3. UnixLoginModule#commit
  4. UserGroupInformation$HadoopLoginModule#commit
private void invoke(String methodName) throws LoginException {
    for (int i = moduleIndex; i < moduleStack.length; i++, moduleIndex++) {
        try {
            int mIndex = 0;
            Method[] methods = null;
            // 獲取login module的methods
            if (moduleStack[i].module != null) {
                methods = moduleStack[i].module.getClass().getMethods();
            } else {
                // 如果login module還沒創建,就反射創建一個,再獲取login module的methods
                ...
            }

            // 遍歷找到對應的方法
            for (mIndex = 0; mIndex < methods.length; mIndex++) {
                if (methods[mIndex].getName().equals(methodName)) {
                    break;
                }
            }

            // 主要就是這里,反射調用了方法名為方法參數methodName的無參方法
            Object[] args = { };
            boolean status = ((Boolean)methods[mIndex].invoke
                            (moduleStack[i].module, args)).booleanValue();

            if (status == true) {
                // 成功后的處理
                ...
            } else {
                // 失敗了的處理
                ...
            }
        } catch (Exception e) {
            // 各種Exception處理
            ...
        }
    }

    // 收尾工作,處理Error,清空狀態
    ...
}
  1. UnixLoginModule的login方法從系統中獲取到了用戶的登錄信息
public boolean login() throws LoginException {
    ...
    ss = new UnixSystem();
    if (ss == null) {
        ...
    } else {
        userPrincipal = new UnixPrincipal(ss.getUsername());
        ...
        return true;
    }
}
  1. UserGroupInformation$HadoopLoginModule的login是空方法,只return了true
public boolean login() throws LoginException {
    if(UserGroupInformation.LOG.isDebugEnabled()) {
        UserGroupInformation.LOG.debug("hadoop login");
    }

    return true;
}
  1. UnixLoginModule的commit方法把獲取到的登錄信息寫到了subject里
public boolean commit() throws LoginException {
    if (succeeded == false) {
        ...
        return false;
    } else {
        if (subject.isReadOnly()) {
            throw new LoginException
                ("commit Failed: Subject is Readonly");
        }
        // 把用戶名塞進subject
        if (!subject.getPrincipals().contains(userPrincipal))
            subject.getPrincipals().add(userPrincipal);
        // 把其他參數塞進subject
        ...
        commitSucceeded = true;
        return true;
    }
}

寫完之后subject里是這樣的,多了用戶和組的信息。

  1. UserGroupInformation$HadoopLoginModule的commit方法,分3種情況來獲取用戶。有KERBEROS,取KERBEROS的用戶信息;有HADOOP_USER_NAME,取HADOOP_USER_NAME的用戶信息;都沒有,就取Unix/Linux系統的用戶信息,就是第3步commit到subject中的用戶信息。
public boolean commit() throws LoginException {
    if(!this.subject.getPrincipals(User.class).isEmpty()) {
        return true;
    } else {
        Principal user = null;

        // 如果啟用了KERBEROS
        if(UserGroupInformation.isAuthenticationMethodEnabled(
              UserGroupInformation.AuthenticationMethod.KERBEROS)) {
            user = this.getCanonicalUser(KerberosPrincipal.class);
        }


        if(!UserGroupInformation.isSecurityEnabled() && user == null) {
            // 從系統環境變量里找HADOOP_USER_NAME
            String envUser = System.getenv("HADOOP_USER_NAME");
            if(envUser == null) {
                // 從Java變量里找HADOOP_USER_NAME
                envUser = System.getProperty("HADOOP_USER_NAME");
            }

            user = envUser == null?null:new User(envUser);
        }

        // 實在找不到了,就用系統的用戶信息
        if(user == null) {
            user = this.getCanonicalUser(UserGroupInformation.OS_PRINCIPAL_CLASS);
            ...
        }

        // 把User實例塞進subject
        if(user != null) {
            this.subject.getPrincipals().add(new User(((Principal)user).getName()));
            return true;
        } else {
            ...
        }
    }
}

Commit執行完以后,User實例就創建完成了,可以看到User實例中只有name。

修改用戶

知道了HBase是如何獲取用戶信息的,就可以相應的改變用戶了。
根據UserGroupInformation$HadoopLoginModule的commit中獲取用戶的3種方法,就可分3種情況修改用戶:

  • KERBEROS
    改變KERBEROS用戶(運維比較復雜,不在考慮范圍)。

  • 系統用戶
    通過切換操作系統的用戶來完成。

  • HADOOP_USER_NAME
    通過設置System環境變量改變用戶,需要重啟進程才會生效。

    export HADOOP_USER_NAME=admin
    

    通過設置System Properties改變用戶,需要在Connection創建之前設置,這里的System指的是JavaVM。

    System.getProperties().setProperty("HADOOP_USER_NAME", "admin");
    

舉例來說,masa用戶是沒有權限的,admin用戶是有權限的。使用默認用戶masa訪問集群,執行這段代碼時,拋出了Exception,原因是沒有權限。

Configuration configuration = HBaseConfiguration.create();
HTable table = new HTable(configuration, TableName.valueOf("masa_test"));
ResultScanner scanner = table.getScanner(new Scan());
System.out.println("get scanner " + scanner);
Exception in thread "main" org.apache.hadoop.hbase.security.AccessDeniedException: org.apache.hadoop.hbase.security.AccessDeniedException: Insufficient permissions for user ‘masa',action: scannerOpen, tableName:liehutest, family:f.
at org.apache.ranger.authorization.hbase.RangerAuthorizationCoprocessor.authorizeAccess(RangerAuthorizationCoprocessor.java:525)
at org.apache.ranger.authorization.hbase.RangerAuthorizationCoprocessor.preScannerOpen(RangerAuthorizationCoprocessor.java:919)
at org.apache.ranger.authorization.hbase.RangerAuthorizationCoprocessor.preScannerOpen(RangerAuthorizationCoprocessor.java:854)
at org.apache.hadoop.hbase.regionserver.RegionCoprocessorHost$50.call(RegionCoprocessorHost.java:1284)
at org.apache.hadoop.hbase.regionserver.RegionCoprocessorHost$RegionOperation.call(RegionCoprocessorHost.java:1673)
at org.apache.hadoop.hbase.regionserver.RegionCoprocessorHost.execOperation(RegionCoprocessorHost.java:1748)
at org.apache.hadoop.hbase.regionserver.RegionCoprocessorHost.execOperationWithResult(RegionCoprocessorHost.java:1722)
at org.apache.hadoop.hbase.regionserver.RegionCoprocessorHost.preScannerOpen(RegionCoprocessorHost.java:1279)
at org.apache.hadoop.hbase.regionserver.RSRpcServices.scan(RSRpcServices.java:2252)
at org.apache.hadoop.hbase.protobuf.generated.ClientProtos$ClientService$2.callBlockingMethod(ClientProtos.java:32205)
at org.apache.hadoop.hbase.ipc.RpcServer.call(RpcServer.java:2114)
at org.apache.hadoop.hbase.ipc.CallRunner.run(CallRunner.java:101)
at org.apache.hadoop.hbase.ipc.RpcExecutor.consumerLoop(RpcExecutor.java:130)
at org.apache.hadoop.hbase.ipc.RpcExecutor$1.run(RpcExecutor.java:107)
at java.lang.Thread.run(Thread.java:745)

在任務執行之前設置用戶名,就可以執行成功了。

System.getProperties().setProperty("HADOOP_USER_NAME", "admin");
Configuration configuration = HBaseConfiguration.create();
HTable table = new HTable(configuration, TableName.valueOf("masa_test"));
ResultScanner scanner = table.getScanner(new Scan());
System.out.println("get scanner " + scanner);
get scanner org.apache.hadoop.hbase.client.ClientScanner@dd8ba08
Process finished with exit code 0

-END-

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,505評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,556評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,463評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,009評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,778評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,218評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,281評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,436評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,969評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,795評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,993評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,537評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,229評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,659評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,917評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,687評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,990評論 2 374

推薦閱讀更多精彩內容