security.xml
11.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
<?xml version='1.0' encoding="utf-8"?>
<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
"http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd" []>
<chapter id="security">
<title>Segurança</title>
<para>
Neste capítulo será tratada uma questão de grande importância para a maioria das aplicações e motivo de infindáveis
discussões nas equipes de desenvolvimento: controle de acesso. Assim como tudo relacionado ao framework, a
implementação de segurança foi projetada de forma simples e flexível, independente de camada de apresentação ou
tecnologia, te deixando livre para implementar sua própria solução ou utilizar as extensões prontas, como a que
atualmente provemos baseada no Apache Shiro (<ulink url="http://shiro.apache.org" />).
</para>
<para>
Para utilizar o modelo de segurança proposto basta utilizar o Demoiselle, pois no núcleo do Framework estão as
interfaces e anotações que definem o comportamento básico da implementação.
</para>
<section>
<title>Configurando</title>
<para>
Para um correto funcionamento do Demoiselle é necessário inserir os interceptadores de segurança no arquivo <filename>src/main/webapp/WEB-INF/beans.xml</filename>.
</para>
<programlisting role="XML"><![CDATA[<beans xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/beans_1_0.xsd">
<interceptors>
<class>br.gov.frameworkdemoiselle.internal.interceptor.RequiredPermissionInterceptor</class>
<class>br.gov.frameworkdemoiselle.internal.interceptor.RequiredRoleInterceptor</class>
</interceptors>
</beans>]]></programlisting>
</section>
<section>
<title>Autenticação</title>
<para>
O mecanismo de autenticação busca verificar a identidade do usuário de um sistema. A forma mais conhecida, e comum,
para executar essa verificação se dá por meio de um formulário de login, geralmente solicitando um nome de usuário e
sua respectiva senha. No entanto, outras formas como reconhecimento biométrico e autenticação por token, para citar
apenas duas, têm ganhado um grande número de adeptos.
</para>
<para>
O Demoiselle deixa o desenvolvedor livre para definir qual forma usar, de acordo com a sua conveniência e necessidade.
A peça chave para tornar isso possível é o contexto de segurança, representado pela interface <literal>SecurityContext</literal>. Nessa
estão definidos os métodos responsáveis por gerenciar os mecanismos de autenticação como, por exemplo, executar
login/logout de usuários e verificar se os mesmos estão ou não autenticados.
</para>
<para>
O contexto de segurança irá direcionar as requisições para a implementação definida pela aplicação. A autenticação será
efetuada por uma classe que implemente a interface <literal>Authenticator</literal>, cujo método <literal>authenticate()</literal> é responsável por
executar os passos necessários para validar a identidade de um usuário. Nesta mesma interface serão encontrados,
ainda, os métodos <literal>unAuthenticate()</literal> e <literal>getUser()</literal>, responsáveis por, respectivamente, desautenticar e retornar o usuário
autenticado.
</para>
<para>
Para exemplificar, consideremos a autenticação baseada em nome de usuário e senha. O primeiro passo é criar um bean para
armazenar essas informações:
</para>
<programlisting role="JAVA"><![CDATA[@SessionScoped
public class Credential {
private String login;
private String senha;
// ...
}]]></programlisting>
<para>
Feito isso, podemos implementar a classe na qual se deseja adicionar o mecanismo de segurança:
</para>
<programlisting role="JAVA"><![CDATA[public class ClasseExemplo {
@Inject
private Credential credential;
@Inject
private SecurityContext context;
public void metodo1() {
credential.setLogin("usuario1");
credential.setSenha("123");
context.login();
// codigo do metodo
context.logout();
}
}]]></programlisting>
<para>
Neste caso, a interface <literal>SecurityContext</literal> e o bean <literal>Credential</literal> estão sendo injetados na classe utilizando o CDI.
Dentro do método, ao definir o usuário e a senha e invocar <literal>context.login()</literal>, a implementação de segurança definida irá
tratar essa requisição de acordo com os critérios estabelecidos.
</para>
</section>
<section>
<title>Autorização</title>
<para>
O mecanismo de autorização é responsável por garantir que apenas usuários autorizados tenham o acesso concedido a
determinados recursos de um sistema. No modelo de segurança do Demoiselle 2, a autorização pode acontecer de duas
formas:
<itemizedlist>
<listitem><para>Permissão por usuário, através da anotação <literal>@RequiredPermission</literal></para></listitem>
<listitem><para>Permissão por papel, através da anotação <literal>@RequiredRole</literal></para></listitem>
</itemizedlist>
</para>
<para>
Novamente a interface <literal>SecurityContext</literal> é a responsável pela interação entre as funcionalidades da aplicação e a implementação de
segurança. Nela estão definidos os métodos que verificam se o usuário possui permissão para acessar um recurso ou se o
usuário está associado a um papel.
</para>
<para>
A anotação <literal>@RequiredPermission</literal> pode ser utilizada tanto em classes como em métodos e possui dois parâmetros opcionais:
<literal>operation</literal> e <literal>resource</literal>. O primeiro define a operação para a qual se deseja permissão e o segundo define em qual
recurso essa operação será realizada. Abaixo serão exemplificadas algumas formas de utilização:
</para>
<programlisting role="JAVA"><![CDATA[class ClasseExemplo {
@RequiredPermission
public void requiredPermissionWithoutDeclaredResourceAndOperation() {
}
@RequiredPermission(resource = "contact", operation = "insert")
public void requiredPermissionWithDeclaredResourceAndOperation() {
}
}]]></programlisting>
<para>
Observe o método cuja anotação não possui parâmetros. Nesse caso serão considerados como recurso e operação o nome da classe e
do método, respectivamente. Uma outra possibilidade seria utilizar a anotação <literal>@Name</literal>, tanto na classe como no método, de
forma a possibilitar uma descrição mais amigável para o usuário.
</para>
<para>
Assim como na autenticação, o contexto de segurança possui métodos destinados a delegar as requisições de autorização para
a implementação de segurança. No caso da anotação <literal>@RequiredPermission</literal>, o método <literal>hasPermission(String resource, String
operationliteral</p> executa esta tarefa. Para tanto, deve existir uma classe que implemente a interface Authorizer, cujo
método <literal>hasPermission(String resource, String operation)</literal> verifica se o usuário logado possui permissão para executar
uma determinada operação em um recurso específico.
</para>
<para>
Ainda na interface <literal>Authorizer</literal>, pode-se notar a existência do método <literal>hasRole(String role)</literal>, responsável por verificar se o
usuário logado possui um papel específico. Este método é chamado pelo contexto de segurança, por meio do seu método
<literal>hasRole(String role)</literal>, para tratar as requisições que possuam a anotação <literal>@RequiredRole</literal>. Essa anotação possui um
parâmetro obrigatório, no qual podem ser definidos uma simples role ou um array delas.
</para>
<programlisting role="JAVA"><![CDATA[class ClasseExemplo {
@RequiredRole("simpleRoleName")
public void requiredRoleWithSingleRole() {
}
@RequiredRole({ "firstRole", "secondRole", "thirdRole", "fourthRole", "fifthRole" })
public void requiredRoleWithArrayOfRoles() {
}
}]]></programlisting>
<para>
As restrições de segurança pode ser utilizadas, ainda, em páginas web, com o auxílio de Expression Language, como no
exemplo abaixo:
</para>
<programlisting role="XHTML"><![CDATA[<p:commandButton value="#{messages['button.save']}" action="#{contactEditMB.insert}"
rendered="#{!contactEditMB.updateMode}" ajax="false"
disabled="#{!securityContext.hasPermission('contact', 'insert')}" />]]></programlisting>
<para>
Nesse caso, a habilitação de um botão está condicionada à existência de permissão para o usuário autenticado no momento
executar a operação <literal>insert</literal> no recurso <literal>contact</literal>.
</para>
</section>
<section>
<title>Criando sua implementação</title>
<para>
Após toda essa explicação, fica a dúvida: como implementar um esquema de segurança sem utilizar a extensão existente?
</para>
<para>
O primeiro passo é criar classes para implementar as interfaces <literal>Authenticator</literal> e <literal>Authorizer</literal>. O Demoiselle detecta automaticamente
a implementação, e torna essa classe a implementação padrão dessas interfaces:<!-- Essas classes devem ser
anotadas com @Alternative para que o CDI saiba que se trata de uma estratégia: -->
</para>
<programlisting role="JAVA"><![CDATA[public class MeuAuthenticator implements Authenticator {
@Override
public boolean authenticate() {
// Escreva aqui seu codigo de autenticacao
return true;
}
@Override
public User getUser() {
// Escreva aqui seu codigo para retornar o usuario logado
return null;
}
@Override
public void unAuthenticate() {
// Escreva aqui seu codigo de desautenticacao
}
}]]></programlisting>
<programlisting role="JAVA"><![CDATA[public class MeuAuthorizer implements Authorizer {
@Override
public boolean hasRole(String role) {
// Escreva aqui seu codigo de verificacao do papel
return false;
}
@Override
public boolean hasPermission(String resource, String operation) {
// Escreva aqui seu codigo de verificação de permissao
return false;
}
}]]></programlisting>
<!-- <para>
Feito isso deve-se definir no arquivo <filename>demoiselle.properties</filename>, as classes criadas:
</para>
<programlisting>
frameworkdemoiselle.security.authenticator.class=projeto.MeuAuthenticator
frameworkdemoiselle.security.authorizer.class=projeto.MeuAuthorizer
</programlisting> -->
<para>
Pronto! Sua aplicação já possui uma implementação de segurança definida. Caso sua ela não implemente essas interfaces, no momento em que
forem chamadas, o framework lançará uma exceção informando que a aplicação precisa implementá-las.
</para>
<para>
Se você tem mais de uma implementação de <literal>Authenticator</literal> e/ou <literal>Authorizer</literal> (o que pode acontecer, por exemplo, quando
se necessite de uma implementação na aplicação principal, e outra para os testes), deverá definir no arquivo <filename>demoiselle.properties</filename>
qual classe será a padrão:
<programlisting>frameworkdemoiselle.security.authenticator.class=projeto.MeuAuthenticatorPadrao
frameworkdemoiselle.security.authorizer.class=projeto.MeuAuthorizerPadrao</programlisting>
</para>
</section>
<caution> <para>O Demoiselle também oferece o componente
<ulink url="http://demoiselle.sourceforge.net/docs/demoiselle-guide-components/1.2.0/html/authorization-master.html">Authorization</ulink>
que facilita o uso de segurança com JAAS. Obviamente, não é possível utilizá-los ao mesmo tempo. Há arquétipos Maven que já trazem esse componente
como dependência, por isso sempre confira o arquivo POM.XML e se for o caso retire essa dependência.</para> </caution>
</chapter>