Actualizado a la versión 1.2.58.

Introducción

Rock21 proporciona un conjunto de herramientas de seguridad para la implementación de soluciones seguras para servidores basados en Java. Opera como extensión del framework Spring, por lo que tiende a ser desplegado en servidores web embebidos.

Conceptos Básicos

Rock21 se debe incorporar como librerías adicionales a un proyecto Java (preferentemente Maven) que usualmente incluye a Spring Boot como proyecto padre:

  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.3.0.RELEASE</version>
  </parent>

Asimismo, se requiere al menos de Spring Security, y empleamos log4j para el logging:

		<dependency>
		    <groupId>org.springframework.boot</groupId>
		    <artifactId>spring-boot-starter</artifactId>
		    <exclusions>
		        <exclusion>
		            <groupId>org.springframework.boot</groupId>
		            <artifactId>spring-boot-starter-logging</artifactId>
		        </exclusion>
		    </exclusions>
		</dependency>
		<dependency>
    		<groupId>org.springframework.boot</groupId>
    		<artifactId>spring-boot-starter-log4j2</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-security</artifactId>
		</dependency>

En donde corresponda también será necesario añador spring-boot-starter-web.

Escenarios de Rock21

Autenticación segura: War usando JSP/JSTL

Este es el mecanismo más tradicional de ejecución en un Servlet Container.

Paso 1

El pom.xml debe contener las dependencias necesarias:

  <packaging>war</packaging>
  <dependencies>
	  <dependency>
	    <groupId>javax.servlet</groupId>
	    <artifactId>jstl</artifactId>
	  </dependency>
	  <dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-web</artifactId>
	  </dependency>
      <dependency>
	    <groupId>org.springframework.boot</groupId>
	    <artifactId>spring-boot-starter-tomcat</artifactId>
	    <scope>provided</scope>
	  </dependency>
  </dependencies>
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-war-plugin</artifactId>
        <configuration>
          <warName>rock21-test-jsp</warName>
        </configuration>
      </plugin>
    </plugins>
  </build>
Paso 2

Editar src/main/java/resources/application.properties:

logging.level.org.springframework=INFO
server.error.whitelabel.enabled=false
server.error.path=/error-page
spring.mvc.view.prefix=/WEB-INF/view/
spring.mvc.view.suffix=.jsp
Paso 3

Definir un formulario. En nuestro caso hemos definido accessform.jsp:

<!DOCTYPE html>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html lang="en">
<body>
    <div>
        <h1>Access Form</h1>
		<form:form method="post" action="access">
			User: <input type="text" name="xuser"/>
			<br/>
			Password: <input type="password" name="xpass"/>
			<br/>
			<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
			<input type="submit"/>
		</form:form>
		<c:if test="${param.error != null}">
            <h2>Authentication error, sorry.</h2>
        </c:if>
        <c:if test="${param.logout != null}">
        	<h2>You were logged out.</h2>
       	</c:if>
    </div>
</body>
</html>

También deberá definirse una página de error y las páginas de la aplicación para el usuario logeado así como para el que no ha ingresado. Esto se mapea en el controlador:

Paso 4

El controlador:

package com.acme.webjsp;

import java.util.Date;
import java.util.Map;

import org.springframework.boot.web.servlet.error.ErrorController;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class AppController implements ErrorController {

    @GetMapping("/access")
    public String login(){
        return "accessform";
    }

    @ExceptionHandler(Exception.class)
    public ModelAndView exceptionHandler(Map<String, Object> model, final Exception e) {
    	ModelAndView mav = new ModelAndView("error-page");
    	mav.addObject("message", e.getMessage());
        return mav;
    }

    @RequestMapping("/error-page")
    public ModelAndView error(HttpStatus status) {
    	ModelAndView mav = new ModelAndView("error-page");
    	if(status != null) {
    		mav.addObject("message", "STATUS: " + status.toString());
    	}
    	return mav;
    }

    @RequestMapping("/welcome")
    public String home(Map<String, Object> model) {
        model.put("message", new Date().toString());
        return "welcome";
    }

    @RequestMapping("/free")
    public String freePage(Map<String, Object> model) {
        return "free";
    }

    @Override
    public String getErrorPath() {
        throw new IllegalStateException();
    }
}
Paso 4

La aplicación se inicializa y configura:

package com.acme.webjsp;

import java.util.List;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

import com.americati.rock21.core.UrlAccessControl;
import com.americati.rock21.core.UserPasswordAuthenticationProvider;
import com.americati.rock21.web.*;

@SpringBootApplication
public class MainApplication extends SpringBootServletInitializer {
	public static void main(String[] args) throws Exception {
		SpringApplication.run(MainApplication.class, args);
	}

	@Configuration
	public static class Config extends WebSpringConfig {

		@Bean
		public WebConfig getConfig() {
			WebConfig webConfig = new WebConfig();
			webConfig.addWebAccessControl(
				UrlAccessControl.permitAllForAntPatterns(
					"/error-page", "/free"));
			webConfig.addWebAccessControl(
				UrlAccessControl.needAuthenticatedForAnyRequest());
			UserPasswordAuthenticationProvider authenticationProvider =
					new UserPasswordAuthenticationProvider() {
				@Override
				public void authenticateUser(String username,
					String password,
					List<String> roles) throws AuthenticationException {
					if(!"maradona".equals(username)) {
						throw new UsernameNotFoundException(
							"Invalid username");
					}
					if(!"1234".equals(password)) {
						throw new BadCredentialsException(
							"Invalid password");
					}
					roles.add("VALID_USER");
				}
			};
			webConfig.setAuthenticationProvider(authenticationProvider);
			webConfig.setLoginConfig(new LoginConfig("/access",
					"/welcome", false,
					"/access?error",
					"xuser", "xpass",
					"/out"));
			return webConfig;
		}
	}

}

Como corresponde a las aplicaciones basadas en servlets, la clase deberá extender SpringBootServletInitializer.

La configuración la efectuamos mediante WebSpringConfig, la cual recibe su configuración mediante un objeto WebConfig. Este objeto define la ruta /access y otras similares que pueden consultarse en su documentación.

Se deberá proporcionar un AuthenticationProvider que para este caso debe ser un UserPasswordAuthenticationProvider para el cual sólo deberá implementarse el método de autenticación.

Interconexión segura mediante colas ActiveMQ

El soporte JMS para ActiveMQ permite interconectar aplicaciones de modo seguro empleando TLS mediante la clase com.americati.rock21.amq.ActiveMQTlsConnectionFactory. Asimismo, es posible evitar la necesidad de traslado de archivos de certificados para conexiones internas (que sin embargo, sí requieren del uso regulatorio de TLS.) El siguiente ejemplo muestra un consumidor de mensajes de ActiveMQ. Notar el uso de PasswordSeededSecureRandom que permite generar dinámicamente un certificado raíz de contenido replicable, así como un certificado de cada nodo participante, lo que en la práctica permite la interconexión de las partes a partir de una contraseña común. Naturalmente, también es posible emplear archivos PEM o keystores si se desea emplear archivos físicos con los certificados.

@SpringBootApplication
@EnableJms
public class ClientApplication {

	private static Logger logger = LoggerFactory.getLogger(ClientApplication.class);

	@Value("${activemq.broker-url}")
	private String brokerUrl;

	public static void main(String[] args) throws Exception {
		SpringApplication.run(ClientApplication.class, args);
	}

	@Bean
	public ActiveMQTlsConnectionFactory clientConnectionFactory() {
	ActiveMQTlsConnectionFactory connectionFactory = new ActiveMQTlsConnectionFactory(brokerUrl);
		PasswordSeededSecureRandom rnd = new PasswordSeededSecureRandom("89(saX*452jgkTGgAH4R32$DFjudGAfen;y<k-+rKF-/byg");
		Rock21CertRoot rootCert = Rock21TlsBuilder.buildRoot(rnd, 1024, "CN=www.acme-ca.com", 10);
		Rock21CertNode nodeCert = Rock21TlsBuilder.buildNode(rootCert, 1024, "CN=www.acme-consumer.com", 1);
		connectionFactory.setKeys(nodeCert, rootCert);
		return connectionFactory;
	}

	@Bean
	@Primary
	public CachingConnectionFactory cachingConnectionFactory() {
	    return new CachingConnectionFactory(clientConnectionFactory());
	}

	@JmsListener(destination = "TEST-SND")
	public void receiveMessage(String req) {
		logger.info("Got message: {}", req);
	}

En forma similar, es posible implementar un broker ActiveMQ embebido capaz de interconectar los nodos como en el caso anterior. Para esto empleamos la clase com.americati.rock21.amq.TlsBrokerService:

	@Bean
	public TlsBrokerService brokerService() throws Exception {
		PasswordSeededSecureRandom rnd = new PasswordSeededSecureRandom("12<zsfe1");
		Rock21CertRoot rootCert = Rock21TlsBuilder.buildRoot(rnd, 1024, "CN=www.acme-ca.com", 10);
		Rock21CertNode nodeCert = Rock21TlsBuilder.buildNode(rootCert, 1024, "CN=www.acme-producer.com", 1);
		TlsBrokerService broker = new TlsBrokerService();
		broker.setUseJmx(false);
		broker.setPersistent(false);
		broker.setBrokerName("PSR-BROKER");
		broker.addSslConnector(brokerUrl, nodeCert, rootCert);
		broker.start();
		return broker;
	}

Autenticación segura: Uber-Jar usando Thymeleaf

El uso de un servidor tradicional stand-alone como Tomcat va quedando obsoleto. En esta sección vemos la generación de un Jar con las dependencias necesarias para ejecutar una aplicación web segura; en estos casos no es recomendable el uso de JSP por lo que emplearemos Thymeleaf.

Por completar.

Autenticación segura: Doble factor con OTP

Por completar.

Interconexión segura para web services: JWT

Por completar.