Nehmen wir mal einen einfachen REST-Web-Service wie den folgenden, der alle Bestellungen des Benutzers liefern soll:
@Path("/bestellungen")
public class BestellungWebService {
@Context
private SecurityContext securityContext;
@Produces({ MediaType.APPLICATION_JSON,
MediaType.APPLICATION_XML })
@GET
public Response getAlleBestellungen() {
final String username =
securityContext.getUserPrincipal().getName();
try {
List<Bestellung> bestellungen = ...;
return Response.ok( bestellungen ).build();
}
catch (Exception e) {
return Response.serverError()
.entity( e.getMessage() ).build();
}
}
}
Mit beispielsweise
Wget könnte dieser Service dann wie folgt aufgerufen werden, wenn der Applikationspfad auf "rest" gemappt ist:
wget http://localhost:8080/meinewebapp/rest/bestellungen
Fast immer muss ein solcher Service abgesichert werden, damit nur bestimmte Anwender Zugriff darauf haben. Man könnte natürlich den UserPrincipal programmatisch auswerten, aber sofern die berechtigten Benutzer im Application-Server als User eingetragen sind, ist eine deklarative Absicherung einfacher - und erfordert keine Änderungen am Quelltext.
Am Beispiel von
GlassFish 3.1.2 schauen wir uns das an. Wir erlauben dem GlassFish-Admin-User, den Web-Service aufzurufen. Dazu sichern wir am Ende des Deployment-Deskriptors
web.xml
alle Ressourcen unterhalb
/rest
so ab, dass nur die "admin"-Rolle darauf zugreifen darf. Mit CONFIDENTIAL erzwingen wir zudem, dass die Übertragung der Daten per https (SSL bzw. TLS) stattfindet:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
...
<security-constraint>
<web-resource-collection>
<url-pattern>/rest/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>admin</role-name>
</auth-constraint>
<user-data-constraint>
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
</user-data-constraint>
</security-constraint>
<login-config>
<auth-method>BASIC</auth-method>
<realm-name>admin-realm</realm-name>
</login-config>
<security-role>
<role-name>admin</role-name>
</security-role>
</web-app>
Die BASIC-Authentication, bei der Benutzername und Passwort im Klartext (!) übertragen werden, ist hier dennoch in Ordnung, weil wir gleichzeitig mit https die verschlüsselte Datenübertragung aktivieren.
Die berechtigten Anwender müssen im "admin-realm" eingetragen sein, das ist der Realm mit allen GlassFish-Admins (der in der Standardinstallation den User "admin" enthält). Als Beispiel für einen Security-Realm ist das hier ausreichend; in der Praxis wird man aus Sicherheitsgründen eher einen separaten Realm im GlassFish konfigurieren und in
web.xml
referenzieren.
Nun müssen wir dem Application-Server noch – produktspezifisch – mitteilen, welche Benutzernamen aus dem Realm auf welche Rollen gemappt werden. Dies erfolgt in der Datei
glassfish-web.xml
(früher
sun-web.xml
):
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE glassfish-web-app PUBLIC
"-//GlassFish.org//DTD GlassFish Application Server 3.1 Servlet 3.0//EN"
"http://glassfish.org/dtds/glassfish-web-app_3_0-1.dtd">
<glassfish-web-app>
<context-root>meinewebapp</context-root>
<security-role-mapping>
<role-name>admin</role-name>
<principal-name>admin</principal-name>
</security-role-mapping>
</glassfish-web-app>
Fertig. Der Aufruf des Web-Services erfolgt über Wget nun mit
wget https://localhost:8081/meinewebapp/rest/bestellungen
--user ... --password ... --no-check-certificate
Ein Aufruf über unverschlüsseltes http ist nicht mehr möglich. Während wir hier der Einfachheit halber auf die Prüfung des https-Zertifikats verzichten, sollte man in der Praxis besser ein geeignetes eigenes Zertifikat im Server installieren und auf Client-Seite verwenden.