新项目中要重构基于JAAS的登录模块,参考了这篇文章。
Today I wrote a custom JAAS LoginModule and configured it to work with JBoss 4.2.2. This post summarizes the steps involved. This is not light reading — this is mostly reference information in case you ever have to write your own LoginModule implementation.
Why do This?
We wanted to add authentication to some web apps, but our administrative database is different than you might expect. Without going into too many proprietary details, the out-of-the-box JAAS LoginModuleimplementations did not work for us. I had to write a custom implementation of the LoginModule interface.
Now that we have a LoginModule implementation configured, we can use the standard <security-constraint>, <login-config>, and <security-role> tags in our web.xml deployment descriptors.
To summarize, we want to:
Implement the Interfaces
The first step involves writing three classes. I am changing the names here in order to avoid publishing proprietary code (sorry!):
The Principal
This is a trivial interface that represents the user’s name, such as “harry”. It also represents a role he belongs to, like “admin” or “guest”. Your Principal should look something like this:
public class MyPrincipal implements Principal, Serializable {
private static final serialVersionUID = 1L;
private final String name;
public MyPrincipal(String name) {
this.name = name;
}
public String getName() {
return name;
}
// also implement equals() and hashCode(), you get the point
} The Group
This is a class that implements java.security.acl.Group:
public class MyGroup implements Group, Serializable {
private static final long serialVersionUID = 1L;
private final String name;
private final Set<Principal> users = new HashSet<Principal>();
public MyGroup(String name) {
this.name = name;
}
public boolean addMember(Principal user) {
return users.add(user);
}
public boolean removeMember(Principal user) {
return users.remove(user);
}
public boolean isMember(Principal member) {
return users.contains(member);
}
public Enumeration<? extends Principal> members() {
return Collections.enumeration(users);
}
public String getName() {
return name;
}
public boolean equals(Object o) {
// you are smart enough to write this; just compare the name
}
// yeah, write your own hashCode method too
}
We’ll see how this group works next, with the LoginModule.
The LoginModule
I wrote my class by copying com.sun.jmx.remote.security.FileLoginModule. I won’t duplicate all of the code here because I can’t — it is proprietary. But here are the highlights (it is really easy, I promise):
public class MyLoginModule implements LoginModule {
private Subject subject;
private CallbackHandler callbackHandler;
private Map<String, ?> sharedState;
private Map<String, ?> options;
private boolean commitSucceeded = false;
private boolean loginSucceeded = false;
private String username;
private MyPrincipal user;
private MyPrincipal[] roles;
public void initialize(Subject subject, CallbackHandler callbackHandler,
Map<String, ?> sharedState, Map<String, ?> options) {
this.subject = subject;
this.callbackHandler = callbackHandler;
this.sharedState = sharedState;
this.options = options;
}
public void login() throws LoginException {
...
NameCallback nameCallback = new NameCallback("Username");
PasswordCallback passwordCallback = new PasswordCallback("Password", false);
Callback[] callbacks = new Callback[]{nameCallback, passwordCallback};
callbackHandler.handle(callbacks);
username = nameCallback.getName();
char[] password = passwordCallback.getPassword();
passwordCallback.clearPassword();
// you now have the username and password, so authenticate against your db
user = new MyPrincipal(username);
roles = new MyPrincipal[] {
new MyPrincipal("admin") // for example
...fill in all of the roles from your database
};
// trust me - just do a "find usages" for classes implementing LoginModule
}
public boolean commit() throws LoginException {
...
// this is the important part to work with JBoss:
subject.getPrincipals().add(user);
// jboss requires the name 'Roles'
MyGroup group = new MyGroup("Roles");
for (MyPrincipal role : roles) {
group.addMember(role);
}
subject.getPrincipals().add(group);
...
return true;
}
...other methods are trivial. just copy the general patterns from FileLoginModule
} Packaging
You need to compile those three classes and put them into a JAR file. We’ll call it myJaas.jar. Next, you’ll generate a .sar file. We can call this myapp.sar. This should also work in an EAR file, but mine is a SAR file. The structure of the SAR file is:
myapp.sar | +--myapp.war (the web application itself) +--my-login-config.xml +--myJaas.jar +--META-INF +--jboss-service.xml The WAR file
This is a normal WAR file, but it does contain a JBoss-specific XML file. It looks like this:
myapp.war | +--WEB-INF +--classes +--lib +--jboss-web.xml +--web.xml
The jboss-web.xml file looks like this:
<jboss-web>
<security-domain>java:/jaas/myjaas</security-domain>
<depends>jboss.jca:service=DataSourceBinding,name=MyDS</depends>
</jboss-web>
That “myjaas” is important because it ties your web app to the application policy we will define in my-login-config.xml. That data source dependency ensures the data source “MyDS” is deployed before the WAR file is deployed.
jboss-service.xml
This file looks like this:
<server>
<mbean code="org.jboss.security.auth.login.DynamicLoginConfig"
name="whatever:service=MyLogin">
<attribute name="AuthConfig">my-login-config.xml</attribute>
<depends optional-attribute-name="LoginConfigService">
jboss.security:service=XMLLoginConfig</depends>
<depends optional-attribute-name="SecurityManagerService">
jboss.security:service=JaasSecurityManager</depends>
</mbean>
</server> my-login-config.xml
Here goes:
<policy>
<application-policy name="myjaas">
<authentication>
<login-module code="com.acme.MyLoginModule"
flag="required">
<module-option name="myKey">myValue</module-option>
</login-module>
</authentication>
</application-policy>
</policy>
That module-option is critical to my own proprietary implementation. It allowed me to pass along something called the “enterprise ID”, a proprietary piece of data we need for login functionality. You can add whatever options you need, they are available in your LoginModule class in the options map.
Final Steps
The last thing is to ensure your web application deployment descriptor (web.xml) declares the security constraint:
<security-constraint>
<web-resource-collection>
<web-resource-name>Secure Content</web-resource-name>
<url-pattern>/feed/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>FeedAdmin</role-name>
</auth-constraint>
<user-data-constraint>
<transport-guarantee>NONE</transport-guarantee>
</user-data-constraint>
</security-constraint>
<login-config>
<auth-method>BASIC</auth-method>
<realm-name>The Restricted Zone</realm-name>
</login-config>
<security-role>
<description>The role required to access restricted content</description>
<role-name>FeedAdmin</role-name>
</security-role> In Summary
I hope this helps someone out there. While there are some JBoss-specific XML files, for the most part the APIs are generic JAAS. Furthermore, the web.xml is completely standard, with no dependencies on JBoss.
原文地址:http://stuffthathappens.com/blog/2008/05/16/writing-a-custom-jaas-loginmodule/。