Desenvolvimento - Web Services

Web Services Enhancements (2.0) - Anexando arquivos e segurança ao seu serviço web

O Web Service Enhancements (WSE) é um complemento para o Framework.NET das funcionalidades dos Web Services, trazendo novas (e importantes) melhorias no desenvolvimento destes serviços...

por Marcelo Barros



1. Introdução

Por tratar de um complemento para o Web Service este artigo requer um prévio conhecimento do assunto e para isso indico o artigo do Microsoft Regional Director Mauro Sant"Anna no site Linha de Código. (http://www.linhadecodigo.com.br/artigos.asp?id_ac=38&pag=1)

Para entender este artigo por completo é importante que saiba:

  • O que é e como funciona o Web Service, o protocolo SOAP e o XML.
  • Como criar um projeto Web Service no Visual Studio ou em uma IDE de sua escolha.
  • As vantagens e desvantagens da utilização de Web Services.

1.1. O que é?

O Web Service Enhancements (WSE) é um complemento para o Framework.NET das funcionalidades dos Web Services, trazendo novas (e importantes) melhorias no desenvolvimento destes serviços.

Tecnicamente é um conjunto de bibliotecas de classes prontas para o programador, trazendo soluções e atualizações para a maioria das necessidades e falhas da versão básica do Web Service. Está disponível em duas versões, o WSE 1.0 e o WSE 2.0. Ambas as versões, além de outros patchs e artigos sobre o tema, estão disponíveis em http://msdn.microsoft.com/webservices/downloads/default.aspx. Problemas de compatibilidade são conhecidos entre as duas versões do WSE. Utilizamos a versão 2.0 neste artigo.

Para entender um pouco mais da necessidade do WSE, tenha em mente a terceira pergunta feita no tópico anterior sobre as vantagens e desvantagens da utilização de Web Services. Bem, as vantagens são inúmeras e após ler alguns artigos você poderá citar uma porção delas. Mas as desvantagens também existem e algumas delas podem ser cruciais na escolha da tecnologia a ser utilizada em uma solução. E dentre as desvantagens do Web Service para o Framework.Net estão:

  • Dificuldade na implementação de segurança, tema extremamente crítico pois toda a funcionalidade do serviço é feita on-line.
  • Impossibilidade do envio de arquivos (imagens, documentos, etc.) à exceção de tipos de arquivos compatíveis com XML.

Foi em resposta a estas e outras dificuldades que o Web Service Enhancements foi criado.

2. Como configurar?

Tendo baixado e instalado o WSE 2.0 a configuração do seu projeto é espantosamente simples. Basta clicar com o botão direito no mouse sobre o projeto e dentre as últimas opções selecionar: "WSE 2.0 Settings...". Através desta configuração você poderá, com alguns cliques de mouse, configurar a aplicação. O mesmo ocorre para os que possuem a versão 1.0 acompanhada do ToolKit (Figura 1).

* Caso você tenha a versão 2.0 e não encontre esta opção refaça a instalação selecionando a instalação completa (Arquivos de Desenvolvedor + Suporte ao Visual Studio). Disponível apenas para Visual Studio 2003+.

Dentro do WSE Settings clique sobre a opção "Enable this project for Web Services Enhancements" e em seguida na opção "Enable Microsoft Web Services SOAP Extensions" para efetuar a configuração do seu projeto.

De qualquer forma é importante entender o que essa configuração está fazendo e conhecer as etapas desta. Vejamos então como configurar o projeto manualmente. 2.1. Referenciando o Assembly

Clique com o botão direito do mouse sobre a divisão "References", logo abaixo do seu projeto. Acesse "Add Reference" e uma lista de Assemblies estará disponível. Ambas as versões do WSE estarão na tab ".NET". Vá direto para a letra M e selecione: Microsoft.Web.Services2.dll e pressione OK.

Agora você pode importar o namespace desta assembly para ter um acesso mais fácil as classes, utilizando:

(C#)
using Microsoft.Web.Services2;

(VB.NET)
Imports Microsoft.Web.Services2

2.2. Web.config

Antes de iniciar as funcionalidades é importante também rever a configuração do seu arquivo de configuração Web.config. Seguem as etapas necessárias.

Dentro de <Configuration> você deve adicionar uma ConfigSection com uma referência ao WSE. Para isso adicione o código:

<Configuration>
.
.
<configSections>
<section name="microsoft.web.services2" 
type="Microsoft.Web.Services2.Configuration.WebServicesConfiguration, Microsoft.Web.Services2, 
Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
</configSections>
.
.
</Configuration>

Para poder utilizar as atualizações da extensão soap, adicione a configuração soapExtensionTypes:

<Configuration>
<System.Web>
.
.
<webServices>
<soapExtensionTypes>
<add type="Microsoft.Web.Services2.WebServicesExtension, Microsoft.Web.Services2, Version=2.0.0.0, 
Culture=neutral, PublicKeyToken=31bf3856ad364e35" priority="1" group="0" />
</soapExtensionTypes>
</webServices>
.
.
</System.Web>
</Configuration>

Para finalizar, dentro de Configuration, mas já fora de System.Web, crie uma cláusula própria para o WSE, que ficará por enquanto em branco, mas possibilitará outras configurações:

<Configuration>
.
.
<microsoft.web.services2>
<diagnostics />
</microsoft.web.services2>

.
.
</Configuration>

3. Funcionalidades - WS-Attachments

Como visto anteriormente o processo de envio de arquivos através de Web Services é complicado. Com o WSE esta operação foi facilitada implementando-se Dime Attachments (Anexos Dime). Mime e Dime são alguns dos padrões de formatação de arquivos para transferências e são utilizados, por exemplo, nos e-mails. Eles facilitam a utilização dos anexos. As características desses formatos não são importantes nesse caso, já que todo o funcionamento é transparente para o programador. O que é importante saber é como criar Anexos Dime e como transportá-los pelo Web Service.

3.1. Streams e Bytes

Para facilitar o entendimento desta operação é preciso entender como estes arquivos são tratados. Primeiramente lembre-se que todos os arquivos de um computador são conjuntos de bytes. Para a sua aplicação os arquivos continuam sendo conjuntos de bytes (chamados de Streams); o Framework.NET apenas possui classes que facilitam o tratamento destes arquivos. Por isso as classes FileStream, StreamReader, StreamWriter e MemoryStream do namespace System.IO são classes que herdam da classe System.IO.Stream e "encapsulam" estes conjunto de bytes (o arquivo). Você pode, através destas classes, abrir arquivos, escrever neles, e até deletá-los. Pode também armazená-los em memória. A classe de leitura (StreamReader) permite a leitura total do arquivo, transformando-o em uma String, assim como a de escrita (StreamWriter) permite escrever strings em um arquivo. Mas não se esqueça que no final das contas o que é armazenado, lido ou deletado, é um conjunto de bytes. Lembre-se também que para anexar arquivos no formato dime é preciso, antes, possuir uma Stream do arquivo em questão. Desta forma existem maneiras diversificadas de ler e armazenar arquivos. Você pode, por exemplo, utilizar a chamada File.Open("nome_arquivo.txt", FileMode.Open) e obter um FileStream do arquivo ou instanciar um StreamReader passando para o construtor o path do arquivo e ler o arquivo de forma bem parecida. Da mesma forma pode-se armazenar um arquivo físico através do StreamWriter e depois obtê-lo com um StreamReader. As possibilidades são inúmeras e dependem da sua necessidade e preferência. 3.2. Criando um DimeAttachment

Primeiramente faça a importação do namespace próprio para os anexos Dime e para as classes de Stream.

(C#)
using Microsoft.Web.Services2.Dime;
using System.IO;

(VB.NET)
Imports Microsoft.Web.Services2.Dime;
Imports System.IO;

Agora é preciso obter um Stream contendo o arquivo que deseja adicionar e em seguida instanciar um objeto da classe DimeAttachment passando para ele o tipo de conteúdo, o tipo do formato e o Stream.

(C#)
StreamReader leitor = new StreamReader("arquivo.txt");
DimeAttachment arquivoDime = new DimeAttachment("text/html", TypeFormat.MediaType, leitor.BaseStream);

(VB.NET)
Dim leitor As New StreamReader("arquivo.txt")
Dim arquivoDime As New DimeAttachment("text/html", TypeFormat.MediaType, leitor.BaseStream)

O tipo de conteúdo (ContentType) indica o tipo de arquivo que está sendo adicionado. Alguns dos tipos mais comuns são:

O tipo do formato é um Enumerator com os formatos possíveis. Em geral utilize o MediaType.

Note que ao invés de passar o StreamReader como parâmetro passamos a propriedade BaseStream do StreamReader. Esta propriedade obtêm o System.IO.Stream puro do leitor.

3.3. Transportando a informação

Com o anexo criado é preciso agora enviar esta informação. É aí que entra a extenção soap configurada no seu web.config. Com ela configurada, o objeto ResponseSoapContext (do namespace Microsoft.Web.Services2) estará instanciado e através da Propriedade Current você poderá obter o SoapContext responsável pelo contexto da mensagem soap, isto é, da informação sendo transportada pela sua aplicação.

Tendo como referência o Web Service, o ResponseSoapContext é o contexto de resposta, isto é, o contexto que será enviado pelo Web Service para o cliente. Já o RequestSoapContext é o contexto de obtenção, ou o contexto que o cliente envia para o web service. Os SoapContexts do WS-Enhancements possuem uma coleção chamada Attachments que contêm o conjunto de anexos a serem enviados. Vamos então adicionar o Anexo Dime ao ResponseSoapContext (que será enviado para o cliente):

(C#)
ResponseSoapContext.Current.Attachments.Add(arquivoDime);

(VB.NET)
ResponseSoapContext.Current.Attachments.Add(arquivoDime)

3.4. Obtendo o arquivo

A maneira de ler este arquivo é similar à utilizada para anexá-lo. Vamos, mais uma vez, tratar o arquivo como um Stream, e obtê-lo através dos Attachments do SoapContext. Agora o objeto em questão será o RequestSoapContext, já que estamos recebendo a informação no cliente. Para obter o contexto é preciso antes instanciar o WebService. Chame o objeto do WebService de Service1:

(C#)
Stream s = Service1.ResponseSoapContext.Attachments[0].Stream;
StreamReader r = new StreamReader(s, System.Text.Encoding.UTF8, false);
Response.Write(r.ReadToEnd());

(VB.NET)
Dim s As Stream =   Service1.ResponseSoapContext.Attachments(0).Stream
Dim r As New StreamReader(s, System.Text.Encoding.UTF8, False)
Response.Write(r.ReadToEnd())

Pronto. Obtivemos o Stream correspondente ao arquivo e podemos efetuar diversas operações neste Stream. No exemplo, escrevemos o conteúdo na tela de uma página Web.

É possível, também, enviar um arquivo do cliente para o servidor. O funcionamento é exatamente o mesmo e você só precisará atentar para o SoapContext correto (Response ou Request) e lembrar que, para o cliente, é preciso antes instanciar o Web Service e obter o contexto através do objeto da classe proxy do Web Service. 3.5. Problemas e Dúvidas

No caso do SoapContext não ser instanciado quando a aplicação é executada ou não conseguir acessar os Attachments você provavelmente está com o Web.config incompleto. Refaça a configuração e o ideal é utilizar o WSE 2.0 Settings para configurar corretamente.

Caso a classe proxy referente ao Web Service não possua as opções atualizadas do WS Enhancements será preciso recompilar o Web Service, verificar as configurações no WSE 2.0 Settings e atualizar a sua Web Reference. Em alguns casos o cliente continua vendo o Web Service sem os enhancements atualizados (Quando se instala a versão 1.0 e depois, por cima, a 2.0, isto pode ocorrer). Para corrigir isso mude a herança da classe proxy para: Microsoft.Web.Services2.WebServicesClientProtocol.

4. Funcionalidades - WS-Security

Como visto antes a segurança nos Web Services é crucial e, até o lançamento dos Enhancements, era similar as de outras aplicações Web, contando com as opções padrões de autenticação (Basic, Digest, Integrated, Forms, etc.) e autorização (Role-Based, etc.). Com o WSE 2.0 é possível incrementar esta segurança utilizando-se Tokens, Assinatura e Criptografia melhorada.

4.1. Tokens de Segurança

Um token é, literalmente, um símbolo. Para os Web Services um Token é uma reivindicação qualquer que geralmente representa a identificação do usuário. Com o WSE é possível adicionar estes tokens à mensagem SOAP e verificar, em qualquer um dos lados, a validade dele.

O exemplo abaixo gera um token e adiciona à classe proxy do Web Service. Antes é preciso importar o namespace necessário:

(C#)
using Microsoft.Web.Services2.Security.Tokens;

UsernameToken token = new UsernameToken( "admin", "admin", PasswordOption.SendPlainText );
Service1.RequestSoapContext.Security.Tokens.Add(token);
Service1.RequestSoapContext.Security.Elements.Add(new MessageSignature(token));

(VB.NET)
Imports Microsoft.Web.Services2.Security.Tokens

Dim UsernameToken As Object

Dim token As New UsernameToken("admin", "admin", PasswordOption.SendPlainText)

Service1.RequestSoapContext.Security.Tokens.Add(token)
Service1.RequestSoapContext.Security.Elements.Add(New MessageSignature(token))

Como pode notar no constructor do UsernameToken é possível informar como a senha será enviada. No exemplo enviamos como texto as você pode optar por "SendHashed" para uma melhor segurança.

Para obter este Token no Web Service você deve percorrer os SecurityElements do SoapContext e obter o UsernameToken referente. O exemplo obtém o username e o password do Token:

(C#)
string username;
string password;

foreach( ISecurityElement securityElement in RequestSoapContext.Current.Security.Elements )
{
if( securityElement is MessageSignature )
{
MessageSignature message = (MessageSignature)securityElement;
UsernameToken token = (UsernameToken)message.SigningToken;

username = token.Username;
password = token.Password;
}
}

(VB.NET)
Dim username As String
Dim password As String

For Each securityElement As ISecurityElement In RequestSoapContext.Current.Security.Elements
If (securityElement Is MessageSignature) Then
Dim message As MessageSignature = CType(securityElement, MessageSignature)
Dim token As UsernameToken = Ctype	(message.SigningToken, UsernameToken)

username = token.Username
password = token.Password
End If
Next

Com o Token em mãos pode-se obter o usuário e senha enviado (como no exemplo acima) e conferir com uma base de dados, criar logs ou verificar as permissões do usuário. Um exemplo simples é utilizar o método IsInRole do UsernameToken.Principal. Com ele pode-se verificar se um usuário está em um determinado grupo:

(C#)
if  (!token.Principal.IsInRole("DOMINIO\\Desenvolvedor"))
throw new Exception("Usuário não tem permissão");

(VB.NET)
If Not (token.Principal.IsInRole("DOMINIO\Desenvolvedor")) Then
Throw New Exception("Usuário não tem permissão")
4.2. UsernameTokenManager

Para facilitar o tratamento dos Tokens você pode optar pelo gerenciador. O objetivo é criar um gerenciador próprio e dentro dos métodos sobrescritos tratar os tokens.

Crie então uma classe derivada da classe UsernameTokenManager. Sobrescreva o método AuthenticateToken para autenticar os usuários da maneira mais adequada. No exemplo vamos autenticar o usuário Admin e retornar a sua senha como string:

(C#)
public class MeuTokenManager : UsernameTokenManager
{
protected override string AuthenticateToken(UsernameToken token)
{
if (token.Username == "Admin" && token.Password == "Admin")
{
return token.Password;
}
else
{
throw new Exception("Usuário não tem permissão");
}
}
}


(VB.NET)
Public Class MeuTokenManager
Inherits UsernameTokenManager

Protected Overrides Function AuthenticateToken(ByVal token As UsernameToken) As String
If (token.Username = "Admin" And token.Password = "Admin") Then
Return token.Password
Else
Throw New Exception("Usuário não tem permissão")
End If
End Function
End Class

Com o TokenManager criado você pode configurar a aplicação para chamar automaticamente a função AuthenticateToken quando um Token for enviado. Para isso vamos alterar mais uma vez o Web.Config, adicionando um item securityTokenManager como no exemplo:

<Configuration>
.
.
<microsoft.web.services2>
		<security>
<securityTokenManager type="MeuTokenManager, MyWebService" 
xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" 
qname="wsse:UsernameToken" />
</security>
</microsoft.web.services2>
.
.
</Configuration>

4.3. WS-Policy

Apesar da importância de conhecer o funcionamento dos Tokens, da Assinatura, e de outros processos importantes para a segurança, é através do Policy que poderemos configurar com praticidade a segurança dos Web Services. Trata-se de um arquivo de configuração (XML) que indica a Política de Segurança do seu projeto. Com o Policy configurado diversas verificações e especificações de segurança serão automatizadas.

Para configurar o Policy do seu projeto vamos utilizar mais uma vez o WSE Settings (clicando com o botão direito do projeto e selecionando o item "WSE Settings 2.0 ..."). Entrando em Policy você poderá ativar o PolicyCache, indicando o arquivo utilizado (geralmente PolicyCache.config). Em seguida, clique em Add dentro de "Edit Application Policy". Selecione o Endpoint padrão (futuramente você poderá ter configurações diferentes para endpoints diferentes). Você acessará agora o Wizard para configurações de segurança (Figura 2).

Pressione o Next da tela inicial do Wizard. Aqui você poderá configurar a estratégia de segurança para o cliente e para o servidor. A opção inferior, "Use Secure Conversation" elimina algumas das configurações básicas pois representa uma estratégia completa de segurança. Para utilizar você precisará de um X.509 Certificate. Como estamos tratando dos UsernameTokens, não marque esta opção, e prossiga. Aqui você poderá escolher que restrições utilizará para as mensagens de Request (Solicitação) e Response (Resposta). Marcando a opção "Require Signatures" para o Request você garante que o cliente utilizará um UsernameToken ao utilizar o serviço. Desta forma a verificação mostrada anteriormente será feita automaticamente. Outras opções, como criptografia na solicitação e assinaturas de resposta, necessitarão de um certificado X.509. Prosseguindo você poderá escolher o tipo de token, no nosso caso o UsernameToken, e em seguida configurar os usuários ou grupos com permissão para utilizar o serviço.

Um resumo da política será mostrado e você deve então finalizar a configuração. Para obter configurações semelhantes para o cliente, configure o projeto cliente utilizando o mesmo processo. Terminada a configuração, toda a verificação de UsernameTokens, Assinaturas e Criptografia (quando selecionado) será automatizada. Você pode então esquecer o código manual para teste de segurança e adicionar um simples Token ao Cache do Policy para ter a segurança ativada. Para isso observe o exemplo abaixo e adicione código semelhante ao seu cliente, antes de chamar o Web Service:

(C#)
using Microsoft.Web.Services2.Security.Policy;

UsernameToken token = new UsernameToken( "admin", "admin", PasswordOption.SendHashed );

PolicyEnforcementSecurityTokenCache.GlobalCache.Add(token)

(VB.NET)
Imports Microsoft.Web.Services2.Security.Policy

Dim UsernameToken As Object

Dim token As New UsernameToken("admin", "admin", PasswordOption.SendHashed)

PolicyEnforcementSecurityTokenCache.GlobalCache.Add(token)

Pronto. Configurar a política de segurança é a maneira mais prática e interessante de obter uma segurança razoável para seu Web Service. Seguindo os passos indicados neste item seu serviço estará prevenido de diversos dos ataques utilizados na Internet.

5. Conclusão

Vimos neste artigo alguns dos problemas do Web Service .NET e como uma atualização desta tecnologia (o Web Services Enhancements 2.0) trouxe soluções práticas e funcionais para a maioria destes problemas. Entendemos então o processo de configuração de uma aplicação com WSE e o funcionamento, passo a passo, das mudanças ocorridas. Vimos então as principais áreas de atuação do WSE 2.0, possibilitando o envio de arquivos anexos, a inclusão de tokens e assinaturas digitais, e, por fim, a utilização de uma política de segurança. Para aplicações mais cruciais aconselhamos uma segurança mais robusta, utilizando as políticas que envolvem certificados X.509, Secure Conversations ou KerberosTokens. Para a grande maioria das aplicações, o WSE 2.0 trouxe soluções suficientes para as aplicações além de funcionalidades de grande importância.

Marcelo Barros

Marcelo Barros - MCP.NET. Experiência de mais de um ano em .NET (VB, C#, ASP.NET, Windows Forms, Web Services) e sete anos com linguagens diversas, foco em ASP 3.0 e Delphi. Participante da célula Braindotnet (www.braindotnet.com) de Fortaleza, Ceará.
Blog:
http://br.thespoke.net/MyBlog/marcelo_barros/MyBlog.aspx.