ASP .NET Core - Segurança com DataProtetorTokenProvider
Hoje veremos como cifrar parâmetros em uma URL usando o recurso DataProteterTokenProvider. |
Atualmente, a segurança das aplicações na internet e dos dados se tornou uma grande preocupação para a maioria das empresas.
Apesar disso não é difícil você encontrar aplicações web que usam URLs para passar parâmetros que serão usados para identificar usuários e/ou realizar alguma tarefa que exige uma segurança maior.
Exemplo de URL com parâmetro usado em uma aplicação web ingênua:
https://localhost:44347/Customers/Edit/1 |
É evidente que tal informação poderá ser hackeada por pessoas mal intencionadas causando prejuízos enormes dependendo do tipo de aplicação.
Será que não podemos fazer nada para contornar esse problema ?
Será que não podemos criptografar ou cifrar o valor do parâmetro em um formato não legível e ainda sim usá-lo no formato decifrado internamente para realizar a operação ?
Sim podemos fazer isso, e, nosso objetivo será exibir a URL cifrada de forma a proteger a URL:
https://localhost:44347/Customers/Edit/CfDJ8FCRQuiQpFtEur77mE5kVpwvgzvO1HQEIAeDGkr5I9NG1... |
Para isso vamos usar os recursos da classe DataProtectorTokenProvider que fornece proteção e validação de tokens de identidade e também é responsável por criptografar e descriptografar esses tokens.
Este seria um sistema de proteção de dados embutido e disponível por padrão na plataforma .NET.
Esse sistema de proteção de dados é construído usando um provedor de proteção de dados (representado pela interface IDataProtectionProvider), que é usado para criar um protetor de dados (representado pela interface IDataProtector).
O protetor de dados é usado para criptografar e descriptografar dados. Como o sistema de proteção de dados foi adicionado à coleção de serviços do aplicativo por padrão, ele pode ser disponibilizado por meio de injeção de dependência.
Vamos usar os métodos Protect() e Unprotect() da interface IDataProtector para criptografar e descriptografar, respectivamente.
Nota: Você pode ver o código fonte da classe DataProtectorTokenProvider neste link: https://github.com/aspnet/Identity/blob/release/2.2/src/Identity/DataProtectionTokenProvider.cs
A seguir eu vou mostrar como cifrar e decifrar o parâmetro em uma URL em uma aplicação ASP .NET Core MVC que faz o CRUD de Clientes ou Customers.
Para simplificar vou partir da aplicação MVC pronta que gerenciar informações de Customers usando o controlador CustomersController e que esta funcionando e expondo o id do Customer na Url. Para evitar isso vou aplicar os recursos da classe DataProtectorTokenProvider.
Criando e registrando a classe para cifrar e decifrar strings
No projeto MVC crie uma pasta Security e nesta pasta crie a classe DataProtectionStrings :
public class DataProtectionStrings
{
public readonly string CustomerIdValue = "CustomerIdValue";
}
|
Esta classe contém chave string exigida para criptografia e descriptografia. No momento, temos apenas uma string definida com o nome de 'CustomerIdValue'.
Agora vamos registrar o serviço para esta classe no método ConfigureServices da classe Startup:
public void ConfigureServices(IServiceCollection services) { services.AddSingleton<DataProtectionStrings>();
services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddControllersWithViews();
}
|
Com isso agora poderemos injetar esta classe no controlador CustomersController do projeto MVC.
Ajustando o modelo de domínio Customer
Em nossa implementação vamos cifrar e decifrar o Id do Customer, e, para isso precisamos armazenar o valor cifrado em uma propriedade que iremos criar na tabela Customer com o nome de EncryptedId:
public class Customer
{
public int Id { get; set; }
[NotMapped]
public string EncryptedId { get; set; }
public string Name { get; set; }
public string Email { get; set; }
}
|
Criamos a propriedade EncryptedId para conter o Id do cliente criptografado. O atributo NotMapped especifica que esta propriedade deve ser excluída do mapeamento para uma coluna da tabela do banco de dados pois não queremos armazenar essas informações na tabela do banco de dados.
O atributo NotMapped está no namespace System.ComponentModel.DataAnnotations.Schema.
Ajustando o controlador CustomersController
Agora vamos usar os recursos da classe DataProtectorTokenProvider onde aplicaremos os métodos Protect e Unprotect da interface IDataProtector.
Para isso vamos injetar uma instância de IDataProtector no construtor da classe e a seguir vamos usar o valor da propriedade CustomerIdValue definida na classe DataProtectionStrings.
Vamos abrir o controlador CustomersController e alterar o código conforme abaixo:
using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Mvc_UrlProtected.Models; using Mvc_UrlProtected.Security; using System; using System.Linq; using System.Threading.Tasks; namespace Mvc_UrlProtected.Controllers public CustomersController(AppDbContext context, this.protector = dataProtectionProvider.CreateProtector( public async Task<IActionResult> Index() return View(resultado.Select(e => } public async Task<IActionResult> Details(string id) int decryptedIntId = DecryptUrlValue(id); var customer = await _context.Customers if (customer == null) return View(customer); private int DecryptUrlValue(string id) public IActionResult Create() [HttpPost] public async Task<IActionResult> Edit(string id) int decryptedIntId = DecryptUrlValue(id); var customer = await _context.Customers.FindAsync(decryptedIntId); if (customer == null) [HttpPost] if (ModelState.IsValid) public async Task<IActionResult> Delete(string id) int decryptedIntId = DecryptUrlValue(id); var customer = await _context.Customers if (customer == null) return View(customer); [HttpPost, ActionName("Delete")] private bool CustomerExists(int id) |
Neste controlador fizemos as seguintes alterações:
1- No construtor injetamos o serviço do IDataProtector e informamos a string a ser usada para cifrar e decifrar o Id do Customer:
private readonly AppDbContext _context; private readonly IDataProtector protector; public CustomersController(AppDbContext context, this.protector = dataProtectionProvider.CreateProtector(
|
2- No método Index ciframos o valor do Id e armazenamos na propriedade EncryptedId:
public async Task<IActionResult> Index() { var resultado = await _context.Customers.ToListAsync(); return View(resultado.Select(e => } |
3- Criamos o método DecriptUrlValue(string id) que recebe o Id cifrado e decifra o Id e faz a conversão para int.
private int DecryptUrlValue(string id) { string decryptedId = protector.Unprotect(id); int decryptedIntId = Convert.ToInt32(decryptedId); return decryptedIntId; } |
4- Alteramos os métodos Action Edit, Details e Delete onde estamos usando o método DecriptUrlValue a alterando a consulta para localizar o customer usando o valor decifrado do id:
1- Edit int decryptedIntId = DecryptUrlValue(id); 2- Details int decryptedIntId = DecryptUrlValue(id); 3- Delete int decryptedIntId = DecryptUrlValue(id); |
E na View Index do controlador CustomersController vamos alterar o código para atribuir o id cifrado à rota usada na aplicação:
@model IEnumerable<Mvc_UrlProtected.Models.Customer> <h1>Index</h1> <p> <a asp-action="Create">Create New</a> </p> <table class="table"> <thead> <tr> <th> @Html.DisplayNameFor(model => model.Name) </th> <th> @Html.DisplayNameFor(model => model.Email) </th> <th></th> </tr> </thead> <tbody> @foreach (var item in Model) { <tr> <td> @Html.DisplayFor(modelItem => item.Name) </td> <td> @Html.DisplayFor(modelItem => item.Email) </td> <td> <a asp-action="Edit" asp-route-id="@item.EncryptedId">Edit</a> | <a asp-action="Details" asp-route-id="@item.EncryptedId">Details</a> | <a asp-action="Delete" asp-route-id="@item.EncryptedId">Delete</a> </td> </tr> } </tbody> </table> |
Vamos precisar ajustar também a view Details para obter o parâmetro referente ao Id a partir da url usando o código abaixo:
<a asp-action="Edit" asp-route-id="@ViewContext.RouteData.Values["id"]">Edit</a>
Os demais ajustes são simples e estão no código do projeto.
Agora é só alegria....
Executando o projeto iremos obter o seguinte resultado:
Pegue o projeto aqui : Mvc_UrlProtected.zip (sem as referências)
"Melhor é o que
tarda em irar-se do que o poderoso, e o que controla o seu ânimo do que aquele
que toma uma cidade."
Provérbios 16:32
Referências:
Formatação de data e hora para uma cultura ...
C# - Calculando a diferença entre duas datas
NET - Padrão de Projeto - Null Object Pattern
C# - Fundamentos : Definindo DateTime como Null ...
ASP .NET Core - Como acessar a configuração .t
ASP .NET - usando o aruqivo de configuração web.config
ASP .NET Core - Fazendo a hospedagem no ..
ASP .NET Core - Modelo de Hospedagem .
NET - Criando uma Self-Host Web API - IV