This time I want to write about the use of JAAS (Java Authentification and Authorization Service) for web applications. To begin, consider simple access control to web resources and authorization. I will try to uncover the basic idea, and also give a hint on the way of deployment (later it will be clear from the text what the problem is).
Before proceeding to practice, you should highlight a few basic concepts and a general scheme for controlling access to web resources.
')
When the web container “tracks” sessions of all machines connected to it,
roles can be associated with each such session, as well as a
user principal . Each role is associated with the possibility or inability to access a certain set of web resources, as well as the implementation of certain actions (
privileged action ).
Initially, a user session is not associated with any
user principal or role. This means that if access to a certain set of web resources is
constrained (constraint), then it will not have access to these resources.
In order for the user session to become associated with some set of
roles , the user must be
authorized .
Authorization is done using JAAS. During authorization, the user sends his
credits (
credentials , in the simple case it can be a login: password or user certificate). On the server, two entities are associated with the control process, namely the
Realm and
Login Module .
The Login Module checks the user's connection with some set
of user
groups (not to be confused with
roles ). In addition, authorization can be successful, however, the user can not be associated with any of the groups.
A web application can determine the relationship between
groups and
roles . The application sets this correspondence using sun-web.xml.
The Login Module and
Realm are not part of the web application, but are shared resources of the application server, so they must be in the server classpath and must be registered to the server accordingly (in login.conf and domain.xml). The application selects
realm by name via web.xml.
The application defines a set of
restrictions on access to its resources via web.xml and indicates which web resources (by URL pattern (url pattern)) can be available for which kinds of requests (get, post, head, etc) and for which
roles . In addition, the application determines the authorization method. For example, you can specify a login page or specify the use of standard HTTP authorization (the browser shows an authorization window).
Executing the
restrictions will be done by the web container, so the application does not need to perform additional checks. Since the restrictions are imposed on the URL pattern, it does not matter what falls under this pattern, whether it is a servlet, JSP, or even nothing (404). Even if there is nothing (404), and the
restriction requires that the user has a certain
role , he will have to go through authorization in order to see that there is nothing there.
Each
realm can be associated with some
Login Module . For this, a special pair “jaas-context” -> “login-module-name” indicates for it in the properties (properties). Sometimes the
Realm and
Login Module work only in pairs. For example, if we want to write our
Login Module and
Realm , then it is quite possible that our
Realm could work only with our
Login Module and with no other.
For example, let's create a simple pair for password authorization.
Let's start by writing
Realm :
public class TestRealm extends AppservRealm {
@Override
public String getAuthType () {
return "magic";
}
@Override
public Enumeration getGroupNames (String string) throws InvalidOperationException, NoSuchUserException {
return Collections.enumeration (Arrays.asList ("users", "guests"));
}
@Override
protected void init (Properties props) throws BadRealmException, NoSuchRealmException {
super.init (props);
System.err.println ("Realm :: Hello !!");
if (props.containsKey (JAAS_CONTEXT_PARAM))
setProperty (JAAS_CONTEXT_PARAM, props.getProperty (JAAS_CONTEXT_PARAM));
}
@Override
public AuthenticationHandler getAuthenticationHandler () {
return null;
}
}
The getGroupNames method returns enum with possible
groups . The init method is called during initialization (in fact, at the start of the domain). In it, we can perform the required initialization (for example, get the necessary paretres from props, and then save them somewhere for later use). It is very important not to forget to pass the JAAS_CONTEXT_PARAM property ("jaas-context"), otherwise the realm will not work.
Next, we describe our
Login Module . We will use password authentication.
public class TestLoginModule extends AppservPasswordLoginModule {
@Override
protected void authenticateUser () throws LoginException {
if (_username == null || _password == null)
throw new LoginException ("Username of password is null");
if ("user" .equals (_username) && "user-pass" .equals (_password))
commitUserAuthentication (new String [] {"users"});
else if ("guest" .equals (_username) && "guest" .equals (_password))
commitUserAuthentication (new String [] {"guests"});
else
throw new LoginException ("bad login / password");
}
}
In this example, we simply “hammered” the password options. Instead, one could “ask” the database or the LDAP server, and maybe even the one and the other.
After the build, we will get a simple jar with two classes (for a successful build in the classpath, you must have javaee.jar, appserv-rt.jar and appserv-ext.jar and the directory glassfish-v2 / lib).
After the build, our jar can be put, for example, in the domain lib directory.
Next, the
Realm and
Login Module should be registered in the domain.
First, you need to take care that our .jar is in the server classpath. Probably, there are several ways to do it properly, however, in the documentation on the topic one is described, here we will use it.
The domain.xml file, among other things, describes the jvm parameters. There you can find the java-config tag. It has a classpath-suffix attribute. It is usually empty. That's where we need to "fit".
For example:
...
<java-config classpath-suffix = "/ home / cy6ergn0m / .domains / domain1 / lib / MyTestRealm.jar" ...
....
After that, you need to add our
Login Module . This is done in a text file login.conf in the domain directory (domain_dir / conf / login.conf). You can simply add to the end, for example, like this:
testRealmLM {
cy6ergn0m.auth.TestLoginModule required;
};
When the login method is described, you can register our named
Realm . It can be added to domain.xml or via web admin console. We will do this through domain.xml.
Among the top-level tags you can find the security-service tag. Usually it already has several auth-realm tags. We will add yours there too. However, it is important that our tag is after the auth-realms that are already there, since The domain.xml schema requires the auth-realm to be in the beginning.
<auth-realm classname = "cy6ergn0m.auth.TestRealm" name = "testRealm">
<property name = "jaas-context" value = "testRealmLM" />
<property name = "auth-type" value = "magic" />
</ auth-realm>
Among the parameters (properties) we can pass and others. These properties then fall into the init method of our
Realm . Here we can put, for example, the address \ port of the server or some other settings.
It should also be noted that you can create several realms, but with different parameters.
The Login Module, in turn, can always find out which of the realms it is using with the _currentRealm protected field. Also, in this way it can get some specific settings from the realm (host, port, etc.).
Now, you should restart the domain. When starting a domain in the log, we will be able to see our record: “Realm :: Hello !!”. If this does not happen, then most likely you did something wrong.
So, our authorization module is ready for use. Let's try to use it.
To do this, we write a tiny web application that will use our module.
Let's create several web pages, for the beginning we will do three: index.html, secret.html and for-guests.html.
Make links from index.html to the other two.
After that, we will create
restrictions on access to our “scary secret” pages. To do this, you have to edit the files web.xml and sun-web.xml.
Our authorization module is able to authorize users of two
groups : “user” and “guest. Let's create two
roles corresponding to these
groups .
Open the sun-web.xml file and add these relationships. To do this, add two sections of <security-role-mapping> to it:
<? xml version = "1.0" encoding = "UTF-8"?>
<! DOCTYPE sun-web-app PUBLIC "- // Sun Microsystems, Inc.//DTD Application Server 9.0 Servlet 2.5 // EN" "http://www.sun.com/software/appserver/dtds/sun-web -app_2_5-0.dtd ">
<sun-web-app error-url = "">
<context-root> / TestRealmClient </ context-root>
<security-role-mapping>
<role-name> user </ role-name>
<group-name> users </ group-name>
</ security-role-mapping>
<security-role-mapping>
<role-name> guest </ role-name>
<group-name> guests </ group-name>
</ security-role-mapping>
<class-loader delegate = "true" />
<jsp-config>
<property name = "keepgenerated" value = "true">
<description> Keep a copy of the generated servlet class' java code. </ description>
</ property>
</ jsp-config>
</ sun-web-app>
Now we will describe the roles that we want to use in web.xml:
<security-role>
<description />
<role-name> user </ role-name>
</ security-role>
<security-role>
<description />
<role-name> guest </ role-name>
</ security-role>
Now you can create
constraints . To do this, we fix web.xml and add restrictions to it:
<security-constraint>
<display-name> Constraint1 </ display-name>
<web-resource-collection>
<web-resource-name> secrets </ web-resource-name>
<description />
<url-pattern> / secret * </ url-pattern>
</ web-resource-collection>
<auth-constraint>
<description />
<role-name> user </ role-name>
</ auth-constraint>
</ security-constraint>
<security-constraint>
<display-name> Constraint2 </ display-name>
<web-resource-collection>
<web-resource-name> guests </ web-resource-name>
<description />
<url-pattern> / for-guests * </ url-pattern
</ web-resource-collection>
<auth-constraint>
<description />
<role-name> guest </ role-name>
<role-name> user </ role-name>
</ auth-constraint>
</ security-constraint>
Thus, we have determined that access to the / secret * pages will be only for users with the user
role , and / for-guests * addresses for users with the user and guest
roles . This means that a user with the user
role can visit all pages, a user with the guest role can only index.html and for-guests.html, and “no one” only index.html.
Now we need to specify the
realm and authorization method. To begin with, we will choose an easy way (HTTP authorization) - BASIC. To do this in web.xml we specify:
<login-config>
<auth-method> BASIC </ auth-method>
<realm-name> testRealm </ realm-name>
</ login-config>
So at the end we’ll have the following web.xml:
<? xml version = "1.0" encoding = "UTF-8"?>
<web-app version = "2.5" 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_2_5.xsd">
<session-config>
<session-timeout>
thirty
</ session-timeout>
</ session-config>
<welcome-file-list>
<welcome-file> index.html </ welcome-file>
</ welcome-file-list>
<security-constraint>
<display-name> Constraint1 </ display-name>
<web-resource-collection>
<web-resource-name> secrets </ web-resource-name>
<description />
<url-pattern> / secret * </ url-pattern>
</ web-resource-collection>
<auth-constraint>
<description />
<role-name> user </ role-name>
</ auth-constraint>
</ security-constraint>
<security-constraint>
<display-name> Constraint2 </ display-name>
<web-resource-collection>
<web-resource-name> guests </ web-resource-name>
<description />
<url-pattern> / for-guests * </ url-pattern
</ web-resource-collection>
<auth-constraint>
<description />
<role-name> guest </ role-name>
<role-name> user </ role-name>
</ auth-constraint>
</ security-constraint>
<login-config>
<auth-method> BASIC </ auth-method>
<realm-name> testRealm </ realm-name>
</ login-config>
<security-role>
<description />
<role-name> user </ role-name>
</ security-role>
<security-role>
<description />
<role-name> guest </ role-name>
</ security-role>
</ web-app>
In the web.xml editor in netbeans, it will look like this:

Is done. Now the application can be started and checked. If you try to follow one of the links, an authorization window will appear.
Let's complicate the task. Suppose we want to make our own login page ourselves.
We can achieve this in two ways. We will consider both.
First we need to change the authorization method in web.xml. Previously, you specify the authorization method BASIC. Now we will select the login form.
<login-config>
<auth-method> FORM </ auth-method>
<realm-name> testRealm </ realm-name>
<form-login-config>
<form-login-page> /login.jsp </ form-login-page>
<form-error-page> /login.jsp?fail </ form-error-page>
</ form-login-config>
</ login-config>
Now we will create the login form login.jsp according to the first method, which is simpler:
<% @ page contentType = "text / html" pageEncoding = "windows-1251"%>
<! DOCTYPE HTML PUBLIC "- // W3C // DTD HTML 4.01 Transitional // EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv = "Content-Type" content = "text / html; charset = windows-1251">
<title> JSP Page </ title>
</ head>
<body>
<h1> Hello World! </ h1>
<h2> Web container's login method </ h2>
<form action = "j_security_check" method = "post">
<input type = "text" name = "j_username" />
<input type = "password" name = "j_password" />
<input type = "submit" />
</ form>
</ body>
</ html>
Is done. Now, when an unauthorized user tries to access the “secure” pages, the web container will “redirect” it to our login form. After logging in, the user will receive the necessary
roles and be able to visit the “protected” pages.
In some cases, we need more control over the authorization process and some j_security_check that we do not control us does not suit us. In this case, we can always use the second method. We can manually login. Since Glassfish uses a non-standard login module (it is not by chance that we implemented AppservPasswordLoginModule instead of the standard JAAS interface), we cannot use the usual authorization method via LoginContext, but we must use a non-standard server API (we can be more exact, but this is more complicated and still not portable).
We will create a LoginServlet that will use ProgrammaticLogin from the Glassfish API [7]. Note that in order for the class to be visible, you need to add the javaee.jar, appserv-rt.jar and appserv-ext.jar files to the classpath during the build (and in your IDE, the same thing is possible).
public class LoginServlet extends HttpServlet {
@Override
protected void doGet (HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.sendError (HttpServletResponse.SC_FORBIDDEN);
}
@Override
protected void doPost (HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
ProgrammaticLogin pl = new ProgrammaticLogin ();
try {
Boolean rc = pl.login (request.getParameter ("name"), request.getParameter ("pass"), "testRealm", request, response, true);
if (rc! = null && rc.booleanValue ()) {
response.sendRedirect ("index.jsp");
return;
}
} catch (Exception ex) {
Logger.getLogger (LoginServlet.class.getName ()) .log (Level.SEVERE, null, ex);
}
response.sendRedirect ("login.jsp? fail");
}
@Override
public String getServletInfo () {
return "Login servlet";
}
}
And the login.jsp page will be changed accordingly so that the form parameters are sent to LoginSerlvet
<% @ page contentType = "text / html" pageEncoding = "windows-1251"%>
<! DOCTYPE HTML PUBLIC "- // W3C // DTD HTML 4.01 Transitional // EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv = "Content-Type" content = "text / html; charset = windows-1251">
<title> JSP Page </ title>
</ head>
<body>
<h1> Hello World! </ h1>
<h2> Web container's login method </ h2>
<form action = "login" method = "post">
<input type = "text" name = "name" />
<input type = "password" name = "pass" />
<input type = "submit" />
</ form>
</ body>
</ html>
Thus, we will get a login servlet, which we can use in any way convenient for us, for example, we can still do a captcha, or check the login with an Ajax and much more.
In conclusion, I want to highlight another point. The fact is that the registration of the
Realm and
Login Module is not too simple, I would like to be able to automatically register them in an arbitrary domain.
To implement the idea, you can make your own custom Task for Ant. You can write it in Java and perform all the necessary actions.
For duty of duty, I had to write a similar thing, but for obvious reasons I can’t give this code here. However, I will give a few hints about its content. You can easily add a couple of lines to the login.conf file through FileWriter, created with the append = true parameter. Manipulating domain.xml is more difficult, however, it’s XML, besides it’s a foreseeable size ... so you can read all XML into the DOM tree using DocumentBuilder, then make the necessary corrections to the tree, add classpath-suffix and auth -realm, and then serialize the DOM tree in domain.xml back. I managed to keep within 200 lines together with checks of input parameters. Such a task can be included in the ant-scripts for creating the entire domain (if the domain is created by ant).
Despite the fact that I tested everything in glassfish v2, however, judging by various sources, it is possible to hope that for v3 this is also relevant, although it may require adjustments.
At this difficult article came to an end. Of course, much remains behind the scenes (privileged actions, user principals), but I hope that I managed to shed light on this difficult niche of web development.
Successes.
CG.
References aka links:
1. Authentication Using Custom Realms in Sun Java System Application Server
developers.sun.com/appserver/reference/techart/as8_authentication2. Using JAAS with Tomcat
www.kopz.org/public/documents/tomcat/jaasintomcat.html3. JAAS Tomcat Login Module
www.owasp.org/index.php/JAAS_Tomcat_Login_Module4. JavaTM Authentication and Authorization Service (JAAS). LoginModule Developer's Guide.
java.sun.com/javase/6/docs/technotes/guides/security/jaas/JAASLMDevGuide.html5. Sun GlassFish Enterprise Server v3 Application Development Guide
Chapter 5 Securing Applications
docs.sun.com/app/docs/doc/820-7695/beabg?l=en&q=glassfish+JAAS&a=view6. Branch on the web login forum via j_security_check
www.sql.ru/Forum/actualthread.aspx?tid=6245087. Javadoc Class ProgrammaticLogin
glassfish.dev.java.net/nonav/docs/v3/api8. Sun GlassFish Enterprise Server v3 Application Development Guide
Programmatic Login
docs.sun.com/app/docs/doc/820-7695/beacm?l=en&a=view9. Implementing JAAS-based Authentication for ADF Faces Applications on OC4J 10.1.3
technology.amis.nl/blog/1426/implement-jaas-based-authentication-and-authorization-for-adf-faces-applications-on-oc4j-101310. Securing a Web Application on Glassfish using JAAS - Part 1 and 2
www.developinjava.com/features/47-enterprise-java/105-securing-a-web-application-on-glassfish-using-jaas.htmlwww.developinjava.com/features/47-enterprise-java/106-securing-a-web-application-on-glassfish-using-jaas-pt-2.html11. Glassfish javadoc: Class AppservRealm
glassfish.java.net/nonav/javaee5/api/com/sun/appserv/security/AppservRealm.html