czwartek, 25 marca 2010

JAAS - prosty moduł

W jednym z poprzednim wpisie, pokazywałem jak wykorzystać JAAS'a w aplikacji webowej. Teraz chciałbym się skupić na tym, jak napisać własny moduł, dzięki któremu będziemy mogli wprowadzić własne zasady uwierzytelnianie i autoryzacji. Na końcu pokażę jak użyć tego modułu w aplikacji webowej, uruchomionej na serwerze aplikacji JBoss.


Kilka słów o samej strukturze
Prosty moduł będzie składał się z 3 klas:

  • MyPrincipal
  • MyGroup
  • MyLoginModule
Klasy należy umieścić gdzieś w naszej aplikacji, u mnie będzie to pakiet com.blogspot.mkorwel.jaas




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();
 }
}
MyGroup
MyGroup jest prostą klasą implementującą interfejsy java.security.acl.Groupjava.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
Initialize 
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>

Brak komentarzy:

Prześlij komentarz