Proteger aplicativos usando autenticação e autorização

pela Microsoft

Baixar PDF

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 você siga os tutoriais Introdução ao MVC 3 ou MVC Music Store.

Etapa 9 do NerdDinner: 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 outro repositório de credenciais de senha. Se a combinação 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 autenticação por 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 /Jantares/Criar 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 MVC ASP.NET 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.

A página mestra do Site.master padrão exibe um link "Logon" no canto superior direito do site quando o usuário que o acessa não é autenticado:

Captura de tela da página Hospedar um Jantar do Nerd Dinner. A opção Entrar está destacada no canto superior direito.

Clicar no link "Logon" leva um usuário para a URL /Account/LogOn :

Captura de tela da Página de Login do Jantar Nerd.

Os visitantes que não se registraram podem fazer isso clicando no link "Registrar" – que os levará para a URL /Conta/Registro e permitirá que eles insiram detalhes da conta:

Captura de tela da página Criar uma Nova Conta do Nerd Dinner.

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 "Logoff" em vez de um "Logon". Clicar no link "Log Off" desloga o usuário.

Captura de tela da página de formulário do Jantar Nerd para Organizar um Jantar. Os botões Bem-vindo e Sair são realçados no canto superior direito.

A funcionalidade de login, logout e registro mencionada anteriormente é implementada na classe AccountController que foi adicionada ao nosso projeto pelo Visual Studio quando ele criou o projeto. A interface do usuário para o AccountController é implementada usando modelos de exibição no diretório \Views\Account:

Captura de tela da árvore de navegação Nerd Dinner. O ponto c do Controlador de Conta está realçado. A Pasta da Conta e os itens de menu também estão realçados.

A classe AccountController usa o sistema de Autenticação de Formulários ASP.NET 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 vem com implementações internas de provedores de autenticação que armazenam nomes 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 SQL Express chamado "ASPNETDB. MDF" no diretório "App_Data" do aplicativo. Se esse banco de dados não existir na primeira vez em 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:

Captura de tela da árvore de navegação Nerd Dinner. Os Dados do Aplicativo são expandidos e A S P NET D B dot M D F está selecionado.

Se, em vez de usar o SQL Express, quiséssemos usar uma instância completa do SQL Server (ou conectar a um banco de dados remoto), tudo o que precisaríamos fazer é atualizar a string de conexão "ApplicationServices" no arquivo web.config e garantir que o esquema de associação apropriado tenha sido adicionado ao banco de dados ao qual a string 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 os outros serviços de aplicativos ASP.NET a um banco de dados.

Autorizando a URL /Dinners/Create usando o filtro [Autorizar]

Não foi necessário escrever nenhum código para habilitar uma autenticação segura e uma implementação de gerenciamento de conta para o aplicativo NerdDinner. Os usuários podem cadastrar novas contas com nosso aplicativo e fazer login/sair 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 /Jantares/Criar devem estar conectados. Se eles não estiverem conectados, redirecionará-os 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 [Authorize] aos nossos métodos de ação Criar assim:

//
// 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 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 ser 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). O AccountController redirecionará o usuário de volta para a URL solicitada originalmente após o logon.

O filtro [Authorize] opcionalmente oferece suporte à habilidade de especificar uma propriedade "Usuários" ou "Funções", que pode ser usada para exigir que o usuário esteja tanto logado quanto em uma lista de usuários permitidos ou seja membro de uma função de segurança permitida. Por exemplo, o código abaixo só permite 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 bastante difícil de manter. Uma abordagem melhor é definir "funções" de nível superior 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 de gerenciamento de função interna, 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 apenas que os usuários dentro de uma função específica de "administrador" acessem a URL /Dinners/Create:

[Authorize(Roles="admin")]
public ActionResult Create() {
   ...
}

Usando a propriedade User.Identity.Name ao criar Dinners

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 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 possam editar apenas as propriedades dos jantares que eles próprios estão organizando.

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 utilizam o método auxiliar Dinner.IsHostedBy(username) para verificar se o usuário conectado corresponde ao anfitrião do 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 tenta editar um evento de jantar do qual não é proprietário, ele receberá uma mensagem de erro:

Captura de tela da mensagem de erro na página da Web do Nerd Dinner.

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.

Estamos vinculando ao método de ação Excluir e Editar da nossa classe DinnersController a partir da nossa URL de Detalhes:

Captura de tela da página Nerd Dinner. Os botões Editar e Excluir são circulados na parte inferior. Os detalhes da URL são circulados na parte superior.

Atualmente, estamos mostrando os links de ação Editar e Excluir, independentemente de o visitante da URL de detalhes ser o anfitrião 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, em seguida, passa-o como o objeto modelo para o 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 a AJAX.