JavaRush /Blog Java /Random-ES /JAAS - Introducción a la tecnología (Parte 1)
Viacheslav
Nivel 3

JAAS - Introducción a la tecnología (Parte 1)

Publicado en el grupo Random-ES
La seguridad de acceso se ha implementado en Java desde hace bastante tiempo y la arquitectura para proporcionar esta seguridad se llama JAAS (Java Authentication and Authorization Service). Esta revisión intentará desentrañar el misterio de qué es la autenticación, la autorización y qué tiene que ver JAAS con ello. Cómo JAAS es amigo de la API de Servlet y dónde tienen problemas en su relación.
JAAS - Introducción a la Tecnología (Parte 1) - 1

Introducción

En esta revisión me gustaría discutir un tema como la seguridad de las aplicaciones web. Java cuenta con varias tecnologías que brindan seguridad: Pero nuestra conversación de hoy será sobre otra tecnología, que se llama "Servicio de autenticación y autorización de Java (JAAS)". Es ella quien describe cosas tan importantes como la autenticación y la autorización. Veamos esto con más detalle.
JAAS - Introducción a la tecnología (Parte 1) - 2

JAAS

JAAS es una extensión de Java SE y se describe en la Guía de referencia del Servicio de autenticación y autorización de Java (JAAS) . Como sugiere el nombre de la tecnología, JAAS describe cómo se debe realizar la autenticación y autorización:
  • " Autenticación ": Traducido del griego, "authentikos" significa "real, genuino". Es decir, la autenticación es una prueba de autenticidad. Que quienquiera que esté siendo autenticado sea verdaderamente quien dice ser.

  • " Autorización ": traducido del inglés significa "permiso". Es decir, la autorización es el control de acceso realizado después de una autenticación exitosa.

Es decir, JAAS trata de determinar quién solicita acceso a un recurso y tomar una decisión sobre si puede obtener este acceso. Una pequeña analogía de la vida: vas conduciendo por la carretera y un inspector te detiene. Proporcione documentos: autenticación. ¿Puedes conducir un coche con documentos - autorización? O, por ejemplo, quieres comprar alcohol en una tienda. Primero, se le solicita un pasaporte: autenticación. A continuación, en función de su edad, se decide si es elegible para comprar alcohol. Esta es la autorización. En las aplicaciones web, iniciar sesión como usuario (ingresando un nombre de usuario y contraseña) es autenticación. Y determinar qué páginas puede abrir está determinada por la autorización. Aquí es donde nos ayuda el “Servicio de autenticación y autorización de Java (JAAS)”. Al considerar JAAS, es importante comprender varios conceptos clave que describe JAAS: Materia, Principales, Credenciales. El sujeto es el sujeto de la autenticación. Es decir, es portador o titular de derechos. En la documentación, Asunto se define como la fuente de una solicitud para realizar alguna acción. El tema o la fuente deben describirse de alguna manera y para ello se utiliza Principal, que en ruso a veces también se llama principal. Es decir, cada Principal es una representación de un Sujeto desde un determinado punto de vista. Para que quede más claro, pongamos un ejemplo: una determinada persona es un Sujeto. Y podrán actuar como Principales:
  • su licencia de conducir como representación de una persona como usuario de la vía
  • su pasaporte, como representación de una persona como ciudadano de su país
  • su pasaporte extranjero, como representación de una persona como participante en las relaciones internacionales
  • su carné de biblioteca en la biblioteca, como representación de una persona como lector adscrito a la biblioteca
Además, el Asunto tiene un conjunto de “Credenciales”, que significa “identidad” en inglés. Así el Sujeto confirma que es él. Por ejemplo, la contraseña del usuario puede ser la Credencial. O cualquier objeto con el que el usuario pueda confirmar que realmente es él. Veamos ahora cómo se utiliza JAAS en aplicaciones web.
JAAS - Introducción a la tecnología (Parte 1) - 3

Aplicación web

Entonces, necesitamos una aplicación web. El sistema de construcción automática de proyectos Gradle nos ayudará a crearlo. Gracias al uso de Gradle podemos, mediante la ejecución de pequeños comandos, montar un proyecto Java en el formato que necesitemos, crear automáticamente la estructura de directorios necesaria y mucho más. Puede leer más sobre Gradle en la breve descripción general: " Una breve introducción a Gradle " o en la documentación oficial " Gradle Getting Started ". Necesitamos inicializar el proyecto (Inicialización), y para ello Gradle tiene un complemento especial: “ Gradle Init Plugin ” (Init es la abreviatura de Inicialización, fácil de recordar). Para utilizar este complemento, ejecute el comando en la línea de comando:
gradle init --type java-application
Una vez completado con éxito, tendremos un proyecto Java. Ahora abramos el script de compilación de nuestro proyecto para editarlo. Un script de compilación es un archivo llamado build.gradle, que describe los matices de la compilación de la aplicación. De ahí el nombre, construir script. Podemos decir que este es un script de construcción de proyecto. Gradle es una herramienta tan versátil cuyas capacidades básicas se amplían con complementos. Por tanto, antes que nada, prestemos atención al bloque “complementos”:
plugins {
    id 'java'
    id 'application'
}
De forma predeterminada, Gradle, de acuerdo con lo que especificamos " --type java-application", ha configurado un conjunto de algunos complementos principales, es decir, aquellos complementos que están incluidos en la distribución de Gradle. Si va a la sección "Documentos" (es decir, documentación) en el sitio web gradle.org , a la izquierda en la lista de temas en la sección "Referencia" vemos la sección " Complementos principales ", es decir. sección con una descripción de estos complementos muy básicos. Elijamos exactamente los complementos que necesitamos y no los que Gradle generó para nosotros. Según la documentación, el " Gradle Java Plugin " proporciona operaciones básicas con código Java, como la compilación de código fuente. Además, según la documentación, el " complemento de aplicación Gradle " nos proporciona herramientas para trabajar con la "aplicación JVM ejecutable", es decir. con una aplicación Java que se puede iniciar como una aplicación independiente (por ejemplo, una aplicación de consola o una aplicación con su propia interfaz de usuario). Resulta que no necesitamos el complemento de “aplicación”, porque... No necesitamos una aplicación independiente, necesitamos una aplicación web. Borrémoslo. Además de la configuración "mainClassName", que solo conoce este complemento. Además, en la misma sección " Embalaje y distribución " donde se proporcionó el enlace a la documentación del complemento de aplicación, hay un enlace al complemento Gradle War. Gradle War Plugin , como se indica en la documentación, brinda soporte para la creación de aplicaciones web Java en formato war. En formato WAR significa que en lugar de un archivo JAR, se creará un archivo WAR. Parece que esto es lo que necesitamos. Además, como dice la documentación, "El complemento War amplía el complemento Java". Es decir, podemos reemplazar el complemento java con el complemento war. Por lo tanto, nuestro bloque de complementos finalmente se verá así:
plugins {
    id 'war'
}
También en la documentación del "Complemento Gradle War" se dice que el complemento utiliza un "Diseño de proyecto" adicional. El diseño se traduce del inglés como ubicación. Es decir, el complemento war espera de forma predeterminada la existencia de una determinada ubicación de archivos que utilizará para sus tareas. Utilizará el siguiente directorio para almacenar archivos de aplicaciones web: src/main/webapp El comportamiento del complemento se describe a continuación:
JAAS - Introducción a la tecnología (Parte 1) - 4
Es decir, el complemento tendrá en cuenta los archivos de esta ubicación al crear el archivo WAR de nuestra aplicación web. Además, la documentación del complemento Gradle War dice que este directorio será la "raíz del archivo". Y ya en él podemos crear un directorio WEB-INF y agregar allí el archivo web.xml. ¿Qué tipo de archivo es este? web.xml- este es un "Descriptor de implementación" o "descriptor de implementación". Este es un archivo que describe cómo configurar nuestra aplicación web para que funcione. Este archivo especifica qué solicitudes manejará nuestra aplicación, configuraciones de seguridad y mucho más. En esencia, es algo similar a un archivo de manifiesto de un archivo JAR (consulte " Trabajar con archivos de manifiesto: conceptos básicos "). El archivo Manifiesto indica cómo trabajar con una aplicación Java (es decir, un archivo JAR) y el web.xml indica cómo trabajar con una aplicación web Java (es decir, un archivo WAR). El concepto mismo de "Descriptor de implementación" no surgió por sí solo, sino que se describe en el documento " Especificación de API de servlet".". Cualquier aplicación web Java depende de esta "API de servlet". Es importante comprender que se trata de una API, es decir, es una descripción de algún contrato de interacción. Las aplicaciones web no son aplicaciones independientes. Se ejecutan en un servidor web. , que proporciona comunicación de red con los usuarios. Es decir, un servidor web es una especie de "contenedor" para aplicaciones web. Esto es lógico, porque queremos escribir la lógica de una aplicación web, es decir, qué páginas verá el usuario y cómo deben reaccionar a las acciones del usuario. Y no queremos escribir código sobre cómo se enviará un mensaje al usuario, cómo se transferirán los bytes de información y otras cosas de bajo nivel y que exigen mucha calidad. Además, resulta que las aplicaciones web son todas diferentes, pero la transferencia de datos es la misma. Es decir, un millón de programadores tendrían que escribir código para el mismo propósito una y otra vez. Por lo tanto, el servidor web es responsable de parte de la interacción del usuario. e intercambio de datos, y la aplicación web y el desarrollador son responsables de generar esos datos. Y para conectar estas dos partes, es decir servidor web y aplicación web, necesita un contrato para su interacción, es decir, ¿Qué reglas seguirán para hacer esto? Para describir de alguna manera el contrato, cómo debería ser la interacción entre una aplicación web y un servidor web, se inventó la API de Servlet. Curiosamente, incluso si usa marcos como Spring, todavía hay una API de servlet ejecutándose bajo el capó. Es decir, usted usa Spring y Spring trabaja con la API de Servlet por usted. Resulta que nuestro proyecto de aplicación web debe depender de la API del Servlet. En este caso, la API de Servlet será una dependencia. Como sabemos, Gradle también le permite describir las dependencias del proyecto de forma declarativa. Los complementos describen cómo se pueden gestionar las dependencias. Por ejemplo, el complemento Java Gradle introduce un método de gestión de dependencias "testImplementation", que dice que dicha dependencia solo es necesaria para las pruebas. Pero el complemento Gradle War agrega un método de administración de dependencias "providedCompile", que dice que dicha dependencia no se incluirá en el archivo WAR de nuestra aplicación web. ¿Por qué no incluimos la API de Servlet en nuestro archivo WAR? Porque la API de Servlet la proporcionará el propio servidor web a nuestra aplicación web. Si un servidor web proporciona una API de servlet, entonces el servidor se denomina contenedor de servlet. Por lo tanto, es responsabilidad del servidor web proporcionarnos la API de Servlet, y es nuestra responsabilidad proporcionar la API de Servlet solo en el momento en que se compila el código. Es por eso providedCompile. Así, el bloque de dependencias quedará así:
dependencies {
    providedCompile 'javax.servlet:javax.servlet-api:3.1.0'
    testImplementation 'junit:junit:4.12'
}
Entonces, volvamos al archivo web.xml. De forma predeterminada, Gradle no crea ningún descriptor de implementación, por lo que debemos hacerlo nosotros mismos. Creemos un directorio src/main/webapp/WEB-INF, y en él crearemos un archivo XML llamado web.xml. Ahora abramos la "Especificación de servlet Java" y el capítulo " CAPÍTULO 14 Descriptor de implementación ". Como se indica en "14.3 Descriptor de implementación", el documento XML del Descriptor de implementación se describe mediante el esquema http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd . Un esquema XML describe de qué elementos puede consistir un documento y en qué orden deben aparecer. Cuáles son obligatorios y cuáles no. En general, describe la estructura del documento y permite comprobar si el documento XML está compuesto correctamente. Ahora usemos el ejemplo del capítulo " 14.5 Ejemplos ", pero el esquema debe especificarse para la versión 3.1, es decir.
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd
Nuestro vacío web.xmlse verá así:
<?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>
Describamos ahora el servlet que protegeremos usando JAAS. Anteriormente, Gradle generó la clase App para nosotros. Convirtámoslo en un servlet. Como se indica en la especificación en " CAPÍTULO 2 La interfaz de servlet ", que " Para la mayoría de los propósitos, los desarrolladores extenderán HttpServlet para implementar sus servlets ", es decir, para convertir una clase en un servlet, debe heredar esta clase de 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());
    }
}
Como decíamos, la API de Servlet es un contrato entre el servidor y nuestra aplicación web. Este contrato nos permite describir que cuando un usuario contacta al servidor, el servidor generará una solicitud del usuario en forma de objeto HttpServletRequesty la pasará al servlet. También proporcionará al servlet un objeto HttpServletResponsepara que el servlet pueda escribirle una respuesta para el usuario. Una vez que el servlet haya terminado de ejecutarse, el servidor podrá proporcionar al usuario una respuesta basada en él HttpServletResponse. Es decir, el servlet no se comunica directamente con el usuario, sino sólo con el servidor. Para que el servidor sepa que tenemos un servlet y para qué solicitudes debe usarse, debemos informarle al servidor sobre esto en el descriptor de implementación:
<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>
En este caso, /secretno todas las solicitudes se dirigirán a nuestro único servlet por nombre app, que corresponde a la clase jaas.App. Como dijimos anteriormente, una aplicación web sólo se puede implementar en un servidor web. El servidor web se puede instalar por separado (independiente). Pero para los propósitos de esta revisión, una opción alternativa es adecuada: ejecutarlo en un servidor integrado. Esto significa que el servidor se creará y ejecutará mediante programación (el complemento lo hará por nosotros) y, al mismo tiempo, se implementará nuestra aplicación web en él. El sistema de compilación Gradle le permite utilizar el complemento " Gradle Gretty Plugin " para estos fines:
plugins {
    id 'war'
    id 'org.gretty' version '2.2.0'
}
Además, el complemento Gretty tiene buena documentación . Comencemos con el hecho de que el complemento Gretty le permite cambiar entre diferentes servidores web. Esto se describe con más detalle en la documentación: " Cambio entre contenedores de servlets ". Cambiemos a Tomcat, porque... es uno de los más populares en uso y también tiene buena documentación y muchos ejemplos y problemas analizados:
gretty {
    // Переключаемся с дефолтного Jetty на Tomcat
    servletContainer = 'tomcat8'
    // Укажем Context Path, он же Context Root
    contextPath = '/jaas'
}
Ahora podemos ejecutar "gradle appRun" y luego nuestra aplicación web estará disponible en http://localhost:8080/jaas/secret
JAAS - Introducción a la tecnología (Parte 1) - 5
Es importante verificar que Tomcat seleccione el contenedor de servlets (ver n.° 1) y verificar en qué dirección está disponible nuestra aplicación web (ver n.° 2).
JAAS - Introducción a la tecnología (Parte 1) - 6

Autenticación

La configuración de autenticación a menudo consta de dos partes: la configuración del lado del servidor y la configuración del lado de la aplicación web que se ejecuta en este servidor. La configuración de seguridad de una aplicación web no puede dejar de interactuar con la configuración de seguridad del servidor web, aunque solo sea por el hecho de que una aplicación web no puede dejar de interactuar con el servidor web. No en vano nos cambiamos a Tomcat, porque... Tomcat tiene una arquitectura bien descrita (consulte " Arquitectura Apache Tomcat 8 "). De la descripción de esta arquitectura queda claro que Tomcat, como servidor web, representa la aplicación web como un contexto determinado, que se denomina " Contexto Tomcat ". Este contexto permite que cada aplicación web tenga su propia configuración, aislada de otras aplicaciones web. Además, la aplicación web puede influir en la configuración de este contexto. Flexible y conveniente. Para una comprensión más profunda, recomendamos leer el artículo " Comprensión de los contenedores de contexto de Tomcat " y la sección de documentación de Tomcat " El contenedor de contexto ". Como se indicó anteriormente, nuestra aplicación web puede influir en el contexto Tomcat de nuestra aplicación utilizando un archivo /META-INF/context.xml. Y una de las configuraciones muy importantes en las que podemos influir es Security Realms. Security Realms es una especie de "área de seguridad". Un área para la cual se especifican configuraciones de seguridad específicas. En consecuencia, cuando utilizamos un ámbito de seguridad, aplicamos la configuración de seguridad definida para este ámbito. Los reinos de seguridad son administrados por un contenedor, es decir. servidor web, no nuestra aplicación web. Solo podemos decirle al servidor qué alcance de seguridad debe extenderse a nuestra aplicación. La documentación de Tomcat en la sección " El componente Realm " describe un Reino como una colección de datos sobre los usuarios y sus roles para realizar la autenticación. Tomcat proporciona un conjunto de implementaciones diferentes de Security Realm, una de las cuales es " Jaas Realm ". Habiendo entendido un poco la terminología, describamos el contexto de Tomcat en el archivo /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- Nombre de la aplicación. Tomcat intentará hacer coincidir este nombre con los nombres especificados en el archivo configFile. configFile- este es el "archivo de configuración de inicio de sesión". Se puede ver un ejemplo de esto en la documentación de JAAS: " Apéndice B: Ejemplos de configuraciones de inicio de sesión ". Además, es importante que este archivo se busque primero en los recursos. Por lo tanto, nuestra aplicación web puede proporcionar este archivo por sí misma. Atributos userClassNamesy roleClassNamescontienen una indicación de las clases que representan el principal del usuario. JAAS separa los conceptos de "usuario" y "rol" como dos conceptos diferentes java.security.Principal. Describamos las clases anteriores. Creemos la implementación más simple para el usuario principal:
public class UserPrincipal implements Principal {
    private String name;
    public UserPrincipal(String name) {
        this.name = name;
    }
    @Override
    public String getName() {
        return name;
    }
}
Repetiremos exactamente la misma implementación para RolePrincipal. Como puede ver en la interfaz, lo principal para Principal es almacenar y devolver algún nombre (o ID) que represente al Principal. Ahora tenemos un Reino de Seguridad, tenemos clases principales. Queda por completar el archivo con el configFileatributo " ", también conocido como login configuration file. Su descripción se puede encontrar en la documentación de Tomcat: " The Realm Component ".
JAAS - Introducción a la tecnología (Parte 1) - 7
Es decir, podemos colocar la configuración JAAS Login Config en los recursos de nuestra aplicación web y gracias a Tomcat Context podremos utilizarla. Este archivo debe estar disponible como recurso para ClassLoader, por lo que su ruta debería ser así: \src\main\resources\jaas.config Configuremos el contenido de este archivo:
JaasLogin {
    jaas.login.JaasLoginModule required debug=true;
};
Vale la pena señalar que context.xmlaquí y en se utiliza el mismo nombre. Esto asigna el ámbito de seguridad al LoginModule. Entonces, Tomcat Context nos dijo qué clases representan los principales, así como qué LoginModule usar. Todo lo que tenemos que hacer es implementar este LoginModule. LoginModule es quizás una de las cosas más interesantes de JAAS. La documentación oficial nos ayudará a desarrollar LoginModule: " Servicio de autenticación y autorización de Java (JAAS): Guía del desarrollador de LoginModule ". Implementemos el módulo de inicio de sesión. Creemos una clase que implemente la interfaz LoginModule:
public class JaasLoginModule implements LoginModule {
}
Primero describimos el método de inicialización 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;
}
Este método guardará los archivos Subject, que autenticaremos y completaremos con información sobre los principales. También guardaremos para uso futuro CallbackHandler, el que se nos entrega. Con ayuda, CallbackHandlerpodremos solicitar información diversa sobre el tema de autenticación un poco más adelante. Puedes leer más al respecto CallbackHandleren la sección correspondiente de la documentación: " Guía de referencia de JAAS: CallbackHandler ". A continuación se ejecuta el método loginde autenticación Subject. Esta es la primera fase de autenticación:
@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 выполнится
			// Для примера проставим группы вручную, "хардcódigoно".
			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());
	}
}
Es importante que loginno cambiemos el Subject. Dichos cambios sólo deberían ocurrir en el método de confirmación commit. A continuación, debemos describir el método para confirmar la autenticación exitosa:
@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;
}
Puede parecer extraño separar el método loginy commit. Pero la cuestión es que los módulos de inicio de sesión se pueden combinar. Y para una autenticación exitosa, puede ser necesario que varios módulos de inicio de sesión funcionen correctamente. Y solo si todos los módulos necesarios han funcionado, guarde los cambios. Esta es la segunda fase de autenticación. Terminemos con los métodos aborty 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;
}
El método abortse llama cuando falla la primera fase de autenticación. El método logoutse llama cuando el sistema cierra la sesión. Una vez implementado el nuestro Login Moduley configurado Security Realm, ahora debemos indicar web.xmlque queremos utilizar uno específico Login Config:
<login-config>
  <auth-method>BASIC</auth-method>
  <realm-name>JaasLogin</realm-name>
</login-config>
Especificamos el nombre de nuestro Reino de Seguridad y especificamos el Método de Autenticación: BÁSICO. Este es uno de los tipos de autenticación descritos en la API de Servlet en la sección " 13.6 Autenticación ". permaneció sustantivo
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION