Proteger aplicativos usando autenticação e autorização
pela Microsoft
Esta é a etapa 9 de um tutorial gratuito de aplicativo "NerdDinner" que explica como criar um aplicativo Web pequeno, mas completo, usando ASP.NET MVC 1.
A etapa 9 mostra como adicionar autenticação e autorização para proteger nosso aplicativo NerdDinner, para que os usuários precisem se registrar e fazer logon no site para criar novos jantares, e somente o usuário que está hospedando um jantar poderá editá-lo mais tarde.
Se você estiver usando ASP.NET MVC 3, recomendamos que siga os tutoriais Introdução With MVC 3 ou MVC Music Store.
NerdDinner Etapa 9: Autenticação e Autorização
Neste momento, nosso aplicativo NerdDinner concede a qualquer pessoa que visite o site a capacidade de criar e editar os detalhes de qualquer jantar. Vamos alterar isso para que os usuários precisem se registrar e fazer logon no site para criar novos jantares e adicionar uma restrição para que apenas o usuário que está hospedando um jantar possa editá-lo mais tarde.
Para habilitar isso, usaremos a autenticação e a autorização para proteger nosso aplicativo.
Noções básicas sobre autenticação e autorização
A autenticação é o processo de identificar e validar a identidade de um cliente acessando um aplicativo. Simplificando, trata-se de identificar "quem" é o usuário final quando ele visita um site. ASP.NET dá suporte a várias maneiras de autenticar usuários do navegador. Para aplicativos Web da Internet, a abordagem de autenticação mais comum usada é chamada de "Autenticação de Formulários". A Autenticação de Formulários permite que um desenvolvedor crie um formulário de logon HTML em seu aplicativo e valide o nome de usuário/senha que um usuário final envia em um banco de dados ou em outro repositório de credenciais de senha. Se a combinação de nome de usuário/senha estiver correta, o desenvolvedor poderá solicitar que ASP.NET emita um cookie HTTP criptografado para identificar o usuário em solicitações futuras. Usaremos a autenticação de formulários com nosso aplicativo NerdDinner.
A autorização é o processo de determinar se um usuário autenticado tem permissão para acessar uma URL/recurso específico ou executar alguma ação. Por exemplo, em nosso aplicativo NerdDinner, queremos autorizar que somente os usuários conectados possam acessar a URL /Dinners/Create e criar novos Jantares. Também queremos adicionar lógica de autorização para que apenas o usuário que está hospedando um jantar possa editá-lo e negar o acesso de edição a todos os outros usuários.
Autenticação de Formulários e AccountController
O modelo de projeto padrão do Visual Studio para ASP.NET MVC habilita automaticamente a autenticação de formulários quando novos aplicativos ASP.NET MVC são criados. Ele também adiciona automaticamente uma implementação de página de logon de conta pré-criada ao projeto , o que facilita a integração de segurança em um site.
O Site padrão. master master página exibe um link "Logon" no canto superior direito do site quando o usuário que o acessa não é autenticado:
Clicar no link "Logon" leva um usuário para a URL /Account/LogOn :
Os visitantes que não se registraram podem fazer isso clicando no link "Registrar" – que os levará para a URL /Account/Register e permitirá que eles insiram detalhes da conta:
Clicar no botão "Registrar" criará um novo usuário no sistema de associação ASP.NET e autenticará o usuário no site usando a autenticação de formulários.
Quando um usuário está conectado, o Site. master altera o canto superior direito da página para gerar uma mensagem "Bem-vindo [nome de usuário]!" e renderiza um link "Log Off" em vez de um "Logon". Clicar no link "Log Off" faz logoff do usuário:
A funcionalidade de logon, logoff e registro acima é implementada na classe AccountController que foi adicionada ao nosso projeto pelo Visual Studio quando ele criou o projeto. A interface do usuário do AccountController é implementada usando modelos de exibição no diretório \Views\Account:
A classe AccountController usa o sistema de Autenticação ASP.NET Forms para emitir cookies de autenticação criptografados e a API de Associação ASP.NET para armazenar e validar nomes de usuário/senhas. A API de Associação ASP.NET é extensível e permite que qualquer repositório de credenciais de senha seja usado. ASP.NET é fornecido com implementações internas do provedor de associação que armazenam nome de usuário/senhas em um banco de dados SQL ou no Active Directory.
Podemos configurar qual provedor de associação nosso aplicativo NerdDinner deve usar abrindo o arquivo "web.config" na raiz do projeto e procurando a <seção de associação> dentro dele. O padrão web.config adicionado quando o projeto foi criado registra o provedor de associação sql e o configura para usar uma cadeia de conexão chamada "ApplicationServices" para especificar o local do banco de dados.
A cadeia de conexão padrão "ApplicationServices" (que é especificada na seção connectionStrings> do arquivo web.config) é configurada para usar o <SQL Express. Ele aponta para um banco de dados do SQL Express chamado "ASPNETDB. MDF" no diretório "App_Data" do aplicativo. Se esse banco de dados não existir na primeira vez que a API de Associação for usada no aplicativo, ASP.NET criará automaticamente o banco de dados e provisionará o esquema de banco de dados de associação apropriado dentro dele:
Se, em vez de usar o SQL Express, quiséssemos usar uma instância de SQL Server completa (ou nos conectar a um banco de dados remoto), bastaria atualizar a cadeia de conexão "ApplicationServices" no arquivo web.config e garantir que o esquema de associação apropriado tenha sido adicionado ao banco de dados em que ele aponta. Você pode executar o utilitário "aspnet_regsql.exe" no diretório \Windows\Microsoft.NET\Framework\v2.0.50727\ para adicionar o esquema apropriado para associação e outros serviços de aplicativos ASP.NET a um banco de dados.
Autorizando a URL /Dinners/Create usando o filtro [Autorizar]
Não precisamos escrever nenhum código para habilitar uma implementação segura de autenticação e gerenciamento de conta para o aplicativo NerdDinner. Os usuários podem registrar novas contas com nosso aplicativo e logoff/logoff do site.
Agora, podemos adicionar lógica de autorização ao aplicativo e usar o status de autenticação e o nome de usuário dos visitantes para controlar o que eles podem ou não fazer dentro do site. Vamos começar adicionando lógica de autorização aos métodos de ação "Criar" da nossa classe DinnersController. Especificamente, exigiremos que os usuários que acessam a URL /Dinners/Create sejam conectados. Se eles não estiverem conectados, os redirecionaremos para a página de logon para que eles possam entrar.
Implementar essa lógica é muito fácil. Tudo o que precisamos fazer é adicionar um atributo de filtro [Autorizar] aos nossos métodos de ação Criar da seguinte maneira:
//
// GET: /Dinners/Create
[Authorize]
public ActionResult Create() {
...
}
//
// POST: /Dinners/Create
[AcceptVerbs(HttpVerbs.Post), Authorize]
public ActionResult Create(Dinner dinnerToCreate) {
...
}
ASP.NET MVC dá suporte à capacidade de criar "filtros de ação" que podem ser usados para implementar a lógica reutilizável que pode ser aplicada declarativamente aos métodos de ação. O filtro [Autorizar] é um dos filtros de ação internos fornecidos pelo ASP.NET MVC e permite que um desenvolvedor aplique declarativamente regras de autorização a métodos de ação e classes de controlador.
Quando aplicado sem parâmetros (como acima), o filtro [Autorizar] impõe que o usuário que está fazendo a solicitação do método de ação deve estar conectado e redirecionará automaticamente o navegador para a URL de logon se não estiver. Ao fazer esse redirecionamento, a URL solicitada originalmente é passada como um argumento querystring (por exemplo: /Account/LogOn? ReturnUrl=%2fDinners%2fCreate). Em seguida, o AccountController redirecionará o usuário de volta para a URL solicitada originalmente após o logon.
Opcionalmente, o filtro [Autorizar] dá suporte à capacidade de especificar uma propriedade "Usuários" ou "Funções" que pode ser usada para exigir que o usuário esteja conectado e dentro de uma lista de usuários permitidos ou um membro de uma função de segurança permitida. Por exemplo, o código abaixo permite apenas que dois usuários específicos, "scottgu" e "billg", acessem a URL /Dinners/Create:
[Authorize(Users="scottgu,billg")]
public ActionResult Create() {
...
}
A inserção de nomes de usuário específicos no código tende a ser muito indistencável. Uma abordagem melhor é definir "funções" de nível superior em que o código verifica e mapear os usuários para a função usando um banco de dados ou um sistema de diretório ativo (permitindo que a lista de mapeamento de usuário real seja armazenada externamente do código). ASP.NET inclui uma API interna de gerenciamento de funções, bem como um conjunto interno de provedores de função (incluindo os do SQL e do Active Directory) que podem ajudar a executar esse mapeamento de usuário/função. Em seguida, podemos atualizar o código para permitir que os usuários dentro de uma função "administrador" específica acessem a URL /Dinners/Create:
[Authorize(Roles="admin")]
public ActionResult Create() {
...
}
Usando a propriedade User.Identity.Name ao criar jantares
Podemos recuperar o nome de usuário do usuário conectado no momento de uma solicitação usando a propriedade User.Identity.Name exposta na classe base Controller.
Anteriormente, quando implementamos a versão HTTP-POST do nosso método de ação Create(), tínhamos codificado a propriedade "HostedBy" do Dinner em uma cadeia de caracteres estática. Agora podemos atualizar esse código para, em vez disso, usar a propriedade User.Identity.Name, bem como adicionar automaticamente um RSVP para o host que cria o Jantar:
//
// POST: /Dinners/Create
[AcceptVerbs(HttpVerbs.Post), Authorize]
public ActionResult Create(Dinner dinner) {
if (ModelState.IsValid) {
try {
dinner.HostedBy = User.Identity.Name;
RSVP rsvp = new RSVP();
rsvp.AttendeeName = User.Identity.Name;
dinner.RSVPs.Add(rsvp);
dinnerRepository.Add(dinner);
dinnerRepository.Save();
return RedirectToAction("Details", new { id=dinner.DinnerID });
}
catch {
ModelState.AddModelErrors(dinner.GetRuleViolations());
}
}
return View(new DinnerFormViewModel(dinner));
}
Como adicionamos um atributo [Authorize] ao método Create(), ASP.NET MVC garante que o método de ação seja executado somente se o usuário que estiver visitando a URL /Dinners/Create estiver conectado no site. Dessa forma, o valor da propriedade User.Identity.Name sempre conterá um nome de usuário válido.
Usando a propriedade User.Identity.Name ao editar jantares
Agora vamos adicionar alguma lógica de autorização que restrinja os usuários para que eles só possam editar as propriedades dos jantares que eles próprios estão hospedando.
Para ajudar com isso, primeiro adicionaremos um método auxiliar "IsHostedBy(username)" ao nosso objeto Dinner (dentro da classe parcial Dinner.cs que criamos anteriormente). Esse método auxiliar retorna true ou false dependendo se um nome de usuário fornecido corresponde à propriedade Dinner HostedBy e encapsula a lógica necessária para executar uma comparação de cadeia de caracteres que não diferencia maiúsculas de minúsculas:
public partial class Dinner {
public bool IsHostedBy(string userName) {
return HostedBy.Equals(userName, StringComparison.InvariantCultureIgnoreCase);
}
}
Em seguida, adicionaremos um atributo [Authorize] aos métodos de ação Editar() em nossa classe DinnersController. Isso garantirá que os usuários devem estar conectados para solicitar uma URL /Dinners/Edit/[id] .
Em seguida, podemos adicionar código aos nossos métodos Editar que usam o método auxiliar Dinner.IsHostedBy(username) para verificar se o usuário conectado corresponde ao host dinner. Se o usuário não for o host, exibiremos uma exibição "InvalidOwner" e encerraremos a solicitação. O código para fazer isso se parece com o seguinte:
//
// GET: /Dinners/Edit/5
[Authorize]
public ActionResult Edit(int id) {
Dinner dinner = dinnerRepository.GetDinner(id);
if (!dinner.IsHostedBy(User.Identity.Name))
return View("InvalidOwner");
return View(new DinnerFormViewModel(dinner));
}
//
// POST: /Dinners/Edit/5
[AcceptVerbs(HttpVerbs.Post), Authorize]
public ActionResult Edit(int id, FormCollection collection) {
Dinner dinner = dinnerRepository.GetDinner(id);
if (!dinner.IsHostedBy(User.Identity.Name))
return View("InvalidOwner");
try {
UpdateModel(dinner);
dinnerRepository.Save();
return RedirectToAction("Details", new {id = dinner.DinnerID});
}
catch {
ModelState.AddModelErrors(dinnerToEdit.GetRuleViolations());
return View(new DinnerFormViewModel(dinner));
}
}
Em seguida, podemos clicar com o botão direito do mouse no diretório \Views\Dinners e escolher o comando de menu Adicionar Exibição> para criar uma nova exibição "InvalidOwner". Vamos preenchê-lo com a mensagem de erro abaixo:
<asp:Content ID="Title" ContentPlaceHolderID="TitleContent" runat="server">
You Don't Own This Dinner
</asp:Content>
<asp:Content ID="Main" ContentPlaceHolderID="MainContent" runat="server">
<h2>Error Accessing Dinner</h2>
<p>Sorry - but only the host of a Dinner can edit or delete it.</p>
</asp:Content>
E agora, quando um usuário tentar editar um jantar que não possui, ele receberá uma mensagem de erro:
Podemos repetir as mesmas etapas para que os métodos de ação Delete() em nosso controlador bloqueiem a permissão para excluir jantares também e garantir que apenas o host de um Jantar possa excluí-lo.
Mostrando/ocultando links de edição e exclusão
Estamos vinculando ao método de ação Editar e Excluir de nossa classe DinnersController de nossa URL de Detalhes:
No momento, estamos mostrando os links de ação Editar e Excluir, independentemente de o visitante para a URL de detalhes ser o host do jantar. Vamos alterar isso para que os links sejam exibidos somente se o usuário visitante for o proprietário do jantar.
O método de ação Details() em nosso DinnersController recupera um objeto Dinner e o passa como o objeto modelo para nosso modelo de exibição:
//
// GET: /Dinners/Details/5
public ActionResult Details(int id) {
Dinner dinner = dinnerRepository.GetDinner(id);
if (dinner == null)
return View("NotFound");
return View(dinner);
}
Podemos atualizar nosso modelo de exibição para mostrar/ocultar condicionalmente os links Editar e Excluir usando o método auxiliar Dinner.IsHostedBy() como abaixo:
<% if (Model.IsHostedBy(Context.User.Identity.Name)) { %>
<%= Html.ActionLink("Edit Dinner", "Edit", new { id=Model.DinnerID }) %> |
<%= Html.ActionLink("Delete Dinner", "Delete", new {id=Model.DinnerID}) %>
<% } %>
Próximas etapas
Agora vamos examinar como podemos habilitar usuários autenticados para RSVP para jantares usando o AJAX.