安全:JAAS LoginModule(轉載)

Java認證和授權API(JAAS)爲應用程序處理用戶的認證和授權問題提供了標準方式。很多人在Unix/Linux系統中比較JAAS和PAM模塊。
本文不對JAAS進行詳細的討論,但給大家簡單的介紹一下。在JAAS架構中,當用戶登錄系統時,系統給用戶一個Subject,Subject中包含有一個或多個Principal。每個Principal給用戶提供身份驗證,例如用戶ID,也可以驗證組和角色。Subject也包含公有的和私有的許可證(credential),例如X.509證書、私鑰等。JAAS通過公開的Permission進行授權,Permission是在外部Policy 文件裏進行維護的。本文也不討論授權。就像下面所討論的,實際的登錄過程是由LoginModule來處理的。如果你想了解更多的信息,這裏有相關 JAAS的詳細介紹:http://www.javaworld.com/javaworld/jw-09-2002/jw-0913- jaas.html。
實現Hibernate JAAS LoginModule的第一步是定義一個或多個Principal。下面是一個典型的實例,但是記住你能把你想要的任何事物――用戶ID,E-mail 地址,電話號碼,公鑰――填進Principal中是很重要的,這些事物都能在持久化的User對象中發現。

final public class HibernatePrincipal implements Principal {
private String name;
public HibernatePrincipal() {
name = "";
}
public HibernatePrincipal(String name) {
this.name = name;
}
public String getName() {
return name;
}
public int hashCode() {
return name.hashCode();
}
public boolean equals(Object o) {
if (!(o instanceof HibernatePrincipal)) {
return false;
}
return name.equals(((HibernatePrincipal) o).name);
}
public String toString() {
return name;
}
}

N.B.,你必須小心防止惡意用戶通過繼承你的類來獲取他們所需要的許可證(credential)。解決上述問題的方法一是你可以聲明你的Principal是final,二是你總是檢查許可證(credential)是否是擴展類型。

public void foo(Principal p) {
// DO NOT USE THIS
if (p instanceof HibernatePrincipal) {
...
}
// use this instead
if (p.getClass().equals(HibernatePrincipal.class)) {
...
}
// or even this
if (p.getClass().getName().equals(HibernatePrincipal.getClass().getName()) {
...
}
}
}

本文也不詳細討論許可證文件(credential),但User屬性映射到Subject許可證是很容易的。例如,用戶名可以作爲公開許可證的一個合理候選。
既然有了Principal,我們可以爲JAAS LoginModule包裝一些標準的Hibernate代碼。
/**
* HibernateLoginModule is a LoginModule that authenticates
* a given username/password credential against a Hibernate
* session.
*
* @see javax.security.auth.spi.LoginModule
*/
public class HibernateLoginModule implements LoginModule {

// initial state
CallbackHandler handler;
Subject subject;
Map sharedState;
Map options;
Digest digest;

// temporary state
Vector principals;

// authentication status
boolean success;

// configurable options
boolean debug;

/** Hibernate session factory */
SessionFactory sf = null;

/** Hibernate query */
private static final String query =
"from u in class " + User.class + " where u.name=?";

public HibernateLoginModule() {
credentials = new Vector();
principals = new Vector();
success = false;
debug = false;
}

/**
* Initialize our state.
*/
public void initialize (Subject subject, CallbackHandler handler,
Map sharedState, Map options) {

this.handler = handler;
this.subject = subject;
this.sharedState = sharedState;
this.options = options;

if (options.containsKey("debug")) {
debug = "true".equalsIgnoreCase((String) options.get("debug"));
}
if (options.containsKey("digest")) {
digest = new Digest((String) options.get("digest"));
} else {
digest = new Digest();
}

// elided: standard code to get Hibernate =SessionFactory=.
}

/**
* First phase of login process.
*/
public boolean login() throws LoginException {
if (handler == null) {
throw new LoginException("Error: no CallbackHandler available");
}

try {
Callback[] callbacks = new Callback[] {
new NameCallback("User: "),
new PasswordCallback("Password: ", false)
};

handler.handle(callbacks);

String username = ((NameCallback) callbacks[0]).getName();
char[] password = ((PasswordCallback) callbacks[1]).getPassword();

((PasswordCallback) callbacks[1]).clearPassword();

success = validate(username, password);

callbacks[0] = null;
callbacks[1] = null;

if (!success) {
throw new LoginException("Authentication failed: Password does not match");
}
return true;
} catch (LoginException e) {
throw e;
} catch (Exception e) {
success = false;
throw new LoginException(e.getMessage());
}
}

/**
* Second phase of login - by now we know user is authenticated
* and we just need to update the subject.
*/
public boolean commit() throws LoginException {
if (success) {
if (subject.isReadOnly()) {
throw new LoginException("Subject is read-only");
}

try {
Iterator i = principals.iterator();
subject.getPrincipals().addAll(principals);
principals.clear();
return true;
} catch (Exception e) {
throw new LoginException(e.getMessage());
}
} else {
principals.clear();
}
return true;
}

/**
* Second phase - somebody else rejected user so we need to
* clear our state.
*/
public boolean abort() throws LoginException {
success = false;
logout();
return true;
}

/**
* User is logging out - clear our information from the subject.
*/
public boolean logout() throws LoginException {
principals.clear();

// remove the principals the login module added
Iterator i = subject.getPrincipals(HibernatePrincipal.class).iterator();
while (i.hasNext()) {
HibernatePrincipal p = (HibernatePrincipal) i.next();
subject.getPrincipals().remove(p);
}

return true;
}

/**
* Validate the user name and password. This is the Hibernate-specific
* code.
*/
private boolean validate(String username, char[] password) throws Exception {
boolean valid = false;
List users = null;

Session s = null;
try {
s = sf.openSession();
users = (List) s.find(query, username, Hibernate.STRING);
} catch (Exception e) {
} finally {
if (s != null) {
try { s.close(); } catch (HibernateException e) { }
}
}

// are there no matching records?...
if (users == null || users.size() == 0) {
return false;
}

// compare passwords...
User user = (User) users.get(0);
String hash = user.getPassword();
if (hash != null && password != null && password.length > 0) {
valid = hash.equals(digest.digest(new String(password)));
}

if (valid) {
this.principals.add(new HibernatePrincipal(user.getId(),
user.getName()));
}
return valid;
}
}
例子中,我們利用了Tomcat類庫中密碼digest功能(password digest function)(你要爲HexUtils類載入catalina.jar文件)。
import org.apache.catalina.util.HexUtils;

/**
* Quick and dirty password digest function. The HexUtils class
* comes from the Tomcat catalina.jar.
*/
public class Digest {

static MessageDigest md = null;

public Digest() {
this("MD5");
}

public Digest(String digest) {
try {
md = MessageDigest.getInstance(digest);
} catch (NoSuchAlgorithmException e) {
try {
md = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) { }
}
}

/**
* Digest function from Tomcat.
*/
public String digest(String credentials) {
if (md == null) {
return credentials;
}

synchronized (this) {
try {
md.reset();
md.update(credentials.getBytes());
return (HexUtils.convert(md.digest()));
} catch (Exception e) {
return credentials;
}
}
}
}
最後一步是爲我們的應用程序配置我們的Hibernate登錄模塊。我們先創建JAAS配置文件,然後通過java.security.auth.login.config參數傳給應用程序。在這個例子裏我們定義JAAS屬性“Example”。
Example {
HibernateLoginModule required debug="true";
};
現在通過我們Hibernate模塊可以認證任何基於JAAS的應用程序了。下面是簡單的測試程序:
/**
* simple CallbackHandler suitable for testing purposes
*/
public static class Handler implements CallbackHandler {

private Test t;
private String username;
private char[] credentials;

public Handler(Test t, String username, char[] credentials) {
super();
this.t = t;
this.username = username;
this.credentials = credentials;
}

public void handle(Callback callbacks[])
throws IOException, UnsupportedCallbackException {

for (int i = 0; i < callbacks.length; i++) {
if (callbacks[i] instanceof NameCallback) {
((NameCallback) callbacks[i]).setName(username);
}
else if (callbacks[i] instanceof PasswordCallback) {
((PasswordCallback) callbacks[i]).setPassword(credentials);
} else {
throw new UnsupportedCallbackException(callbacks[i]);
}
}
}
}
/**
* Simple JAAS-aware application.
*/
public class Test {

LoginContext l = null;

/**
* attempt to log in as the user, returning the =Subject=
* if successful.
*/
public Subject login(String username, char[] credentials) {
try {
CallbackHandler cb = new Handler(this, username, credentials);
l = new LoginContext("Example", cb);
} catch (LoginException e) {
return null;
}

Subject subject = null;
try {
l.login();
subject = l.getSubject();
if (subject == null) {
return null;
}
} catch (AccountExpiredException e) {
} catch (CredentialExpiredException e) {
} catch (FailedLoginException e) {
} catch (LoginException e) {
}
return subject;
}

/**
* log out of application
*/
public void logout() {
if (l != null) {
try {
l.logout();
} catch (LoginException e) {
}
}
}

public static void main(String[] args) throws Exception {
Test t = new Test();
String username = "test";
String password = "test";

Subject subj = t.login(username, password.toCharArray());
if (subj != null) {
Iterator i = subj.getPrincipals(HibernatePrincipal.class).iterator();
while (i.hasNext()) {
HibernatePrincipal p = (HibernatePrincipal) i.next();
System.out.println("logged in as: " + p.getName());
}
t.logout();
}
else {
System.out.println("unable to log in as user");
}
}
}
正如上文間接提到的,JAAS的真正威力不在於它處理用戶登錄的靈活性,而是通過Permissions提供的公開的安全模塊和處理許可證的機制。

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