Kilka słów o samej strukturze
Prosty moduł będzie składał się z 3 klas:
- MyPrincipal
- MyGroup
- MyLoginModule
MyPrincipal
MyPrincipal jest prostą klasą implementującą interfejs java.security.Principal. Klasa przechowuje identyfikator (login, publiczne id, email) który pozwala jednoznacznie określić użytkowania lub rolę.
package com.blogspot.mkorwel.jaas;
import java.io.Serializable;
import java.security.Principal;
/**
*
* @author mkorwel
*
*/
public class MyPrincipal implements Principal, Serializable {
/**
*
*/
private static final long serialVersionUID = 1933073722894960007L;
private final String name;
public MyPrincipal(String name) {
this.name = name;
}
public String getName() {
return name;
}
public boolean equals(Object o) {
if (o == null)
return false;
if (this == o)
return true;
if (!(o instanceof MyPrincipal))
return false;
MyPrincipal that = (MyPrincipal) o;
if (this.getName().equals(that.getName()))
return true;
return false;
}
public int hashCode() {
return name.hashCode();
}
}
MyGroupMyGroup jest prostą klasą implementującą interfejsy java.security.acl.Group i java.io.Serializable. Klasa przechowuje kolekcję ról.
package com.blogspot.mkorwel.jaas;
import java.io.Serializable;
import java.security.Principal;
import java.security.acl.Group;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
/**
*
* @author mkorwel
*
*/
public class MyGroup implements Group, Serializable {
/**
*
*/
private static final long serialVersionUID = -3978735590793434379L;
private final String name;
private final Set users = new HashSet();
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 members() {
return Collections.enumeration(users);
}
public String getName() {
return name;
}
public boolean equals(Object o) {
if (o == null)
return false;
if (this == o)
return true;
if (!(o instanceof MyGroup))
return false;
MyGroup that = (MyGroup) o;
if (this.getName().equals(that.getName()))
return true;
return false;
}
public int hashCode() {
return name.hashCode();
}
}
MyLoginModule
Klasa MyLoginModule jest miejscem gdzie implementujemy całą logikę modułu. Klasa implementuje interfejs LoginModule, czyli 5 metod:
- initialize
- login
- commit
- abort
- logout
Metoda initialize wywoływana jest przy każdej próbie logowania. Zajmuje się wczytaniem wszystkich niezbędnych parametrów (takich jak miejsce zapisu loginów, haseł, itd.)
Login
Metoda odpowiada za poprawną uwierzytelnianie. W tradycyjnym modelu uwierzytelniania metoda sprawdza login i hasło.
Commit
Metoda wykonuje się jeżeli uwierzytelnianie powiodło się. Zapisuje osobę i role do obiektu Subject.
Abort
Metoda wykonuje się jeżeli uwierzytelnianie się nie powiodło.
Logout
Metoda wywoływana podczas wylogowania użytkownika.
package com.blogspot.mkorwel.jaas;
import java.util.HashMap;
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.LoginException;
import javax.security.auth.spi.LoginModule;
/**
*
* @author mkorwel
*
*/
public class MyLoginModule implements LoginModule {
private Map usernameAndPassword = new HashMap() {
private static final long serialVersionUID = -2695491152140640122L;
{
put("user1", "user");
put("admin", "admin");
}
};
private Map usernameAndRoles = new HashMap() {
private static final long serialVersionUID = 1136203053244663646L;
{
put("user1", "user");
put("admin", "admin");
}
};
// initial state
private Subject subject;
private CallbackHandler callbackHandler;
@SuppressWarnings("unused")
private Map sharedState;
@SuppressWarnings("unused")
private Map options;
// username and password
private String username;
private String password;
private MyPrincipal userPrincipal;
@Override
public void initialize(Subject subject, CallbackHandler callbackHandler,
Map sharedState, Map options) {
this.subject = subject;
this.callbackHandler = callbackHandler;
this.sharedState = sharedState;
this.options = options;
}
@Override
public boolean login() throws LoginException {
Callback[] callbacks = new Callback[2];
callbacks[0] = new NameCallback("user name: ");
callbacks[1] = new PasswordCallback("password: ", false);
try {
// pobranie loginu
callbackHandler.handle(callbacks);
username = ((NameCallback) callbacks[0]).getName();
// pobranie hasla
char[] tmpPassword = ((PasswordCallback) callbacks[1])
.getPassword();
if (tmpPassword == null) {
tmpPassword = new char[0];
}
password = new String(tmpPassword);
((PasswordCallback) callbacks[1]).clearPassword();
} catch (java.io.IOException ioe) {
throw new LoginException(ioe.toString());
} catch (UnsupportedCallbackException uce) {
throw new LoginException("Error: " + uce.getCallback().toString()
+ " not available to garner authentication information "
+ "from the user");
}
// weryfikacje loginu i hasla
return (usernameAndPassword.containsKey(username) && password
.equals(usernameAndPassword.get(username)));
}
@Override
public boolean commit() throws LoginException {
userPrincipal = new MyPrincipal(username);
if (!subject.getPrincipals().contains(userPrincipal)) {
subject.getPrincipals().add(userPrincipal);
MyGroup role = new MyGroup("Roles");
role.addMember(new MyPrincipal(usernameAndRoles.get(username)));
subject.getPrincipals().add(role);
}
return true;
}
@Override
public boolean abort() throws LoginException {
username = null;
password = null;
userPrincipal = null;
return true;
}
@Override
public boolean logout() throws LoginException {
subject.getPrincipals().remove(userPrincipal);
username = null;
password = null;
userPrincipal = null;
return true;
}
}
Nasz prosty moduł nie posiada żadnych parametrów przekazywanych z zewnątrz. Korzysta z bardzo prostego mechanizmu uwierzytelniania login/hasło które są zapisane w dwóch mapach na początku klasy.Ważna uwaga!!!
Jeśli używamy serwera aplikacyjnego JBoss, tak jak ja w przykładzie, musimy pamiętać o tym że wszystkie role muszę być wewnątrz obiektu "Roles".
Własny moduł pod JBoss'em
Tak jak wspominałem na początku, pokaże jak szybko wykorzystać nasz moduł w aplikacji uruchomionej na serwerze aplikacji JBoss.
Jeśli korzystamy ze standardowego modułu uwierzytelniania i autoryzacji jaki przedstawiłem w poprzednim wpisie, to musimy podmienić tylko plik jboss-service.xml. Zmiany dotyczą tylko wskazania klasy modułu i usunięciu wszystkich dodatkowych parametrów takich jak: wskazanie zapytania SELECT dzięki któremu pobieramy login/hasło/role, itd.
Aktualny jboss-service.xml powinien wyglądać tak:
<?xml version="1.0" encoding="UTF-8"?>
<server>
<mbean code="org.jboss.security.auth.login.DynamicLoginConfig"
name="jboss.seminarium:name=seminarium.login,service=DynamicLoginConfig">
<attribute name="PolicyConfig" serialDataType="jbxb">
<jaas:policy
xsi:schemaLocation="urn:jboss:security-config:4.1 resource:security-config_4_1.xsd"
xmlns:jaas="urn:jboss:security-config:4.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<jaas:application-policy name="my-auth-module">
<jaas:authentication>
<jaas:login-module code="com.blogspot.mkorwel.jaas.MyLoginModule"
flag="required">
</jaas:login-module>
</jaas:authentication>
</jaas:application-policy>
</jaas:policy>
</attribute>
<depends optional-attribute-name="LoginConfigService">
jboss.security:service=XMLLoginConfig
</depends>
<depends optional-attribute-name="SecurityManagerService">
jboss.security:service=JaasSecurityManager
</depends>
</mbean>
</server>

