# RMI Anwendung mit Zugriffsschutz versehen



## itstata (17. Apr 2008)

Hallo,
ich stehe momentan vor folgendem Problem.
Ich möchte, dass mein RMI-Server nur Clients den Zugriff gewährt, die auch eine berächtigung haben (vereinfacht erstmal mit passwort). In meinem Buch über Rmi konnte ich nicht sehen, dass es dafür schon was gibt.
Hat jemand von euch schon dieses Problem gelöst? Ist ja eigentlich was grundlegendes. Ich hatte daran gedacht, dass er die Client-Verbindung zurückweist, allerdings weiss ich nicht, wie man das realisieren kann. Wo wäre ein eleganter weg?


----------



## ms (17. Apr 2008)

Der vorgesehene Weg ist JAAS.
Allerdings ist diese Thematik nicht ganz einfach.
http://java.sun.com/javase/6/docs/technotes/guides/security/

ms


----------



## itstata (17. Apr 2008)

ok danke erstmal, werd mal in die problematik einsteigen


----------



## itstata (18. Apr 2008)

ich hab mir jetzt mal einige beispiele angeschaut und auch erfolgreich getestet. das grundlegende konzept hab ich wohl erstmal nachvollzogen, allerdings will mir nicht so recht in den kopf, wie ich nun eigentlich meine klasse schütze, bzw. wo :shock: 

ich muss ja irgendwo festlegen, was geschützt werden soll.

der client muss sich ja zwingend am server mit dem .login anmelden...

nur wenn dies erfolgreich ist kommt keine login-exception und mann kann methoden auf dem server nutzen


ich nutze folgende klassen:

SampleAcn

```
package sample;

import java.io.*;
import java.util.*;
import javax.security.auth.login.*;
import javax.security.auth.callback.*;

public class SampleAcn {

	public static void main(String[] args) {
		System.setProperty("java.security.auth.login.config",
				"D:\\Programmierung\\localTests\\bin\\samplejaas.conf");

		LoginContext lc = null;
		try {
			lc = new LoginContext("Sample", new MyCallbackHandler());
		} catch (LoginException le) {
			System.err
					.println("Cannot create LoginContext. " + le.getMessage());
			System.exit(-1);
		} catch (SecurityException se) {
			System.err
					.println("Cannot create LoginContext. " + se.getMessage());
			System.exit(-1);
		}
		// the user has 3 attempts to authenticate successfully
		int i;
		for (i = 0; i < 3; i++) {
			try {
				// attempt authentication
				lc.login();
				// if we return with no exception, authentication succeeded
				break;

			} catch (LoginException le) {

				System.err.println("Authentication failed:");
				System.err.println("  " + le.getMessage());
				try {
					Thread.currentThread().sleep(3000);
				} catch (Exception e) {
					// ignore
				}
			}
		}
		// did they fail three times?
		if (i == 3) {
			System.out.println("Sorry");
			System.exit(-1);
		}
		System.out.println("Authentication succeeded!");
	}
}

class MyCallbackHandler implements CallbackHandler {

	public void handle(Callback[] callbacks) throws IOException,
			UnsupportedCallbackException {

		for (int i = 0; i < callbacks.length; i++) {
			if (callbacks[i] instanceof TextOutputCallback) {

				// display the message according to the specified type
				TextOutputCallback toc = (TextOutputCallback) callbacks[i];
				switch (toc.getMessageType()) {
				case TextOutputCallback.INFORMATION:
					System.out.println(toc.getMessage());
					break;
				case TextOutputCallback.ERROR:
					System.out.println("ERROR: " + toc.getMessage());
					break;
				case TextOutputCallback.WARNING:
					System.out.println("WARNING: " + toc.getMessage());
					break;
				default:
					throw new IOException("Unsupported message type: "
							+ toc.getMessageType());
				}

			} else if (callbacks[i] instanceof NameCallback) {

				// prompt the user for a username
				NameCallback nc = (NameCallback) callbacks[i];

				System.err.print(nc.getPrompt());
				System.err.flush();
				nc
						.setName((new BufferedReader(new InputStreamReader(
								System.in))).readLine());

			} else if (callbacks[i] instanceof PasswordCallback) {

				// prompt the user for sensitive information
				PasswordCallback pc = (PasswordCallback) callbacks[i];
				System.err.print(pc.getPrompt());
				System.err.flush();
				pc.setPassword(readPassword(System.in));

			} else {
				throw new UnsupportedCallbackException(callbacks[i],
						"Unrecognized Callback");
			}
		}
	}

	// Reads user password from given input stream.
	private char[] readPassword(InputStream in) throws IOException {

		char[] lineBuffer;
		char[] buf;
		int i;

		buf = lineBuffer = new char[128];

		int room = buf.length;
		int offset = 0;
		int c;

		loop: while (true) {
			switch (c = in.read()) {
			case -1:
			case '\n':
				break loop;

			case '\r':
				int c2 = in.read();
				if ((c2 != '\n') && (c2 != -1)) {
					if (!(in instanceof PushbackInputStream)) {
						in = new PushbackInputStream(in);
					}
					((PushbackInputStream) in).unread(c2);
				} else
					break loop;

			default:
				if (--room < 0) {
					buf = new char[offset + 128];
					room = buf.length - offset - 1;
					System.arraycopy(lineBuffer, 0, buf, 0, offset);
					Arrays.fill(lineBuffer, ' ');
					lineBuffer = buf;
				}
				buf[offset++] = (char) c;
				break;
			}
		}

		if (offset == 0) {
			return null;
		}

		char[] ret = new char[offset];
		System.arraycopy(buf, 0, ret, 0, offset);
		Arrays.fill(buf, ' ');

		return ret;
	}
}
```

SampleLoginModule

```
package sample.module;

import java.util.*;
import java.io.IOException;
import javax.security.auth.*;
import javax.security.auth.callback.*;
import javax.security.auth.login.*;
import javax.security.auth.spi.*;
import sample.principal.SamplePrincipal;

public class SampleLoginModule implements LoginModule {

    // initial state
    private Subject subject;
    private CallbackHandler callbackHandler;
    private Map sharedState;
    private Map options;

    // configurable option
    private boolean debug = false;

    // the authentication status
    private boolean succeeded = false;
    private boolean commitSucceeded = false;

    // username and password
    private String username;
    private char[] password;

    // testUser's SamplePrincipal
    private SamplePrincipal userPrincipal;

    public void initialize(Subject subject, 
	           CallbackHandler callbackHandler,
			 Map<java.lang.String, ?> sharedState, 
			 Map<java.lang.String, ?> options) {
 
	this.subject = subject;
	this.callbackHandler = callbackHandler;
	this.sharedState = sharedState;
	this.options = options;

	// initialize any configured options
	debug = "true".equalsIgnoreCase((String)options.get("debug"));
    }
    public boolean login() throws LoginException {

	// prompt for a user name and password
	if (callbackHandler == null)
	    throw new LoginException("Error: no CallbackHandler available " +
			"to garner authentication information from the user");

	Callback[] callbacks = new Callback[2];
	callbacks[0] = new NameCallback("user name: ");
	callbacks[1] = new PasswordCallback("password: ", false);
 
	try {
	    callbackHandler.handle(callbacks);
	    username = ((NameCallback)callbacks[0]).getName();
	    char[] tmpPassword = ((PasswordCallback)callbacks[1]).getPassword();
	    if (tmpPassword == null) {
		// treat a NULL password as an empty password
		tmpPassword = new char[0];
	    }
	    password = new char[tmpPassword.length];
	    System.arraycopy(tmpPassword, 0,
			password, 0, tmpPassword.length);
	    ((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");
	}

	// print debugging information
	if (debug) {
	    System.out.println("\t\t[SampleLoginModule] " +
				"user entered user name: " +
				username);
	    System.out.print("\t\t[SampleLoginModule] " +
				"user entered password: ");
	    for (int i = 0; i < password.length; i++)
		System.out.print(password[i]);
	    System.out.println();
	}

	// verify the username/password
	boolean usernameCorrect = false;
	boolean passwordCorrect = false;
	if (username.equals("testUser"))
	    usernameCorrect = true;
	if (usernameCorrect &&
	    password.length == 12 &&
	    password[0] == 't' &&
	    password[1] == 'e' &&
	    password[2] == 's' &&
	    password[3] == 't' &&
	    password[4] == 'P' &&
	    password[5] == 'a' &&
	    password[6] == 's' &&
	    password[7] == 's' &&
	    password[8] == 'w' &&
	    password[9] == 'o' &&
	    password[10] == 'r' &&
	    password[11] == 'd') {

	    // authentication succeeded!!!
	    passwordCorrect = true;
	    if (debug)
		System.out.println("\t\t[SampleLoginModule] " +
				"authentication succeeded");
	    succeeded = true;
	    return true;
	} else {

	    // authentication failed -- clean out state
	    if (debug)
		System.out.println("\t\t[SampleLoginModule] " +
				"authentication failed");
	    succeeded = false;
	    username = null;
	    for (int i = 0; i < password.length; i++)
		password[i] = ' ';
	    password = null;
	    if (!usernameCorrect) {
		throw new FailedLoginException("User Name Incorrect");
	    } else {
		throw new FailedLoginException("Password Incorrect");
	    }
	}
    }

    public boolean commit() throws LoginException {
	if (succeeded == false) {
	    return false;
	} else {
	    // add a Principal (authenticated identity)
	    // to the Subject

	    // assume the user we authenticated is the SamplePrincipal
	    userPrincipal = new SamplePrincipal(username);
	    if (!subject.getPrincipals().contains(userPrincipal))
		subject.getPrincipals().add(userPrincipal);

	    if (debug) {
		System.out.println("\t\t[SampleLoginModule] " +
				"added SamplePrincipal to Subject");
	    }

	    // in any case, clean out state
	    username = null;
	    for (int i = 0; i < password.length; i++)
		password[i] = ' ';
	    password = null;

	    commitSucceeded = true;
	    return true;
	}
    }

    public boolean abort() throws LoginException {
	if (succeeded == false) {
	    return false;
	} else if (succeeded == true && commitSucceeded == false) {
	    // login succeeded but overall authentication failed
	    succeeded = false;
	    username = null;
	    if (password != null) {
		for (int i = 0; i < password.length; i++)
		    password[i] = ' ';
		password = null;
	    }
	    userPrincipal = null;
	} else {
	    // overall authentication succeeded and commit succeeded,
	    // but someone else's commit failed
	    logout();
	}
	return true;
    }

    public boolean logout() throws LoginException {

	subject.getPrincipals().remove(userPrincipal);
	succeeded = false;
	succeeded = commitSucceeded;
	username = null;
	if (password != null) {
	    for (int i = 0; i < password.length; i++)
		password[i] = ' ';
	    password = null;
	}
	userPrincipal = null;
	return true;
    }
}
```

SamplePrincipal

```
package sample.principal;

import java.security.Principal;

public class SamplePrincipal implements Principal, java.io.Serializable {

    /**
     * @serial
     */
    private String name;

    public SamplePrincipal(String name) {
	if (name == null)
	    throw new NullPointerException("illegal null input");

	this.name = name;
    }

    public String getName() {
	return name;
    }

    public String toString() {
	return("SamplePrincipal:  " + name);
    }
    
    public boolean equals(Object o) {
	if (o == null)
	    return false;

        if (this == o)
            return true;
 
        if (!(o instanceof SamplePrincipal))
            return false;
        SamplePrincipal that = (SamplePrincipal)o;

	if (this.getName().equals(that.getName()))
	    return true;
	return false;
    }
 
    public int hashCode() {
	return name.hashCode();
    }
}
```


```
meine conf datei sieht so aus und funktioniert auch:
Sample {
   sample.module.SampleLoginModule required debug=true;
};
```
[/list]


----------



## tuxedo (18. Apr 2008)

Ich mach das meist so:

Ich hab ein Serverobject das als einzigste Methode eine login() Methode anbietet. Der Methode wird benutzername und Passwort etc. übergeben. Als Ergebnis bekommt der Client entweder NULL oder eine Exception wenn der Login falsch war, ODER er bekommt ein weiteres Serverobject das sich nur über diese Loginmethode bekommen lässt. Und in diesem Objekt sind dann die ganzen Methoden die ein authentifizierter und autorisierter Client benutzen darf.

Denke das ist einer der einfachsten und unkompliziertesten Wege.

- Alex


----------



## itstata (18. Apr 2008)

Aber wozu soll denn JAAS überhaupt gut sein, wenn man den server dazu auch noch neustarten muss, wenn es änderungen an der policy-datei gibt. das kommt mir alles sehr eigenartig vor. hat denn jemand schon mal JAAS implementiert und gute erfahrungen gemacht?

danke alex, ich werd das dann auch so versuchen - habs mir wohl komplizierter gemacht als es ist.


----------



## HoaX (18. Apr 2008)

man muss bei jaas ja kein properties nehmen, man kann auch gegen eine datenbank authentifizieren


----------



## tuxedo (19. Apr 2008)

Je nachdem was man machen will und vor hat, ist JAAS etwas "überdimensioniert". Denke für einfache Sachen ist der Session-Ansatz mit einem extre Remote-Objekt einfacher zu handhaben.

- Alex


----------



## itstata (22. Apr 2008)

also ich hab das jetzt per rückgabe eines objekts implementiert, guter tipp von euch. damit ist das thema zumindest für mich gelöst, falls jemand mal das gleiche problem bekommt, hier der code:

auf dem Server:

```
...
Registry registry = LocateRegistry.createRegistry(Registry.REGISTRY_PORT);
// LoginModul
RemoteLogin remote = new RemoteLoginImpl();
registry.bind("Abruf", remote);
```

RemoteLoginImpl (liegt nur auf Server)

```
public class RemoteLoginImpl extends UnicastRemoteObject implements RemoteLogin {

	private static final long serialVersionUID = 33009593583244040L;

	public RemoteLoginImpl() throws Exception {
		super(Registry.REGISTRY_PORT, new RMISSLClientSocketFactory(), new RMISSLServerSocketFactory());
	}

	public Abruf login(String in_username, String in_password) throws Exception {
		String username = "myusername";
		String password = "mypassword";

		if (username.equals(in_username) && password.equals(in_password))
			return (new AbrufImpl());
		else
			throw new RemoteLoginException("username or password incorrect");
	}
}
```

RemoteLogin

Abruf sind bei mir die geschützten Methoden

```
public interface RemoteLogin extends Remote {

	Abruf login(String username, String password) throws RemoteException, RemoteLoginException, Exception;
}
```

Client

```
Registry registry = LocateRegistry.getRegistry(host, Registry.REGISTRY_PORT);

		RemoteLogin login = (RemoteLogin) registry.lookup("Abruf");
//falls das Passwort falsch ist, kommt man also nicht an die internen methoden ran
		Abruf fd = login.login(username, password);
		String message = fd.halloweltodersowas();
```
[/code]


----------

