JAAS : Java Authentication and Authorization Service

Introductie

Java Authentication and Authorization Service (JAAS) is onderdeel vanaf JEE versie 1.4.
Het voorziet in autorisatie en authenticatie. Dit wordt zodanig transparant gedaan dat de applicatie daar geen weet van hoeft te hebben. Eigenlijk klopt dat niet, want als de configuratie met de annotatie techniek wordt gedaan, dan zit dit dus wel degelijk in de applicatie.

Basic HTTP Authenticatie

Basic HTTP Authenticatie is de eenvoudigste vorm van authenticatie: standaard user/password. In de web.xml deployment descriptor wordt de volgende configuratie opgenomen:

  <login-config>
 	<auth-method>DIGEST</auth-method>
	<realm-name>Editor Login</realm-name>
  </login-config>

Met wordt het type authenticatie gespecificeerd, in dit geval “DIGEST”.
Andere mogelijkheden zijn :

  • NONE : Geen authenticatie
  • BASIC: Basic user/password authenticatie. Password wordt verzonden in plain tekst
  • DIGEST : idem, maar password wordt niet verzonden in plain tekst, maar als digest
  • FORM : Authenticatie via eigen form definitie
  • CLIENT-CERT : Authenticatie tussen browser en server, dus niet tussen browser en applicatie.

De gebruikte <realm-name> is niet echt van belang. De naam hierin wordt in de default http Login dialog gebruikt.

Gebruik eigen gedefinieerd login form

Als in de login-config in web.xml “FORM” wordt gebruikt als auth-method, dan wordt een eigen form gebruikt i.p.v. de browser standaard. Dan wordt de configuratie als volgt:

  <login-config>
 	<auth-method>FORM</auth-method>
	<realm-name>Login Departments</realm-name>
	<form-login-config>
		<form-login-page>/login.html</form-login-page>
		<form-error-page>/error.html</form-error-page>
	</form-login-config>
  </login-config>

In de <form-login-config> worden de login en error page gespecificeerd

Hieronder een voorbeeld van een login form. Belangrijk is de standaard action “j_security_check” en user/password “j_username” en “j_password”.

<body>
	<h1>Login</h1>
	<form action="j_security_check" method="post">
		username: <input type="text" name="j_username"/>
		<br/>
		password: <input type="text" name="j_password"/>
		<br/>
		<input type="submit" name="submit" value="Login"/>
	</form>
</body>
</html>

Standaard TomCat Login module

Om gebruik te maken van de standaard login faciliteit van TomCat is in server.xml de volgende Realm gedefinieerd:

<Realm className="org.apache.catalina.realm.LockOutRealm">
   <Realm className="org.apache.catalina.realm.UserDatabaseRealm"
    resourceName="UserDatabase"/>
</Realm>

Deze Realm gebruikt de UserDatabase die is geconfigureerd in de global JNDI resources met de key “UserDatabase”. Standaard is die ingesteld op gebruik van het tomcat-users.xml bestand in de /conf directory. Deze global JNDI resource configuratie staat ook in server.xml:

<GlobalNamingResources>
    <Resource auth="Container" description="User database that can be updated and saved"
        factory="org.apache.catalina.users.MemoryUserDatabaseFactory" name="UserDatabase"
        pathname="conf/tomcat-users.xml" type="org.apache.catalina.UserDatabase"/>
</GlobalNamingResources>

Hieronder een voorbeeld van het tomcat-users.xml bestand:

<tomcat-users>
<role rolename="role1"/>
<role rolename="role2"/>
<role rolename="manager-gui"/>

<user username="user1" password="secret1" roles="role1" />
<user username="user2" password="secret2" roles="role1,role2" />
<user username="admin" password="admin" roles="manager-gui,CalcUser" />
<user username="CalcUser" password="CalcUser" roles="CalcUser" />
<user username="Guest" password="Guest" roles="CalcUser" />
</tomcat-users>

De rol “manager-gui” is in tomcat standaard bedoeld voor de /manager/html web applicatie.

JAAS Login Module

Om gebruik te maken van de Jaas LoginModule binnen TomEE (TomCat) zijn de volgende configuraties nodig.

Stap 1 : Maak een login.config bestand
Voeg een bestand login.config toe aan de /conf directory (in Eclipse aanwezig in de betreffende server map voor TomCat / TomEE). Dit bestand is Jaas standaard en configureert de implementatie van de Jaas LoginModule. Voor TomCat/TomEE zijn er 2 mogelijke implementaties : de PropertiesLoginModule en de SQLLoginModule. De PropertiesLoginModule maakt heel eenvoudig gebruik van user/password en role gegevens in tekstbestanden.
De SQLLoginModule haalt deze gegevens uit een database.

Hieronder het voorbeeld van een login.conf bestand voor een PropertiesLoginModule.

PropertiesLoginModule {
    org.apache.openejb.core.security.jaas.PropertiesLoginModule required
    Debug=false
    UsersFile="users.properties"
    GroupsFile="groups.properties";
};

Deze loginmodule specificeert de users en groups in de bestanden “groups.properties” en “groups.properties” die ook moeten worden toegevoegd in de /conf directory , bijv:

groups.properties :
Role1 = user1, user2
CalcUser = admin, Guest

users.properties :
user1 = passw1
user2 = passw2
admin = admin
Guest = Guest

Hieronder een voorbeeld van een SQLLoginModule:

SQLLoginModule {
    org.apache.openejb.core.security.jaas.SQLLoginModule required
    jdbcURL="jdbc:mysql://localhost:3306/employeedb"
    jdbcDriver="com.mysql.jdbc.Driver"
    jdbcUser="username"
    jdbcPassword="secret"
    userSelect="SELECT userName, password FROM user WHERE userName = ?"
    groupSelect="SELECT u.userName, r.name  FROM user AS u, userrole AS ur,
    role AS r WHERE ur.userId = u.userId AND
    ur.roleId = r.roleId AND u.userName = ?;";
};

Stap 2 : Definieer login.conf
Nadat het login.conf bestand is gemaakt, moet deze ook worden geconfigureerd.
Er is hier een groot verschil of er wordt gewerkt met de testomgeving vanuit Eclipse of de runtime TomCat / TomEE omgeving.
Binnen de runtime omgeving wordt de volgende optie toegevoegd in de omgevingsvariabele “CATALINA_OPTS”. Dit kan worden gedaan in het startup script (catalina.sh of cataline.bat).

-Djava.security.auth.login.config=$CATALINA_BASE/conf/login.config

In de Eclipse test omgeving waar gebruik wordt gemaakt van een TomCat/ TomEE server met workspace metadata gaat het heel anders. In dat geval worden de configuratie bestanden gebruikt die voor de server in de project explorer onder de server directories zijn opgeslagen. Het pad naar login.config ziet er in dat geval heel anders uit.
Open de server in Eclipse, en klik op “Open lauch configuration”.
In de Arguments tab wordt de volgende optie toegevoegd:

-Djava.security.auth.login.config= "C:\Opdrachten\JEE6\EclipseWorkspace\.metadata\.plugins\org.eclipse.wst.server.core\tmp1\conf\login.config"

NB: Kies dit pad zorgvuldig t.o.v. andere argumenten die in de arguments tab staan.
Bij het creëren van een server worden paden automatisch ingesteld binnen\.metadata\.plugins\org.eclipse.wst.server.core\ . Het kan zijn dat er tmp1 gebruikt wordt , maar er kan ook wat anders zijn ingesteld.

Stap 3 : Definieer Realm
In de server.xml (ook in Eclipse aanwezig in de betreffende server map voor TomCat / TomEE) wordt de volgende toevoeging gedaan voor PropertiesLoginModule:

<Realm className="org.apache.catalina.realm.JAASRealm" appName="PropertiesLoginModule"
          userClassNames="org.apache.openejb.core.security.AbstractSecurityService$User"
          roleClassNames="org.apache.openejb.core.security.AbstractSecurityService$Group">
</Realm>

De SQLLoginModule heeft de volgende definitie nodig:

<Realm className="org.apache.catalina.realm.JAASRealm" appName="SQLLoginModule"
userClassNames="org.apache.openejb.core.security.jaas.UserPrincipal"
roleClassNames="org.apache.openejb.core.security.jaas.GroupPrincipal">
</Realm>

Deze wordt geplaatst binnen :

<Server port="8005" shutdown="SHUTDOWN">
   <Service name="Catalina">
      <Engine defaultHost="localhost" name="Catalina">
         <Realm ...></Realm>
      </Engine>
   </Service>
</Server>

Gebruik van programmatische login via de server

Uiteraard zijn de voorgaande voorbeelden met de web.xml deployment descriptor configuraties alleen van toepassing als er gebruik wordt gemaakt van JEE gebaseerde schermen dus JSP, JSF of Servlets. Bij gebruik van HTML en REST webservices hebben we daar niets aan. In dat geval moeten we een eigen login form maken. Nu niet zoals eerder beschreven een custom login form die door de basic authenticatie logic via web.xml config wordt aangeroepen.
We gaan nu uit van een form definitie die ergens rechts bovenin een HTML pagina is opgenomen en via JavaScript werkt. Bijv. ziet dat er als volgt uit :

De form definitie voor dit voorbeeld ziet er als volgt uit:

<form>
   <div style="width:50%;float:left">
      <label style="width:50%">gebruikersnaam: </label>
      <input id="inp1-username" type="text" />
   </div>
   <div style="width:50%;float:right">
      <label style="width:50%">wachtwoord: </label>
      <input id="inp1-password" type="password" />
   </div>
   <div style="clear: both">
      <input type="button"  name="login" value="Login" onclick="loginREST()" />
      <div style="width:75%;float:right">
		<a href="#" onclick="…">Registreer nieuw Account</a>
		<a href="#" onclick=”…">Wachtwoord vergeten?</a>
      </div>
   </div>
</form>

Een deel van de JavaScript is hier voor de overzichtelijkheid weggelaten. Belangrijkste is de onClick van de login button die de loginREST() functie aanroept. Dat is een JavaScript functie die de REST webservice voor login aanroept. Deze is geimplementeerd in de SecurityServiceImpl REST Webservice EJB Singleton bean in de register() method met het path /security/login. Voordat de JavaScript echter de webservice aanroept worden eerst user en password encrypt en als HEADER AUTH parameter meegestuurd. Met de volgende JavaScript functie wordt een token gemaakt :

function createTokenREST(user, password) {
	  userName = user;
	  var tok = user + ':' + password;
	  var hash = Base64.encode(tok);
	  return "Basic " + hash;
}

(zie http://www.webtoolkit.info/javascript-base64.html voor de gebruikte enctyptie lib).

In de JavaScript login functie wordt deze als volgt aangeroepen:

auth = createTokenREST($("#inp1-username").val(),$("#inp1-password").val());

waarna deze met de volgende call in de http request header wordt opgeslagen voordat de webservice wordt aangeroepen:

req.setRequestHeader('Authorization', auth);

Aan de Java backend kant bevindt zich de aangeroepen REST webservice login method:

@Path("/login")
@POST
public Response login(@Context HttpServletRequest req) {

String[] result = decodeToken(req.getHeader("Authorization"));

   if (result[0].equals("OK")) {

     if (checkPassword(result[1], result[2])) {
        try {
           req.login(result[1], result[2]);
           return Response.ok().entity(new Gson().toJson(
           new ReturnStatus(true,getroles(result[1])))).build();
	 } catch (ServletException e) {
           e.printStackTrace();
           return Response.ok().entity(new Gson().toJson(
           new ReturnStatus(false,e.getLocalizedMessage()))).build();
	 }
     } else {
         return Response.ok().entity(new Gson().toJson(
         new ReturnStatus(false,"user niet gevonden (null)"))).build();
     }
   } else {
         return Response.ok().entity(new Gson().toJson(
         new ReturnStatus(false,result[1]))).build();
   }
}

Eerst zal deze het vanuit JavaScript meegestuurde token decrypten en gebruikt daarvoor de (custom) functie decodeToken() waaraan de uit de header gelezen Authorization parameter wordt meegegeven. Deze functie geeft een array van Strings terug met als eerste de status “OK” (of niet). Als OK dan zijn de volgende strings in de array de gedecrypte user en password.

Het belangrijkste wat hier nu gebeurt is dat van de HttpServletRequest de login() method wordt aangeroepen. Deze functie is nieuw in JEE6 en in de servlet 3.0 standaard.
De login() method zal een login context aanmaken en deze via een cookie in de session opslaan.

NB: Dit gaat niet zomaar goed. In geval van basic authentication met standaard Java schermen wordt de session met security cookie automatisch aangemaakt. Dat gebeurt hier niet en zullen we zelf nog moeten doen. De keuze hiervoor is gemaakt om dat in de Servlet Filter XHRFilter te doen met de getSession() call :

public class XHRFilter implements javax.servlet.Filter {

  @Override
  public void doFilter( ServletRequest req,
 			     ServletResponse resp,
     FilterChain chain) throws IOException, ServletException {

     HttpServletRequest request = (HttpServletRequest) req;
     HttpServletResponse response = (HttpServletResponse) resp;

     HttpSession ses = request.getSession();
     chain.doFilter(req, resp);
  }
}

Wat de method decodeToken() doet is het decrypten van het token.
Het belangrijkste deel is de onderstaande code:

    public String[]  decodeToken(String authHeader) {

        String result[] = new String[3];
        result[0] = "ERROR";

        if (authHeader != null) {
            System.out.println("Authorization Header gevonden");
            StringTokenizer st = new StringTokenizer(authHeader);
            if (st.hasMoreTokens()) {
                System.out.println("Tokens gevonden");

         	   String basic = st.nextToken();

               if (basic.equalsIgnoreCase("Basic")) {
                  System.out.println("Basic Authenticatie gevonden");
                  String credentials = st.nextToken();
                  System.out.println("Credentials gevonden : " + credentials);
                  try {
                      @SuppressWarnings("restriction")
                      BASE64Decoder decoder = new BASE64Decoder();

                      @SuppressWarnings("restriction")
                      String userPass = new String(decoder.decodeBuffer(credentials));

	              int p = userPass.indexOf(":");
 	              if (p != -1) {
	                    String userName = userPass.substring(0, p);
 	                    String password = userPass.substring(p+1);

Met een StringTokenizer worden de 2 onderdelen “Basic” en het encrypted deel “credentials” uit elkaar gehaald. Vervolgens wordt het encrypted deel gedecrypt.
NB: voor decryptie worden outdated functies gebruikt. Deze zullen vervangen moeten worden.

Na decryptie worden de met “:” gescheiden user en password uit elkaar gehaald en in de terug te geven string array opgeslagen.

Autorisatie

Autorisatie kan op 3 manieren worden gerealiseerd:

  • Via annotaties
  • Via deployment descriptor (web.xml) configuratie
  • Programmatisch

Autorisatie via Annotaties

Alleen servlets kunnen volledig met annotaties worden geconfigureerd. Andere resources zoals EJB’s hebben ook configuratie via web.xml nodig. Hieronder voorbeeld van Servlet annotatie:

@WebServlet("/CalculatorSecure")
@ServletSecurity(@HttpConstraint(transportGuarantee = TransportGuarantee.CONFIDENTIAL, rolesAllowed = {"CalcUser"}))
public class CalculatorServletSecure extends HttpServlet {}

Binnen de @ServletSecurity annotatie zorgt @HttpConstraint ervoor dat de request uri /CalculatorSecure alleen toegankelijk is voor geauthenticeerde gebruikers die geauthoriseerd zijn met de rol “CalcUser”.

NB: De volgende imports zijn hiervoor essentieel. Met name HttpConstraint en TransportGuarantee worden niet door Eclipse automatisch opgelost.

import javax.servlet.annotation.ServletSecurity;
import javax.servlet.annotation.HttpConstraint;
import javax.servlet.annotation.ServletSecurity.TransportGuarantee;

Het volgende voorbeeld laat zien hoe authorisatie rollen met de @rolesAllowed annotatie worden ingesteld voor een EJB session bean.

@Stateful
public class Movies {

    @RolesAllowed({"Employee", "Manager"})
    public void addMovie(Movie movie) throws Exception {}
}

Autorisatie via Deployment Descriptor (web.xml) configuratie

Hieronder een voorbeeld van autorisatie configuratie in web.xml:

  <security-constraint>
	<web-resource-collection>
	    	<web-resource-name>CalculatorSecure</web-resource-name>
	    	<url-pattern>/CalculatorSecure</url-pattern>
	</web-resource-collection>
	<auth-constraint>
		<role-name>CalcUser</role-name>
		<role-name>Guest</role-name>
	</auth-constraint>
	<user-data-constraint>
	    <transport-guarantee>CONFIDENTIAL</transport-guarantee>
       </user-data-constraint>
  </security-constraint>

Met <security-role> en <role-name> worden de rollen gedefinieerd t.b.v. autorisatie.

Binnen <security-constraint> worden de te beveiligen uri paden gedefinieerd en de vereiste rol of rollen daarvoor.

De user-data-constraint en transport-guarantee definieert gebruik van al dan niet SSL / HTTPS:

  • Als NONE wordt gedefinieerd of de hele user-data-constraint wordt weggelaten, dan wordt gewoon http gebruikt.
  • Als CONFIDENTIAL wordt gebruikt dan wordt HPPTS / SSL afgedwongen.
This entry was posted in JEE, Security and tagged , . Bookmark the permalink.

Geef een reactie

Je e-mailadres wordt niet gepubliceerd.

De volgende HTML tags en attributen zijn toegestaan: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>