Java安全之認證與授權

Java安全之認證與授權

Java平臺提供的認證與授權服務(Java Authentication and Authorization Service (JAAS)),能夠控制代碼對敏感或關鍵資源的訪問,例如文件系統,網絡服務,系統屬性訪問等,加強代碼的安全性。主要包含認證與授權兩部分,認證的目的在於可靠安全地確定當前是誰在執行代碼,代碼可以是一個應用,applet,bean,servlet;授權的目的在於確定了當前執行代碼的用戶有什麼權限,資源是否可以進行訪問。雖然JAAS表面上分爲了兩大部分,而實際上兩者是密不可分的,下面看一段代碼:

public class App {  

    public static void main(String[] args) {  
        System.out.println(System.getProperty("java.home"));  
    }  
} 

非常簡單只是輸出java.home系統屬性,現在肯定是沒有任何問題,屬性會能正常輸出。把上述代碼改爲如下後:

public class App {  

    public static void main(String[] args) {  
        //安裝安全管理器  
        System.setSecurityManager(new SecurityManager());  

        System.out.println(System.getProperty("java.home"));  
    }  
}  

拋出瞭如下異常:java.security.AccessControlException: access denied (“java.util.PropertyPermission” “java.home” “read”),異常提示沒有對java.home的讀取權限,系統屬性也是一種資源,與文件訪問類似;默認情況下對於普通Java應用是沒有安裝安全管理器,在手動安裝安全管理器後,如果沒有爲應用授權則沒有任何權限,所以應用無法訪問java.home系統屬性。

授權的方式是爲安全管理器綁定一個授權策略文件。由於我是在eclipse Java工程中直接運行main方法,所以就在工程根目錄下新建一個demo.policy文件,文件內容如下:

grant  {  
    permission java.util.PropertyPermission "java.home", "read";  
};

該授權的效果是任何用戶運行的任何程序都有對java.home的讀權限,policy文件的具體格式請參看:http://docs.oracle.com/javase/7/docs/technotes/guides/security/PolicyFiles.html
爲安全管理器綁定policy文件的方式有兩種:一、在運行程序的時候加入-Djava.security.policy=demo.policy虛擬機啓動參數;二、執行System.setProperty(“java.security.policy”, “demo.policy”);其實兩者的效果一樣,都是在設置系統屬性,其中demo.policy是路徑,這裏爲了簡單指定的是相對路徑,絕對路徑當然也沒問題。再次運行程序不再拋出異常,說明程序擁有了對java.home系統屬性的讀取權限。在Java中權限有很多,具體可參考:http://docs.oracle.com/javase/7/docs/technotes/guides/security/spec/security-spec.doc3.html#17001

在上述過程中雖然完成了授權,但授權的針對性不強,在程序綁定了該policy文件後,無論是哪個用戶執行都將擁有java.home系統屬性的讀權限,現在我們希望做更加細粒度的權限控制,這裏需要用到認證服務了。

認證服務有點“麻煩”,一般情況下主要都涉及到了LoginContext,LoginModule,CallbackHandler,Principal,後三者還需要開發者自己實現。這裏先解釋一下這幾個類的作用:
1.LoginContext:認證核心類,也是入口類,用於觸發登錄認證,具體的登錄模塊由構造方法name參數指定
2.LoginModule:登錄模塊,封裝具體的登錄認證邏輯,如果認證失敗則拋出異常,成爲則向Subject中添加一個Principal
3.CallbackHandler:回調處理器,用於蒐集認證信息
4.Principal:代表程序用戶的某一身份,與其密切相關的爲Subject,用於代表程序用戶,而一個用戶可以多種身份,授權時可以針對某用戶的多個身份分別授權

下面看一個認證例子:

package com.xtayfjpk.security.jaas.demo;  

import javax.security.auth.login.LoginContext;  
import javax.security.auth.login.LoginException;  

public class App {  

    public static void main(String[] args) {  
        System.setProperty("java.security.auth.login.config", "demo.config");  
        System.setProperty("java.security.policy", "demo.policy");  
        System.setSecurityManager(new SecurityManager());  

        try {  
            //創建登錄上下文  
            LoginContext context = new LoginContext("demo", new DemoCallbackHander());  
            //進行登錄,登錄不成功則系統退出  
            context.login();  
        } catch (LoginException le) {  
            System.err.println("Cannot create LoginContext. " + le.getMessage());  
            System.exit(-1);  
        } catch (SecurityException se) {  
            System.err.println("Cannot create LoginContext. " + se.getMessage());  
            System.exit(-1);  
        }  


        //訪問資源  
        System.out.println(System.getProperty("java.home"));  
    }  
}  
package com.xtayfjpk.security.jaas.demo;  

import java.io.IOException;  
import java.security.Principal;  
import java.util.Iterator;  
import java.util.Map;  

import javax.security.auth.Subject;  
import javax.security.auth.callback.Callback;  
import javax.security.auth.callback.CallbackHandler;  
import javax.security.auth.callback.NameCallback;  
import javax.security.auth.callback.PasswordCallback;  
import javax.security.auth.callback.UnsupportedCallbackException;  
import javax.security.auth.login.FailedLoginException;  
import javax.security.auth.login.LoginException;  
import javax.security.auth.spi.LoginModule;  

public class DemoLoginModule implements LoginModule {  
    private Subject subject;  
    private CallbackHandler callbackHandler;  
    private boolean success = false;  
    private String user;  
    private String password;  

    @Override  
    public void initialize(Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState, Map<String, ?> options) {  
        this.subject = subject;  
        this.callbackHandler = callbackHandler;  
    }  

    @Override  
    public boolean login() throws LoginException {  
        NameCallback nameCallback = new NameCallback("請輸入用戶名");  
        PasswordCallback passwordCallback = new PasswordCallback("請輸入密碼", false);  
        Callback[] callbacks = new Callback[]{nameCallback, passwordCallback};  
        try {  
            //執行回調,回調過程中獲取用戶名與密碼  
            callbackHandler.handle(callbacks);  
            //得到用戶名與密碼  
            user = nameCallback.getName();  
            password = new String(passwordCallback.getPassword());  
        } catch (IOException | UnsupportedCallbackException e) {  
            success = false;  
            throw new FailedLoginException("用戶名或密碼獲取失敗");  
        }  
        //爲簡單起見認證條件寫死  
        if(user.length()>3 && password.length()>3) {  
            success = true;//認證成功  
        }  
        return true;  
    }  

    @Override  
    public boolean commit() throws LoginException {  
        if(!success) {  
            return false;  
        } else {  
            //如果認證成功則得subject中添加一個Principal對象  
            //這樣某身份用戶就認證通過並登錄了該應用,即表明了誰在執行該程序  
            this.subject.getPrincipals().add(new DemoPrincipal(user));  
            return true;  
        }  
    }  

    @Override  
    public boolean abort() throws LoginException {  
        logout();  
        return true;  
    }  

    @Override  
    public boolean logout() throws LoginException {  
        //退出時將相應的Principal對象從subject中移除  
        Iterator<Principal> iter = subject.getPrincipals().iterator();  
        while(iter.hasNext()) {  
            Principal principal = iter.next();  
            if(principal instanceof DemoPrincipal) {  
                if(principal.getName().equals(user)) {  
                    iter.remove();  
                    break;  
                }  
            }  
        }  
        return true;  
    }  

} 
package com.xtayfjpk.security.jaas.demo;  

import java.io.IOException;  

import javax.security.auth.callback.Callback;  
import javax.security.auth.callback.CallbackHandler;  
import javax.security.auth.callback.NameCallback;  
import javax.security.auth.callback.PasswordCallback;  
import javax.security.auth.callback.UnsupportedCallbackException;  

public class DemoCallbackHander implements CallbackHandler {  

    @Override  
    public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {  
        NameCallback nameCallback = (NameCallback) callbacks[0];  
        PasswordCallback passwordCallback = (PasswordCallback) callbacks[1];  
        //設置用戶名與密碼  
        nameCallback.setName(getUserFromSomeWhere());  
        passwordCallback.setPassword(getPasswordFromSomeWhere().toCharArray());  
    }  


    //爲簡單起見用戶名與密碼寫死直接返回,真實情況可以由用戶輸入等具體獲取  
    public String getUserFromSomeWhere() {  
        return "zhangsan";  
    }  
    public String getPasswordFromSomeWhere() {  
        return "zhangsan";  
    }  
} 
package com.xtayfjpk.security.jaas.demo;  

import java.security.Principal;  

public class DemoPrincipal implements Principal {  
    private String name;  

    public DemoPrincipal(String name) {  
        this.name = name;  
    }  

    @Override  
    public String getName() {  
        return this.name;  
    }  

}  

使用認證服務時,需要綁定一個認證配置文件,在例子中通過System.setProperty(“java.security.auth.login.config”, “demo.config”);實現,當然也可以設置虛擬屬性-Djava.security.auth.login.config=demo.config實現。配置文件內容如下:

demo {  
   com.xtayfjpk.security.jaas.demo.DemoLoginModule required debug=true;  
}; 

其中demo爲配置名稱,其內容指定了需要使用到哪登錄模塊(LoginModule),認證配置文件具體格式請參看:http://docs.oracle.com/javase/6/docs/technotes/guides/security/jgss/tutorials/LoginConfigFile.html

前面說到認證與授權密不可分,這裏就可以說明,在創建LoginContext對象時就需要有createLoginContext.demo的認證權限,demo就是認證配置文件中的配置名稱,該名稱在構造LoginContext對象時指定。由於在DemoLoginModule中修改了Subject的principals集合,還需要有modifyPrincipals認證權限,所以授權策略文件內容變爲:

grant  {  
    permission javax.security.auth.AuthPermission "createLoginContext.demo";  
    permission javax.security.auth.AuthPermission "modifyPrincipals";  
    permission java.util.PropertyPermission "java.home", "read";  
}; 

再次運行程序,java.home系統屬性正常輸出,但此時我們還是沒有針對某特定用戶身份進行授權,這個就需要在授權文件中配置Principal,現在將授權文件改寫爲:

grant principal com.xtayfjpk.security.jaas.demo.DemoPrincipal "zhangsan"{  
    permission java.util.PropertyPermission "java.home", "read";  
};  
grant {  
    permission javax.security.auth.AuthPermission "createLoginContext.demo";  
    permission javax.security.auth.AuthPermission "modifyPrincipals";  
    permission javax.security.auth.AuthPermission "doAsPrivileged";  
};  

這就意味着只有以名爲zhangsan的DemoPrincipal登錄應用纔會擁有java.home系統屬性的讀權限,此時讀取java.home的代碼需要做一定的修改,如下:

Subject subject = context.getSubject();  
//該方法調用需要"doAsPrivileged"權限  
Subject.doAsPrivileged(subject, new PrivilegedAction<Object>() {  
    @Override  
    public Object run() {  
        System.out.println(System.getProperty("java.home"));  
        return null;  
    }  
}, null);  

因爲在Subject中才有Principal信息,這樣就可以針對每一種用戶身份制定一套權限方案。

發佈了61 篇原創文章 · 獲贊 7 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章