Adicionando segurança em webservices e EJBs

Este é mais um post sobre JavaEE, nele vamos adicionar segurança em alguns endpoints rest que farão as chamadas em um EJB local também com segurança habilitada. Todo o código está no meu github.

Caso queira, você também pode ver um hangout que fizemos no JUG Vale sobre o tema.

Para o exemplo vamos utilizar um projeto web, criado no maven. Estou usando Java 8 para isso. Os endpoints serão por meio do RestEasy. E executaremos no no Wildfly.

Após criar seu projeto web, adicione as seguintes linhas no arquivo pom.xml:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

(...)
	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<maven.compiler.source>1.8</maven.compiler.source>
		<maven.compiler.target>1.8</maven.compiler.target>
		<version.jboss.bom>8.2.0.Final</version.jboss.bom>
		<version.wildfly.maven.plugin>1.0.2.Final</version.wildfly.maven.plugin>
		<version.surefire.plugin>2.10</version.surefire.plugin>
		<version.war.plugin>2.1.1</version.war.plugin>
	</properties>

	<dependencyManagement>
		<dependencies>
			<dependency>
				<groupId>org.wildfly.bom</groupId>
				<artifactId>jboss-javaee-7.0-with-tools</artifactId>
				<version>${version.jboss.bom}</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
		</dependencies>
	</dependencyManagement>

	<dependencies>
		<!-- Import the CDI API, we use provided scope as the API is included in  			JBoss EAP -->
		<dependency>
			<groupId>javax.enterprise</groupId>
			<artifactId>cdi-api</artifactId>
			<scope>provided</scope>
		</dependency>

		<!-- Import the Common Annotations API (JSR-250), we use provided scope  			as the API is included in JBoss EAP -->
		<dependency>
			<groupId>org.jboss.spec.javax.annotation</groupId>
			<artifactId>jboss-annotations-api_1.2_spec</artifactId>
			<scope>provided</scope>
		</dependency>

		<!-- Import the interceptors API. -->
		<dependency>
			<groupId>org.jboss.spec.javax.interceptor</groupId>
			<artifactId>jboss-interceptors-api_1.2_spec</artifactId>
			<scope>provided</scope>
		</dependency>

		<!-- Import the Servlet API, we use provided scope as the API is included  			in JBoss EAP. -->
		<dependency>
			<groupId>org.jboss.spec.javax.servlet</groupId>
			<artifactId>jboss-servlet-api_3.1_spec</artifactId>
			<scope>provided</scope>
		</dependency>

		<dependency>
			<groupId>org.jboss.spec.javax.annotation</groupId>
			<artifactId>jboss-annotations-api_1.2_spec</artifactId>
			<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>org.jboss.resteasy</groupId>
			<artifactId>jaxrs-api</artifactId>
			<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>org.jboss.resteasy</groupId>
			<artifactId>resteasy-jaxb-provider</artifactId>
			<version>3.0.11.Final</version>
			<scope>provided</scope>
		</dependency>

		<dependency>
			<groupId>org.jboss.resteasy</groupId>
			<artifactId>resteasy-client</artifactId>
			<version>3.0.11.Final</version>
			<scope>provided</scope>
		</dependency>
		<!-- Import the EJB API, we use provided scope as the API is included in  			JBoss EAP -->
		<dependency>
			<groupId>org.jboss.spec.javax.ejb</groupId>
			<artifactId>jboss-ejb-api_3.2_spec</artifactId>
			<scope>provided</scope>
		</dependency>

	</dependencies>

	<build>
		<!-- Set the name of the WAR, used as the context root when the app is  			deployed -->
		<finalName>${project.artifactId}</finalName>
		<plugins>
			<plugin>
				<artifactId>maven-war-plugin</artifactId>
				<version>${version.war.plugin}</version>
				<configuration>
					<!-- Java EE doesn't require web.xml, Maven needs to catch up! -->
					<failOnMissingWebXml>false</failOnMissingWebXml>
				</configuration>
			</plugin>
			<!-- WildFly plug-in to deploy the WAR -->
			<plugin>
				<groupId>org.wildfly.plugins</groupId>
				<artifactId>wildfly-maven-plugin</artifactId>
				<version>${version.wildfly.maven.plugin}</version>
			</plugin>
		</plugins>
	</build>
</project>

Vamos para o próximo passo, antes de adicionar a segurança, vamos criar as classes com acesso total.

A primeira classe que vamos criar será o EJB local que retornará uma mensagem de Hello World. A classe chamará HelloEJB deve ser parecida com a seguir:

import javax.annotation.Resource;
import javax.annotation.security.RolesAllowed;
import javax.ejb.Local;
import javax.ejb.SessionContext;
import javax.ejb.Stateless;

/**
 *
 * @author pedro-hos
 *
 */

@Local //1
@Stateless //2
public class HelloEJB {

	@Resource
	private SessionContext ctx; // 3

	public String sayHello() {
		return "Hello World: ".concat(ctx.getCallerPrincipal().toString()); //4
	}

}

É uma classe bem simples:

  1. Este é um EJB @Local
  2. Ele é @Stateless ou seja não guarda estado
  3. Injeta @Resource do SessionContext para pegarmos algumas informações de segunraça.
  4. Retorna a mensagem “Hello World: ” e concatena o nome de quem estiver acessando o EJB

Vamos agora criar a classe que irá ativar os resources RestEasy. A classe chamará ResourceActivator e deverá extender a classe Application de javax.ws.rs.core.Application. A classe deve ser como a seguir:

import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;

/**
 * @author pedro-hos
 *
 */
@ApplicationPath("api") // 1
public class ResourceActivator extends Application { }
  1. Todos os resources serão chamados a partir da url http://{url}:{porta}/api/{endpoint}

O próximo passo será criar os endpoints. Teremos uma interface HelloResteasy e a classe que implementa ela, HelloResteasyImpl.

  • Interface:
import javax.annotation.security.PermitAll;
import javax.annotation.security.RolesAllowed;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

/**
 *
 * @author pedro-hos
 *
 */

@Path("hello") // 1
@Consumes(MediaType.APPLICATION_JSON) // 2
@Produces(MediaType.APPLICATION_JSON) // 3
public interface HelloResteasy {

	@GET // 4
	Response hello();

	@GET
	@Path("/security") //5
	Response helloProtected();

}
  1. Este endpoint será acesso a partir da url http://{ip}:{port}/api/hello
  2. Estamos consumindo JSON
  3. E vamos produzir JSON
  4. Os métodos hello() e helloProtected() são GET
  5. Porém o metodo helloProtected()  será acessado à partir do path http://{url}:{porta}/api/security (esta será nosso método acessado apenas por autenticação)
  • Classe:
import javax.ejb.Stateless;
import javax.inject.Inject;
import javax.ws.rs.core.Response;

import br.com.pedrohos.security.jaxrs.ejb.HelloEJB;

/**
 *
 * @author pedro-hos
 *
 */
@Stateless // 1
public class HelloResteasyImpl implements HelloResteasy {

	@Inject // 2
	private HelloEJB helloEjb;

	@Override
	public Response hello() {
		return Response.ok().entity(helloEjb.sayHello()).build();
	}

	@Override
	public Response helloProtected() {
		return Response.ok().entity(helloEjb.sayHello()).build();
	}
}
  1. Mais uma vez teremos uma classe @Stateless
  2. Estamos injetando a classe EJB anteriormente criada

Se formos executar o código até agora, ele provavelmente irá falhar, isso por estamos injetando classes em nosso código, mas ainda falta uma pequena configuração à ser feita. Para ativar as injeções, temos que criar um arquivo chamado beans.xml dentro de WEB-INF.

Agora sim, conseguimos subir o código no WildFly e chamar as APIs sem problema. Vamos adicionar segurança nelas agora.

A primeira coisa que precisamos saber é que existe uma configuração no Wildfly que trata essa parte de segurança. Nos arquivos de configuração, standalone.xml por exemplo, conseguimos ver o seguinte subsystem:

  • WILDFLY_HOME/standalone/configuration/standalone.xml
        <subsystem xmlns="urn:jboss:domain:security:1.2">
            <security-domains>
                <security-domain name="other" cache-type="default">
                    <authentication>
                        <login-module code="Remoting" flag="optional">
                            <module-option name="password-stacking" value="useFirstPass"/>
                        </login-module>
                        <login-module code="RealmDirect" flag="required">
                            <module-option name="password-stacking" value="useFirstPass"/>
                        </login-module>
                    </authentication>
                </security-domain>
                <security-domain name="jboss-web-policy" cache-type="default">
                    <authorization>
                        <policy-module code="Delegating" flag="required"/>
                    </authorization>
                </security-domain>
                <security-domain name="jboss-ejb-policy" cache-type="default">
                    <authorization>
                        <policy-module code="Delegating" flag="required"/>
                    </authorization>
                </security-domain>
                <security-domain name="jaspitest" cache-type="default">
                    <authentication-jaspi>
                        <login-module-stack name="dummy">
                            <login-module code="Dummy" flag="optional"/>
                        </login-module-stack>
                        <auth-module code="Dummy"/>
                    </authentication-jaspi>
                </security-domain>
            </security-domains>
        </subsystem>

O security domain que nos interesse é o , claro que podemos criar um novo security domain para usar, mas este não é o foco do post, logo vamos utilizar este na nossa aplicação. Futuramente vamos abordar a criação do security domain em outras postagens.

Vamos então dizer à nossa aplicação que queremos utilizar o security domain “other” existe duas maneiras, uma é adicionando a seguinte anotação à classe que você queira utilizar o security domain, como mostrado a seguir:

// (...)

import org.jboss.security.annotation.SecurityDomain;
@SecurityDomain("other")
public interface HelloResteasy {

//(...)

}

A outra é criando um arquivo dentro de WEB-INF chamado jboss-web.xml com o seguinte trecho:

<?xml version="1.0" encoding="UTF-8"?>
<jboss-web>
	<security-domain>other</security-domain>
</jboss-web>

Vamos optar pela opção acima. Agora precisamos editar o arquivo web.xml, com os trechos a seguir:


<!-- ... -->
  <security-constraint> <!-- 1 -->
    <display-name>Security to hello world</display-name>
    <web-resource-collection>
      <web-resource-name>Resteasy</web-resource-name>
      <url-pattern>/api/hello/security</url-pattern>
    </web-resource-collection>
    <auth-constraint>
      <role-name>admin</role-name>
    </auth-constraint>
  </security-constraint>

  <login-config> <!-- 2 -->
    <auth-method>BASIC</auth-method>
    <realm-name>ApplicationRealm</realm-name>
  </login-config>

  <security-role> <!-- 3 -->
    <role-name>admin</role-name>
  </security-role>

<!-- ... -->

  1. Criamos o para falar qual url vamos aplicar segurança, e quais ROLES podem acessar aquela url. Existem outras configurações que podemos aplicar nesse parte, como por exemplo, dizer quais métodos HTTP podem acessar livremente, como GET e podemos falar que o POST e PUT terão que ter autenticação por exemplo.
  2. Apenas estamos falando que queremos que apareça um “popup” com login e senha para os endpoints que vamos aplicar segurança.
  3. Por fim criamos a role admin.

Precisamos alterar as classes EJB e REST para que sejam habilitadas a segurança. Primeiro vamos editar a interface HelloResteasy, e anotar os métodos como mostrado abaixo:


// ...

import javax.annotation.security.PermitAll;
import javax.annotation.security.RolesAllowed;

// ...<br data-mce-bogus="1">
 @PermitAll
 Response hello();

 @RolesAllowed({"admin"})
 Response helloProtected();

// ...

E por fim vamos editar a classe HelloEJB colocando a anotação abaixo na classe:

// ...

import javax.annotation.security.RolesAllowed;

//...

@RolesAllowed({"admin"})
public class HelloEJB {
// ...
}

A última coisa que temos que fazer é criar um usuario ApplicationRealm no Wildfly, com a role admin, para isso execute o seguinte comando no terminal:

WILDFLY_HOME/bin/add-user.sh -a -u 'administrador' -p 'q1w2e3r4!' -g 'admin'

Se tudo estiver certo, ao acessar a url http://localhost:8080/security-pocs/api/hello deverá mostrar um erro e http://localhost:8080/security-pocs/api/hello/security deverá pedir login e senha.

Até a próxima. \o/

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair /  Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair /  Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair /  Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair /  Alterar )

Conectando a %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.