users@glassfish.java.net

Problems with LoginModule and custom Realms

From: <glassfish_at_javadesktop.org>
Date: Wed, 11 Mar 2009 08:08:28 PDT

Hello, i am new and i have the same problem since a week.
At first I want to take, the default LoginModule:

default {
        com.sun.enterprise.security.auth.login.ClientPasswordLoginModule required debug=false;
};

also i don't edit the "appclientlogin.conf"-file.
In the next Step i wrote a custom Realm:

 package MyFileRealm;

 import java.lang.*;
 import java.util.*;
 import java.util.logging.Logger ;
 import java.util.logging.Level ;
 import com.sun.logging.LogDomains;
 import java.io.*;
 import java.security.*;
 import javax.security.auth.login.*;
 import com.sun.enterprise.security.auth.realm.User;
 import com.sun.enterprise.security.auth.realm.Realm;
 import com.sun.enterprise.security.auth.realm.BadRealmException;
 import com.sun.enterprise.security.auth.realm.NoSuchUserException;
 import com.sun.enterprise.security.auth.realm.NoSuchRealmException;
 import com.sun.enterprise.security.auth.realm.AuthenticationHandler;
 import com.sun.enterprise.security.auth.realm.InvalidOperationException;
 import com.sun.enterprise.server.*;
 import com.sun.enterprise.security.RealmConfig;
 import com.sun.enterprise.security.util.*;
 import com.sun.enterprise.security.auth.realm.IASRealm;
 import MyFileRealm.FileRealmUser;
 import sun.misc.BASE64Decoder;
 import sun.misc.BASE64Encoder;


 final public class FileRealm extends IASRealm
 {
     // Descriptive string of the authentication type of this realm.
 public static final String AUTH_TYPE = "BASIC";

     // These are property names which should be in auth-realm in server.xml
 public static final String PARAM_KEYFILE="file2";

     // Separators in keyfile (user;pwd-info;group[,group]*)
 private static final String FIELD_SEP=";";
     private static final String GROUP_SEP=",";
     private static final String COMMENT="#";

     // Valid non-alphanumeric/whitespace chars in user/group name
 public static final String MISC_VALID_CHARS="_-.";

     // Number of bytes of salt for SSHA
 private static final int SALT_SIZE=8;

     // Contains cache of keyfile data
 private Map userTable; // user=>FileRealmUser
 private Hashtable groupSizeMap; // maps of groups with value cardinality of group

     private boolean constructed = false;

     public FileRealm(String keyfile)
          throws BadRealmException, NoSuchRealmException
     {
         File fp = new File(keyfile);
                                 // if not existent, try to create
 if (!fp.exists()) {
             FileOutputStream fout = null;
             try {
                 fout = new FileOutputStream(fp);
                 fout.write("\n".getBytes());
             } catch (Exception e) {
                 String msg = sm.getString("filerealm.noaccess", e.toString());
                 throw new BadRealmException(msg);
             } finally {
                 if (fout != null) {
                     try {
                         fout.close();
                     } catch(Exception ex) {
                         // ignore close exception
 }
                 }
             }
         }

         constructed = true;
         Properties p = new Properties();
         p.setProperty(PARAM_KEYFILE, keyfile);
         p.setProperty(IASRealm.JAAS_CONTEXT_PARAM, "ignore");
         this.init(p);
     }


     /**
      * Constructor.
      *
      * <P>Do not use directly.
      */
     public FileRealm()
     {
     }

     protected void init(Properties props)
         throws BadRealmException, NoSuchRealmException
     {
         String file = props.getProperty(PARAM_KEYFILE);
         if (file == null) {
             String msg = sm.getString("filerealm.nofile");
             throw new BadRealmException(msg);
         }
         this.setProperty(PARAM_KEYFILE, file);

         String jaasCtx = props.getProperty(IASRealm.JAAS_CONTEXT_PARAM);
         if (jaasCtx == null) {
             String msg = sm.getString("filerealm.nomodule");
             throw new BadRealmException(msg);
         }
         this.setProperty(IASRealm.JAAS_CONTEXT_PARAM, jaasCtx);

         _logger.fine("FileRealm : "+PARAM_KEYFILE+"="+file);
         _logger.fine("FileRealm : "+IASRealm.JAAS_CONTEXT_PARAM+"="+
                      jaasCtx);

         loadKeyFile();
     }

     public String getAuthType()
     {
         return AUTH_TYPE;
     }

     public Enumeration getUserNames()
          throws BadRealmException
     {
         return (new Vector(userTable.keySet())).elements(); // ugh
 }

     public User getUser(String name)
         throws NoSuchUserException
     {
         FileRealmUser u = (FileRealmUser)userTable.get(name);
         if (u == null) {
             String msg = sm.getString("filerealm.nouser", name);
             throw new NoSuchUserException(msg);
         }
         return u;
     }


     public Enumeration getGroupNames()
              throws BadRealmException
     {
         return groupSizeMap.keys();
     }

     public Enumeration getGroupNames(String username)
         throws NoSuchUserException
     {
         FileRealmUser ud = (FileRealmUser)userTable.get(username);
         if (ud == null) {
             String msg = sm.getString("filerealm.nouser", username);
             throw new NoSuchUserException(msg);
         }

         String [] groups = ud.getGroups();
         Vector v = new Vector();
         if (groups != null) {
             for (int i = 0; i < groups.length; i++) {
                v.add(groups[i]);
             }
         }
         return v.elements();
     }

     public void refresh()
          throws BadRealmException
     {
         _logger.fine("Reloading file realm data.");

         FileRealm newRealm = new FileRealm();

         try {
             newRealm.init(getProperties());
             Realm.updateInstance(newRealm, this.getName());
         } catch (Exception e) {
             throw new BadRealmException(e.toString());
         }
     }

     public String [] authenticate(String user, String password)
     {
         FileRealmUser ud = (FileRealmUser)userTable.get(user);
         if (ud == null) {
             if (_logger.isLoggable(Level.FINE)) {
                 _logger.fine("No such user: [" + user + "]");
             }
             return null;
         }

         boolean ok = false;
         try {
             ok = SSHA.verify(ud.getSalt(), ud.getHash(), password.getBytes());

         } catch (Exception e) {
             _logger.fine("File authentication failed: "+e.toString());
             return null;
         }

         if (!ok) {
             if (_logger.isLoggable(Level.FINE)) {
                 _logger.fine("File authentication failed for: ["+user+"]");
             }
             return null;
         }

         return ud.getGroups();
     }

     private static boolean isValid(String s, boolean userName)
     {
         for (int i=0; i<s.length(); i++) {
             char c = s.charAt(i);
             if (!Character.isLetterOrDigit(c) &&
                 !Character.isWhitespace(c) &&
                 MISC_VALID_CHARS.indexOf(c) == -1) {
                 if (userName && (c == '@')){
                     continue;
                 }
                 return false;
             }
         }
         return true;
     }

     public static void validateUserName(String name)
         throws IASSecurityException
     {
         if (name == null || name.length() == 0) {
             String msg = sm.getString("filerealm.noname");
             throw new IASSecurityException(msg);
         }

         if (!isValid(name, true)) {
             String msg = sm.getString("filerealm.badname", name);
             throw new IASSecurityException(msg);
         }

         if (!name.equals(name.trim())) {
             String msg = sm.getString("filerealm.badspaces", name);
             throw new IASSecurityException(msg);
         }
     }

     public static void validatePassword(String pwd)
         throws IASSecurityException
     {
         if (pwd == null) {
             String msg = sm.getString("filerealm.emptypwd");
             throw new IASSecurityException(msg);
         }

         if (!pwd.equals(pwd.trim())) {
             String msg = sm.getString("filerealm.badspacespwd");
             throw new IASSecurityException(msg);
         }
     }


     public static void validateGroupName(String group)
         throws IASSecurityException
     {
         if (group == null || group.length() == 0) {
             String msg = sm.getString("filerealm.nogroup");
             throw new IASSecurityException(msg);
         }

         if (!isValid(group, false)) {
             String msg = sm.getString("filerealm.badchars", group);
             throw new IASSecurityException(msg);
         }

         if (!group.equals(group.trim())) {
             String msg = sm.getString("filerealm.badspaces", group);
             throw new IASSecurityException(msg);
         }
     }

     public static void validateGroupList(String [] groupList)
         throws IASSecurityException
     {
         if (groupList == null || groupList.length == 0) {
             return; // empty list is ok
 }

         for (int i=0; i<groupList.length; i++) {
             validateGroupName(groupList[i]);
         }

     }

     public synchronized void addUser(String name, String password,
                         String [] groupList)
         throws BadRealmException, IASSecurityException
     {
         validateUserName(name);
         validatePassword(password);
         validateGroupList(groupList);

         if (userTable.containsKey(name)) {
             String msg = sm.getString("filerealm.dupuser", name);
             throw new BadRealmException(msg);
         }

         addGroupNames(groupList);
         FileRealmUser ud = createNewUser(name, password, groupList);
         userTable.put(name, ud);
     }

     public synchronized void removeUser(String name)
         throws NoSuchUserException
     {
         if (!userTable.containsKey(name)) {
             String msg = sm.getString("filerealm.nouser", name);
             throw new NoSuchUserException(msg);
         }

         FileRealmUser oldUser = (FileRealmUser)userTable.get(name);
         userTable.remove(name);
         reduceGroups(oldUser.getGroups());
     }


     public synchronized void updateUser(String name, String password,
                            String [] groups)
         throws NoSuchUserException, BadRealmException,
                                IASSecurityException
     {
         updateUser(name, name, password, groups);
     }


     public synchronized void updateUser(String name, String newName, String password,
                            String [] groups)
         throws NoSuchUserException, BadRealmException,
                                IASSecurityException
     {
                                 // user to modify must exist first
  validateUserName(name);
          if (!userTable.containsKey(name)) {
             String msg = sm.getString("filerealm.nouser", name);
             throw new NoSuchUserException(msg);
         }

                                 // do general validation
 validateUserName(newName);
         validateGroupList(groups);
         if (password != null) { // null here means re-use previous so is ok
 validatePassword(password);
         }

                                 // can't duplicate unless modifying itself
 if (!name.equals(newName) && userTable.containsKey(newName)) {
             String msg = sm.getString("filerealm.dupuser", name);
             throw new BadRealmException(msg);
         }


         FileRealmUser oldUser = (FileRealmUser)userTable.get(name);
         assert (oldUser != null);

                                 // create user using new name
 FileRealmUser newUser = new FileRealmUser(newName);

                                 // set groups as provided by parameter
 changeGroups(oldUser.getGroups(), groups);
         newUser.setGroups(groups);

                                 // use old password if no new pwd given
 if (password==null) {
             newUser.setSalt(oldUser.getSalt());
             newUser.setHash(oldUser.getHash());

         } else {
             setPassword(newUser, password);
         }

         userTable.remove(name);
         userTable.put(newName, newUser);
     }

     public void writeKeyFile(String filename)
          throws IOException
     {
         synchronized(FileRealm.class) {
             FileOutputStream out = null;
             try {
                 out = new FileOutputStream(filename);

                 Iterator names = userTable.keySet().iterator();
                 while (names.hasNext()) {

                     String name = (String )names.next();
                     FileRealmUser ud = (FileRealmUser)userTable.get(name);

                     String entry = encodeUser(name, ud);
                     out.write(entry.getBytes());
                 }
             } catch (IOException e) {
                 throw e;

             } catch (Exception e) {
                 String msg = sm.getString("filerealm.badwrite", e.toString());
                 throw new IOException(msg);
             } finally {
                 if (out != null) {
                     out.close();
                 }
             }
         }

         _logger.fine("Done writing "+filename);
     }

     private void addGroupNames(String [] groupList) {
         if (groupList != null) {
             for (int i=0; i < groupList.length; i++) {
                Integer groupSize = (Integer )groupSizeMap.get(groupList[i]);
                 groupSizeMap.put(groupList[i],
                    (groupSize != null) ?
                     new Integer (groupSize.intValue() + 1): new Integer (1));
             }
         }
    }

     private void reduceGroups(String [] groupList) {
         if (groupList != null) {
             for (int i=0; i < groupList.length; i++) {
                 Integer groupSize = (Integer )groupSizeMap.get(groupList[i]);
                 if (groupSize != null) {
                     int gpSize = groupSize.intValue() - 1;
                     if (gpSize > 0) {
                         groupSizeMap.put(groupList[i], new Integer (gpSize));
                     } else {
                         groupSizeMap.remove(groupList[i]);
                     }
                 }
             }
         }
     }

     /**
      * This method update the internal group list.
      */
     private void changeGroups(String [] oldGroupList, String [] newGroupList) {
         addGroupNames(newGroupList);
         reduceGroups(oldGroupList);
     }


     /**
      * Load keyfile from config and populate internal cache.
      *
      */
     private void loadKeyFile() throws BadRealmException
     {
         String file = this.getProperty(PARAM_KEYFILE);

         _logger.fine("Reading file realm: "+file);

         userTable = new Hashtable();
         groupSizeMap = new Hashtable();
        BufferedReader input = null;

         try {
             input = new BufferedReader(new FileReader(file));

             while (input.ready()) {

                 String line = input.readLine();
                 if (!line.startsWith(COMMENT) &&
                     line.indexOf(FIELD_SEP) > 0) {
                     FileRealmUser ud = decodeUser(line, groupSizeMap);
                     userTable.put(ud.getName(), ud);
                 }
             }
         } catch (Exception e) {
             _logger.log(Level.WARNING, "filerealm.readerror", e);
             throw new BadRealmException(e.toString());
         } finally {
             if (input != null) {
                 try {
                     input.close();
                 } catch(Exception ex) {
                 }
             }
         }
     }


     private static String encodeUser(String name, FileRealmUser ud)
     {
         StringBuffer sb = new StringBuffer ();
         String cryptPwd = null;

         sb.append(name);
         sb.append(FIELD_SEP);

         String ssha = SSHA.encode(ud.getSalt(), ud.getHash());

         sb.append(ssha);
         sb.append(FIELD_SEP);

         String [] groups = ud.getGroups();
         if (groups != null) {
             for (int grp = 0; grp < groups.length; grp++) {
                 if (grp > 0) {
                     sb.append(GROUP_SEP);
                 }
                 sb.append((String )groups[grp]);
             }
         }
         sb.append("\n");
         return sb.toString();
     }

     private static FileRealmUser decodeUser(String encodedLine,
                                             Map newGroupSizeMap)
         throws IASSecurityException
     {
         StringTokenizer st = new StringTokenizer(encodedLine, FIELD_SEP);

         String user = null;
         String pwdInfo = null;
         String groupList = null;

         try { // these must be present
 user = st.nextToken();
             pwdInfo = st.nextToken();
         } catch (Exception e) {
             String msg = sm.getString("filerealm.syntaxerror", encodedLine);
             throw new IASSecurityException(msg);
         }

         if (st.hasMoreTokens()) { // groups are optional
                         groupList = st.nextToken();
         }

         byte[] hash = new byte[20];
         byte[] salt = SSHA.decode(pwdInfo, hash);

         FileRealmUser ud = new FileRealmUser(user);
         ud.setHash(hash);
         ud.setSalt(salt);

         Vector membership = new Vector();

         if (groupList != null) {
             StringTokenizer gst = new StringTokenizer(groupList,
                                                       GROUP_SEP);
             while (gst.hasMoreTokens()) {
                String g = gst.nextToken();
                 membership.add(g);
                 Integer groupSize = (Integer )newGroupSizeMap.get(g);
                 newGroupSizeMap.put(g, (groupSize != null) ?
                     new Integer (groupSize.intValue() + 1) : new Integer (1));
             }
         }
        ud.setGroups(membership);
         return ud;
     }

     private static FileRealmUser createNewUser(String name, String pwd,
                                                String [] groups)
         throws IASSecurityException
     {
         FileRealmUser ud = new FileRealmUser(name);

         if (groups == null) {
             groups = new String [0];
         }
         ud.setGroups(groups);

         setPassword(ud, pwd);

         return ud;
     }

     private static void setPassword(FileRealmUser user, String pwd)
         throws IASSecurityException
     {
         assert (user != null);
         byte[] pwdBytes = pwd.getBytes();

         SecureRandom rng=new SecureRandom();
         byte[] salt=new byte[SALT_SIZE];
         rng.nextBytes(salt);
         user.setSalt(salt);

         byte[] hash = SSHA.compute(salt, pwdBytes);
         user.setHash(hash);
     }


     public static void main(String [] args)
     {
         if (args.length==0) {
             help();
         }

         try {
             if ("-c".equals(args[0])) {
                 String [] groups=new String [0];
                 if (args.length>3) {
                     groups=new String [args.length-3];
                     for (int i=3; i<args.length; i++) {
                         groups[i-3]=args[i];
                     }
                 }
                 FileRealmUser ud = createNewUser(args[1], args[2], groups);
                 String out=encodeUser(args[1], ud);
                 System.out.println(out);

                 FileRealmUser u=decodeUser(out, new Hashtable());
                 System.out.println("verifies: "+
                                    SSHA.verify(u.getSalt(), u.getHash(),
                                                args[2].getBytes()));

             } else if ("-v".equals(args[0])) {
                 FileRealmUser u=decodeUser(args[2], new Hashtable());
                 System.out.println("user: "+u.getName());
                 System.out.println("verifies: "+
                                    SSHA.verify(u.getSalt(), u.getHash(),
                                                args[1].getBytes()));
             }
         } catch (Exception e) {
             e.printStackTrace(System.out);
         }
     }

     private static void help()
     {
         System.out.println("FileRealm -c <name <pwd [group]*");
         System.out.println("FileRealm -v <pwd `output of -c`");
         System.exit(1);
     }
 }

thats the "FileRealm" form "com.sun.enterprise.security.auth.realm.file.FileRealm". I copied the file in a java-class. after the building i copied the *.jar in the lib-directory of my glassfish-server. Now i must restart my glassfish and take a edit in Security - Realm - new:

Name: file2
Classname: MyFileRealm.FileRealm
jaas-context: FileRealm
file: ${com.sun.aas.instanceRoot}/config/keyfile2 (this file exists)

Now I create a simple EEApplication.
here the client:
import fileauthserver.serverRemote;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.rmi.PortableRemoteObject;
import javax.security.auth.login.LoginException;
public class Main {
    public static void main(String[] args) throws NamingException, LoginException {
        InitialContext ctx = new InitialContext();
        Object ref = ctx.lookup("fileauthserver");
        serverRemote objekt = (serverRemote) PortableRemoteObject.narrow(ref,serverRemote.class);
        System.err.println("\n\n" + objekt.konto() + "\n\n");
}}

and here the serverapp:

package fileauthserver;
import javax.annotation.security.RolesAllowed;
import javax.ejb.Stateless;
@Stateless(mappedName="fileauthserver")
public class serverBean implements serverRemote {
    @RolesAllowed("USERS")
    public String konto(){
        return "Kontostand: 11 Euro";
    }
}

and here the remote interace:

package fileauthserver;
import javax.ejb.Remote;
@Remote
public interface serverRemote {
    String konto();
}

in the last step, i create a "glassfish deployment desc. - sun-application.xml":

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE sun-application PUBLIC "-//Sun Microsystems, Inc.//DTD Application Server 9.0 Java EE Application 5.0//EN" "http://www.sun.com/software/appserver/dtds/sun-application_5_0-0.dtd">
<sun-application>
  <realm>file2</realm>
  <security-role-mapping>
    <role-name>USERS</role-name>
    <group-name>bank_users</group-name>
  </security-role-mapping>
</sun-application>

Now a main question. when i start my application, the bean asks me for username and password, but when i press "ok" the following Exeption comes:
com.sun.enterprise.appclient.MainWithModuleSupport <init>
WARNUNG: ACC003: Ausnahmefehler bei Anwendung.
javax.naming.NamingException: ejb ref resolution error for remote business interfacefileauthserver.serverRemote [Root exception is java.rmi.AccessException: CORBA NO_PERMISSION 0 No; nested exception is:
        org.omg.CORBA.NO_PERMISSION: ----------BEGIN server-side stack trace----------
org.omg.CORBA.NO_PERMISSION: vmcid: 0x0 minor code: 0 completed: No
        and so on..

when my realm is: <realm>file</realm> it works:
"Kontostand: 11 Euro"

why i have no permisson, then i want to take the realm <realm>file2</realm>?

sorry for the bad english...
[Message sent by forum member 'capaelia' (capaelia)]

http://forums.java.net/jive/thread.jspa?messageID=336396