Mostrando las entradas con la etiqueta WCF. Mostrar todas las entradas
Mostrando las entradas con la etiqueta WCF. Mostrar todas las entradas

ADFS - Seguridad en Servicios Web

Seguramente si estás leyendo este artículo es porque ya implementaste seguridad con Adfs en tus sitios y ahora necesitás extender esa seguridad a los servicios web.

Sino te recomiendo que empieces por la serie dedicada a la autenticación y autorización en sitios web con Adfs.

Lo que vamos a hacer es permitir que la aplicación web indique al servicio web quien la está usando de una manera confiable y segura. A este proceso se lo llama Delegación de la Identidad.

Objetivos de la delegación

  • Controlar el acceso al servicio web tanto a nivel de la aplicación que lo utiliza como del usuario final.
  • Conocer, en el servicio web, quien es el usuario de la aplicación.

Escenario de delegación

El proceso de delegación ocurre en dos etapas, la primera es para acceder al sitio web donde el usuario presenta sus credenciales. La segunda etapa es para acceder al servicio web actuando como el usuario autenticado en el sitio. De aquí en adelante nos referiremos a “Juan” como el usuario y a “AppWeb” como las credenciales con que se autentica la aplicación web (ya sea indicando usuario y password o siendo la cuenta con que ejecuta el application pool del sitio).

ADFS - esquema de delegación

La negociación que incluyen los mensajes 1 y 2 es para obtener el token de ingreso a la “Aplicación Web”, a este token lo llamaremos “Token A”. Esta negociación en aplicaciones web se suele obtener en forma pasiva (a base de redirecciones del browser).

La negociación que incluye los mensajes 3 y 4 es la correspondiente al token para que AppWeb acceda a “Servicio Web” actuando como Juan, a este token lo llamaremos “Token B”. Esta negociación se realiza en forma activa, invocando alguno de los endpoints de los publicados en Adfs. En dicha invocación se incluye las credenciales de AppWeb y el “Token A”.

Pipeline de ejecución del Claim Engine

El siguiente gráfico es la representación de lo que sucede dentro de ADFS en el escenario de delegación. La mitad izquierda del gráfico corresponde a la negociación del “Token A” para acceder a la “Aplicación Web” mientras que la mitad derecha corresponde a la negociación del “Token B” para que la aplicación pueda acceder al “Servicio Web” actuando en nombre del usuario autenticado, es decir en nombre de Juan.

ADFS - flujo de claims con delegación

El pipeline de ejecución para obtener un token actuando como otro usuario (delegación) tiene diferencias con el pipeline de autenticación donde no se delega la identidad, algunas de estas son:

Existen dos conjuntos de claims de entrada, el correspondiente a AppWeb y el correspondiente a Juan. El primero proviene de la autenticación de AppWeb ya sea que AppWeb indica su password o utilizando kerberos o mediante un certificado. El segundo conjunto de claims proviene del “Token A” que identifica a Juan en la “Aplicación Web”.

La ejecución de reglas para la obtención del “Token B” alterna operaciones sobre los claims de AppWeb (quien delega) y los claims de Juan (en nombre de quien se actúa).

  1. Se ejecutan las “Delegation Authorization Rules” sobre los claims de AppWeb.
  2. Se ejecutan las “Issuance Authorization Rules” sobre los claims de AppWeb.
  3. Se ejecutan las “Issuance Authorization Rules” sobre los claims de Juan.
  4. Se ejecutan las “Issuance Transform Rules” sobre los claims de AppWeb.
  5. Se ejecutan las “Issuance Transform Rules” sobre los claims de Juan.

Si en alguno de los pasos 1, 2 o 3 no fuese autorizado el ingreso, se aborta la generación del “Token B” en ese paso.

Los claims de entrada para Juan en la obtención del “Token B” son los correspondientes a los claims del “Token A”, obtenido durante la autenticación de Juan a “Aplicación Web”.

Es necesario guardar el token inicial del usuario (Token A), esto se hace poniendo la propiedad saveBootstrapTokens en true (en microsoft.identityModel –> service).

Referencias

Android y WCF REST

image

Este es un ejemplo sobre como conectar una aplicación Android a una servicio WCF REST

El servicio WCF está hosteado en un IIS bajo el nombre de notescenter (para este post) y el cliente desarrollado en Android consume dicho servicio.

El Servicio WCF

Nuestro servicio es muy simple y lo vamos a crear con el template “WCF REST Service Template 40(CS)” ya que el objetivo del ejemplo es mostrar como acceder desde la aplicación Android.

Dicho servicio mantiene una lista de notas en un repositorio en el servidor, si escribimos notescenter.neluz.int/note/help nos va a mostrar las acciones referente al servicio de notas:

image

donde cada acción, siguiendo los principios REST, significa:

  • Acciones sobre http://notecenter.neluz.int/note/
    • GET: obtiene la lista de notas.
    • POST: crea una nueva nota (el contenido se envía en el cuerpo del mensaje).
  • Acciones sobre http://notecenter.neluz.int/note/[id]
    • GET: obtiene la nota cuyo id es [id].
    • PUT: actualiza la nota cuyo id es [id] (el contenido se envía en el cuerpo del mensaje).
    • DELETE: elimina la nota cuyo id es [id]

El código completo del servicio se lo pueden bajar hacienco click aquí.

El cliente Android

Esta aplicación cuenta con 2 layout, uno que nos permite ingresar nuevas notas y otra que lista las notas existentes donde, si seleccionamos una nota la elimina. No hay confirmaciones ni detalles de presentación ya que no es el objetivo del post.

Android-1 Screenshot2

crear una nueva nota

listado de notas

La aplicación va a necesitar permisos de acceso a internet:

<uses-permission android:name="android.permission.INTERNET"></uses-permission>

Mientras que la clase que se encarga de la comunicación Http con nuestro servicio WCF hace uso de los objetos HttpGet, HttpPost y HttpDelete. En el caso del POST podemos ver como son serealizados los datos como JSON.

El código es el siguiente:

package com.neluz.messageClient;
 
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URI;
import java.net.URISyntaxException;
 
import org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.apache.http.StatusLine;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.json.JSONException;
import org.json.JSONObject;
 
public class HttpNote {
 
       public static String doGet(String path) throws URISyntaxException, IOException, ClientProtocolException {
               BufferedReader in = null;
               try {
                       HttpClient client = new DefaultHttpClient();
                       HttpGet request = new HttpGet();
                       request.setURI(new URI(path));
                       HttpResponse response = client.execute(request);
 
                       in = new BufferedReader(new InputStreamReader(response.getEntity().getContent()));
 
                       StringBuffer sb = new StringBuffer("");
                       String line = "";
                       String NL = System.getProperty("line.separator");
                       while ((line = in.readLine()) != null) {
                               sb.append(line + NL);
                       }
                       in.close();
                       String array = sb.toString();
                       return array;
               } finally {
                       if (in != null) {
                               try {
                                       in.close();
                               } catch (IOException e) {
                                       e.printStackTrace();
                               }
                       }
               }
       }
 
       public static String doPost(String path, String text) throws URISyntaxException, JSONException, ClientProtocolException, IOException {
               HttpPost httpost = new HttpPost(new URI(path));
               httpost.setHeader("Accept", "application/json");
               httpost.setHeader("Content-type", "application/json; charset=utf-8");
 
               JSONObject holder = new JSONObject();
               holder.put("Text", text);
 
               StringEntity se = new StringEntity(holder.toString());
 
               httpost.setEntity(se);
 
               DefaultHttpClient httpclient = new DefaultHttpClient();
               HttpResponse response = httpclient.execute(httpost);
 
               StringBuffer sb = new StringBuffer();
 
               BufferedReader in = new BufferedReader(new InputStreamReader(response.getEntity().getContent()));
 
               String line;
               String NL = System.getProperty("line.separator");
               while ((line = in.readLine()) != null) {
                       sb.append(line + NL);
               }
               in.close();
               return sb.toString();
       }
 
       public static boolean doDelete(String path, int id) throws ClientProtocolException, IOException {
               StringBuilder url = new StringBuilder();
               url.append(path).append(id);
 
               HttpDelete httpdelete = new HttpDelete(url.toString());
 
               DefaultHttpClient httpclient = new DefaultHttpClient();
               HttpResponse response = httpclient.execute(httpdelete);
 
               StatusLine statusLine = response.getStatusLine();
 
               return statusLine.getStatusCode() == 200;
       }
}

y para deserealizar el contenido de los mensajes recibidos usaremos:

package com.neluz.messageClient;
 
import java.util.ArrayList;
import java.util.List;
 
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
 
public class JsonNote {
       public static List<Note> transformJSON2Notes(String response) throws JSONException {
               List<Note> result = new ArrayList<Note>();
 
               JSONArray notes = new JSONArray(response);
               for (int i = 0; i < notes.length(); i++) {
                       JSONObject note = notes.getJSONObject(i);
                       Note n = transformJSON2Note(note);
                       result.add(n);
               }
               return result;
       }
 
       public static Note transformJSON2Note(String note) throws JSONException {
               JSONObject n = new JSONObject(note);
               return transformJSON2Note(n);
       }
 
       private static Note transformJSON2Note(JSONObject note) throws JSONException {
               Note n = new Note();
               n.setId(note.getInt("Id"));
               n.setText(note.getString("Text"));
               return n;
       }
}

el código completo del cliente lo pueden descargar haciendo click aquí.

Nota: estoy trabajando con la versión 7 del API de Android y tuve problemas con la URL, si pongo “http://notecenter.neluz.int/note” al hacer el POST me termina ejecutando el GET, esto se debe a que el IIS cuando recibe la url “http://notecenter.neluz.int/note” devuelve en 307 (Redirect) y el componente de Android hace un GET a la dirección indicada, es decir a “http://notecenter.neluz.int/note/”. Si directamente ponemos “notecenter.neluz.int/note/” en la url del POST funciona perfectamente (notese la diferencia en la / final).