Documentos de Académico
Documentos de Profesional
Documentos de Cultura
Para asegurar nuestros servicios REST implementados con JAX-RS es necesario utilizar
texto cifrado (tokens) utilizando el estándar JWT. JWT (JSON Web Token) define una
forma compacta y autónoma para transmitir información de forma segura entre las partes
como un objeto JSON, esta información se puede verificar y confiar porque está firmada
digitalmente. Los JWT se pueden firmar usando una clave (con el algoritmo HMAC ) o un
par de claves pública/privada usando el sistema criptográfico RSA, iniciales de los tres
creadores (Rivest, Shamir y Adleman) o el algoritmo de firma digital de curva elíptica
(ECDSA, Elliptic Curve Digital Signature Algorithm).
La estructura de JWT consta de tres partes separadas por puntos «.» que son:
- Encabezado (Header). Es una cadena JSON cifrada en Base64 que contiene el tipo de
token y la codificación utilizada para la firma.
Header = '{"alg": "HS256", "typ": "JWT"}'
- Cuerpo del mensaje (Payload). Es un conjunto de información en formato JSON que
contiene las peticiones (claims), que son un conjunto de campos predefinidos, así como
algunos propios que podemos añadir, como los roles, este mensaje también es cifrada
en Base64.
Payload = '{"sub":"ichagua","name":"Irenio Luis",
"roles":"admin "}'
- Firma (Signature). Está formado por el encabezado y el cuerpo del mensaje, encriptado
con una clave secreta. Esta parte es la que garantiza la integridad de los datos, es decir,
que los datos anteriores no han sido alterados.
clave = 'secretkey'
unsignedToken = encodeBase64Url (encabezado) + '.' +
EncodeBase64Url (mensaje)
signature = HMAC-SHA256 (clave, unsignedToken)
El resultado son tres cadenas de URL Base64 separadas por puntos que se pueden pasar
fácilmente en entornos HTML y HTTP, a la vez que son más compactas en comparación con
los estándares basados en XML. Se muestra un JWT que tiene el encabezado anterior y el
mensaje del cuerpo codificado, y está firmado con una clave.
eyJhbGciOiJIUzUxMiJ9.
eyJyb2xlcyI6IkFETUlOSVNUUkFET1IiLCJzdWIiOiJhZG1pbiIsImlzcyI6Imh0dHA6Ly93d
3cubnNwc2FjLmNvbSIsImlhdCI6MTU5MDMyMzc1NSwiZXhwIjoxNTkwMzI1NTU1fQ.
AUl6mGs__Zj9SlVZLuC_DnDDEu8loO4Bsoi6d2mBTtFG1ByuuzKn-
ElW6vo7wCS_B7kv_Cs3H-JCUeS_w0GD9w
Este token cifrado es la que senviará en el encabezado del mensaje en los accesos a
recursos protegidos. Para generar este token y enviar los mensajes utilizaremos la librería
JJWT, que implementa el estándar JWT, para esto agregamos las dependencias en el
proyecto Maven de nuestra aplicación RESTful AplicacionREST, el código será el siguiente.
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>org.codehaus.jettison</groupId>
<artifactId>jettison</artifactId>
<version>1.4.1</version>
</dependency>
El filtro que habíamos creado para la autenticación básica, lo tenemos que reescribir, ya
que ahora interceptará los mensajes con el cifrado de token JWT. Para esto modificamos
la clase AuthenticationFilter y el código será el siguiente.
package com.nspsac.academico.filter;
import java.io.IOException;
import java.lang.reflect.Method;
import java.security.Key;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.annotation.security.DenyAll;
import javax.annotation.security.PermitAll;
import javax.annotation.security.RolesAllowed;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.container.ResourceInfo;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import io.jsonwebtoken.ClaimJwtException;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureException;
import io.jsonwebtoken.impl.crypto.MacProvider;
import com.nspsac.academico.service.ResponseBuilder;
@Context
private ResourceInfo resourceInfo;
@Override
public void filter(ContainerRequestContext requestContext) throws IOException {
Method method = resourceInfo.getResourceMethod();
if (!method.isAnnotationPresent(PermitAll.class)) {
if (method.isAnnotationPresent(DenyAll.class)) {
requestContext.abortWith(
ResponseBuilder.create(Response.Status.FORBIDDEN,ACCESS_FORBIDDEN)
);
return;
}
Para generar el token hemos utilizado la clase MacProvider de la propia libreria JWT. El
método usado genera una clave secreta aleatoria de 512 bits. Podría utilizarse también
alguna clave almacenada en un keystore. En el mensaje Jws<Claims> claims se tiene los
atributos definidos en las credenciales de acceso. Para varios casos que se presente en la
interceptación del mensaje, se ha creado un mensaje personalizado y de diferentes formas
de enviar un mensaje de tipo JSON, le hemos dado el nombre de la clase ResponseBuilder
que tiene el siguiente código.
package com.nspsac.academico.service;
import java.util.List;
import java.util.Map;
import javax.ws.rs.core.Response;
import org.codehaus.jettison.json.JSONArray;
import org.codehaus.jettison.json.JSONException;
import org.codehaus.jettison.json.JSONObject;
import com.fasterxml.jackson.databind.JsonSerializable;
package com.nspsac.academico.service;
import java.util.Base64;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.StringTokenizer;
import javax.annotation.security.PermitAll;
import javax.naming.AuthenticationException;
import javax.ws.rs.GET;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Response;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import com.nspsac.academico.filter.AuthenticationFilter;
@Path("/usuarios")
public class UsuarioService {
@PermitAll
@GET
@Path("/login")
@Produces("application/json;charset=UTF-8")
public Response authenticateUser(
@HeaderParam("Authorization") String tokenAuth) {
try {
final String encodedUserPasswd = tokenAuth.replaceFirst("Basic ", "");
byte[] decodedBytes = Base64.getDecoder().decode(encodedUserPasswd);
String userAndPasswd = new String(decodedBytes, "UTF-8");
StringTokenizer tokenizer = new StringTokenizer(userAndPasswd, ":");
String username = tokenizer.nextToken();
String password = tokenizer.nextToken();
String roles = authenticate(username, password);
String token = issueToken(username, roles);
System.out.println("token:"+token);
HashMap<String, Object> map = new HashMap<String,Object>();
map.put( AuthenticationFilter.AUTHORIZATION_PROPERTY, token );
return Response.ok().header(HttpHeaders.AUTHORIZATION, map).build();
} catch (Exception e) {
return Response.status(Response.Status.CONFLICT).build();
}
}
private String issueToken(String username, String roles) {
Date issueDate = new Date();
Calendar calendar = Calendar.getInstance();
calendar.setTime(issueDate);
calendar.add(Calendar.MINUTE, 30);
Date expireDate = calendar.getTime();
//Creamos el token
String jwtToken = Jwts.builder()
.claim("roles", roles)
.setSubject(username)
.setIssuer("http://www.nspsac.com")
.setIssuedAt(issueDate)
.setExpiration(expireDate)
.signWith(SignatureAlgorithm.HS512, AuthenticationFilter.KEY)
.compact();
return jwtToken;
}
private String authenticate(String user, String password)
throws AuthenticationException{
if("test".equals(user) && "test".equals(password)) {
return "COORDINADOR";
} else if("admin".equals(user) && "admin".equals(password)) {
return "ADMINISTRADOR";
} else {
throw new AuthenticationException();
}
}
}
En este servicio se tiene que invocar a la URI /s/usuarios/login, en este recurso se tiene
que enviar las credenciales de acceso con la autenticación básica en el encabezado del
mensaje, tratado en esta misma sección, se obtiene los roles de este usuario, en este
código se hace una validación simple, hemos indicado que debería ser desde una base de
datos, luego se genera el token del cuerpo del mensaje (claim), al generar el token se le
da los valores a los atributos, como nombre de usuario, roles, la fecha de creación, el
tiempo de expiración del token de 30 minutos y el cifrado de la firma digital, al invocar a
este servicio se tiene el token generado en el encabezado del mensaje, tal como se
muestra en la Figura 5.13.