Autorização baseada em função (C#)

por Scott Mitchell

Baixar código ou baixar PDF

Este tutorial começa com uma visão de como a estrutura de funções associa as funções de um usuário com seu contexto de segurança. Em seguida, ele examina como aplicar regras de autorização de URL baseadas em função. Depois disso, veremos o uso de meios declarativos e programáticos para alterar os dados exibidos e a funcionalidade oferecida por uma página do ASP.NET.

Introdução

No tutorial de autorização baseada no usuário , vimos como usar a autorização de URL para especificar o que os usuários podiam visitar em um determinado conjunto de páginas. Com apenas um pouco de marcação no Web.config , poderíamos instruir o ASP.net a permitir que apenas usuários autenticados visitem uma página. Ou poderíamos ditar que somente os usuários Tito e Bob eram permitidos, ou indicam que todos os usuários autenticados, exceto o Sam, eram permitidos.

Além da autorização de URL, também examinamos técnicas declarativas e Programáticas para controlar os dados exibidos e a funcionalidade oferecida por uma página com base no usuário que está visitando. Em particular, criamos uma página que listou o conteúdo do diretório atual. Qualquer pessoa pode visitar esta página, mas somente os usuários autenticados poderão exibir o conteúdo dos arquivos e somente Tito poderá excluir os arquivos.

A aplicação de regras de autorização a uma base de usuário por usuário pode crescer em um pesadelo de escrituração. Uma abordagem mais passível de manutenção é usar a autorização baseada em função. A boa notícia é que as ferramentas de nossa disposição para aplicar as regras de autorização funcionam igualmente bem com as funções que são para contas de usuário. As regras de autorização de URL podem especificar funções em vez de usuários. O controle LoginView, que renderiza uma saída diferente para usuários autenticados e anônimos, pode ser configurado para exibir conteúdo diferente com base nas funções do usuário conectado. E a API de funções inclui métodos para determinar as funções do usuário conectado.

Este tutorial começa com uma visão de como a estrutura de funções associa as funções de um usuário com seu contexto de segurança. Em seguida, ele examina como aplicar regras de autorização de URL baseadas em função. Depois disso, veremos o uso de meios declarativos e programáticos para alterar os dados exibidos e a funcionalidade oferecida por uma página do ASP.NET. Vamos começar!

Compreendendo como as funções são associadas ao contexto de segurança de um usuário

Sempre que uma solicitação entra no pipeline ASP.NET, ela é associada a um contexto de segurança, que inclui informações que identificam o solicitante. Ao usar a autenticação de formulários, um tíquete de autenticação é usado como um token de identidade. Como discutimos em uma visão geral do tutorial de autenticação de formulários , o FormsAuthenticationModule é responsável por determinar a identidade do solicitante, que ele faz durante o AuthenticateRequest evento.

Se um tíquete de autenticação válido e não expirado for encontrado, o o FormsAuthenticationModule decodificará para determinar a identidade do solicitante. Ele cria um novo GenericPrincipal objeto e o atribui ao HttpContext.User objeto. A finalidade de uma entidade de segurança, como GenericPrincipal , é identificar o nome do usuário autenticado e a quais funções ela pertence. Essa finalidade é evidente pelo fato de que todos os objetos principais têm uma Identity propriedade e um IsInRole(roleName) método. FormsAuthenticationModuleNo entanto, o não está interessado em registrar informações de função e o GenericPrincipal objeto criado por ele não especifica nenhuma função.

Se a estrutura de funções estiver habilitada, o RoleManagerModule módulo HTTP será etapas em após o FormsAuthenticationModule e identificará as funções do usuário autenticado durante o PostAuthenticateRequest evento, que é acionado após o AuthenticateRequest evento. Se a solicitação for de um usuário autenticado, o RoleManagerModule GenericPrincipal substituirá o objeto criado pelo e o FormsAuthenticationModule substitui por um RolePrincipal objeto. A RolePrincipal classe usa a API de funções para determinar a quais funções o usuário pertence.

A Figura 1 descreve o fluxo de trabalho do pipeline do ASP.NET ao usar a autenticação de formulários e a estrutura de funções. O FormsAuthenticationModule é executado primeiro, identifica o usuário por meio de seu tíquete de autenticação e cria um novo GenericPrincipal objeto. Em seguida, as RoleManagerModule etapas em e substitui o GenericPrincipal objeto por um RolePrincipal objeto.

Se um usuário anônimo visitar o site, nem o FormsAuthenticationModule nem o RoleManagerModule criará um objeto principal.

Os eventos de pipeline ASP.NET para um usuário autenticado ao usar a autenticação de formulários e a estrutura de funções

Figura 1: os eventos de pipeline ASP.net para um usuário autenticado ao usar a autenticação de formulários e a estrutura de funções (clique para exibir a imagem em tamanho normal)

O RolePrincipal método do objeto IsInRole(roleName) chama Roles.GetRolesForUser para obter as funções para o usuário a fim de determinar se o usuário é membro de roleName. Ao usar o SqlRoleProvider , isso resulta em uma consulta para o banco de dados de repositório de funções. Ao usar regras de autorização de URL baseadas em função, o RolePrincipal IsInRole método do será chamado em cada solicitação para uma página protegida pelas regras de autorização de URL baseadas em função. Em vez de ter que pesquisar as informações de função no banco de dados em cada solicitação, a estrutura de funções inclui uma opção para armazenar em cache as funções do usuário em um cookie.

Se a estrutura de funções estiver configurada para armazenar em cache as funções do usuário em um cookie, o RoleManagerModule criará o cookie durante o EndRequest eventodo pipeline do ASP.net. Esse cookie é usado em solicitações subsequentes no PostAuthenticateRequest , que é quando o RolePrincipal objeto é criado. Se o cookie for válido e não tiver expirado, os dados no cookie serão analisados e usados para popular as funções do usuário, salvando assim o RolePrincipal de ter que fazer uma chamada para a Roles classe para determinar as funções do usuário. A Figura 2 descreve esse fluxo de trabalho.

As informações de função do usuário podem ser armazenadas em um cookie para melhorar o desempenho

Figura 2: as informações de função do usuário podem ser armazenadas em um cookie para melhorar o desempenho (clique para exibir a imagem em tamanho normal)

Por padrão, o mecanismo de cookie de cache de função está desabilitado. Ele pode ser habilitado por meio da <roleManager> marcação de configuração no Web.config . Discutimos o uso do <roleManager> elemento para especificar provedores de função no tutorial criando e gerenciando funções , de modo que você já deve ter esse elemento no arquivo do aplicativo Web.config . As configurações de cookie de cache de função são especificadas como atributos do <roleManager> elemento e são resumidas na tabela 1.

Note

As definições de configuração listadas na tabela 1 especificam as propriedades do cookie de cache de função resultante. Para obter mais informações sobre cookies, como eles funcionam e suas várias propriedades, leia este tutorial de cookies.

Propriedade Descrição
cacheRolesInCookie Um valor booliano que indica se o cache de cookie é usado. Assume o padrão de false.
cookieName O nome do cookie de cache de função. O valor padrão é ". ASPXROLES".
cookiePath O caminho para o cookie de nome de funções. O atributo Path permite que um desenvolvedor limite o escopo de um cookie a uma hierarquia de diretório específica. O valor padrão é "/", que informa ao navegador para enviar o cookie de tíquete de autenticação para qualquer solicitação feita ao domínio.
cookieProtection Indica quais técnicas são usadas para proteger o cookie de cache de função. Os valores permitidos são: All (o padrão); Encryption ;; None e Validation .
cookieRequireSSL Um valor booliano que indica se uma conexão SSL é necessária para transmitir o cookie de autenticação. O valor padrão é false.
cookieSlidingExpiration Um valor booliano que indica se o tempo limite do cookie é redefinido cada vez que o usuário visita o site durante uma única sessão. O valor padrão é false. Esse valor só é pertinente quando createPersistentCookie é definido como true .
cookieTimeout Especifica o tempo, em minutos, após o qual o cookie de tíquete de autenticação expira. O valor padrão é 30. Esse valor só é pertinente quando createPersistentCookie é definido como true .
createPersistentCookie Um valor booliano que especifica se o cookie de cache de função é um cookie de sessão ou persistente. Se false (o padrão), será usado um cookie de sessão, que será excluído quando o navegador for fechado. Se true , um cookie persistente é usado; ele expira o cookieTimeout número de minutos após ter sido criado ou após a visita anterior, dependendo do valor de cookieSlidingExpiration .
domain Especifica o valor de domínio do cookie. O valor padrão é uma cadeia de caracteres vazia, o que faz com que o navegador use o domínio do qual foi emitido (como www.yourdomain.com). Nesse caso, o cookie não será enviado ao fazer solicitações para subdomínios, como admin.yourdomain.com. Se desejar que o cookie seja passado para todos os subdomínios, você precisará personalizar o domain atributo, definindo-o como "yourdomain.com".
maxCachedResults Especifica o número máximo de nomes de função que são armazenados em cache no cookie. O padrão é 25. O RoleManagerModule não cria um cookie para usuários que pertencem a mais de maxCachedResults funções. Consequentemente, o RolePrincipal método do objeto IsInRole usará a Roles classe para determinar as funções do usuário. O motivo maxCachedResults existe porque muitos agentes de usuário não permitem cookies maiores que 4.096 bytes. Portanto, esse limite destina-se a reduzir a probabilidade de exceder essa limitação de tamanho. Se você tiver nomes de função extremamente longos, talvez queira considerar a especificação de um maxCachedResults valor menor; contrariwise, se você tiver nomes de função extremamente curtos, provavelmente poderá aumentar esse valor.

Tabela 1: As opções de configuração de cookie de cache de função

Vamos configurar nosso aplicativo para usar cookies de cache de função não persistente. Para fazer isso, atualize o <roleManager> elemento no Web.config para incluir os seguintes atributos relacionados ao cookie:

<roleManager enabled="true"    
          defaultProvider="SecurityTutorialsSqlRoleProvider"    
          cacheRolesInCookie="true"    
          createPersistentCookie="false"    
          cookieProtection="All">    

     <providers>    
     ...    
     </providers>    
</roleManager>

Atualizei o <roleManager> elemento adicionando três atributos: cacheRolesInCookie , createPersistentCookie e cookieProtection . Ao definir cacheRolesInCookie como true , o RoleManagerModule agora armazenará em cache automaticamente as funções do usuário em um cookie em vez de ter que pesquisar as informações de função do usuário em cada solicitação. Defini explicitamente os createPersistentCookie atributos e cookieProtection como false e All , respectivamente. Tecnicamente, não precisei especificar valores para esses atributos, pois acabei de atribuí-los a seus valores padrão, mas os coloco para torná-lo explicitamente claro que não estou usando cookies persistentes e que o cookie é criptografado e validado.

Isso é tudo! Daqui em diante, a estrutura de funções armazenará em cache as funções dos usuários em cookies. Se o navegador do usuário não oferecer suporte a cookies, ou se seus cookies forem excluídos ou perdidos, de alguma forma, não será muito importante – o RolePrincipal objeto simplesmente usará a Roles classe caso nenhum cookie (ou um (ou um) inválido ou expirado esteja disponível.

Note

O grupo de práticas de padrões da Microsoft & desencoraja o uso de cookies de cache de função persistente. Como a posse do cookie de cache de função é suficiente para provar a associação de função, se um hacker puder de alguma forma obter acesso a um cookie de usuário válido, ele poderá representar esse usuário. A probabilidade de isso acontecer aumentará se o cookie persistir no navegador do usuário. Para obter mais informações sobre essa recomendação de segurança, bem como outras questões de segurança, consulte a lista de perguntas de segurança para ASP.NET 2,0.

Etapa 1: definindo regras de autorização de URL de Role-Based

Conforme discutido no tutorial de autorização baseada no usuário , a autorização de URL oferece um meio de restringir o acesso a um conjunto de páginas por usuário ou função por função. As regras de autorização de URL são escritas no Web.config uso do <authorization> elemento com <allow> <deny> elementos filho e. Além das regras de autorização relacionadas ao usuário discutidas nos tutoriais anteriores, cada <allow> <deny> elemento filho também pode incluir:

  • Uma função específica
  • Uma lista delimitada por vírgulas de funções

Por exemplo, as regras de autorização de URL concedem acesso a esses usuários nas funções de administradores e supervisores, mas negam acesso a todos os outros:

<authorization>
     <allow roles="Administrators, Supervisors" />
     <deny users="*" />
</authorization>

O <allow> elemento na marcação acima declara que as funções Administrators e supervisores são permitidas; o <deny> elemento instrui que todos os usuários são negados.

Vamos configurar nosso aplicativo para que as ManageRoles.aspx páginas, UsersAndRoles.aspx e CreateUserWizardWithRoles.aspx só sejam acessíveis a esses usuários na função Administradores, enquanto a RoleBasedAuthorization.aspx página permanece acessível a todos os visitantes.

Para fazer isso, comece adicionando um Web.config arquivo à Roles pasta.

Adicionar um arquivo de Web.config ao diretório de funções

Figura 3: adicionar um Web.config arquivo ao Roles diretório (clique para exibir a imagem em tamanho normal)

Em seguida, adicione a seguinte marcação de configuração a Web.config :

<?xml version="1.0"?>    

<configuration>    
     <system.web>    
          <authorization>    
               <allow roles="Administrators" />    
               <deny users="*"/>    
          </authorization>    

     </system.web>

     <!-- Allow all users to visit RoleBasedAuthorization.aspx -->    
     <location path="RoleBasedAuthorization.aspx">    
          <system.web>    
               <authorization>    
                    <allow users="*" />    

               </authorization>    
          </system.web>    
     </location>    
</configuration>

O <authorization> elemento na <system.web> seção indica que somente os usuários na função administradores podem acessar os recursos ASP.net no Roles diretório. O <location> elemento define um conjunto alternativo de regras de autorização de URL para a RoleBasedAuthorization.aspx página, permitindo que todos os usuários visitem a página.

Depois de salvar as alterações no Web.config , faça logon como um usuário que não está na função Administradores e tente visitar uma das páginas protegidas. O UrlAuthorizationModule detectará que você não tem permissão para visitar o recurso solicitado; consequentemente, o FormsAuthenticationModule irá redirecioná-lo para a página de logon. A página de logon será redirecionada para a UnauthorizedAccess.aspx página (consulte a Figura 4). Esse redirecionamento final da página de logon UnauthorizedAccess.aspx ocorre devido ao código que adicionamos à página de logon na etapa 2 do tutorial de autorização baseada no usuário . Em particular, a página de logon redireciona automaticamente qualquer usuário autenticado para UnauthorizedAccess.aspx se a QueryString contiver um ReturnUrl parâmetro, pois esse parâmetro indica que o usuário chegou na página de logon depois de tentar exibir uma página que não estava autorizada a exibir.

Somente os usuários na função administradores podem exibir as páginas protegidas

Figura 4: somente os usuários na função administradores podem exibir as páginas protegidas (clique para exibir a imagem em tamanho normal)

Faça logoff e, em seguida, faça logon como um usuário que está na função Administradores. Agora você deve ser capaz de exibir as três páginas protegidas.

Tito pode visitar a página UsersAndRoles. aspx porque ela está na função Administradores

Figura 5: o Tito pode visitar a UsersAndRoles.aspx página porque ela está na função Administradores (clique para exibir a imagem em tamanho normal)

Note

Ao especificar regras de autorização de URL – para funções ou usuários – é importante ter em mente que as regras são analisadas uma de cada vez, de cima para baixo. Assim que uma correspondência for encontrada, o usuário terá o acesso concedido ou negado, dependendo de se a correspondência foi encontrada em um <allow> <deny> elemento ou. Se nenhuma correspondência for encontrada, o usuário receberá acesso. Consequentemente, se você quiser restringir o acesso a uma ou mais contas de usuário, é imperativo usar um <deny> elemento como o último elemento na configuração de autorização de URL. Se suas regras de autorização de URL não incluírem um <deny> , todos os usuários receberão acesso. Para obter uma discussão mais detalhada sobre como as regras de autorização de URL são analisadas, consulte a seção "A ver como a UrlAuthorizationModule usa as regras de autorização para conceder ou negar acesso" do tutorial de autorização baseada no usuário .

Etapa 2: limitando a funcionalidade com base nas funções do usuário conectado no momento

A autorização de URL facilita a especificação de regras de autorização grosseira que determinam quais identidades são permitidas e quais delas são negadas de exibir uma determinada página (ou todas as páginas de uma pasta e suas subpastas). No entanto, em alguns casos, podemos permitir que todos os usuários visitem uma página, mas limitamos a funcionalidade da página com base nas funções do usuário de visita. Isso pode envolver a exibição ou ocultação de dados com base na função do usuário ou a oferta de funcionalidade adicional aos usuários que pertencem a uma função específica.

Essas regras refinadas de autorização baseada em função podem ser implementadas de forma declarativa ou programática (ou por meio de uma combinação das duas). Na próxima seção, veremos como implementar a autorização refinada declarativa por meio do controle LoginView. Depois disso, exploraremos técnicas programáticas. Antes de examinarmos a aplicação de regras de autorização refinadas, no entanto, primeiro precisamos criar uma página cuja funcionalidade depende da função do usuário que a está visitando.

Vamos criar uma página que lista todas as contas de usuário no sistema em um GridView. O GridView incluirá o nome de usuário, o endereço de email, a data do último logon e os comentários do usuário. Além de exibir as informações de cada usuário, o GridView incluirá os recursos de edição e exclusão. Inicialmente, criaremos essa página com a funcionalidade Editar e excluir disponível para todos os usuários. Nas seções "usando o controle LoginView" e "limitando programaticamente a funcionalidade", veremos como habilitar ou desabilitar esses recursos com base na função do usuário de visita.

Note

A página ASP.NET que estamos prestes a Compilar usa um controle GridView para exibir as contas de usuário. Como essa série de tutoriais se concentra em autenticação de formulários, autorização, contas de usuário e funções, não quero gastar muito tempo discutindo o funcionamento interno do controle GridView. Embora este tutorial forneça instruções passo a passo específicas para configurar essa página, ele não se aprofunda nos detalhes de por que determinadas opções foram feitas ou quais propriedades específicas de efeito têm sobre a saída renderizada. Para um exame completo do controle GridView, Confira meu trabalho com dados na série de tutoriais do ASP.NET 2,0 .

Comece abrindo a RoleBasedAuthorization.aspx página na Roles pasta. Arraste um GridView da página para o designer e defina seu ID como UserGrid . Em alguns instantes, escreveremos o código que chama o Membership.GetAllUsers método e associa o MembershipUserCollection objeto resultante ao GridView. O MembershipUserCollection contém um MembershipUser objeto para cada conta de usuário no sistema; os MembershipUser objetos têm propriedades como UserName , Email , LastLoginDate e assim por diante.

Antes de escrevermos o código que associa as contas de usuário à grade, vamos definir primeiro os campos do GridView. Na marca inteligente do GridView, clique no link "Editar colunas" para iniciar a caixa de diálogo campos (consulte a Figura 6). A partir daqui, desmarque a caixa de seleção "gerar campos automaticamente" no canto inferior esquerdo. Como queremos que esse GridView inclua recursos de edição e exclusão, adicione um CommandField e defina suas ShowEditButton ShowDeleteButton Propriedades e como true. Em seguida, adicione quatro campos para exibir UserName as Email Propriedades,, e LastLoginDate Comment . Use um BoundField para as duas propriedades somente leitura ( UserName e LastLoginDate ) e TemplateFields para os dois campos editáveis ( Email e Comment ).

Faça com que o primeiro BoundField exiba a UserName Propriedade; defina suas HeaderText DataField Propriedades e como "username". Esse campo não será editável, portanto, defina sua ReadOnly propriedade como true. Configure o LastLoginDate BoundField definindo seu HeaderText como "último logon" e seu DataField como "LastLoginDate". Vamos Formatar a saída desse BoundField para que apenas a data seja exibida (em vez da data e hora). Para fazer isso, defina a propriedade de BoundField HtmlEncode como false e sua DataFormatString propriedade como " {0:d} ". Defina também a ReadOnly propriedade como true.

Defina as HeaderText Propriedades dos dois TemplateFields como "email" e "comentário".

Os campos do GridView podem ser configurados por meio da caixa de diálogo campos

Figura 6: os campos do GridView podem ser configurados por meio da caixa de diálogo campos (clique para exibir a imagem em tamanho normal)

Agora, precisamos definir o ItemTemplate e EditItemTemplate para o TemplateFields "email" e "comentário". Adicione um controle rótulo da Web a cada um dos ItemTemplate s e associe suas Text Propriedades às Email Comment Propriedades e, respectivamente.

Para o modelo de "email", adicione uma caixa de texto chamada Email ao seu EditItemTemplate e associe sua Text Propriedade à Email propriedade usando a associação de dados bidirecional. Adicione um RequiredFieldValidator e um RegularExpressionValidator ao EditItemTemplate para garantir que um visitante editando a propriedade email tenha inserido um endereço de email válido. Para o modelo do "comentário", adicione uma caixa de texto de várias linhas nomeada Comment a sua EditItemTemplate . Defina a caixa de texto Columns e Rows as propriedades como 40 e 4, respectivamente, e, em seguida, associe sua Text Propriedade à Comment propriedade usando a associação de dados bidirecional.

Depois de configurar essas TemplateFields, sua marcação declarativa deve ser semelhante ao seguinte:

<asp:TemplateField HeaderText="Email">    
     <ItemTemplate>    
          <asp:Label runat="server" ID="Label1" Text='<%# Eval("Email") %>'></asp:Label>    

     </ItemTemplate>    
     <EditItemTemplate>    
          <asp:TextBox runat="server" ID="Email" Text='<%# Bind("Email") %>'></asp:TextBox>    

          <asp:RequiredFieldValidator ID="RequiredFieldValidator1" runat="server"    
               ControlToValidate="Email" Display="Dynamic"    
               ErrorMessage="You must provide an email address." 
               SetFocusOnError="True">*</asp:RequiredFieldValidator>    

          <asp:RegularExpressionValidator ID="RegularExpressionValidator1" runat="server"    
               ControlToValidate="Email" Display="Dynamic"    
               ErrorMessage="The email address you have entered is not valid. Please fix 
               this and try again."    
               SetFocusOnError="True"    

               ValidationExpression="\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*">*
          </asp:RegularExpressionValidator>    
     </EditItemTemplate>    
</asp:TemplateField>

<asp:TemplateField HeaderText="Comment">    
     <ItemTemplate>    
          <asp:Label runat="server" ID="Label2" Text='<%# Eval("Comment") %>'></asp:Label>    

     </ItemTemplate>    
     <EditItemTemplate>    
          <asp:TextBox runat="server" ID="Comment" TextMode="MultiLine"
               Columns="40" Rows="4" Text='<%# Bind("Comment") %>'>

          </asp:TextBox>    
     </EditItemTemplate>    
</asp:TemplateField>

Ao editar ou excluir uma conta de usuário, precisaremos saber o valor da UserName Propriedade do usuário. Defina a propriedade do GridView DataKeyNames como "username" para que essas informações estejam disponíveis por meio da coleção do GridView DataKeys .

Por fim, adicione um controle ValidationSummary à página e defina sua ShowMessageBox propriedade como true e sua ShowSummary propriedade como false. Com essas configurações, o ValidationSummary exibirá um alerta do lado do cliente se o usuário tentar editar uma conta de usuário com um endereço de email ausente ou inválido.

<asp:ValidationSummary ID="ValidationSummary1"
               runat="server"
               ShowMessageBox="True"
               ShowSummary="False" />

Agora, concluímos a marcação declarativa desta página. Nossa próxima tarefa é associar o conjunto de contas de usuário ao GridView. Adicione um método chamado BindUserGrid à RoleBasedAuthorization.aspx classe code-behind da página que associa o MembershipUserCollection retornado por Membership.GetAllUsers ao UserGrid GridView. Chame esse método do Page_Load manipulador de eventos na primeira página de visita.

protected void Page_Load(object sender, EventArgs e)    
{    
     if (!Page.IsPostBack)    
          BindUserGrid();    
}

private void BindUserGrid()    
{    
     MembershipUserCollection allUsers = Membership.GetAllUsers();    
     UserGrid.DataSource = allUsers;    
     UserGrid.DataBind();    
}

Com esse código em vigor, visite a página por meio de um navegador. Como mostra a Figura 7, você verá um GridView listando informações sobre cada conta de usuário no sistema.

O GridView do usergrid lista informações sobre cada usuário no sistema

Figura 7: o UserGrid GridView lista informações sobre cada usuário no sistema (clique para exibir a imagem em tamanho normal)

Note

O UserGrid GridView lista todos os usuários em uma interface não paginável. Essa interface de grade simples não é adequada para cenários em que há várias dúzias ou mais de usuários. Uma opção é configurar o GridView para habilitar a paginação. O Membership.GetAllUsers método tem duas sobrecargas: uma que não aceita parâmetros de entrada e retorna todos os usuários e um que usa valores inteiros para o índice de página e o tamanho da página e retorna apenas o subconjunto especificado dos usuários. A segunda sobrecarga pode ser usada para percorrer com mais eficiência os usuários, uma vez que ele retorna apenas o subconjunto preciso de contas de usuário em vez de todos eles. Se você tiver milhares de contas de usuário, talvez queira considerar uma interface baseada em filtro, uma que mostre apenas os usuários cujo nome de usuário começa com um caractere selecionado, por exemplo. O Membership.FindUsersByName method é ideal para criar uma interface do usuário baseada em filtro. Vamos examinar a criação de uma interface desse tipo em um tutorial futuro.

O controle GridView oferece suporte interno para edição e exclusão quando o controle está associado a um controle de fonte de dados configurado corretamente, como SqlDataSource ou ObjectDataSource. UserGridNo entanto, o GridView tem seus dados limitados de forma programática; portanto, devemos escrever código para executar essas duas tarefas. Em particular, precisaremos criar manipuladores de eventos para os RowEditing RowCancelingEdit eventos,, e do GridView RowUpdating RowDeleting , que são acionados quando um visitante clica nos botões editar, cancelar, atualizar ou excluir do GridView.

Comece criando os manipuladores de eventos para os RowEditing eventos, e do GridView RowCancelingEdit RowUpdating e, em seguida, adicione o seguinte código:

protected void UserGrid_RowEditing(object sender, GridViewEditEventArgs e)
{
     // Set the grid's EditIndex and rebind the data

     UserGrid.EditIndex = e.NewEditIndex;
     BindUserGrid();
}

protected void UserGrid_RowCancelingEdit(object sender, GridViewCancelEditEventArgs e)
{
     // Revert the grid's EditIndex to -1 and rebind the data
     UserGrid.EditIndex = -1;
     BindUserGrid();
}

protected void UserGrid_RowUpdating(object sender, GridViewUpdateEventArgs e)
{
     // Exit if the page is not valid
     if (!Page.IsValid)
          return;

     // Determine the username of the user we are editing
     string UserName = UserGrid.DataKeys[e.RowIndex].Value.ToString();

     // Read in the entered information and update the user
     TextBox EmailTextBox = UserGrid.Rows[e.RowIndex].FindControl("Email") as TextBox;
     TextBox CommentTextBox = UserGrid.Rows[e.RowIndex].FindControl("Comment") as TextBox;

     // Return information about the user
     MembershipUser UserInfo = Membership.GetUser(UserName);

     // Update the User account information
     UserInfo.Email = EmailTextBox.Text.Trim();
     UserInfo.Comment = CommentTextBox.Text.Trim();

     Membership.UpdateUser(UserInfo);

     // Revert the grid's EditIndex to -1 and rebind the data
     UserGrid.EditIndex = -1;
     BindUserGrid();
}

Os RowEditing RowCancelingEdit manipuladores de eventos e simplesmente definem a EditIndex Propriedade do GridView e, em seguida, reassociam a lista de contas de usuário à grade. A coisa interessante acontece no RowUpdating manipulador de eventos. Esse manipulador de eventos começa garantindo que os dados são válidos e, em seguida, captura o UserName valor da conta de usuário editada da DataKeys coleção. As Email caixas de Texte Comment nos dois TemplateFields EditItemTemplate são, então, referenciadas por meio de programação. Suas Text Propriedades contêm o endereço de email editado e o comentário.

Para atualizar uma conta de usuário por meio da API Membership, precisamos primeiro obter as informações do usuário, que fazemos por meio de uma chamada para Membership.GetUser(userName) . As MembershipUser Propriedades e do objeto retornado Email Comment são então atualizadas com os valores inseridos nas duas caixas de entrada da interface de edição. Por fim, essas modificações são salvas com uma chamada para Membership.UpdateUser . O RowUpdating manipulador de eventos é concluído por meio da reversão do GridView para sua interface de pré-edição.

Em seguida, crie o RowDeleting manipulador de eventos e, em seguida, adicione o seguinte código:

protected void UserGrid_RowDeleting(object sender, GridViewDeleteEventArgs e)
{
     // Determine the username of the user we are editing
     string UserName = UserGrid.DataKeys[e.RowIndex].Value.ToString();

     // Delete the user
     Membership.DeleteUser(UserName);

     // Revert the grid's EditIndex to -1 and rebind the data
     UserGrid.EditIndex = -1;
     BindUserGrid();
}

O manipulador de eventos acima começa pegando o UserName valor da DataKeys coleção GridView; esse UserName valor é passado para o DeleteUser métododa classe Membership. O DeleteUser método exclui a conta de usuário do sistema, incluindo os dados de associação relacionados (como as funções às quais este usuário pertence). Depois de excluir o usuário, a grade EditIndex é definida como-1 (caso o usuário tenha clicado em excluir enquanto outra linha estava no modo de edição) e o BindUserGrid método é chamado.

Note

O botão excluir não requer nenhum tipo de confirmação do usuário antes de excluir a conta de usuário. Recomendo que você adicione alguma forma de confirmação do usuário para diminuir a chance de uma conta ser excluída acidentalmente. Uma das maneiras mais fáceis de confirmar uma ação é por meio de uma caixa de diálogo de confirmação do lado do cliente. Para obter mais informações sobre essa técnica, consulte adicionar Client-Side confirmação ao excluir.

Verifique se essa página funciona conforme o esperado. Você deve ser capaz de editar o endereço de email e o comentário de qualquer usuário, bem como excluir qualquer conta de usuário. Como a RoleBasedAuthorization.aspx página pode ser acessada por todos os usuários, qualquer usuário – mesmo visitantes anônimos – pode visitar esta página e editar e excluir contas de usuário! Vamos atualizar essa página para que somente os usuários nas funções de supervisores e administradores possam editar o endereço de email e o comentário de um usuário e apenas os administradores possam excluir uma conta de usuário.

A seção "usando o controle LoginView" examina o uso do controle LoginView para mostrar instruções específicas à função do usuário. Se uma pessoa na função Administradores visitar esta página, mostraremos instruções sobre como editar e excluir usuários. Se um usuário na função de supervisores chegar a essa página, mostraremos instruções sobre como editar usuários. E se o visitante for anônimo ou não estiver na função de supervisores ou administradores, exibiremos uma mensagem explicando que eles não podem editar ou excluir informações de conta de usuário. Na seção "limitando a funcionalidade de forma programática", escreveremos o código que mostra ou oculta os botões editar e excluir de forma programática com base na função do usuário.

Usando o controle LoginView

Como vimos nos tutoriais anteriores, o controle LoginView é útil para exibir interfaces diferentes para usuários autenticados e anônimos, mas o controle LoginView também pode ser usado para exibir diferentes marcações com base nas funções do usuário. Vamos usar um controle LoginView para exibir instruções diferentes com base na função do usuário de visita.

Comece adicionando um LoginView acima do UserGrid GridView. Como discutimos anteriormente, o controle LoginView tem dois modelos internos: AnonymousTemplate e LoggedInTemplate . Insira uma breve mensagem em ambos os modelos que informa ao usuário que eles não podem editar nem excluir informações do usuário.

<asp:LoginView ID="LoginView1" runat="server">
     <LoggedInTemplate>
          You are not a member of the Supervisors or Administrators roles. Therefore you
          cannot edit or delete any user information.
     </LoggedInTemplate>
     <AnonymousTemplate>
          You are not logged into the system. Therefore you cannot edit or delete any user

          information.
     </AnonymousTemplate>
</asp:LoginView>

Além de AnonymousTemplate e LoggedInTemplate , o controle LoginView pode incluir RoleGroups, que são modelos específicos de função. Cada RoleGroup contém uma única propriedade, Roles , que especifica a quais funções o RoleGroup se aplica. A Roles propriedade pode ser definida como uma única função (como "administradores") ou em uma lista delimitada por vírgulas de funções (como "administradores, supervisores").

Para gerenciar o RoleGroups, clique no link "Editar RoleGroups" na marca inteligente do controle para abrir o editor de coleção RoleGroup. Adicione dois novos RoleGroups. Defina a propriedade do primeiro RoleGroup Roles como "administradores" e a segunda como "supervisores".

Gerenciar os modelos de Role-Specific do LoginView por meio do editor de coleção RoleGroup

Figura 8: gerenciar os modelos de Role-Specific do LoginView por meio do editor de coleção RoleGroup (clique para exibir a imagem em tamanho normal)

Clique em OK para fechar o editor de coleção RoleGroup; Isso atualiza a marcação declarativa do LoginView para incluir uma <RoleGroups> seção com um <asp:RoleGroup> elemento filho para cada RoleGroup definido no editor de coleção RoleGroup. Além disso, a lista suspensa "views" na marca inteligente do LoginView, que inicialmente é listada apenas AnonymousTemplate LoggedInTemplate como e – agora também inclui o RoleGroups adicionado.

Edite o RoleGroups para que os usuários na função de supervisores sejam exibidos instruções sobre como editar contas de usuário, enquanto os usuários na função Administradores são mostrados instruções para edição e exclusão. Depois de fazer essas alterações, a marcação declarativa de LoginView deve ser semelhante ao seguinte.

<asp:LoginView ID="LoginView1" runat="server">
     <RoleGroups>
          <asp:RoleGroup Roles="Administrators">
               <ContentTemplate>
                    As an Administrator, you may edit and delete user accounts. 
                    Remember: With great power comes great responsibility!

               </ContentTemplate>
          </asp:RoleGroup>
          <asp:RoleGroup Roles="Supervisors">
               <ContentTemplate>
                    As a Supervisor, you may edit users&#39; Email and Comment information. 
                    Simply click the Edit button, make your changes, and then click Update.
               </ContentTemplate>
          </asp:RoleGroup>
     </RoleGroups>

     <LoggedInTemplate>
          You are not a member of the Supervisors or Administrators roles. 
          Therefore you cannot edit or delete any user information.
     </LoggedInTemplate>
     <AnonymousTemplate>
          You are not logged into the system. Therefore you cannot edit or delete any user
          information.
     </AnonymousTemplate>
</asp:LoginView>

Depois de fazer essas alterações, salve a página e, em seguida, visite-a por meio de um navegador. Primeiro, visite a página como um usuário anônimo. Você deve ser mostrado na mensagem "você não está conectado ao sistema. Portanto, você não pode editar ou excluir nenhuma informação do usuário. " Em seguida, faça logon como um usuário autenticado, mas um que não esteja na função supervisoras nem administradores. Desta vez, você verá a mensagem "você não é membro das funções supervisores ou administradores. Portanto, você não pode editar ou excluir nenhuma informação do usuário. "

Em seguida, faça logon como um usuário que seja membro da função de supervisores. Desta vez, você deverá ver a mensagem específica de função de supervisores (consulte a Figura 9). E se você fizer logon como um usuário na função Administradores, deverá ver a mensagem específica de função de administradores (consulte a Figura 10).

Bruce mostra os supervisores Role-Specific mensagem

Figura 9: Bruce é mostrado na mensagem de Role-Specific de supervisores (clique para exibir a imagem em tamanho normal)

Tito é mostrado a mensagem administradores Role-Specific

Figura 10: o Tito é mostrado na mensagem administradores Role-Specific (clique para exibir a imagem em tamanho normal)

Como as capturas de tela nas figuras 9 e 10 mostram, o LoginView só renderiza um modelo, mesmo que vários modelos se apliquem. Bruce e Tito são usuários conectados, mas o LoginView renderiza apenas o RoleGroup correspondente e não o LoggedInTemplate . Além disso, o Tito pertence às funções de administradores e supervisores, mas o controle LoginView renderiza o modelo específico de função de administradores em vez dos supervisores.

A Figura 11 ilustra o fluxo de trabalho usado pelo controle LoginView para determinar qual modelo renderizar. Observe que, se houver mais de um RoleGroup especificado, o modelo LoginView renderizará o primeiro RoleGroup que corresponde a. Em outras palavras, se tivéssemos colocado os supervisores RoleGroup como o primeiro RoleGroup e os administradores como o segundo, quando Tito visitou essa página, ele veria a mensagem de supervisores.

O fluxo de trabalho do controle LoginView para determinar qual modelo processar

Figura 11: o fluxo de trabalho do controle LoginView para determinar qual modelo processar (clique para exibir a imagem em tamanho normal)

Limitando programaticamente a funcionalidade

Enquanto o controle LoginView exibe instruções diferentes com base na função do usuário que está visitando a página, os botões editar e cancelar permanecem visíveis para todos. Precisamos ocultar programaticamente os botões editar e excluir de visitantes anônimos e usuários que não estão na função supervisores nem administradores. Precisamos ocultar o botão excluir para todos que não sejam um administrador. Para fazer isso, escreveremos um pouco de código que referencia programaticamente os LinkButtons Edit e Delete de CommandField e define suas Visible Propriedades como false , se necessário.

A maneira mais fácil de fazer referência programaticamente a controles em um CommandField é primeiro convertê-lo em um modelo. Para fazer isso, clique no link "Editar colunas" na marca inteligente do GridView, selecione o CommandField na lista de campos atuais e clique no link "converter este campo em um TemplateField". Isso transforma o CommandField em um TemplateField por um ItemTemplate e EditItemTemplate . O ItemTemplate contém os LinkButtons Edit e Delete enquanto o EditItemTemplate hospeda a atualização e cancela LinkButton.

Converter o CommandField em um TemplateField

Figura 12: converter o CommandField em um TemplateField (clique para exibir a imagem em tamanho normal)

Atualize os LinkButtons editar e excluir no ItemTemplate , definindo suas ID Propriedades como valores de EditButton e DeleteButton , respectivamente.

<asp:TemplateField ShowHeader="False">
     <EditItemTemplate>
          <asp:LinkButton ID="LinkButton1" runat="server" CausesValidation="True"
               CommandName="Update" Text="Update"></asp:LinkButton>

           <asp:LinkButton ID="LinkButton2" runat="server" CausesValidation="False"
                CommandName="Cancel" Text="Cancel"></asp:LinkButton>

     </EditItemTemplate>
     <ItemTemplate>
          <asp:LinkButton ID="EditButton" runat="server" CausesValidation="False"
               CommandName="Edit" Text="Edit"></asp:LinkButton>

           <asp:LinkButton ID="DeleteButton" runat="server" CausesValidation="False"
               CommandName="Delete" Text="Delete"></asp:LinkButton>

     </ItemTemplate>
</asp:TemplateField>

Sempre que os dados são associados ao GridView, o GridView enumera os registros em sua DataSource propriedade e gera um GridViewRow objeto correspondente. À medida que cada GridViewRow objeto é criado, o RowCreated evento é acionado. Para ocultar os botões editar e excluir para usuários não autorizados, precisamos criar um manipulador de eventos para esse evento e fazer referência programaticamente a editar e excluir LinkButton, definindo suas propriedades de forma Visible adequada.

Crie um manipulador de eventos no RowCreated evento e, em seguida, adicione o seguinte código:

protected void UserGrid_RowCreated(object sender, GridViewRowEventArgs e)
{
     if (e.Row.RowType == DataControlRowType.DataRow && e.Row.RowIndex != UserGrid.EditIndex)
     {
          // Programmatically reference the Edit and Delete LinkButtons
          LinkButton EditButton = e.Row.FindControl("EditButton") as LinkButton;

          LinkButton DeleteButton = e.Row.FindControl("DeleteButton") as LinkButton;

          EditButton.Visible = (User.IsInRole("Administrators") || User.IsInRole("Supervisors"));
          DeleteButton.Visible = User.IsInRole("Administrators");
     }
}

Tenha em mente que o RowCreated evento é acionado para todas as linhas GridView, incluindo o cabeçalho, o rodapé, a interface do pager e assim por diante. Só queremos fazer referência de forma programática para editar e excluir LinkButtons se estivermos lidando com uma linha de dados que não está no modo de edição (já que a linha no modo de edição tem os botões atualizar e cancelar em vez de editar e excluir). Essa verificação é tratada pela if instrução.

Se estivermos lidando com uma linha de dados que não está no modo de edição, os LinkButtons editar e excluir serão referenciados e suas Visible Propriedades serão definidas com base nos valores Boolianos retornados pelo User método do objeto IsInRole(roleName) . O objeto de usuário faz referência à entidade de segurança criada pelo RoleManagerModule ; consequentemente, o IsInRole(roleName) método usa a API de funções para determinar se o visitante atual pertence a roleName.

Note

Poderíamos ter usado a classe Roles diretamente, substituindo a chamada para User.IsInRole(roleName) por uma chamada para o Roles.IsUserInRole(roleName) método. Decidi usar o método do objeto principal IsInRole(roleName) neste exemplo porque ele é mais eficiente do que usar a API de funções diretamente. Anteriormente neste tutorial, configuramos o Gerenciador de funções para armazenar em cache as funções do usuário em um cookie. Esses dados de cookie armazenados em cache só são utilizados quando o método da entidade de segurança IsInRole(roleName) é chamado; chamadas diretas para a API de funções sempre envolvem uma viagem para o armazenamento de função. Mesmo que as funções não sejam armazenadas em cache em um cookie, a chamada do método do objeto principal IsInRole(roleName) é geralmente mais eficiente, pois quando é chamada pela primeira vez durante uma solicitação, ele armazena os resultados em cache. A API de funções, por outro lado, não executa nenhum cache. Como o RowCreated evento é disparado uma vez para cada linha no GridView, o uso User.IsInRole(roleName) envolve apenas uma viagem ao repositório de funções Roles.IsUserInRole(roleName) , enquanto requer n viagens, em que n é o número de contas de usuário exibidas na grade.

A propriedade do botão Editar Visible é definida como true se o usuário que está visitando esta página estiver na função Administradores ou supervisores; caso contrário, será definido como false . A propriedade do botão excluir Visible será definida como true somente se o usuário estiver na função Administradores.

Testar esta página por meio de um navegador. Se você visitar a página como um visitante anônimo ou como um usuário que não seja um supervisor nem um administrador, o Comandofield estará vazio; Ele ainda existe, mas como um prata fino sem os botões editar ou excluir.

Note

É possível ocultar o CommandField completamente quando um não supervisor e não administrador estiver visitando a página. Eu deixe isso como um exercício para o leitor.

Os botões editar e excluir estão ocultos para não-supervisores e não-administradores

Figura 13: os botões editar e excluir estão ocultos para não-supervisores e não-administradores (clique para exibir a imagem em tamanho normal)

Se um usuário que pertence à função de supervisores (mas não à função de administradores) visitar, ele verá apenas o botão Editar.

Enquanto o botão Editar está disponível para supervisores, o botão excluir fica oculto

Figura 14: enquanto o botão Editar está disponível para supervisores, o botão excluir está oculto (clique para exibir a imagem em tamanho normal)

E se um administrador visitar, ela terá acesso aos botões editar e excluir.

Os botões editar e excluir estão disponíveis somente para administradores

Figura 15: os botões editar e excluir estão disponíveis somente para administradores (clique para exibir a imagem em tamanho normal)

Etapa 3: aplicando regras de autorização de Role-Based a classes e métodos

Na etapa 2, limitamos os recursos de edição aos usuários nas funções de supervisores e administradores e os recursos de exclusão apenas aos administradores. Isso foi feito ocultando os elementos de interface do usuário associados para usuários não autorizados por meio de técnicas programáticas. Essas medidas não garantem que um usuário não autorizado não consiga executar uma ação privilegiada. Pode haver elementos de interface do usuário que são adicionados posteriormente ou que esquecemos de ocultar para usuários não autorizados. Ou um hacker pode descobrir alguma outra maneira de obter a página ASP.NET para executar o método desejado.

Uma maneira fácil de garantir que uma parte de funcionalidade específica não possa ser acessada por um usuário não autorizado é decorar essa classe ou método com o PrincipalPermission atributo. Quando o tempo de execução do .NET usa uma classe ou executa um de seus métodos, ele verifica se o contexto de segurança atual tem permissão. O PrincipalPermission atributo fornece um mecanismo no qual podemos definir essas regras.

Examinamos o uso do PrincipalPermission atributo de volta no tutorial de autorização baseada no usuário . Especificamente, vimos como decorar os GridView SelectedIndexChanged e o manipulador de RowDeleting eventos para que eles pudessem ser executados apenas por usuários autenticados e Tito, respectivamente. O PrincipalPermission atributo funciona muito bem com funções.

Vamos demonstrar o uso do PrincipalPermission atributo nos RowUpdating manipuladores de eventos e do GridView RowDeleting para proibir a execução de usuários não autorizados. Tudo o que precisamos fazer é adicionar o atributo apropriado acima de cada definição de função:

[PrincipalPermission(SecurityAction.Demand, Role = "Administrators")]
[PrincipalPermission(SecurityAction.Demand, Role = "Supervisors")]
protected void UserGrid_RowUpdating(object sender, GridViewUpdateEventArgs e)
{
     ...
}

[PrincipalPermission(SecurityAction.Demand, Role = "Administrators")]
protected void UserGrid_RowDeleting(object sender, GridViewDeleteEventArgs e)
{
     ...
}

O atributo para o RowUpdating manipulador de eventos determina que somente os usuários nas funções Administradores ou supervisores podem executar o manipulador de eventos, onde o atributo no RowDeleting manipulador de eventos limita a execução aos usuários na função Administradores.

Note

O PrincipalPermission atributo é representado como uma classe no System.Security.Permissions namespace. Certifique-se de adicionar uma using System.Security.Permissions instrução na parte superior do arquivo de classe code-behind para importar esse namespace.

Se, de alguma forma, um não administrador tentar executar o RowDeleting manipulador de eventos ou se um não supervisor ou não administrador tentar executar o manipulador de RowUpdating eventos, o tempo de execução do .NET gerará um SecurityException .

Se o contexto de segurança não estiver autorizado a executar o método, uma SecurityException será gerada

Figura 16: se o contexto de segurança não estiver autorizado a executar o método, um SecurityException será lançado (clique para exibir a imagem em tamanho normal)

Além das páginas ASP.NET, muitos aplicativos também têm uma arquitetura que inclui várias camadas, como lógica de negócios e camadas de acesso a dados. Essas camadas normalmente são implementadas como bibliotecas de classes e oferecem classes e métodos para executar a lógica comercial e a funcionalidade relacionada a dados. O PrincipalPermission atributo também é útil para aplicar regras de autorização a essas camadas.

Para obter mais informações sobre como usar o PrincipalPermission atributo para definir regras de autorização em classes e métodos, consulte a entrada do blog de Scott Guthrie adicionando regras de autorização a PrincipalPermissionAttributes camadas de dados e de negócios usando o.

Resumo

Neste tutorial, examinamos como especificar regras de autorização de grande e refinamento com base nas funções do usuário. ASP. O recurso de autorização de URL do NET permite que um desenvolvedor de página Especifique quais identidades têm permissão ou acesso negado a quais páginas. Como vimos de volta no tutorial de autorização baseada no usuário , as regras de autorização de URL podem ser aplicadas de cada usuário. Eles também podem ser aplicados em uma base função por função, como vimos na etapa 1 deste tutorial.

Regras de autorização refinadas podem ser aplicadas declarativamente ou de forma programática. Na etapa 2, examinamos o uso do recurso RoleGroups do controle LoginView para processar uma saída diferente com base nas funções do usuário visitantes. Também vimos maneiras de determinar programaticamente se um usuário pertence a uma função específica e como ajustar a funcionalidade da página de forma adequada.

Boa programação!

Leitura Adicional

Para obter mais informações sobre os tópicos discutidos neste tutorial, consulte os seguintes recursos:

Sobre o autor

Scott Mitchell, autor de vários livros sobre ASP/ASP. NET e fundador da 4GuysFromRolla.com, tem trabalhado com tecnologias Web da Microsoft desde 1998. Scott trabalha como consultor, instrutor e escritor independentes. Seu livro mais recente é que a Sams ensina a ASP.NET 2,0 em 24 horas. Scott pode ser acessado em mitchell@4guysfromrolla.com ou por meio de seu blog em http://ScottOnWriting.NET .

Agradecimentos especiais a...

Esta série de tutoriais foi revisada por muitos revisores úteis. Os revisores potenciais para este tutorial incluem Banerjee e Teresa Murphy. Está interessado em revisar meus artigos futuros do MSDN? Nesse caso, me solte uma linha em mitchell@4GuysFromRolla.com