Die wohl einfachste Methode eine Java EE Anwendung ab zu sichern ist durch eine Basic Authentifikation die durch einen entsprechenden Eintrag in der web.xml aktiviert wird. Anfragen ohne berechtigen Benutzer führen dann in der Antwort zu einem 401 Fehler der den Browser dazu veranlasst einen Dialog an zu zeigen der um die Eingabe der Benutzerdaten bittet. Doch was wenn wir diesen Dialog unterbinden wollen und stattdessen z.B. eine eigene Möglichkeit bieten wollen einen Login durch zu führen?
Ein einfach gehaltenes Beispiel zur Basic Authentifizierung
web.xml:
<security-constraint> <web-resource-collection> <web-resource-name>webresources</web-resource-name> <url-pattern>/*</url-pattern> </web-resource-collection> <auth-constraint> <role-name>admin</role-name> <role-name>customer</role-name> </auth-constraint> </security-constraint> <login-config> <auth-method>BASIC</auth-method> <realm-name>ApplicationRealm</realm-name> </login-config> <security-role> <role-name>admin</role-name> </security-role> <security-role> <role-name>customer</role-name> </security-role>
Eine solche Definition hat zur Folge, dass der angesprochene Dialog vom Browser angezeigt wird:
Und hier fangen die Probleme an. Der Browser selbst erkennt den Status (401) und prüft auf das Vorhandensein eines WWW-Authenticate-Headers. Ist dies der Fall zeigt der Browser den dargestellten Dialog an. Auf Clientseite, z.B. einer AngularJS Anwendung, hat der Entwickler keinerlei Chance auf einen solchen Fehler zu reagieren da die Response gar nicht zum aufrufenden Code weiter gegeben wird. Die Lösung des Problems besteht darin auf Seite des Servers sicher zu stellen das in einem solchen Fehlerfall ein anderer Statuscode geliefert wird als der Standard es vorsieht. Wer hier an einen einfachen Servlet-Filter denkt liegt leider falsch, da auch ein Servlet-Filter in einem solchen Fall auf Server Seite nicht angesprochen wird. Trotzdem bietet uns der Java EE Standard eine einfache Möglichkeit auf 401-Fehler zu reagieren
error-page
Die web.xml unserer Anwendung bietet uns die Möglichkeit eigene Fehlerseiten zu definieren um auf spezifische Fehler zu reagieren (basierend auf Statuscode oder Excption-Typ). Dieser Konfigurationswert darf jedoch nicht nur eine statische Seite als Wert annehmen sondern auch ein Servlet. Das machen wir uns zu Nutzen um 401 Fehler auf einen entsprechend eigens definieren Statuscode zu setzen.
<error-page> <error-code>401</error-code> <location>/AccessExceptionHandler</location> </error-page>
AccessExceptionHandler:
@WebServlet("/AccessExceptionHandler") public class AccessExceptionHandler extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { processError(request, response); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { processError(request, response); } private void processError(HttpServletRequest request, HttpServletResponse response) throws IOException { response.setContentType("text/html"); response.setStatus(455); PrintWriter out = response.getWriter(); out.write("User not allowed to perform specified Method"); } }
Der hier gewähle Status-Code “455” ist beliebig Wählbar, jedoch sollte darauf geachtet werden keine bereits im Standard definierten Code zu verwenden (s. HTTP-Spec-StatusCodes ).
Security-Regel auf Service – Ebene
Im obigen Beispiel legen wir unsere Security-Richtlinien auf konkrete Pfade innerhalb unserer Webanwendung. Denkbar wäre jedoch auch auf diese Deklaration zu verzichten und lediglich auf der Ebene unserer Services mit entsprechenden Annotationen zu arbeiten die für eine Benutzer / Rollen Prüfung sorgt.
Beispiel einer Service-EJB (die wiederrum über eine Rest-Ressource verwendet wird):
@RolesAllowed({"admin"}) public Customer updateCustomer(Customer customer) { return customerRepostory.updateCustomer(customer); }
Dieses Vorgehen hat einen entscheidenden Vorteil: Sowohl jegliche Rest-Zugriffe als auch Serverseitige Aufrufe von z.B. JSF, werden zentral durch diese Annotationen in der Berechtigung geprüft. Aufrufe mittels eines nicht berechtigten Users (oder keinem) führen im Falle eines Fehlers zu einer javax.ejb.EJBAccessException. Zusätzlich zu der obigen Definition eines Servlets als Error-Page können wir für unsere Rest-Schnittstelle diesen Fehler mittels eines Exception Mappers abhandeln (ohne diesen würde die Fehler mit einem Statu-Code 500 zurückgewiesen werden):
package de.gedoplan.jaxgui.system.security; import javax.ejb.EJBAccessException; import javax.ws.rs.core.Response; import javax.ws.rs.ext.ExceptionMapper; import javax.ws.rs.ext.Provider; @Provider public class AccessExceptionHandler implements ExceptionMapper<EJBAccessException> { @Override public Response toResponse(EJBAccessException exception) { return Response.status(455).type("text/html").build(); } }
Ergebnis:
Zugegeben, das Verhindern des Login-Popups ist komplizierter als gedacht. Dennoch bietet auch der Java Standard für diesen Fall entsprechende Mechanismen um in die Verarbeitung ein zu greifen. Der umgeschriebene Fehlercode wird in diesem Beispiel als “normale” Fehlerresponse an die Anwendung weiter gegeben sodass individuell darauf regiert werden kann.