安全:JAAS LoginModule HibernateTomcatLinuxSecurityUnix
程序员文章站
2022-07-11 08:15:13
...
原文:http://www.hibernate.org/139.html
译者:jredfox
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提供的公开的安全模块和处理许可证的机制。
译者:jredfox
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提供的公开的安全模块和处理许可证的机制。