JavaRush /Java Blog /Random EN /JAAS - Introduction to Technology (Part 1)
Viacheslav
Level 3

JAAS - Introduction to Technology (Part 1)

Published in the Random EN group
Access security has been implemented in Java for quite a long time and the architecture for providing this security is called JAAS - Java Authentication and Authorization Service. This review will try to unravel the mystery of what authentication, authorization is and what JAAS has to do with it. How JAAS is friends with the Servlet API, and where they have problems in their relationship.
JAAS - Introduction to Technology (Part 1) - 1

Introduction

In this review I would like to discuss such a topic as web application security. Java has several technologies that provide security: But our conversation today will be about another technology, which is called “Java Authentication and Authorization Service (JAAS)”. It is she who describes such important things as authentication and authorization. Let's look at this in more detail.
JAAS - Introduction to Technology (Part 1) - 2

JAAS

JAAS is an extension to Java SE and is described in the Java Authentication and Authorization Service (JAAS) Reference Guide . As the name of the technology suggests, JAAS describes how authentication and authorization should be performed:
  • " Authentication ": Translated from Greek, "authentikos" means "real, genuine". That is, authentication is a test of authenticity. That whoever is being authenticated is truly who they say they are.

  • " Authorization ": translated from English means "permission". That is, authorization is access control performed after successful authentication.

That is, JAAS is about determining who is requesting access to a resource and making a decision as to whether he can obtain this access. A small analogy from life: you are driving along the road and an inspector stops you. Please provide documents - authentication. Can you drive a car with documents - authorization. Or, for example, you want to buy alcohol in a store. First, you are asked for a passport - authentication. Next, based on your age, it is decided whether you are eligible to buy alcohol. This is authorization. In web applications, logging in as a user (entering a username and password) is authentication. And determining which pages you can open is determined by authorization. This is where “The Java Authentication and Authorization Service (JAAS)” helps us. When considering JAAS, it is important to understand several key concepts that JAAS describes: Subject, Principals, Credentials. Subject is the subject of authentication. That is, it is the bearer or holder of rights. In the documentation, Subject is defined as the source of a request to perform some action. The subject or source must be somehow described and for this purpose Principal is used, which in Russian is also sometimes called principal. That is, each Principal is a representation of a Subject from a certain point of view. To make it clearer, let's give an example: A certain person is a Subject. And the following can act as Principals:
  • his driver's license as a representation of a person as a road user
  • his passport, as a representation of a person as a citizen of his country
  • his foreign passport, as a representation of a person as a participant in international relations
  • his library card in the library, as a representation of a person as a reader attached to the library
In addition, Subject has a set of “Credential”, which means “identity” in English. This is how the Subject confirms that he is he. For example, the user's password can be the Credential. Or any object with which the user can confirm that he is really him. Let us now see how JAAS is used in web applications.
JAAS - Introduction to Technology (Part 1) - 3

Web application

So, we need a web application. The Gradle automatic project build system will help us create it. Thanks to the use of Gradle, we can, by executing small commands, assemble a Java project in the format we need, automatically create the necessary directory structure, and much more. You can read more about Gradle in the short overview: " A Brief Introduction to Gradle " or in the official documentation " Gradle Getting Started ". We need to initialize the project (Initialization), and for this purpose Gradle has a special plugin: “ Gradle Init Plugin ” (Init is short for Initialization, easy to remember). To use this plugin, run the command on the command line:
gradle init --type java-application
After successful completion, we will have a Java project. Let’s now open the build script of our project for editing. A build script is a file called build.gradle, which describes the nuances of the application build. Hence the name, build script. We can say that this is a project build script. Gradle is such a versatile tool, the basic capabilities of which are expanded with plugins. Therefore, first of all, let’s pay attention to the “plugins” block:
plugins {
    id 'java'
    id 'application'
}
By default, Gradle, in accordance with what we specified " --type java-application", has set up a set of some core plugins, that is, those plugins that are included in the distribution of Gradle itself. If you go to the "Docs" (that is, documentation) section on the gradle.org website, then on the left in the list of topics in the "Reference" section we see the " Core Plugins " section, i.e. section with a description of these very basic plugins. Let's choose exactly the plugins that we need, and not those that Gradle generated for us. According to the documentation, the " Gradle Java Plugin " provides basic operations with Java code, such as compiling source code. Also, according to the documentation, the " Gradle application plugin " provides us with tools for working with the "executable JVM application", i.e. with a java application that can be launched as a standalone application (for example, a console application or an application with its own UI). It turns out that we don’t need the “application” plugin, because... we don't need a standalone app, we need a web app. Let's delete it. As well as the “mainClassName” setting, which is known only to this plugin. Further, in the same " Packaging and distribution " section where the link to the Application Plugin documentation was provided, there is a link to the Gradle War Plugin. Gradle War Plugin , as stated in the documentation, provides support for creating Java web applications in war format. In WAR format means that instead of a JAR archive, a WAR archive will be created. It seems that this is what we need. Also, as the documentation says, "The War plugin extends the Java plugin". That is, we can replace the java plugin with the war plugin. Therefore, our plugin block will ultimately look like this:
plugins {
    id 'war'
}
Also in the documentation for the "Gradle War Plugin" it is said that the plugin uses an additional "Project Layout". Layout is translated from English as location. That is, the war plugin by default expects the existence of a certain location of files that it will use for its tasks. It will use the following directory to store web application files: src/main/webapp The behavior of the plugin is described as follows:
JAAS - Introduction to Technology (Part 1) - 4
That is, the plugin will take into account files from this location when building the WAR archive of our web application. In addition, the Gradle War Plugin documentation says that this directory will be the "root of the archive". And already in it we can create a WEB-INF directory and add the web.xml file there. What kind of file is this? web.xml- this is a "Deployment Descriptor" or "deployment descriptor". This is a file that describes how to configure our web application to work. This file specifies what requests our application will handle, security settings, and much more. At its core, it is somewhat similar to a manifest file from a JAR file (see " Working with Manifest Files: The Basics "). The Manifest file tells how to work with a Java Application (i.e. a JAR archive), and the web.xml tells how to work with a Java Web Application (i.e. a WAR archive). The very concept of "Deployment Descriptor" did not arise on its own, but is described in the document " Servlet API Specification"". Any Java web application depends on this "Servlet API". It is important to understand that this is an API - that is, it is a description of some interaction contract. Web applications are not independent applications. They run on a web server, which provides network communication with users. That is, a web server is a kind of “container" for web applications. This is logical, because we want to write the logic of a web application, i.e. what pages the user will see and how they should react to the user’s actions. And we don’t want to write code for how a message will be sent to the user, how bytes of information will be transferred and other low-level and very quality-demanding things. In addition, it turns out that web applications are all different, but the data transfer is the same. That is, a million programmers would have to write code for the same purpose over and over again.So the web server is responsible for some of the user interaction and data exchange, and the web application and the developer are responsible for generating that data. And in order to connect these two parts, i.e. web server and web application, you need a contract for their interaction, i.e. what rules will they follow to do this? In order to somehow describe the contract, what the interaction between a web application and a web server should look like, the Servlet API was invented. Interestingly, even if you use frameworks like Spring, there is still a Servlet API running under the hood. That is, you use Spring, and Spring works with the Servlet API for you. It turns out that our web application project must depend on the Servlet API. In this case, the Servlet API will be a dependency. As we know, Gradle also allows you to describe project dependencies in a declarative manner. Plugins describe how dependencies can be managed. For example, the Java Gradle Plugin introduces a "testImplementation" dependency management method, which says that such a dependency is only needed for tests. But the Gradle War Plugin adds a dependency management method “providedCompile”, which says that such a dependency will not be included in the WAR archive of our web application. Why don't we include the Servlet API in our WAR archive? Because the Servlet API will be provided to our web application by the web server itself. If a web server provides a Servlet API, then the server is called a servlet container. Therefore, it is the responsibility of the web server to provide us with the Servlet API, and it is our responsibility to provide the ServletAPI only at the time the code is compiled. That's why providedCompile. Thus, the dependencies block will look like this:
dependencies {
    providedCompile 'javax.servlet:javax.servlet-api:3.1.0'
    testImplementation 'junit:junit:4.12'
}
So, let's go back to the web.xml file. By default, Gradle does not create any Deployment Descriptor, so we need to do this ourselves. Let's create a directory src/main/webapp/WEB-INF, and in it we will create an XML file called web.xml. Now let's open the "Java Servlet Specification" itself and the chapter " CHAPTER 14 Deployment Descriptor ". As stated in "14.3 Deployment Descriptor", the Deployment Descriptor's XML document is described by the schema http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd . An XML schema describes what elements a document can consist of and in what order they should appear. Which are mandatory and which are not. In general, it describes the structure of the document and allows you to check whether the XML document is composed correctly. Now let's use the example from the chapter " 14.5 Examples ", but the scheme must be specified for version 3.1, i.e.
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd
Our empty one web.xmlwill look like this:
<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
         http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1">
    <display-name>JAAS Example</display-name>
</web-app>
Let's now describe the servlet that we will protect using JAAS. Previously, Gradle generated the App class for us. Let's turn it into a servlet. As stated in the specification in " CHAPTER 2 The Servlet Interface ", that " For most purposes, Developers will extend HttpServlet to implement their servlets ", that is, to make a class a servlet, you need to inherit this class from HttpServlet:
public class App extends HttpServlet {
	public String getGreeting() {
        return "Secret!";
    }
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.getWriter().print(getGreeting());
    }
}
As we said, the Servlet API is a contract between the server and our web application. This contract allows us to describe that when a user contacts the server, the server will generate a request from the user in the form of an object HttpServletRequestand pass it to the servlet. It will also provide the servlet with an object HttpServletResponseso that the servlet can write a response to it for the user. Once the servlet has finished running, the server will be able to provide the user with a response based on it HttpServletResponse. That is, the servlet does not directly communicate with the user, but only with the server. In order for the server to know that we have a servlet and for what requests it needs to be used, we need to tell the server about this in the deployment descriptor:
<servlet>
	<servlet-name>app</servlet-name>
	<servlet-class>jaas.App</servlet-class>
</servlet>
<servlet-mapping>
	<servlet-name>app</servlet-name>
	<url-pattern>/secret</url-pattern>
</servlet-mapping>
In this case, all requests /secretwill not be addressed to our one servlet by name app, which corresponds to the class jaas.App. As we said earlier, a web application can only be deployed on a web server. The web server can be installed separately (standalone). But for the purposes of this review, an alternative option is suitable - running on an embedded server. This means that the server will be created and launched programmatically (the plugin will do this for us), and at the same time our web application will be deployed on it. The Gradle build system allows you to use the " Gradle Gretty Plugin " plugin for these purposes:
plugins {
    id 'war'
    id 'org.gretty' version '2.2.0'
}
Additionally, the Gretty plugin has good documentation . Let's start with the fact that the Gretty plugin allows you to switch between different web servers. This is described in more detail in the documentation: " Switching between servlet containers ". Let's switch to Tomcat, because... it is one of the most popular in use, and also has good documentation and many examples and analyzed problems:
gretty {
    // Переключаемся с дефолтного Jetty на Tomcat
    servletContainer = 'tomcat8'
    // Укажем Context Path, он же Context Root
    contextPath = '/jaas'
}
Now we can run "gradle appRun" and then our web application will be available at http://localhost:8080/jaas/secret
JAAS - Introduction to Technology (Part 1) - 5
It is important to check that the servlet container is selected by Tomcat (see #1) and check at which address our web application is available (see #2).
JAAS - Introduction to Technology (Part 1) - 6

Authentication

Authentication settings often consist of two parts: settings on the server side and settings on the side of the web application that runs on this server. The security settings of a web application cannot but interact with the security settings of the web server, if for no other reason than that a web application cannot but interact with the web server. It was not in vain that we switched to Tomcat, because... Tomcat has a well-described architecture (see " Apache Tomcat 8 Architecture "). From the description of this architecture it is clear that Tomcat, as a web server, represents the web application as a certain context, which is called “ Tomcat Context ”. This context allows each web application to have its own settings, isolated from other web applications. Additionally, the web application can influence the settings of this context. Flexible and convenient. For a deeper understanding, we recommend reading the article " Understanding Tomcat Context Containers " and the Tomcat documentation section " The Context Container ". As stated above, our web application can influence our application's Tomcat Context using a /META-INF/context.xml. And one of the very important settings that we can influence is Security Realms. Security Realms is a kind of “security area”. An area for which specific security settings are specified. Accordingly, when using a Security Realm, we apply the security settings defined for this Realm. Security Realms are managed by a container, i.e. web server, not our web application. We can only tell the server which security scope needs to be extended to our application. The Tomcat documentation in the section " The Realm Component " describes a Realm as a collection of data about users and their roles for performing authentication. Tomcat provides a set of different Security Realm implementations, one of which is the " Jaas Realm ". Having understood a little terminology, let's describe the Tomcat Context in the file /META-INF/context.xml:
<?xml version="1.0" encoding="UTF-8"?>
<Context>
    <Realm className="org.apache.catalina.realm.JAASRealm"
           appName="JaasLogin"
           userClassNames="jaas.login.UserPrincipal"
           roleClassNames="jaas.login.RolePrincipal"
           configFile="jaas.config" />
</Context>
appName— application name. Tomcat will try to match this name with the names specified in the configFile. configFile- this is the "login configuration file". An example of this can be seen in the JAAS documentation: " Appendix B: Example Login Configurations ". In addition, it is important that this file will be searched first in resources. Therefore, our web application can provide this file itself. Attributes userClassNamesand roleClassNamescontain an indication of the classes that represent the user's principal. JAAS separates the concepts of "user" and "role" as two different concepts java.security.Principal. Let's describe the above classes. Let's create the simplest implementation for the user principal:
public class UserPrincipal implements Principal {
    private String name;
    public UserPrincipal(String name) {
        this.name = name;
    }
    @Override
    public String getName() {
        return name;
    }
}
We will repeat exactly the same implementation for RolePrincipal. As you can see from the interface, the main thing for Principal is to store and return some name (or ID) that represents the Principal. Now, we have a Security Realm, we have principal classes. It remains to fill the file from the " configFile" attribute, aka login configuration file. Its description can be found in the Tomcat documentation: " The Realm Component ".
JAAS - Introduction to Technology (Part 1) - 7
That is, we can place the JAAS Login Config setting in the resources of our web application and thanks to Tomcat Context we will be able to use it. This file must be available as a resource for ClassLoader, so its path should be like this: \src\main\resources\jaas.config Let's set the contents of this file:
JaasLogin {
    jaas.login.JaasLoginModule required debug=true;
};
It is worth noting that context.xmlthe same name is used here and in. This maps the Security Realm to the LoginModule. So, Tomcat Context told us which classes represent the principals, as well as which LoginModule to use. All we have to do is implement this LoginModule. LoginModule is perhaps one of the most interesting things in JAAS. The official documentation will help us in developing LoginModule: " Java Authentication and Authorization Service (JAAS): LoginModule Developer's Guide ". Let's implement the login module. Let's create a class that implements the interface LoginModule:
public class JaasLoginModule implements LoginModule {
}
First we describe the initialization method LoginModule:
private CallbackHandler handler;
private Subject subject;
@Override
public void initialize(Subject subject, CallbackHandler callbackHandler, <String, ?> sharedState, Map<String, ?> options) {
	handler = callbackHandler;
	this.subject = subject;
}
This method will save Subject, which we will further authenticate and fill with information about the principals. We will also save for future use CallbackHandler, which is given to us. With help, CallbackHandlerwe can request various information about the authentication subject a little later. You can read more about it CallbackHandlerin the corresponding section of the documentation: " JAAS Reference Guide: CallbackHandler ". Next, the method loginfor authentication is executed Subject. This is the first phase of authentication:
@Override
public boolean login() throws LoginException {
	// Добавляем колбэки
	Callback[] callbacks = new Callback[2];
	callbacks[0] = new NameCallback("login");
	callbacks[1] = new PasswordCallback("password", true);
	// При помощи колбэков получаем через CallbackHandler логин и пароль
	try {
		handler.handle(callbacks);
		String name = ((NameCallback) callbacks[0]).getName();
		String password = String.valueOf(((PasswordCallback) callbacks[1]).getPassword());
		// Далее выполняем валидацию.
		// Тут просто для примера проверяем определённые значения
		if (name != null && name.equals("user123") && password != null && password.equals("pass123")) {
			// Сохраняем информацию, которая будет использована в методе commit
			// Не "пачкаем" Subject, т.к. не факт, что commit выполнится
			// Для примера проставим группы вручную, "хардcodeно".
			login = name;
			userGroups = new ArrayList<String>();
			userGroups.add("admin");
			return true;
		} else {
			throw new LoginException("Authentication failed");
		}
	} catch (IOException | UnsupportedCallbackException e) {
		throw new LoginException(e.getMessage());
	}
}
It is important that loginwe should not change the Subject. Such changes should only occur in the confirmation method commit. Next, we must describe the method for confirming successful authentication:
@Override
public boolean commit() throws LoginException {
	userPrincipal = new UserPrincipal(login);
	subject.getPrincipals().add(userPrincipal);
	if (userGroups != null && userGroups.size() > 0) {
		for (String groupName : userGroups) {
			rolePrincipal = new RolePrincipal(groupName);
			subject.getPrincipals().add(rolePrincipal);
		}
	}
	return true;
}
It may seem strange to separate the method loginand commit. But the point is that login modules can be combined. And for successful authentication it may be necessary for several login modules to work successfully. And only if all the necessary modules have worked, then save the changes. This is the second phase of authentication. Let's finish with the abortand methods logout:
@Override
public boolean abort() throws LoginException {
	return false;
}
@Override
public boolean logout() throws LoginException {
	subject.getPrincipals().remove(userPrincipal);
	subject.getPrincipals().remove(rolePrincipal);
	return true;
}
The method abortis called when the first phase of authentication fails. The method logoutis called when the system logs out. Having implemented ours Login Moduleand configured it Security Realm, Now we need to indicate web.xmlthe fact that we want to use a specific one Login Config:
<login-config>
  <auth-method>BASIC</auth-method>
  <realm-name>JaasLogin</realm-name>
</login-config>
We specified the name of our Security Realm and specified the Authentication Method - BASIC. This is one of the types of authentication described in the Servlet API in section " 13.6 Authentication ". Remained n
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION