ASP .NET MVC 3 - Bloqueando o acesso de IPs (Black List) (VB .NET)


Neste artigo eu vou mostrar como podemos restringir o acesso a nossa aplicação ASP .NET MVC de uma lista de IPs através da criação de um mecanismo de negação de acesso definido através do recurso de roteamento de urls das aplicações ASP .NET MVC.

Esta é uma forma de você ter controle sobre as requisições feitas a sua aplicação e embora você possa implementar isso via HttpHanlder ou HttpModule usar o recurso do roteamento é mais elegante.

Como exemplo vou implementar um filtro através do roteamento ASP .NET MVC onde irei verificar todas as requisições que chegam contra uma lista de ips definidos de forma que se o IP da requisição estiver na lista (negra) poderemos encaminhar a requisição para fora do nosso site ou para uma página criada em nosso site para atender essas ocorrências.

Conceitos

Quando você inicia sua aplicação ASP .NET MVC o arquivo Global.asax é executado e o evento Application_Start() é disparado. Neste processo é criada uma tabela de rotas que foi definida no arquivo Global.asax.

A ASP .NET MVC possui um mecanismo chamado ASP .NET Routing que é usado para rotear as requisições para as ações (Actions) definidas no Controlador. Ele utiliza a tabela de rotas que foi criada quando do arquivo Global.asax foi executado para saber como endereçar a requisição.

Veja abaixo o conteúdo de um arquivo Global.asax:

Public Class MvcApplication
    Inherits System.Web.HttpApplication

    Shared Sub RegisterGlobalFilters(ByVal filters As GlobalFilterCollection)
        filters.Add(New HandleErrorAttribute())
    End Sub

   
Shared Sub RegisterRoutes(ByVal routes As RouteCollection)
        routes.IgnoreRoute("{resource}.axd/{*pathInfo}")
      
 ' Route name
        ' URL with parameters
        ' Parameter defaults

        routes.MapRoute("Default", "{controller}/{action}/{id}", New With { _
          .controller = "Home", _
          .action = "Index", _
          .id = UrlParameter.[Optional] _
        })

    End Sub


    Sub Application_Start()
        AreaRegistration.RegisterAllAreas()
        RegisterGlobalFilters(GlobalFilters.Filters)
        RegisterRoutes(RouteTable.Routes)
    End Sub
End Class

Assim quando uma aplicação ASP .NET é iniciada pela primeira vez o método Application_Start() é chamado, e, pelo código acima vemos que este método chama o método  RegisterRoutes(RouteTable.Routes),  e este método cria a tabela de rotas padrão.

A tabela de rotas padrão possui uma rota , e , esta rota padrão divide todas as requisições de entrada em três segmentos:("{controller}/{action}/{id}",)   

Por exemplo , considera a seguinte URL:   /Produto/Detalhes/3

Esta URL é analisada como 3 parâmetros :

A rota padrão definida no arquivo Global.asax inclui valores padrões para todos os 3 parâmetros:( { controller = "Home", action = "Index", id = "" })

com base nesta definição vejamos como ficaria mapeada a seguinte URL =>  /Produto

Da mesma maneira se chamássemos uma aplicação ASP .NET MVC sem indicar uma URL (Ex: http://localhost)  a mesma seria mapeada da seguinte forma:

A requisição será roteada para a ação Index() da classe HomeController.aspx.

Este é o mecanismo padrão usado em aplicações ASP .NET MVC.

Criando o projeto ASP .NET MVC

Vamos criar um novo projeto usando o Visual Web Developer 2010 Express Edition.

No menu File -> New Project selecione/informe :

Selecione o template Internet Application e o View Engine ASPX e clique em OK;

A estrutura do projeto criado pode ser vista na janela Solution Explorer conforme a figura abaixo:

Definindo o roteamento no arquivo Global.asax

Vamos definir uma novo rota no arquivo Global.asax que se encontra na raiz do projeto da aplicação.

Abra o arquivo Global.asax e na seção de roteamento no início do arquivo vamos criar uma nova rota chamada FiltroListaNegra que conterá apenas um critério para o seu caminho padrão que irá essencialmente filtrar todas as requisições para esta rota efetuando uma verificação.

Para isso vamos definir um tratamento de roteamento (RouteHandler) chamado a classe ListaNegraModel que iremos definir em nosso Model.

O código abaixo definir a nova rota:

Public Class MvcApplication
    Inherits System.Web.HttpApplication

    Shared Sub RegisterGlobalFilters(ByVal filters As GlobalFilterCollection)
        filters.Add(New HandleErrorAttribute())
    End Sub

   
Shared Sub RegisterRoutes(ByVal routes As RouteCollection)
        routes.IgnoreRoute("{resource}.axd/{*pathInfo}")

        routes.MapRoute("FiltroListaNegra", "{*path}", New With {.controller = "ListaNegra", .action = "Index"}, _
                        New With {.EstaNaListaNegra = New ListaNegraModel()})


      
 ' Route name
        ' URL with parameters
        ' Parameter defaults

        routes.MapRoute("Default", "{controller}/{action}/{id}", New With { _
          .controller = "Home", _
          .action = "Index", _
          .id = UrlParameter.[Optional] _
        })

    End Sub


    Sub Application_Start()
        AreaRegistration.RegisterAllAreas()
        RegisterGlobalFilters(GlobalFilters.Filters)
        RegisterRoutes(RouteTable.Routes)
    End Sub
End Class

Definindo o Model

Após definir a nova rota temos que definir em nosso Model a classe ListaNegraModel() que deverá implementar a interface IRouteConstraint.

A interface IRouteConstraint define o contrato que uma classe deve implementar para verificar se um valor de parâmetro de URL é válido para uma restrição.

Quando você inclui uma restrição para um parâmetro de URL em uma definição de rota, você deve representar a restrição como uma seqüência de caracteres ou um objeto que implementa a interface IRouteConstraint.

Se a restrição é uma seqüência de caracteres, ela é tratada como uma expressão regular. Quando você não puder representar a lógica de validação usando uma expressão regular você pode criar uma classe que implementa a interface IRouteConstraint e neste caso adicionar a lógica de validação no método Match. É isso que estamos fazendo.

O método Match implementado a partir da interface é quem vai realizar as verificações definindo se a requisição pode ou não ser atendida.

No nosso exemplo definimos listas para ip, nome de usuário e email e realizamos as verificações retornando True ou False.

Clique com o botão direito do mouse sobre a pasta Models e selecione Add->Class; A seguir informe o nome ListaNegraModel.vb e clique em OK;

Imports System.Collections.Generic
Imports System.Net
Imports System.Web
Imports System.Web.Routing

Public Class ListaNegraModel
    Implements IRouteConstraint

    Public Function Match(ByVal httpContext As System.Web.HttpContextBase, ByVal route As System.Web.Routing.Route, _
 ByVal parameterName As String, ByVal values As System.Web.Routing.RouteValueDictionary, ByVal routeDirection As System.Web.Routing.RouteDirection) _ 
As Boolean Implements System.Web.Routing.IRouteConstraint.Match
        Dim taNaListaNegra As Boolean = False

        'voce pode pegar os dados da lisat negra em um banco de dados
        'lista de endereços ip
        Dim _ipListaNegra As New List(Of String)() From { _
	         "127.0.0.1", _
	         "192.168.1.100", _
	         "192.168.1.101", _
	         "192.168.1.107", _
	         "192.168.1.108" _
	        }

        'lista de usuários
        Dim _usernameListaNegra As New List(Of String)() From { _
         "macoratti", _
         "anonymous" _
        }

        'lista de emails
        Dim _emailListaNegra As New List(Of String)() From { _
         "macoratti@yahoo.com", _
         "teste@hotmail.com" _
        }

        'verifica ip na lista
        If _ipListaNegra.Contains(httpContext.Request.UserHostAddress) Then
            values.Add("Motivo", "Seu IP esta na lista Negra: " & Convert.ToString(httpContext.Request.UserHostAddress))
            taNaListaNegra = True
        End If

        'verifica usuário na lista
        If httpContext.Profile IsNot Nothing AndAlso _usernameListaNegra.Contains(httpContext.Profile.UserName.ToLower()) Then
            values.Add("Motivo", "Seu usuário esta na lista Negra: " & httpContext.Profile.UserName.ToLower())
            taNaListaNegra = True
        End If

        'verifica endereço de email
        If _emailListaNegra.Contains("values['email'].ToString()") Then
            values.Add("Motivo", "Seu endereço de email esta na lista Negra: values['email'].ToString()")
            taNaListaNegra = True
        End If

        If taNaListaNegra Then
            'se você quiser despachar o usuário...descomenta a linha abaixo
            'httpContext.Response.Redirect("http://www.macoratti.net")

            'define o código do status para acesso negado
            httpContext.Response.StatusCode = CInt(HttpStatusCode.Forbidden)
        End If

        'retorna o resultado
        Return taNaListaNegra
    End Function
End Class

Definindo o Controller

Vamos definir um novo Controlador na pasta Controller que irá retornar o motivo pelo qual o acesso foi restringido.

Clique com o botão direito do mouse sobre a pasta Controller e selecione Add->Controller; A seguir informe o nome ListaNegraController e clique em OK;

Em seguida defina o seguinte código no arquivo:

Namespace RestricaoAcessoIP_Roteamento
    Public Class ListaNegraController
        Inherits System.Web.Mvc.Controller
        '
        ' GET: /ListaNegra
        Public Function Index(ByVal motivo As String) As ActionResult
            ViewData("Motivo") = motivo
            Return View()
        End Function

    End Class
End Namespace

Definindo a View

Com a nova rota definida e o nosso Model que irá realizar o filtro também delineado definimos o controlador que irá retornar o resultado para nossa view.

Vamos então definir agora a nossa visualização.

Clique com o botão direito do mouse sobre o método Index do controlador ListaNegraController e selecione a opção Add->View e defina as seguintes configurações:

Após isso digite o seguinte código neste arquivo:

<%@ Page Title="" Language="VB" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage(Of RestricaoAcessoIP_Roteamento.ListaNegraModel)" %>

<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
Index
</asp:Content>

<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">

 <h2>Atenção !</h2>
    <div style="color: #CC0000; font-size: medium">
        <strong>Parece que o seu perfil possui restrição de acesso ao nosso site. <br />
        Se você sentir que esta mensagem não se aplica a você entre em contato...
        </strong>
    </div>
    <br />
    <fieldset>
        <legend>Qual o motivo para a restrição de meu acesso ?</legend>
         <b><%= ViewData("motivo")%></b>
    </fieldset>

</asp:Content>

Com isso já temos todos os elementos prontos para podermos testar a nossa aplicação.

Executando o projeto iremos obter o seguinte resultado:

Por quê ?

Porque estamos iniciando a aplicação a partir da máquina local (127.0.0.1) e este IP esta na lista negra definida em nosso Model, logo, ao realizar a verificação o método Match irá retornar o valor True indicando que o acesso deverá ser restringido.

Pegue o projeto completo aqui: RestricaoAcessoIP_Roteamento.zip

1Timóteo 2:8 Quero, pois, que os homens orem em todo lugar, levantando mãos santas, sem ira nem contenda.

Referências:


José Carlos Macoratti